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