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