1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * RFC2195 CRAM-MD5 authentication
22  * RFC2595 Using TLS with IMAP, POP3 and ACAP
23  * RFC2831 DIGEST-MD5 authentication
24  * RFC3501 IMAPv4 protocol
25  * RFC4422 Simple Authentication and Security Layer (SASL)
26  * RFC4616 PLAIN authentication
27  * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism
28  * RFC4959 IMAP Extension for SASL Initial Client Response
29  * RFC5092 IMAP URL Scheme
30  * RFC6749 OAuth 2.0 Authorization Framework
31  * RFC8314 Use of TLS for Email Submission and Access
32  * Draft   LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
33  *
34  ***************************************************************************/
35 
36 #include "curl_setup.h"
37 
38 #ifndef CURL_DISABLE_IMAP
39 
40 #ifdef HAVE_NETINET_IN_H
41 #include <netinet/in.h>
42 #endif
43 #ifdef HAVE_ARPA_INET_H
44 #include <arpa/inet.h>
45 #endif
46 #ifdef HAVE_UTSNAME_H
47 #include <sys/utsname.h>
48 #endif
49 #ifdef HAVE_NETDB_H
50 #include <netdb.h>
51 #endif
52 #ifdef __VMS
53 #include <in.h>
54 #include <inet.h>
55 #endif
56 
57 #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
58 #undef in_addr_t
59 #define in_addr_t unsigned long
60 #endif
61 
62 #include <curl/curl.h>
63 #include "urldata.h"
64 #include "sendf.h"
65 #include "hostip.h"
66 #include "progress.h"
67 #include "transfer.h"
68 #include "escape.h"
69 #include "http.h" /* for HTTP proxy tunnel stuff */
70 #include "socks.h"
71 #include "imap.h"
72 #include "mime.h"
73 #include "strtoofft.h"
74 #include "strcase.h"
75 #include "vtls/vtls.h"
76 #include "connect.h"
77 #include "strerror.h"
78 #include "select.h"
79 #include "multiif.h"
80 #include "url.h"
81 #include "strcase.h"
82 #include "curl_sasl.h"
83 #include "warnless.h"
84 
85 /* The last 3 #include files should be in this order */
86 #include "curl_printf.h"
87 #include "curl_memory.h"
88 #include "memdebug.h"
89 
90 /* Local API functions */
91 static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done);
92 static CURLcode imap_do(struct connectdata *conn, bool *done);
93 static CURLcode imap_done(struct connectdata *conn, CURLcode status,
94                           bool premature);
95 static CURLcode imap_connect(struct connectdata *conn, bool *done);
96 static CURLcode imap_disconnect(struct connectdata *conn, bool dead);
97 static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done);
98 static int imap_getsock(struct connectdata *conn, curl_socket_t *socks);
99 static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done);
100 static CURLcode imap_setup_connection(struct connectdata *conn);
101 static char *imap_atom(const char *str, bool escape_only);
102 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...);
103 static CURLcode imap_parse_url_options(struct connectdata *conn);
104 static CURLcode imap_parse_url_path(struct connectdata *conn);
105 static CURLcode imap_parse_custom_request(struct connectdata *conn);
106 static CURLcode imap_perform_authenticate(struct connectdata *conn,
107                                           const char *mech,
108                                           const char *initresp);
109 static CURLcode imap_continue_authenticate(struct connectdata *conn,
110                                            const char *resp);
111 static void imap_get_message(char *buffer, char **outptr);
112 
113 /*
114  * IMAP protocol handler.
115  */
116 
117 const struct Curl_handler Curl_handler_imap = {
118   "IMAP",                           /* scheme */
119   imap_setup_connection,            /* setup_connection */
120   imap_do,                          /* do_it */
121   imap_done,                        /* done */
122   ZERO_NULL,                        /* do_more */
123   imap_connect,                     /* connect_it */
124   imap_multi_statemach,             /* connecting */
125   imap_doing,                       /* doing */
126   imap_getsock,                     /* proto_getsock */
127   imap_getsock,                     /* doing_getsock */
128   ZERO_NULL,                        /* domore_getsock */
129   ZERO_NULL,                        /* perform_getsock */
130   imap_disconnect,                  /* disconnect */
131   ZERO_NULL,                        /* readwrite */
132   ZERO_NULL,                        /* connection_check */
133   PORT_IMAP,                        /* defport */
134   CURLPROTO_IMAP,                   /* protocol */
135   PROTOPT_CLOSEACTION|              /* flags */
136   PROTOPT_URLOPTIONS
137 };
138 
139 #ifdef USE_SSL
140 /*
141  * IMAPS protocol handler.
142  */
143 
144 const struct Curl_handler Curl_handler_imaps = {
145   "IMAPS",                          /* scheme */
146   imap_setup_connection,            /* setup_connection */
147   imap_do,                          /* do_it */
148   imap_done,                        /* done */
149   ZERO_NULL,                        /* do_more */
150   imap_connect,                     /* connect_it */
151   imap_multi_statemach,             /* connecting */
152   imap_doing,                       /* doing */
153   imap_getsock,                     /* proto_getsock */
154   imap_getsock,                     /* doing_getsock */
155   ZERO_NULL,                        /* domore_getsock */
156   ZERO_NULL,                        /* perform_getsock */
157   imap_disconnect,                  /* disconnect */
158   ZERO_NULL,                        /* readwrite */
159   ZERO_NULL,                        /* connection_check */
160   PORT_IMAPS,                       /* defport */
161   CURLPROTO_IMAPS,                  /* protocol */
162   PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
163   PROTOPT_URLOPTIONS
164 };
165 #endif
166 
167 #define IMAP_RESP_OK       1
168 #define IMAP_RESP_NOT_OK   2
169 #define IMAP_RESP_PREAUTH  3
170 
171 /* SASL parameters for the imap protocol */
172 static const struct SASLproto saslimap = {
173   "imap",                     /* The service name */
174   '+',                        /* Code received when continuation is expected */
175   IMAP_RESP_OK,               /* Code to receive upon authentication success */
176   0,                          /* Maximum initial response length (no max) */
177   imap_perform_authenticate,  /* Send authentication command */
178   imap_continue_authenticate, /* Send authentication continuation */
179   imap_get_message            /* Get SASL response message */
180 };
181 
182 
183 #ifdef USE_SSL
imap_to_imaps(struct connectdata * conn)184 static void imap_to_imaps(struct connectdata *conn)
185 {
186   /* Change the connection handler */
187   conn->handler = &Curl_handler_imaps;
188 
189   /* Set the connection's upgraded to TLS flag */
190   conn->bits.tls_upgraded = TRUE;
191 }
192 #else
193 #define imap_to_imaps(x) Curl_nop_stmt
194 #endif
195 
196 /***********************************************************************
197  *
198  * imap_matchresp()
199  *
200  * Determines whether the untagged response is related to the specified
201  * command by checking if it is in format "* <command-name> ..." or
202  * "* <number> <command-name> ...".
203  *
204  * The "* " marker is assumed to have already been checked by the caller.
205  */
imap_matchresp(const char * line,size_t len,const char * cmd)206 static bool imap_matchresp(const char *line, size_t len, const char *cmd)
207 {
208   const char *end = line + len;
209   size_t cmd_len = strlen(cmd);
210 
211   /* Skip the untagged response marker */
212   line += 2;
213 
214   /* Do we have a number after the marker? */
215   if(line < end && ISDIGIT(*line)) {
216     /* Skip the number */
217     do
218       line++;
219     while(line < end && ISDIGIT(*line));
220 
221     /* Do we have the space character? */
222     if(line == end || *line != ' ')
223       return FALSE;
224 
225     line++;
226   }
227 
228   /* Does the command name match and is it followed by a space character or at
229      the end of line? */
230   if(line + cmd_len <= end && strncasecompare(line, cmd, cmd_len) &&
231      (line[cmd_len] == ' ' || line + cmd_len + 2 == end))
232     return TRUE;
233 
234   return FALSE;
235 }
236 
237 /***********************************************************************
238  *
239  * imap_endofresp()
240  *
241  * Checks whether the given string is a valid tagged, untagged or continuation
242  * response which can be processed by the response handler.
243  */
imap_endofresp(struct connectdata * conn,char * line,size_t len,int * resp)244 static bool imap_endofresp(struct connectdata *conn, char *line, size_t len,
245                            int *resp)
246 {
247   struct IMAP *imap = conn->data->req.protop;
248   struct imap_conn *imapc = &conn->proto.imapc;
249   const char *id = imapc->resptag;
250   size_t id_len = strlen(id);
251 
252   /* Do we have a tagged command response? */
253   if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') {
254     line += id_len + 1;
255     len -= id_len + 1;
256 
257     if(len >= 2 && !memcmp(line, "OK", 2))
258       *resp = IMAP_RESP_OK;
259     else if(len >= 7 && !memcmp(line, "PREAUTH", 7))
260       *resp = IMAP_RESP_PREAUTH;
261     else
262       *resp = IMAP_RESP_NOT_OK;
263 
264     return TRUE;
265   }
266 
267   /* Do we have an untagged command response? */
268   if(len >= 2 && !memcmp("* ", line, 2)) {
269     switch(imapc->state) {
270       /* States which are interested in untagged responses */
271       case IMAP_CAPABILITY:
272         if(!imap_matchresp(line, len, "CAPABILITY"))
273           return FALSE;
274         break;
275 
276       case IMAP_LIST:
277         if((!imap->custom && !imap_matchresp(line, len, "LIST")) ||
278           (imap->custom && !imap_matchresp(line, len, imap->custom) &&
279            (!strcasecompare(imap->custom, "STORE") ||
280             !imap_matchresp(line, len, "FETCH")) &&
281            !strcasecompare(imap->custom, "SELECT") &&
282            !strcasecompare(imap->custom, "EXAMINE") &&
283            !strcasecompare(imap->custom, "SEARCH") &&
284            !strcasecompare(imap->custom, "EXPUNGE") &&
285            !strcasecompare(imap->custom, "LSUB") &&
286            !strcasecompare(imap->custom, "UID") &&
287            !strcasecompare(imap->custom, "NOOP")))
288           return FALSE;
289         break;
290 
291       case IMAP_SELECT:
292         /* SELECT is special in that its untagged responses do not have a
293            common prefix so accept anything! */
294         break;
295 
296       case IMAP_FETCH:
297         if(!imap_matchresp(line, len, "FETCH"))
298           return FALSE;
299         break;
300 
301       case IMAP_SEARCH:
302         if(!imap_matchresp(line, len, "SEARCH"))
303           return FALSE;
304         break;
305 
306       /* Ignore other untagged responses */
307       default:
308         return FALSE;
309     }
310 
311     *resp = '*';
312     return TRUE;
313   }
314 
315   /* Do we have a continuation response? This should be a + symbol followed by
316      a space and optionally some text as per RFC-3501 for the AUTHENTICATE and
317      APPEND commands and as outlined in Section 4. Examples of RFC-4959 but
318      some e-mail servers ignore this and only send a single + instead. */
319   if(imap && !imap->custom && ((len == 3 && line[0] == '+') ||
320      (len >= 2 && !memcmp("+ ", line, 2)))) {
321     switch(imapc->state) {
322       /* States which are interested in continuation responses */
323       case IMAP_AUTHENTICATE:
324       case IMAP_APPEND:
325         *resp = '+';
326         break;
327 
328       default:
329         failf(conn->data, "Unexpected continuation response");
330         *resp = -1;
331         break;
332     }
333 
334     return TRUE;
335   }
336 
337   return FALSE; /* Nothing for us */
338 }
339 
340 /***********************************************************************
341  *
342  * imap_get_message()
343  *
344  * Gets the authentication message from the response buffer.
345  */
imap_get_message(char * buffer,char ** outptr)346 static void imap_get_message(char *buffer, char **outptr)
347 {
348   size_t len = strlen(buffer);
349   char *message = NULL;
350 
351   if(len > 2) {
352     /* Find the start of the message */
353     len -= 2;
354     for(message = buffer + 2; *message == ' ' || *message == '\t';
355         message++, len--)
356       ;
357 
358     /* Find the end of the message */
359     for(; len--;)
360       if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
361          message[len] != '\t')
362         break;
363 
364     /* Terminate the message */
365     if(++len) {
366       message[len] = '\0';
367     }
368   }
369   else
370     /* junk input => zero length output */
371     message = &buffer[len];
372 
373   *outptr = message;
374 }
375 
376 /***********************************************************************
377  *
378  * state()
379  *
380  * This is the ONLY way to change IMAP state!
381  */
state(struct connectdata * conn,imapstate newstate)382 static void state(struct connectdata *conn, imapstate newstate)
383 {
384   struct imap_conn *imapc = &conn->proto.imapc;
385 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
386   /* for debug purposes */
387   static const char * const names[]={
388     "STOP",
389     "SERVERGREET",
390     "CAPABILITY",
391     "STARTTLS",
392     "UPGRADETLS",
393     "AUTHENTICATE",
394     "LOGIN",
395     "LIST",
396     "SELECT",
397     "FETCH",
398     "FETCH_FINAL",
399     "APPEND",
400     "APPEND_FINAL",
401     "SEARCH",
402     "LOGOUT",
403     /* LAST */
404   };
405 
406   if(imapc->state != newstate)
407     infof(conn->data, "IMAP %p state change from %s to %s\n",
408           (void *)imapc, names[imapc->state], names[newstate]);
409 #endif
410 
411   imapc->state = newstate;
412 }
413 
414 /***********************************************************************
415  *
416  * imap_perform_capability()
417  *
418  * Sends the CAPABILITY command in order to obtain a list of server side
419  * supported capabilities.
420  */
imap_perform_capability(struct connectdata * conn)421 static CURLcode imap_perform_capability(struct connectdata *conn)
422 {
423   CURLcode result = CURLE_OK;
424   struct imap_conn *imapc = &conn->proto.imapc;
425   imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
426   imapc->sasl.authused = SASL_AUTH_NONE;  /* Clear the auth. mechanism used */
427   imapc->tls_supported = FALSE;           /* Clear the TLS capability */
428 
429   /* Send the CAPABILITY command */
430   result = imap_sendf(conn, "CAPABILITY");
431 
432   if(!result)
433     state(conn, IMAP_CAPABILITY);
434 
435   return result;
436 }
437 
438 /***********************************************************************
439  *
440  * imap_perform_starttls()
441  *
442  * Sends the STARTTLS command to start the upgrade to TLS.
443  */
imap_perform_starttls(struct connectdata * conn)444 static CURLcode imap_perform_starttls(struct connectdata *conn)
445 {
446   /* Send the STARTTLS command */
447   CURLcode result = imap_sendf(conn, "STARTTLS");
448 
449   if(!result)
450     state(conn, IMAP_STARTTLS);
451 
452   return result;
453 }
454 
455 /***********************************************************************
456  *
457  * imap_perform_upgrade_tls()
458  *
459  * Performs the upgrade to TLS.
460  */
imap_perform_upgrade_tls(struct connectdata * conn)461 static CURLcode imap_perform_upgrade_tls(struct connectdata *conn)
462 {
463   /* Start the SSL connection */
464   struct imap_conn *imapc = &conn->proto.imapc;
465   CURLcode result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET,
466                                                  &imapc->ssldone);
467 
468   if(!result) {
469     if(imapc->state != IMAP_UPGRADETLS)
470       state(conn, IMAP_UPGRADETLS);
471 
472     if(imapc->ssldone) {
473       imap_to_imaps(conn);
474       result = imap_perform_capability(conn);
475     }
476   }
477 
478   return result;
479 }
480 
481 /***********************************************************************
482  *
483  * imap_perform_login()
484  *
485  * Sends a clear text LOGIN command to authenticate with.
486  */
imap_perform_login(struct connectdata * conn)487 static CURLcode imap_perform_login(struct connectdata *conn)
488 {
489   CURLcode result = CURLE_OK;
490   char *user;
491   char *passwd;
492 
493   /* Check we have a username and password to authenticate with and end the
494      connect phase if we don't */
495   if(!conn->bits.user_passwd) {
496     state(conn, IMAP_STOP);
497 
498     return result;
499   }
500 
501   /* Make sure the username and password are in the correct atom format */
502   user = imap_atom(conn->user, false);
503   passwd = imap_atom(conn->passwd, false);
504 
505   /* Send the LOGIN command */
506   result = imap_sendf(conn, "LOGIN %s %s", user ? user : "",
507                       passwd ? passwd : "");
508 
509   free(user);
510   free(passwd);
511 
512   if(!result)
513     state(conn, IMAP_LOGIN);
514 
515   return result;
516 }
517 
518 /***********************************************************************
519  *
520  * imap_perform_authenticate()
521  *
522  * Sends an AUTHENTICATE command allowing the client to login with the given
523  * SASL authentication mechanism.
524  */
imap_perform_authenticate(struct connectdata * conn,const char * mech,const char * initresp)525 static CURLcode imap_perform_authenticate(struct connectdata *conn,
526                                           const char *mech,
527                                           const char *initresp)
528 {
529   CURLcode result = CURLE_OK;
530 
531   if(initresp) {
532     /* Send the AUTHENTICATE command with the initial response */
533     result = imap_sendf(conn, "AUTHENTICATE %s %s", mech, initresp);
534   }
535   else {
536     /* Send the AUTHENTICATE command */
537     result = imap_sendf(conn, "AUTHENTICATE %s", mech);
538   }
539 
540   return result;
541 }
542 
543 /***********************************************************************
544  *
545  * imap_continue_authenticate()
546  *
547  * Sends SASL continuation data or cancellation.
548  */
imap_continue_authenticate(struct connectdata * conn,const char * resp)549 static CURLcode imap_continue_authenticate(struct connectdata *conn,
550                                            const char *resp)
551 {
552   struct imap_conn *imapc = &conn->proto.imapc;
553 
554   return Curl_pp_sendf(&imapc->pp, "%s", resp);
555 }
556 
557 /***********************************************************************
558  *
559  * imap_perform_authentication()
560  *
561  * Initiates the authentication sequence, with the appropriate SASL
562  * authentication mechanism, falling back to clear text should a common
563  * mechanism not be available between the client and server.
564  */
imap_perform_authentication(struct connectdata * conn)565 static CURLcode imap_perform_authentication(struct connectdata *conn)
566 {
567   CURLcode result = CURLE_OK;
568   struct imap_conn *imapc = &conn->proto.imapc;
569   saslprogress progress;
570 
571   /* Check if already authenticated OR if there is enough data to authenticate
572      with and end the connect phase if we don't */
573   if(imapc->preauth ||
574      !Curl_sasl_can_authenticate(&imapc->sasl, conn)) {
575     state(conn, IMAP_STOP);
576     return result;
577   }
578 
579   /* Calculate the SASL login details */
580   result = Curl_sasl_start(&imapc->sasl, conn, imapc->ir_supported, &progress);
581 
582   if(!result) {
583     if(progress == SASL_INPROGRESS)
584       state(conn, IMAP_AUTHENTICATE);
585     else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
586       /* Perform clear text authentication */
587       result = imap_perform_login(conn);
588     else {
589       /* Other mechanisms not supported */
590       infof(conn->data, "No known authentication mechanisms supported!\n");
591       result = CURLE_LOGIN_DENIED;
592     }
593   }
594 
595   return result;
596 }
597 
598 /***********************************************************************
599  *
600  * imap_perform_list()
601  *
602  * Sends a LIST command or an alternative custom request.
603  */
imap_perform_list(struct connectdata * conn)604 static CURLcode imap_perform_list(struct connectdata *conn)
605 {
606   CURLcode result = CURLE_OK;
607   struct Curl_easy *data = conn->data;
608   struct IMAP *imap = data->req.protop;
609 
610   if(imap->custom)
611     /* Send the custom request */
612     result = imap_sendf(conn, "%s%s", imap->custom,
613                         imap->custom_params ? imap->custom_params : "");
614   else {
615     /* Make sure the mailbox is in the correct atom format if necessary */
616     char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true)
617                                   : strdup("");
618     if(!mailbox)
619       return CURLE_OUT_OF_MEMORY;
620 
621     /* Send the LIST command */
622     result = imap_sendf(conn, "LIST \"%s\" *", mailbox);
623 
624     free(mailbox);
625   }
626 
627   if(!result)
628     state(conn, IMAP_LIST);
629 
630   return result;
631 }
632 
633 /***********************************************************************
634  *
635  * imap_perform_select()
636  *
637  * Sends a SELECT command to ask the server to change the selected mailbox.
638  */
imap_perform_select(struct connectdata * conn)639 static CURLcode imap_perform_select(struct connectdata *conn)
640 {
641   CURLcode result = CURLE_OK;
642   struct Curl_easy *data = conn->data;
643   struct IMAP *imap = data->req.protop;
644   struct imap_conn *imapc = &conn->proto.imapc;
645   char *mailbox;
646 
647   /* Invalidate old information as we are switching mailboxes */
648   Curl_safefree(imapc->mailbox);
649   Curl_safefree(imapc->mailbox_uidvalidity);
650 
651   /* Check we have a mailbox */
652   if(!imap->mailbox) {
653     failf(conn->data, "Cannot SELECT without a mailbox.");
654     return CURLE_URL_MALFORMAT;
655   }
656 
657   /* Make sure the mailbox is in the correct atom format */
658   mailbox = imap_atom(imap->mailbox, false);
659   if(!mailbox)
660     return CURLE_OUT_OF_MEMORY;
661 
662   /* Send the SELECT command */
663   result = imap_sendf(conn, "SELECT %s", mailbox);
664 
665   free(mailbox);
666 
667   if(!result)
668     state(conn, IMAP_SELECT);
669 
670   return result;
671 }
672 
673 /***********************************************************************
674  *
675  * imap_perform_fetch()
676  *
677  * Sends a FETCH command to initiate the download of a message.
678  */
imap_perform_fetch(struct connectdata * conn)679 static CURLcode imap_perform_fetch(struct connectdata *conn)
680 {
681   CURLcode result = CURLE_OK;
682   struct IMAP *imap = conn->data->req.protop;
683   /* Check we have a UID */
684   if(imap->uid) {
685 
686     /* Send the FETCH command */
687     if(imap->partial)
688       result = imap_sendf(conn, "UID FETCH %s BODY[%s]<%s>",
689                             imap->uid,
690                             imap->section ? imap->section : "",
691                             imap->partial);
692     else
693       result = imap_sendf(conn, "UID FETCH %s BODY[%s]",
694                             imap->uid,
695                             imap->section ? imap->section : "");
696   }
697   else if(imap->mindex) {
698 
699     /* Send the FETCH command */
700     if(imap->partial)
701       result = imap_sendf(conn, "FETCH %s BODY[%s]<%s>",
702                             imap->mindex,
703                             imap->section ? imap->section : "",
704                             imap->partial);
705     else
706       result = imap_sendf(conn, "FETCH %s BODY[%s]",
707                             imap->mindex,
708                             imap->section ? imap->section : "");
709   }
710   else {
711         failf(conn->data, "Cannot FETCH without a UID.");
712         return CURLE_URL_MALFORMAT;
713   }
714   if(!result)
715     state(conn, IMAP_FETCH);
716 
717   return result;
718 }
719 
720 /***********************************************************************
721  *
722  * imap_perform_append()
723  *
724  * Sends an APPEND command to initiate the upload of a message.
725  */
imap_perform_append(struct connectdata * conn)726 static CURLcode imap_perform_append(struct connectdata *conn)
727 {
728   CURLcode result = CURLE_OK;
729   struct Curl_easy *data = conn->data;
730   struct IMAP *imap = data->req.protop;
731   char *mailbox;
732 
733   /* Check we have a mailbox */
734   if(!imap->mailbox) {
735     failf(data, "Cannot APPEND without a mailbox.");
736     return CURLE_URL_MALFORMAT;
737   }
738 
739   /* Prepare the mime data if some. */
740   if(data->set.mimepost.kind != MIMEKIND_NONE) {
741     /* Use the whole structure as data. */
742     data->set.mimepost.flags &= ~MIME_BODY_ONLY;
743 
744     /* Add external headers and mime version. */
745     curl_mime_headers(&data->set.mimepost, data->set.headers, 0);
746     result = Curl_mime_prepare_headers(&data->set.mimepost, NULL,
747                                        NULL, MIMESTRATEGY_MAIL);
748 
749     if(!result)
750       if(!Curl_checkheaders(conn, "Mime-Version"))
751         result = Curl_mime_add_header(&data->set.mimepost.curlheaders,
752                                       "Mime-Version: 1.0");
753 
754     /* Make sure we will read the entire mime structure. */
755     if(!result)
756       result = Curl_mime_rewind(&data->set.mimepost);
757 
758     if(result)
759       return result;
760 
761     data->state.infilesize = Curl_mime_size(&data->set.mimepost);
762 
763     /* Read from mime structure. */
764     data->state.fread_func = (curl_read_callback) Curl_mime_read;
765     data->state.in = (void *) &data->set.mimepost;
766   }
767 
768   /* Check we know the size of the upload */
769   if(data->state.infilesize < 0) {
770     failf(data, "Cannot APPEND with unknown input file size\n");
771     return CURLE_UPLOAD_FAILED;
772   }
773 
774   /* Make sure the mailbox is in the correct atom format */
775   mailbox = imap_atom(imap->mailbox, false);
776   if(!mailbox)
777     return CURLE_OUT_OF_MEMORY;
778 
779   /* Send the APPEND command */
780   result = imap_sendf(conn, "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}",
781                       mailbox, data->state.infilesize);
782 
783   free(mailbox);
784 
785   if(!result)
786     state(conn, IMAP_APPEND);
787 
788   return result;
789 }
790 
791 /***********************************************************************
792  *
793  * imap_perform_search()
794  *
795  * Sends a SEARCH command.
796  */
imap_perform_search(struct connectdata * conn)797 static CURLcode imap_perform_search(struct connectdata *conn)
798 {
799   CURLcode result = CURLE_OK;
800   struct IMAP *imap = conn->data->req.protop;
801 
802   /* Check we have a query string */
803   if(!imap->query) {
804     failf(conn->data, "Cannot SEARCH without a query string.");
805     return CURLE_URL_MALFORMAT;
806   }
807 
808   /* Send the SEARCH command */
809   result = imap_sendf(conn, "SEARCH %s", imap->query);
810 
811   if(!result)
812     state(conn, IMAP_SEARCH);
813 
814   return result;
815 }
816 
817 /***********************************************************************
818  *
819  * imap_perform_logout()
820  *
821  * Performs the logout action prior to sclose() being called.
822  */
imap_perform_logout(struct connectdata * conn)823 static CURLcode imap_perform_logout(struct connectdata *conn)
824 {
825   /* Send the LOGOUT command */
826   CURLcode result = imap_sendf(conn, "LOGOUT");
827 
828   if(!result)
829     state(conn, IMAP_LOGOUT);
830 
831   return result;
832 }
833 
834 /* For the initial server greeting */
imap_state_servergreet_resp(struct connectdata * conn,int imapcode,imapstate instate)835 static CURLcode imap_state_servergreet_resp(struct connectdata *conn,
836                                             int imapcode,
837                                             imapstate instate)
838 {
839   struct Curl_easy *data = conn->data;
840   (void)instate; /* no use for this yet */
841 
842   if(imapcode == IMAP_RESP_PREAUTH) {
843     /* PREAUTH */
844     struct imap_conn *imapc = &conn->proto.imapc;
845     imapc->preauth = TRUE;
846     infof(data, "PREAUTH connection, already authenticated!\n");
847   }
848   else if(imapcode != IMAP_RESP_OK) {
849     failf(data, "Got unexpected imap-server response");
850     return CURLE_WEIRD_SERVER_REPLY;
851   }
852 
853   return imap_perform_capability(conn);
854 }
855 
856 /* For CAPABILITY responses */
imap_state_capability_resp(struct connectdata * conn,int imapcode,imapstate instate)857 static CURLcode imap_state_capability_resp(struct connectdata *conn,
858                                            int imapcode,
859                                            imapstate instate)
860 {
861   CURLcode result = CURLE_OK;
862   struct Curl_easy *data = conn->data;
863   struct imap_conn *imapc = &conn->proto.imapc;
864   const char *line = data->state.buffer;
865 
866   (void)instate; /* no use for this yet */
867 
868   /* Do we have a untagged response? */
869   if(imapcode == '*') {
870     line += 2;
871 
872     /* Loop through the data line */
873     for(;;) {
874       size_t wordlen;
875       while(*line &&
876             (*line == ' ' || *line == '\t' ||
877               *line == '\r' || *line == '\n')) {
878 
879         line++;
880       }
881 
882       if(!*line)
883         break;
884 
885       /* Extract the word */
886       for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
887             line[wordlen] != '\t' && line[wordlen] != '\r' &&
888             line[wordlen] != '\n';)
889         wordlen++;
890 
891       /* Does the server support the STARTTLS capability? */
892       if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
893         imapc->tls_supported = TRUE;
894 
895       /* Has the server explicitly disabled clear text authentication? */
896       else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
897         imapc->login_disabled = TRUE;
898 
899       /* Does the server support the SASL-IR capability? */
900       else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
901         imapc->ir_supported = TRUE;
902 
903       /* Do we have a SASL based authentication mechanism? */
904       else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
905         size_t llen;
906         unsigned int mechbit;
907 
908         line += 5;
909         wordlen -= 5;
910 
911         /* Test the word for a matching authentication mechanism */
912         mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
913         if(mechbit && llen == wordlen)
914           imapc->sasl.authmechs |= mechbit;
915       }
916 
917       line += wordlen;
918     }
919   }
920   else if(imapcode == IMAP_RESP_OK) {
921     if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) {
922       /* We don't have a SSL/TLS connection yet, but SSL is requested */
923       if(imapc->tls_supported)
924         /* Switch to TLS connection now */
925         result = imap_perform_starttls(conn);
926       else if(data->set.use_ssl == CURLUSESSL_TRY)
927         /* Fallback and carry on with authentication */
928         result = imap_perform_authentication(conn);
929       else {
930         failf(data, "STARTTLS not supported.");
931         result = CURLE_USE_SSL_FAILED;
932       }
933     }
934     else
935       result = imap_perform_authentication(conn);
936   }
937   else
938     result = imap_perform_authentication(conn);
939 
940   return result;
941 }
942 
943 /* For STARTTLS responses */
imap_state_starttls_resp(struct connectdata * conn,int imapcode,imapstate instate)944 static CURLcode imap_state_starttls_resp(struct connectdata *conn,
945                                          int imapcode,
946                                          imapstate instate)
947 {
948   CURLcode result = CURLE_OK;
949   struct Curl_easy *data = conn->data;
950 
951   (void)instate; /* no use for this yet */
952 
953   if(imapcode != IMAP_RESP_OK) {
954     if(data->set.use_ssl != CURLUSESSL_TRY) {
955       failf(data, "STARTTLS denied");
956       result = CURLE_USE_SSL_FAILED;
957     }
958     else
959       result = imap_perform_authentication(conn);
960   }
961   else
962     result = imap_perform_upgrade_tls(conn);
963 
964   return result;
965 }
966 
967 /* For SASL authentication responses */
imap_state_auth_resp(struct connectdata * conn,int imapcode,imapstate instate)968 static CURLcode imap_state_auth_resp(struct connectdata *conn,
969                                      int imapcode,
970                                      imapstate instate)
971 {
972   CURLcode result = CURLE_OK;
973   struct Curl_easy *data = conn->data;
974   struct imap_conn *imapc = &conn->proto.imapc;
975   saslprogress progress;
976 
977   (void)instate; /* no use for this yet */
978 
979   result = Curl_sasl_continue(&imapc->sasl, conn, imapcode, &progress);
980   if(!result)
981     switch(progress) {
982     case SASL_DONE:
983       state(conn, IMAP_STOP);  /* Authenticated */
984       break;
985     case SASL_IDLE:            /* No mechanism left after cancellation */
986       if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
987         /* Perform clear text authentication */
988         result = imap_perform_login(conn);
989       else {
990         failf(data, "Authentication cancelled");
991         result = CURLE_LOGIN_DENIED;
992       }
993       break;
994     default:
995       break;
996     }
997 
998   return result;
999 }
1000 
1001 /* For LOGIN responses */
imap_state_login_resp(struct connectdata * conn,int imapcode,imapstate instate)1002 static CURLcode imap_state_login_resp(struct connectdata *conn,
1003                                       int imapcode,
1004                                       imapstate instate)
1005 {
1006   CURLcode result = CURLE_OK;
1007   struct Curl_easy *data = conn->data;
1008 
1009   (void)instate; /* no use for this yet */
1010 
1011   if(imapcode != IMAP_RESP_OK) {
1012     failf(data, "Access denied. %c", imapcode);
1013     result = CURLE_LOGIN_DENIED;
1014   }
1015   else
1016     /* End of connect phase */
1017     state(conn, IMAP_STOP);
1018 
1019   return result;
1020 }
1021 
1022 /* For LIST and SEARCH responses */
imap_state_listsearch_resp(struct connectdata * conn,int imapcode,imapstate instate)1023 static CURLcode imap_state_listsearch_resp(struct connectdata *conn,
1024                                            int imapcode,
1025                                            imapstate instate)
1026 {
1027   CURLcode result = CURLE_OK;
1028   char *line = conn->data->state.buffer;
1029   size_t len = strlen(line);
1030 
1031   (void)instate; /* No use for this yet */
1032 
1033   if(imapcode == '*') {
1034     /* Temporarily add the LF character back and send as body to the client */
1035     line[len] = '\n';
1036     result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1);
1037     line[len] = '\0';
1038   }
1039   else if(imapcode != IMAP_RESP_OK)
1040     result = CURLE_QUOTE_ERROR;
1041   else
1042     /* End of DO phase */
1043     state(conn, IMAP_STOP);
1044 
1045   return result;
1046 }
1047 
1048 /* For SELECT responses */
imap_state_select_resp(struct connectdata * conn,int imapcode,imapstate instate)1049 static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode,
1050                                        imapstate instate)
1051 {
1052   CURLcode result = CURLE_OK;
1053   struct Curl_easy *data = conn->data;
1054   struct IMAP *imap = conn->data->req.protop;
1055   struct imap_conn *imapc = &conn->proto.imapc;
1056   const char *line = data->state.buffer;
1057 
1058   (void)instate; /* no use for this yet */
1059 
1060   if(imapcode == '*') {
1061     /* See if this is an UIDVALIDITY response */
1062     char tmp[20];
1063     if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1064       Curl_safefree(imapc->mailbox_uidvalidity);
1065       imapc->mailbox_uidvalidity = strdup(tmp);
1066     }
1067   }
1068   else if(imapcode == IMAP_RESP_OK) {
1069     /* Check if the UIDVALIDITY has been specified and matches */
1070     if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1071        !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1072       failf(conn->data, "Mailbox UIDVALIDITY has changed");
1073       result = CURLE_REMOTE_FILE_NOT_FOUND;
1074     }
1075     else {
1076       /* Note the currently opened mailbox on this connection */
1077       imapc->mailbox = strdup(imap->mailbox);
1078 
1079       if(imap->custom)
1080         result = imap_perform_list(conn);
1081       else if(imap->query)
1082         result = imap_perform_search(conn);
1083       else
1084         result = imap_perform_fetch(conn);
1085     }
1086   }
1087   else {
1088     failf(data, "Select failed");
1089     result = CURLE_LOGIN_DENIED;
1090   }
1091 
1092   return result;
1093 }
1094 
1095 /* For the (first line of the) FETCH responses */
imap_state_fetch_resp(struct connectdata * conn,int imapcode,imapstate instate)1096 static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode,
1097                                       imapstate instate)
1098 {
1099   CURLcode result = CURLE_OK;
1100   struct Curl_easy *data = conn->data;
1101   struct imap_conn *imapc = &conn->proto.imapc;
1102   struct pingpong *pp = &imapc->pp;
1103   const char *ptr = data->state.buffer;
1104   bool parsed = FALSE;
1105   curl_off_t size = 0;
1106 
1107   (void)instate; /* no use for this yet */
1108 
1109   if(imapcode != '*') {
1110     Curl_pgrsSetDownloadSize(data, -1);
1111     state(conn, IMAP_STOP);
1112     return CURLE_REMOTE_FILE_NOT_FOUND;
1113   }
1114 
1115   /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1116      the continuation data contained within the curly brackets */
1117   while(*ptr && (*ptr != '{'))
1118     ptr++;
1119 
1120   if(*ptr == '{') {
1121     char *endptr;
1122     if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) {
1123       if(endptr - ptr > 1 && endptr[0] == '}' &&
1124          endptr[1] == '\r' && endptr[2] == '\0')
1125         parsed = TRUE;
1126     }
1127   }
1128 
1129   if(parsed) {
1130     infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download\n",
1131           size);
1132     Curl_pgrsSetDownloadSize(data, size);
1133 
1134     if(pp->cache) {
1135       /* At this point there is a bunch of data in the header "cache" that is
1136          actually body content, send it as body and then skip it. Do note
1137          that there may even be additional "headers" after the body. */
1138       size_t chunk = pp->cache_size;
1139 
1140       if(chunk > (size_t)size)
1141         /* The conversion from curl_off_t to size_t is always fine here */
1142         chunk = (size_t)size;
1143 
1144       if(!chunk) {
1145         /* no size, we're done with the data */
1146         state(conn, IMAP_STOP);
1147         return CURLE_OK;
1148       }
1149       result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk);
1150       if(result)
1151         return result;
1152 
1153       data->req.bytecount += chunk;
1154 
1155       infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU
1156             " bytes are left for transfer\n", chunk, size - chunk);
1157 
1158       /* Have we used the entire cache or just part of it?*/
1159       if(pp->cache_size > chunk) {
1160         /* Only part of it so shrink the cache to fit the trailing data */
1161         memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1162         pp->cache_size -= chunk;
1163       }
1164       else {
1165         /* Free the cache */
1166         Curl_safefree(pp->cache);
1167 
1168         /* Reset the cache size */
1169         pp->cache_size = 0;
1170       }
1171     }
1172 
1173     if(data->req.bytecount == size)
1174       /* The entire data is already transferred! */
1175       Curl_setup_transfer(data, -1, -1, FALSE, -1);
1176     else {
1177       /* IMAP download */
1178       data->req.maxdownload = size;
1179       Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1);
1180     }
1181   }
1182   else {
1183     /* We don't know how to parse this line */
1184     failf(pp->conn->data, "Failed to parse FETCH response.");
1185     result = CURLE_WEIRD_SERVER_REPLY;
1186   }
1187 
1188   /* End of DO phase */
1189   state(conn, IMAP_STOP);
1190 
1191   return result;
1192 }
1193 
1194 /* For final FETCH responses performed after the download */
imap_state_fetch_final_resp(struct connectdata * conn,int imapcode,imapstate instate)1195 static CURLcode imap_state_fetch_final_resp(struct connectdata *conn,
1196                                             int imapcode,
1197                                             imapstate instate)
1198 {
1199   CURLcode result = CURLE_OK;
1200 
1201   (void)instate; /* No use for this yet */
1202 
1203   if(imapcode != IMAP_RESP_OK)
1204     result = CURLE_WEIRD_SERVER_REPLY;
1205   else
1206     /* End of DONE phase */
1207     state(conn, IMAP_STOP);
1208 
1209   return result;
1210 }
1211 
1212 /* For APPEND responses */
imap_state_append_resp(struct connectdata * conn,int imapcode,imapstate instate)1213 static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode,
1214                                        imapstate instate)
1215 {
1216   CURLcode result = CURLE_OK;
1217   struct Curl_easy *data = conn->data;
1218 
1219   (void)instate; /* No use for this yet */
1220 
1221   if(imapcode != '+') {
1222     result = CURLE_UPLOAD_FAILED;
1223   }
1224   else {
1225     /* Set the progress upload size */
1226     Curl_pgrsSetUploadSize(data, data->state.infilesize);
1227 
1228     /* IMAP upload */
1229     Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET);
1230 
1231     /* End of DO phase */
1232     state(conn, IMAP_STOP);
1233   }
1234 
1235   return result;
1236 }
1237 
1238 /* For final APPEND responses performed after the upload */
imap_state_append_final_resp(struct connectdata * conn,int imapcode,imapstate instate)1239 static CURLcode imap_state_append_final_resp(struct connectdata *conn,
1240                                              int imapcode,
1241                                              imapstate instate)
1242 {
1243   CURLcode result = CURLE_OK;
1244 
1245   (void)instate; /* No use for this yet */
1246 
1247   if(imapcode != IMAP_RESP_OK)
1248     result = CURLE_UPLOAD_FAILED;
1249   else
1250     /* End of DONE phase */
1251     state(conn, IMAP_STOP);
1252 
1253   return result;
1254 }
1255 
imap_statemach_act(struct connectdata * conn)1256 static CURLcode imap_statemach_act(struct connectdata *conn)
1257 {
1258   CURLcode result = CURLE_OK;
1259   curl_socket_t sock = conn->sock[FIRSTSOCKET];
1260   int imapcode;
1261   struct imap_conn *imapc = &conn->proto.imapc;
1262   struct pingpong *pp = &imapc->pp;
1263   size_t nread = 0;
1264 
1265   /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1266   if(imapc->state == IMAP_UPGRADETLS)
1267     return imap_perform_upgrade_tls(conn);
1268 
1269   /* Flush any data that needs to be sent */
1270   if(pp->sendleft)
1271     return Curl_pp_flushsend(pp);
1272 
1273   do {
1274     /* Read the response from the server */
1275     result = Curl_pp_readresp(sock, pp, &imapcode, &nread);
1276     if(result)
1277       return result;
1278 
1279     /* Was there an error parsing the response line? */
1280     if(imapcode == -1)
1281       return CURLE_WEIRD_SERVER_REPLY;
1282 
1283     if(!imapcode)
1284       break;
1285 
1286     /* We have now received a full IMAP server response */
1287     switch(imapc->state) {
1288     case IMAP_SERVERGREET:
1289       result = imap_state_servergreet_resp(conn, imapcode, imapc->state);
1290       break;
1291 
1292     case IMAP_CAPABILITY:
1293       result = imap_state_capability_resp(conn, imapcode, imapc->state);
1294       break;
1295 
1296     case IMAP_STARTTLS:
1297       result = imap_state_starttls_resp(conn, imapcode, imapc->state);
1298       break;
1299 
1300     case IMAP_AUTHENTICATE:
1301       result = imap_state_auth_resp(conn, imapcode, imapc->state);
1302       break;
1303 
1304     case IMAP_LOGIN:
1305       result = imap_state_login_resp(conn, imapcode, imapc->state);
1306       break;
1307 
1308     case IMAP_LIST:
1309     case IMAP_SEARCH:
1310       result = imap_state_listsearch_resp(conn, imapcode, imapc->state);
1311       break;
1312 
1313     case IMAP_SELECT:
1314       result = imap_state_select_resp(conn, imapcode, imapc->state);
1315       break;
1316 
1317     case IMAP_FETCH:
1318       result = imap_state_fetch_resp(conn, imapcode, imapc->state);
1319       break;
1320 
1321     case IMAP_FETCH_FINAL:
1322       result = imap_state_fetch_final_resp(conn, imapcode, imapc->state);
1323       break;
1324 
1325     case IMAP_APPEND:
1326       result = imap_state_append_resp(conn, imapcode, imapc->state);
1327       break;
1328 
1329     case IMAP_APPEND_FINAL:
1330       result = imap_state_append_final_resp(conn, imapcode, imapc->state);
1331       break;
1332 
1333     case IMAP_LOGOUT:
1334       /* fallthrough, just stop! */
1335     default:
1336       /* internal error */
1337       state(conn, IMAP_STOP);
1338       break;
1339     }
1340   } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1341 
1342   return result;
1343 }
1344 
1345 /* Called repeatedly until done from multi.c */
imap_multi_statemach(struct connectdata * conn,bool * done)1346 static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done)
1347 {
1348   CURLcode result = CURLE_OK;
1349   struct imap_conn *imapc = &conn->proto.imapc;
1350 
1351   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1352     result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone);
1353     if(result || !imapc->ssldone)
1354       return result;
1355   }
1356 
1357   result = Curl_pp_statemach(&imapc->pp, FALSE, FALSE);
1358   *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1359 
1360   return result;
1361 }
1362 
imap_block_statemach(struct connectdata * conn,bool disconnecting)1363 static CURLcode imap_block_statemach(struct connectdata *conn,
1364                                      bool disconnecting)
1365 {
1366   CURLcode result = CURLE_OK;
1367   struct imap_conn *imapc = &conn->proto.imapc;
1368 
1369   while(imapc->state != IMAP_STOP && !result)
1370     result = Curl_pp_statemach(&imapc->pp, TRUE, disconnecting);
1371 
1372   return result;
1373 }
1374 
1375 /* Allocate and initialize the struct IMAP for the current Curl_easy if
1376    required */
imap_init(struct connectdata * conn)1377 static CURLcode imap_init(struct connectdata *conn)
1378 {
1379   CURLcode result = CURLE_OK;
1380   struct Curl_easy *data = conn->data;
1381   struct IMAP *imap;
1382 
1383   imap = data->req.protop = calloc(sizeof(struct IMAP), 1);
1384   if(!imap)
1385     result = CURLE_OUT_OF_MEMORY;
1386 
1387   return result;
1388 }
1389 
1390 /* For the IMAP "protocol connect" and "doing" phases only */
imap_getsock(struct connectdata * conn,curl_socket_t * socks)1391 static int imap_getsock(struct connectdata *conn, curl_socket_t *socks)
1392 {
1393   return Curl_pp_getsock(&conn->proto.imapc.pp, socks);
1394 }
1395 
1396 /***********************************************************************
1397  *
1398  * imap_connect()
1399  *
1400  * This function should do everything that is to be considered a part of the
1401  * connection phase.
1402  *
1403  * The variable 'done' points to will be TRUE if the protocol-layer connect
1404  * phase is done when this function returns, or FALSE if not.
1405  */
imap_connect(struct connectdata * conn,bool * done)1406 static CURLcode imap_connect(struct connectdata *conn, bool *done)
1407 {
1408   CURLcode result = CURLE_OK;
1409   struct imap_conn *imapc = &conn->proto.imapc;
1410   struct pingpong *pp = &imapc->pp;
1411 
1412   *done = FALSE; /* default to not done yet */
1413 
1414   /* We always support persistent connections in IMAP */
1415   connkeep(conn, "IMAP default");
1416 
1417   /* Set the default response time-out */
1418   pp->response_time = RESP_TIMEOUT;
1419   pp->statemach_act = imap_statemach_act;
1420   pp->endofresp = imap_endofresp;
1421   pp->conn = conn;
1422 
1423   /* Set the default preferred authentication type and mechanism */
1424   imapc->preftype = IMAP_TYPE_ANY;
1425   Curl_sasl_init(&imapc->sasl, &saslimap);
1426 
1427   /* Initialise the pingpong layer */
1428   Curl_pp_init(pp);
1429 
1430   /* Parse the URL options */
1431   result = imap_parse_url_options(conn);
1432   if(result)
1433     return result;
1434 
1435   /* Start off waiting for the server greeting response */
1436   state(conn, IMAP_SERVERGREET);
1437 
1438   /* Start off with an response id of '*' */
1439   strcpy(imapc->resptag, "*");
1440 
1441   result = imap_multi_statemach(conn, done);
1442 
1443   return result;
1444 }
1445 
1446 /***********************************************************************
1447  *
1448  * imap_done()
1449  *
1450  * The DONE function. This does what needs to be done after a single DO has
1451  * performed.
1452  *
1453  * Input argument is already checked for validity.
1454  */
imap_done(struct connectdata * conn,CURLcode status,bool premature)1455 static CURLcode imap_done(struct connectdata *conn, CURLcode status,
1456                           bool premature)
1457 {
1458   CURLcode result = CURLE_OK;
1459   struct Curl_easy *data = conn->data;
1460   struct IMAP *imap = data->req.protop;
1461 
1462   (void)premature;
1463 
1464   if(!imap)
1465     return CURLE_OK;
1466 
1467   if(status) {
1468     connclose(conn, "IMAP done with bad status"); /* marked for closure */
1469     result = status;         /* use the already set error code */
1470   }
1471   else if(!data->set.connect_only && !imap->custom &&
1472           (imap->uid || imap->mindex || data->set.upload ||
1473           data->set.mimepost.kind != MIMEKIND_NONE)) {
1474     /* Handle responses after FETCH or APPEND transfer has finished */
1475 
1476     if(!data->set.upload && data->set.mimepost.kind == MIMEKIND_NONE)
1477       state(conn, IMAP_FETCH_FINAL);
1478     else {
1479       /* End the APPEND command first by sending an empty line */
1480       result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", "");
1481       if(!result)
1482         state(conn, IMAP_APPEND_FINAL);
1483     }
1484 
1485     /* Run the state-machine */
1486     if(!result)
1487       result = imap_block_statemach(conn, FALSE);
1488   }
1489 
1490   /* Cleanup our per-request based variables */
1491   Curl_safefree(imap->mailbox);
1492   Curl_safefree(imap->uidvalidity);
1493   Curl_safefree(imap->uid);
1494   Curl_safefree(imap->mindex);
1495   Curl_safefree(imap->section);
1496   Curl_safefree(imap->partial);
1497   Curl_safefree(imap->query);
1498   Curl_safefree(imap->custom);
1499   Curl_safefree(imap->custom_params);
1500 
1501   /* Clear the transfer mode for the next request */
1502   imap->transfer = FTPTRANSFER_BODY;
1503 
1504   return result;
1505 }
1506 
1507 /***********************************************************************
1508  *
1509  * imap_perform()
1510  *
1511  * This is the actual DO function for IMAP. Fetch or append a message, or do
1512  * other things according to the options previously setup.
1513  */
imap_perform(struct connectdata * conn,bool * connected,bool * dophase_done)1514 static CURLcode imap_perform(struct connectdata *conn, bool *connected,
1515                              bool *dophase_done)
1516 {
1517   /* This is IMAP and no proxy */
1518   CURLcode result = CURLE_OK;
1519   struct Curl_easy *data = conn->data;
1520   struct IMAP *imap = data->req.protop;
1521   struct imap_conn *imapc = &conn->proto.imapc;
1522   bool selected = FALSE;
1523 
1524   DEBUGF(infof(conn->data, "DO phase starts\n"));
1525 
1526   if(conn->data->set.opt_no_body) {
1527     /* Requested no body means no transfer */
1528     imap->transfer = FTPTRANSFER_INFO;
1529   }
1530 
1531   *dophase_done = FALSE; /* not done yet */
1532 
1533   /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1534      has already been selected on this connection */
1535   if(imap->mailbox && imapc->mailbox &&
1536      strcasecompare(imap->mailbox, imapc->mailbox) &&
1537      (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1538       strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1539     selected = TRUE;
1540 
1541   /* Start the first command in the DO phase */
1542   if(conn->data->set.upload || data->set.mimepost.kind != MIMEKIND_NONE)
1543     /* APPEND can be executed directly */
1544     result = imap_perform_append(conn);
1545   else if(imap->custom && (selected || !imap->mailbox))
1546     /* Custom command using the same mailbox or no mailbox */
1547     result = imap_perform_list(conn);
1548   else if(!imap->custom && selected && (imap->uid || imap->mindex))
1549     /* FETCH from the same mailbox */
1550     result = imap_perform_fetch(conn);
1551   else if(!imap->custom && selected && imap->query)
1552     /* SEARCH the current mailbox */
1553     result = imap_perform_search(conn);
1554   else if(imap->mailbox && !selected &&
1555          (imap->custom || imap->uid || imap->mindex || imap->query))
1556     /* SELECT the mailbox */
1557     result = imap_perform_select(conn);
1558   else
1559     /* LIST */
1560     result = imap_perform_list(conn);
1561 
1562   if(result)
1563     return result;
1564 
1565   /* Run the state-machine */
1566   result = imap_multi_statemach(conn, dophase_done);
1567 
1568   *connected = conn->bits.tcpconnect[FIRSTSOCKET];
1569 
1570   if(*dophase_done)
1571     DEBUGF(infof(conn->data, "DO phase is complete\n"));
1572 
1573   return result;
1574 }
1575 
1576 /***********************************************************************
1577  *
1578  * imap_do()
1579  *
1580  * This function is registered as 'curl_do' function. It decodes the path
1581  * parts etc as a wrapper to the actual DO function (imap_perform).
1582  *
1583  * The input argument is already checked for validity.
1584  */
imap_do(struct connectdata * conn,bool * done)1585 static CURLcode imap_do(struct connectdata *conn, bool *done)
1586 {
1587   CURLcode result = CURLE_OK;
1588 
1589   *done = FALSE; /* default to false */
1590 
1591   /* Parse the URL path */
1592   result = imap_parse_url_path(conn);
1593   if(result)
1594     return result;
1595 
1596   /* Parse the custom request */
1597   result = imap_parse_custom_request(conn);
1598   if(result)
1599     return result;
1600 
1601   result = imap_regular_transfer(conn, done);
1602 
1603   return result;
1604 }
1605 
1606 /***********************************************************************
1607  *
1608  * imap_disconnect()
1609  *
1610  * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1611  * resources. BLOCKING.
1612  */
imap_disconnect(struct connectdata * conn,bool dead_connection)1613 static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection)
1614 {
1615   struct imap_conn *imapc = &conn->proto.imapc;
1616 
1617   /* We cannot send quit unconditionally. If this connection is stale or
1618      bad in any way, sending quit and waiting around here will make the
1619      disconnect wait in vain and cause more problems than we need to. */
1620 
1621   /* The IMAP session may or may not have been allocated/setup at this
1622      point! */
1623   if(!dead_connection && imapc->pp.conn && imapc->pp.conn->bits.protoconnstart)
1624     if(!imap_perform_logout(conn))
1625       (void)imap_block_statemach(conn, TRUE); /* ignore errors on LOGOUT */
1626 
1627   /* Disconnect from the server */
1628   Curl_pp_disconnect(&imapc->pp);
1629 
1630   /* Cleanup the SASL module */
1631   Curl_sasl_cleanup(conn, imapc->sasl.authused);
1632 
1633   /* Cleanup our connection based variables */
1634   Curl_safefree(imapc->mailbox);
1635   Curl_safefree(imapc->mailbox_uidvalidity);
1636 
1637   return CURLE_OK;
1638 }
1639 
1640 /* Call this when the DO phase has completed */
imap_dophase_done(struct connectdata * conn,bool connected)1641 static CURLcode imap_dophase_done(struct connectdata *conn, bool connected)
1642 {
1643   struct IMAP *imap = conn->data->req.protop;
1644 
1645   (void)connected;
1646 
1647   if(imap->transfer != FTPTRANSFER_BODY)
1648     /* no data to transfer */
1649     Curl_setup_transfer(conn->data, -1, -1, FALSE, -1);
1650 
1651   return CURLE_OK;
1652 }
1653 
1654 /* Called from multi.c while DOing */
imap_doing(struct connectdata * conn,bool * dophase_done)1655 static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done)
1656 {
1657   CURLcode result = imap_multi_statemach(conn, dophase_done);
1658 
1659   if(result)
1660     DEBUGF(infof(conn->data, "DO phase failed\n"));
1661   else if(*dophase_done) {
1662     result = imap_dophase_done(conn, FALSE /* not connected */);
1663 
1664     DEBUGF(infof(conn->data, "DO phase is complete\n"));
1665   }
1666 
1667   return result;
1668 }
1669 
1670 /***********************************************************************
1671  *
1672  * imap_regular_transfer()
1673  *
1674  * The input argument is already checked for validity.
1675  *
1676  * Performs all commands done before a regular transfer between a local and a
1677  * remote host.
1678  */
imap_regular_transfer(struct connectdata * conn,bool * dophase_done)1679 static CURLcode imap_regular_transfer(struct connectdata *conn,
1680                                       bool *dophase_done)
1681 {
1682   CURLcode result = CURLE_OK;
1683   bool connected = FALSE;
1684   struct Curl_easy *data = conn->data;
1685 
1686   /* Make sure size is unknown at this point */
1687   data->req.size = -1;
1688 
1689   /* Set the progress data */
1690   Curl_pgrsSetUploadCounter(data, 0);
1691   Curl_pgrsSetDownloadCounter(data, 0);
1692   Curl_pgrsSetUploadSize(data, -1);
1693   Curl_pgrsSetDownloadSize(data, -1);
1694 
1695   /* Carry out the perform */
1696   result = imap_perform(conn, &connected, dophase_done);
1697 
1698   /* Perform post DO phase operations if necessary */
1699   if(!result && *dophase_done)
1700     result = imap_dophase_done(conn, connected);
1701 
1702   return result;
1703 }
1704 
imap_setup_connection(struct connectdata * conn)1705 static CURLcode imap_setup_connection(struct connectdata *conn)
1706 {
1707   /* Initialise the IMAP layer */
1708   CURLcode result = imap_init(conn);
1709   if(result)
1710     return result;
1711 
1712   /* Clear the TLS upgraded flag */
1713   conn->bits.tls_upgraded = FALSE;
1714 
1715   return CURLE_OK;
1716 }
1717 
1718 /***********************************************************************
1719  *
1720  * imap_sendf()
1721  *
1722  * Sends the formatted string as an IMAP command to the server.
1723  *
1724  * Designed to never block.
1725  */
imap_sendf(struct connectdata * conn,const char * fmt,...)1726 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...)
1727 {
1728   CURLcode result = CURLE_OK;
1729   struct imap_conn *imapc = &conn->proto.imapc;
1730   char *taggedfmt;
1731   va_list ap;
1732 
1733   DEBUGASSERT(fmt);
1734 
1735   /* Calculate the next command ID wrapping at 3 digits */
1736   imapc->cmdid = (imapc->cmdid + 1) % 1000;
1737 
1738   /* Calculate the tag based on the connection ID and command ID */
1739   msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1740             'A' + curlx_sltosi(conn->connection_id % 26), imapc->cmdid);
1741 
1742   /* Prefix the format with the tag */
1743   taggedfmt = aprintf("%s %s", imapc->resptag, fmt);
1744   if(!taggedfmt)
1745     return CURLE_OUT_OF_MEMORY;
1746 
1747   /* Send the data with the tag */
1748   va_start(ap, fmt);
1749   result = Curl_pp_vsendf(&imapc->pp, taggedfmt, ap);
1750   va_end(ap);
1751 
1752   free(taggedfmt);
1753 
1754   return result;
1755 }
1756 
1757 /***********************************************************************
1758  *
1759  * imap_atom()
1760  *
1761  * Checks the input string for characters that need escaping and returns an
1762  * atom ready for sending to the server.
1763  *
1764  * The returned string needs to be freed.
1765  *
1766  */
imap_atom(const char * str,bool escape_only)1767 static char *imap_atom(const char *str, bool escape_only)
1768 {
1769   /* !checksrc! disable PARENBRACE 1 */
1770   const char atom_specials[] = "(){ %*]";
1771   const char *p1;
1772   char *p2;
1773   size_t backsp_count = 0;
1774   size_t quote_count = 0;
1775   bool others_exists = FALSE;
1776   size_t newlen = 0;
1777   char *newstr = NULL;
1778 
1779   if(!str)
1780     return NULL;
1781 
1782   /* Look for "atom-specials", counting the backslash and quote characters as
1783      these will need escaping */
1784   p1 = str;
1785   while(*p1) {
1786     if(*p1 == '\\')
1787       backsp_count++;
1788     else if(*p1 == '"')
1789       quote_count++;
1790     else if(!escape_only) {
1791       const char *p3 = atom_specials;
1792 
1793       while(*p3 && !others_exists) {
1794         if(*p1 == *p3)
1795           others_exists = TRUE;
1796 
1797         p3++;
1798       }
1799     }
1800 
1801     p1++;
1802   }
1803 
1804   /* Does the input contain any "atom-special" characters? */
1805   if(!backsp_count && !quote_count && !others_exists)
1806     return strdup(str);
1807 
1808   /* Calculate the new string length */
1809   newlen = strlen(str) + backsp_count + quote_count + (escape_only ? 0 : 2);
1810 
1811   /* Allocate the new string */
1812   newstr = (char *) malloc((newlen + 1) * sizeof(char));
1813   if(!newstr)
1814     return NULL;
1815 
1816   /* Surround the string in quotes if necessary */
1817   p2 = newstr;
1818   if(!escape_only) {
1819     newstr[0] = '"';
1820     newstr[newlen - 1] = '"';
1821     p2++;
1822   }
1823 
1824   /* Copy the string, escaping backslash and quote characters along the way */
1825   p1 = str;
1826   while(*p1) {
1827     if(*p1 == '\\' || *p1 == '"') {
1828       *p2 = '\\';
1829       p2++;
1830     }
1831 
1832    *p2 = *p1;
1833 
1834     p1++;
1835     p2++;
1836   }
1837 
1838   /* Terminate the string */
1839   newstr[newlen] = '\0';
1840 
1841   return newstr;
1842 }
1843 
1844 /***********************************************************************
1845  *
1846  * imap_is_bchar()
1847  *
1848  * Portable test of whether the specified char is a "bchar" as defined in the
1849  * grammar of RFC-5092.
1850  */
imap_is_bchar(char ch)1851 static bool imap_is_bchar(char ch)
1852 {
1853   switch(ch) {
1854     /* bchar */
1855     case ':': case '@': case '/':
1856     /* bchar -> achar */
1857     case '&': case '=':
1858     /* bchar -> achar -> uchar -> unreserved */
1859     case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1860     case '7': case '8': case '9':
1861     case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1862     case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1863     case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1864     case 'V': case 'W': case 'X': case 'Y': case 'Z':
1865     case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1866     case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1867     case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1868     case 'v': case 'w': case 'x': case 'y': case 'z':
1869     case '-': case '.': case '_': case '~':
1870     /* bchar -> achar -> uchar -> sub-delims-sh */
1871     case '!': case '$': case '\'': case '(': case ')': case '*':
1872     case '+': case ',':
1873     /* bchar -> achar -> uchar -> pct-encoded */
1874     case '%': /* HEXDIG chars are already included above */
1875       return true;
1876 
1877     default:
1878       return false;
1879   }
1880 }
1881 
1882 /***********************************************************************
1883  *
1884  * imap_parse_url_options()
1885  *
1886  * Parse the URL login options.
1887  */
imap_parse_url_options(struct connectdata * conn)1888 static CURLcode imap_parse_url_options(struct connectdata *conn)
1889 {
1890   CURLcode result = CURLE_OK;
1891   struct imap_conn *imapc = &conn->proto.imapc;
1892   const char *ptr = conn->options;
1893 
1894   imapc->sasl.resetprefs = TRUE;
1895 
1896   while(!result && ptr && *ptr) {
1897     const char *key = ptr;
1898     const char *value;
1899 
1900     while(*ptr && *ptr != '=')
1901         ptr++;
1902 
1903     value = ptr + 1;
1904 
1905     while(*ptr && *ptr != ';')
1906       ptr++;
1907 
1908     if(strncasecompare(key, "AUTH=", 5))
1909       result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1910                                                value, ptr - value);
1911     else
1912       result = CURLE_URL_MALFORMAT;
1913 
1914     if(*ptr == ';')
1915       ptr++;
1916   }
1917 
1918   switch(imapc->sasl.prefmech) {
1919   case SASL_AUTH_NONE:
1920     imapc->preftype = IMAP_TYPE_NONE;
1921     break;
1922   case SASL_AUTH_DEFAULT:
1923     imapc->preftype = IMAP_TYPE_ANY;
1924     break;
1925   default:
1926     imapc->preftype = IMAP_TYPE_SASL;
1927     break;
1928   }
1929 
1930   return result;
1931 }
1932 
1933 /***********************************************************************
1934  *
1935  * imap_parse_url_path()
1936  *
1937  * Parse the URL path into separate path components.
1938  *
1939  */
imap_parse_url_path(struct connectdata * conn)1940 static CURLcode imap_parse_url_path(struct connectdata *conn)
1941 {
1942   /* The imap struct is already initialised in imap_connect() */
1943   CURLcode result = CURLE_OK;
1944   struct Curl_easy *data = conn->data;
1945   struct IMAP *imap = data->req.protop;
1946   const char *begin = &data->state.up.path[1]; /* skip leading slash */
1947   const char *ptr = begin;
1948 
1949   /* See how much of the URL is a valid path and decode it */
1950   while(imap_is_bchar(*ptr))
1951     ptr++;
1952 
1953   if(ptr != begin) {
1954     /* Remove the trailing slash if present */
1955     const char *end = ptr;
1956     if(end > begin && end[-1] == '/')
1957       end--;
1958 
1959     result = Curl_urldecode(data, begin, end - begin, &imap->mailbox, NULL,
1960                             REJECT_CTRL);
1961     if(result)
1962       return result;
1963   }
1964   else
1965     imap->mailbox = NULL;
1966 
1967   /* There can be any number of parameters in the form ";NAME=VALUE" */
1968   while(*ptr == ';') {
1969     char *name;
1970     char *value;
1971     size_t valuelen;
1972 
1973     /* Find the length of the name parameter */
1974     begin = ++ptr;
1975     while(*ptr && *ptr != '=')
1976       ptr++;
1977 
1978     if(!*ptr)
1979       return CURLE_URL_MALFORMAT;
1980 
1981     /* Decode the name parameter */
1982     result = Curl_urldecode(data, begin, ptr - begin, &name, NULL,
1983                             REJECT_CTRL);
1984     if(result)
1985       return result;
1986 
1987     /* Find the length of the value parameter */
1988     begin = ++ptr;
1989     while(imap_is_bchar(*ptr))
1990       ptr++;
1991 
1992     /* Decode the value parameter */
1993     result = Curl_urldecode(data, begin, ptr - begin, &value, &valuelen,
1994                             REJECT_CTRL);
1995     if(result) {
1996       free(name);
1997       return result;
1998     }
1999 
2000     DEBUGF(infof(conn->data, "IMAP URL parameter '%s' = '%s'\n", name, value));
2001 
2002     /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2003        PARTIAL) stripping of the trailing slash character if it is present.
2004 
2005        Note: Unknown parameters trigger a URL_MALFORMAT error. */
2006     if(strcasecompare(name, "UIDVALIDITY") && !imap->uidvalidity) {
2007       if(valuelen > 0 && value[valuelen - 1] == '/')
2008         value[valuelen - 1] = '\0';
2009 
2010       imap->uidvalidity = value;
2011       value = NULL;
2012     }
2013     else if(strcasecompare(name, "UID") && !imap->uid) {
2014       if(valuelen > 0 && value[valuelen - 1] == '/')
2015         value[valuelen - 1] = '\0';
2016 
2017       imap->uid = value;
2018       value = NULL;
2019     }
2020     else if(strcasecompare(name, "MAILINDEX") && !imap->mindex) {
2021       if(valuelen > 0 && value[valuelen - 1] == '/')
2022         value[valuelen - 1] = '\0';
2023 
2024       imap->mindex = value;
2025       value = NULL;
2026     }
2027     else if(strcasecompare(name, "SECTION") && !imap->section) {
2028       if(valuelen > 0 && value[valuelen - 1] == '/')
2029         value[valuelen - 1] = '\0';
2030 
2031       imap->section = value;
2032       value = NULL;
2033     }
2034     else if(strcasecompare(name, "PARTIAL") && !imap->partial) {
2035       if(valuelen > 0 && value[valuelen - 1] == '/')
2036         value[valuelen - 1] = '\0';
2037 
2038       imap->partial = value;
2039       value = NULL;
2040     }
2041     else {
2042       free(name);
2043       free(value);
2044 
2045       return CURLE_URL_MALFORMAT;
2046     }
2047 
2048     free(name);
2049     free(value);
2050   }
2051 
2052   /* Does the URL contain a query parameter? Only valid when we have a mailbox
2053      and no UID as per RFC-5092 */
2054   if(imap->mailbox && !imap->uid && !imap->mindex) {
2055     /* Get the query parameter, URL decoded */
2056     (void)curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
2057                        CURLU_URLDECODE);
2058   }
2059 
2060   /* Any extra stuff at the end of the URL is an error */
2061   if(*ptr)
2062     return CURLE_URL_MALFORMAT;
2063 
2064   return CURLE_OK;
2065 }
2066 
2067 /***********************************************************************
2068  *
2069  * imap_parse_custom_request()
2070  *
2071  * Parse the custom request.
2072  */
imap_parse_custom_request(struct connectdata * conn)2073 static CURLcode imap_parse_custom_request(struct connectdata *conn)
2074 {
2075   CURLcode result = CURLE_OK;
2076   struct Curl_easy *data = conn->data;
2077   struct IMAP *imap = data->req.protop;
2078   const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2079 
2080   if(custom) {
2081     /* URL decode the custom request */
2082     result = Curl_urldecode(data, custom, 0, &imap->custom, NULL, REJECT_CTRL);
2083 
2084     /* Extract the parameters if specified */
2085     if(!result) {
2086       const char *params = imap->custom;
2087 
2088       while(*params && *params != ' ')
2089         params++;
2090 
2091       if(*params) {
2092         imap->custom_params = strdup(params);
2093         imap->custom[params - imap->custom] = '\0';
2094 
2095         if(!imap->custom_params)
2096           result = CURLE_OUT_OF_MEMORY;
2097       }
2098     }
2099   }
2100 
2101   return result;
2102 }
2103 
2104 #endif /* CURL_DISABLE_IMAP */
2105