ff25e54e099e3091e820ea2745be62836934d3c1
[platform/upstream/curl.git] / lib / imap.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2016, 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
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, bool escape_only);
102 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...);
103 static CURLcode imap_parse_url_options(struct connectdata *conn);
104 static CURLcode imap_parse_url_path(struct connectdata *conn);
105 static CURLcode imap_parse_custom_request(struct connectdata *conn);
106 static CURLcode imap_perform_authenticate(struct connectdata *conn,
107                                           const char *mech,
108                                           const char *initresp);
109 static CURLcode imap_continue_authenticate(struct connectdata *conn,
110                                            const char *resp);
111 static void imap_get_message(char *buffer, char** outptr);
112
113 /*
114  * IMAP protocol handler.
115  */
116
117 const struct Curl_handler Curl_handler_imap = {
118   "IMAP",                           /* scheme */
119   imap_setup_connection,            /* setup_connection */
120   imap_do,                          /* do_it */
121   imap_done,                        /* done */
122   ZERO_NULL,                        /* do_more */
123   imap_connect,                     /* connect_it */
124   imap_multi_statemach,             /* connecting */
125   imap_doing,                       /* doing */
126   imap_getsock,                     /* proto_getsock */
127   imap_getsock,                     /* doing_getsock */
128   ZERO_NULL,                        /* domore_getsock */
129   ZERO_NULL,                        /* perform_getsock */
130   imap_disconnect,                  /* disconnect */
131   ZERO_NULL,                        /* readwrite */
132   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   /* 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 && Curl_raw_nequal(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->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 SessionHandle *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 SessionHandle *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 SessionHandle *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_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
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 SessionHandle *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 SessionHandle *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. %c", imapcode);
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 SessionHandle *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 SessionHandle *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 responses */
1024 static CURLcode imap_state_list_resp(struct connectdata *conn, int imapcode,
1025                                      imapstate instate)
1026 {
1027   CURLcode result = CURLE_OK;
1028   char *line = conn->data->state.buffer;
1029   size_t len = strlen(line);
1030
1031   (void)instate; /* No use for this yet */
1032
1033   if(imapcode == '*') {
1034     /* Temporarily add the LF character back and send as body to the client */
1035     line[len] = '\n';
1036     result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1);
1037     line[len] = '\0';
1038   }
1039   else if(imapcode != 'O')
1040     result = CURLE_QUOTE_ERROR; /* TODO: Fix error code */
1041   else
1042     /* End of DO phase */
1043     state(conn, IMAP_STOP);
1044
1045   return result;
1046 }
1047
1048 /* For SELECT responses */
1049 static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode,
1050                                        imapstate instate)
1051 {
1052   CURLcode result = CURLE_OK;
1053   struct SessionHandle *data = conn->data;
1054   struct IMAP *imap = conn->data->req.protop;
1055   struct imap_conn *imapc = &conn->proto.imapc;
1056   const char *line = data->state.buffer;
1057   char tmp[20];
1058
1059   (void)instate; /* no use for this yet */
1060
1061   if(imapcode == '*') {
1062     /* See if this is an UIDVALIDITY response */
1063     if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1064       Curl_safefree(imapc->mailbox_uidvalidity);
1065       imapc->mailbox_uidvalidity = strdup(tmp);
1066     }
1067   }
1068   else if(imapcode == 'O') {
1069     /* Check if the UIDVALIDITY has been specified and matches */
1070     if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1071        strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1072       failf(conn->data, "Mailbox UIDVALIDITY has changed");
1073       result = CURLE_REMOTE_FILE_NOT_FOUND;
1074     }
1075     else {
1076       /* Note the currently opened mailbox on this connection */
1077       imapc->mailbox = strdup(imap->mailbox);
1078
1079       if(imap->custom)
1080         result = imap_perform_list(conn);
1081       else if(imap->query)
1082         result = imap_perform_search(conn);
1083       else
1084         result = imap_perform_fetch(conn);
1085     }
1086   }
1087   else {
1088     failf(data, "Select failed");
1089     result = CURLE_LOGIN_DENIED;
1090   }
1091
1092   return result;
1093 }
1094
1095 /* For the (first line of the) FETCH responses */
1096 static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode,
1097                                       imapstate instate)
1098 {
1099   CURLcode result = CURLE_OK;
1100   struct SessionHandle *data = conn->data;
1101   struct imap_conn *imapc = &conn->proto.imapc;
1102   struct pingpong *pp = &imapc->pp;
1103   const char *ptr = data->state.buffer;
1104   bool parsed = FALSE;
1105   curl_off_t size = 0;
1106
1107   (void)instate; /* no use for this yet */
1108
1109   if(imapcode != '*') {
1110     Curl_pgrsSetDownloadSize(data, -1);
1111     state(conn, IMAP_STOP);
1112     return CURLE_REMOTE_FILE_NOT_FOUND; /* TODO: Fix error code */
1113   }
1114
1115   /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1116      the continuation data contained within the curly brackets */
1117   while(*ptr && (*ptr != '{'))
1118     ptr++;
1119
1120   if(*ptr == '{') {
1121     char *endptr;
1122     size = curlx_strtoofft(ptr + 1, &endptr, 10);
1123     if(endptr - ptr > 1 && endptr[0] == '}' &&
1124        endptr[1] == '\r' && endptr[2] == '\0')
1125       parsed = TRUE;
1126   }
1127
1128   if(parsed) {
1129     infof(data, "Found %" CURL_FORMAT_CURL_OFF_TU " bytes to download\n",
1130           size);
1131     Curl_pgrsSetDownloadSize(data, size);
1132
1133     if(pp->cache) {
1134       /* At this point there is a bunch of data in the header "cache" that is
1135          actually body content, send it as body and then skip it. Do note
1136          that there may even be additional "headers" after the body. */
1137       size_t chunk = pp->cache_size;
1138
1139       if(chunk > (size_t)size)
1140         /* The conversion from curl_off_t to size_t is always fine here */
1141         chunk = (size_t)size;
1142
1143       result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk);
1144       if(result)
1145         return result;
1146
1147       data->req.bytecount += chunk;
1148
1149       infof(data, "Written %" CURL_FORMAT_CURL_OFF_TU
1150             " bytes, %" CURL_FORMAT_CURL_OFF_TU
1151             " bytes are left for transfer\n", (curl_off_t)chunk,
1152             size - chunk);
1153
1154       /* Have we used the entire cache or just part of it?*/
1155       if(pp->cache_size > chunk) {
1156         /* Only part of it so shrink the cache to fit the trailing data */
1157         memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1158         pp->cache_size -= chunk;
1159       }
1160       else {
1161         /* Free the cache */
1162         Curl_safefree(pp->cache);
1163
1164         /* Reset the cache size */
1165         pp->cache_size = 0;
1166       }
1167     }
1168
1169     if(data->req.bytecount == size)
1170       /* The entire data is already transferred! */
1171       Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
1172     else {
1173       /* IMAP download */
1174       data->req.maxdownload = size;
1175       Curl_setup_transfer(conn, FIRSTSOCKET, size, FALSE, NULL, -1, NULL);
1176     }
1177   }
1178   else {
1179     /* We don't know how to parse this line */
1180     failf(pp->conn->data, "Failed to parse FETCH response.");
1181     result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */
1182   }
1183
1184   /* End of DO phase */
1185   state(conn, IMAP_STOP);
1186
1187   return result;
1188 }
1189
1190 /* For final FETCH responses performed after the download */
1191 static CURLcode imap_state_fetch_final_resp(struct connectdata *conn,
1192                                             int imapcode,
1193                                             imapstate instate)
1194 {
1195   CURLcode result = CURLE_OK;
1196
1197   (void)instate; /* No use for this yet */
1198
1199   if(imapcode != 'O')
1200     result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: Fix error code */
1201   else
1202     /* End of DONE phase */
1203     state(conn, IMAP_STOP);
1204
1205   return result;
1206 }
1207
1208 /* For APPEND responses */
1209 static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode,
1210                                        imapstate instate)
1211 {
1212   CURLcode result = CURLE_OK;
1213   struct SessionHandle *data = conn->data;
1214
1215   (void)instate; /* No use for this yet */
1216
1217   if(imapcode != '+') {
1218     result = CURLE_UPLOAD_FAILED;
1219   }
1220   else {
1221     /* Set the progress upload size */
1222     Curl_pgrsSetUploadSize(data, data->state.infilesize);
1223
1224     /* IMAP upload */
1225     Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL);
1226
1227     /* End of DO phase */
1228     state(conn, IMAP_STOP);
1229   }
1230
1231   return result;
1232 }
1233
1234 /* For final APPEND responses performed after the upload */
1235 static CURLcode imap_state_append_final_resp(struct connectdata *conn,
1236                                              int imapcode,
1237                                              imapstate instate)
1238 {
1239   CURLcode result = CURLE_OK;
1240
1241   (void)instate; /* No use for this yet */
1242
1243   if(imapcode != 'O')
1244     result = CURLE_UPLOAD_FAILED;
1245   else
1246     /* End of DONE phase */
1247     state(conn, IMAP_STOP);
1248
1249   return result;
1250 }
1251
1252 /* For SEARCH responses */
1253 static CURLcode imap_state_search_resp(struct connectdata *conn, int imapcode,
1254                                        imapstate instate)
1255 {
1256   CURLcode result = CURLE_OK;
1257   char *line = conn->data->state.buffer;
1258   size_t len = strlen(line);
1259
1260   (void)instate; /* No use for this yet */
1261
1262   if(imapcode == '*') {
1263     /* Temporarily add the LF character back and send as body to the client */
1264     line[len] = '\n';
1265     result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1);
1266     line[len] = '\0';
1267   }
1268   else if(imapcode != 'O')
1269     result = CURLE_QUOTE_ERROR; /* TODO: Fix error code */
1270   else
1271     /* End of DO phase */
1272     state(conn, IMAP_STOP);
1273
1274   return result;
1275 }
1276
1277 static CURLcode imap_statemach_act(struct connectdata *conn)
1278 {
1279   CURLcode result = CURLE_OK;
1280   curl_socket_t sock = conn->sock[FIRSTSOCKET];
1281   int imapcode;
1282   struct imap_conn *imapc = &conn->proto.imapc;
1283   struct pingpong *pp = &imapc->pp;
1284   size_t nread = 0;
1285
1286   /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1287   if(imapc->state == IMAP_UPGRADETLS)
1288     return imap_perform_upgrade_tls(conn);
1289
1290   /* Flush any data that needs to be sent */
1291   if(pp->sendleft)
1292     return Curl_pp_flushsend(pp);
1293
1294   do {
1295     /* Read the response from the server */
1296     result = Curl_pp_readresp(sock, pp, &imapcode, &nread);
1297     if(result)
1298       return result;
1299
1300     /* Was there an error parsing the response line? */
1301     if(imapcode == -1)
1302       return CURLE_FTP_WEIRD_SERVER_REPLY;
1303
1304     if(!imapcode)
1305       break;
1306
1307     /* We have now received a full IMAP server response */
1308     switch(imapc->state) {
1309     case IMAP_SERVERGREET:
1310       result = imap_state_servergreet_resp(conn, imapcode, imapc->state);
1311       break;
1312
1313     case IMAP_CAPABILITY:
1314       result = imap_state_capability_resp(conn, imapcode, imapc->state);
1315       break;
1316
1317     case IMAP_STARTTLS:
1318       result = imap_state_starttls_resp(conn, imapcode, imapc->state);
1319       break;
1320
1321     case IMAP_AUTHENTICATE:
1322       result = imap_state_auth_resp(conn, imapcode, imapc->state);
1323       break;
1324
1325     case IMAP_LOGIN:
1326       result = imap_state_login_resp(conn, imapcode, imapc->state);
1327       break;
1328
1329     case IMAP_LIST:
1330       result = imap_state_list_resp(conn, imapcode, imapc->state);
1331       break;
1332
1333     case IMAP_SELECT:
1334       result = imap_state_select_resp(conn, imapcode, imapc->state);
1335       break;
1336
1337     case IMAP_FETCH:
1338       result = imap_state_fetch_resp(conn, imapcode, imapc->state);
1339       break;
1340
1341     case IMAP_FETCH_FINAL:
1342       result = imap_state_fetch_final_resp(conn, imapcode, imapc->state);
1343       break;
1344
1345     case IMAP_APPEND:
1346       result = imap_state_append_resp(conn, imapcode, imapc->state);
1347       break;
1348
1349     case IMAP_APPEND_FINAL:
1350       result = imap_state_append_final_resp(conn, imapcode, imapc->state);
1351       break;
1352
1353     case IMAP_SEARCH:
1354       result = imap_state_search_resp(conn, imapcode, imapc->state);
1355       break;
1356
1357     case IMAP_LOGOUT:
1358       /* fallthrough, just stop! */
1359     default:
1360       /* internal error */
1361       state(conn, IMAP_STOP);
1362       break;
1363     }
1364   } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1365
1366   return result;
1367 }
1368
1369 /* Called repeatedly until done from multi.c */
1370 static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done)
1371 {
1372   CURLcode result = CURLE_OK;
1373   struct imap_conn *imapc = &conn->proto.imapc;
1374
1375   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1376     result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone);
1377     if(result || !imapc->ssldone)
1378       return result;
1379   }
1380
1381   result = Curl_pp_statemach(&imapc->pp, FALSE);
1382   *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1383
1384   return result;
1385 }
1386
1387 static CURLcode imap_block_statemach(struct connectdata *conn)
1388 {
1389   CURLcode result = CURLE_OK;
1390   struct imap_conn *imapc = &conn->proto.imapc;
1391
1392   while(imapc->state != IMAP_STOP && !result)
1393     result = Curl_pp_statemach(&imapc->pp, TRUE);
1394
1395   return result;
1396 }
1397
1398 /* Allocate and initialize the struct IMAP for the current SessionHandle if
1399    required */
1400 static CURLcode imap_init(struct connectdata *conn)
1401 {
1402   CURLcode result = CURLE_OK;
1403   struct SessionHandle *data = conn->data;
1404   struct IMAP *imap;
1405
1406   imap = data->req.protop = calloc(sizeof(struct IMAP), 1);
1407   if(!imap)
1408     result = CURLE_OUT_OF_MEMORY;
1409
1410   return result;
1411 }
1412
1413 /* For the IMAP "protocol connect" and "doing" phases only */
1414 static int imap_getsock(struct connectdata *conn, curl_socket_t *socks,
1415                         int numsocks)
1416 {
1417   return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks);
1418 }
1419
1420 /***********************************************************************
1421  *
1422  * imap_connect()
1423  *
1424  * This function should do everything that is to be considered a part of the
1425  * connection phase.
1426  *
1427  * The variable 'done' points to will be TRUE if the protocol-layer connect
1428  * phase is done when this function returns, or FALSE if not.
1429  */
1430 static CURLcode imap_connect(struct connectdata *conn, bool *done)
1431 {
1432   CURLcode result = CURLE_OK;
1433   struct imap_conn *imapc = &conn->proto.imapc;
1434   struct pingpong *pp = &imapc->pp;
1435
1436   *done = FALSE; /* default to not done yet */
1437
1438   /* We always support persistent connections in IMAP */
1439   connkeep(conn, "IMAP default");
1440
1441   /* Set the default response time-out */
1442   pp->response_time = RESP_TIMEOUT;
1443   pp->statemach_act = imap_statemach_act;
1444   pp->endofresp = imap_endofresp;
1445   pp->conn = conn;
1446
1447   /* Set the default preferred authentication type and mechanism */
1448   imapc->preftype = IMAP_TYPE_ANY;
1449   Curl_sasl_init(&imapc->sasl, &saslimap);
1450
1451   /* Initialise the pingpong layer */
1452   Curl_pp_init(pp);
1453
1454   /* Parse the URL options */
1455   result = imap_parse_url_options(conn);
1456   if(result)
1457     return result;
1458
1459   /* Start off waiting for the server greeting response */
1460   state(conn, IMAP_SERVERGREET);
1461
1462   /* Start off with an response id of '*' */
1463   strcpy(imapc->resptag, "*");
1464
1465   result = imap_multi_statemach(conn, done);
1466
1467   return result;
1468 }
1469
1470 /***********************************************************************
1471  *
1472  * imap_done()
1473  *
1474  * The DONE function. This does what needs to be done after a single DO has
1475  * performed.
1476  *
1477  * Input argument is already checked for validity.
1478  */
1479 static CURLcode imap_done(struct connectdata *conn, CURLcode status,
1480                           bool premature)
1481 {
1482   CURLcode result = CURLE_OK;
1483   struct SessionHandle *data = conn->data;
1484   struct IMAP *imap = data->req.protop;
1485
1486   (void)premature;
1487
1488   if(!imap)
1489     /* When the easy handle is removed from the multi interface while libcurl
1490        is still trying to resolve the host name, the IMAP struct is not yet
1491        initialized. However, the removal action calls Curl_done() which in
1492        turn calls this function, so we simply return success. */
1493     return CURLE_OK;
1494
1495   if(status) {
1496     connclose(conn, "IMAP done with bad status"); /* marked for closure */
1497     result = status;         /* use the already set error code */
1498   }
1499   else if(!data->set.connect_only && !imap->custom &&
1500           (imap->uid || data->set.upload)) {
1501     /* Handle responses after FETCH or APPEND transfer has finished */
1502     if(!data->set.upload)
1503       state(conn, IMAP_FETCH_FINAL);
1504     else {
1505       /* End the APPEND command first by sending an empty line */
1506       result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", "");
1507       if(!result)
1508         state(conn, IMAP_APPEND_FINAL);
1509     }
1510
1511     /* Run the state-machine
1512
1513        TODO: when the multi interface is used, this _really_ should be using
1514        the imap_multi_statemach function but we have no general support for
1515        non-blocking DONE operations, not in the multi state machine and with
1516        Curl_done() invokes on several places in the code!
1517     */
1518     if(!result)
1519       result = imap_block_statemach(conn);
1520   }
1521
1522   /* Cleanup our per-request based variables */
1523   Curl_safefree(imap->mailbox);
1524   Curl_safefree(imap->uidvalidity);
1525   Curl_safefree(imap->uid);
1526   Curl_safefree(imap->section);
1527   Curl_safefree(imap->partial);
1528   Curl_safefree(imap->query);
1529   Curl_safefree(imap->custom);
1530   Curl_safefree(imap->custom_params);
1531
1532   /* Clear the transfer mode for the next request */
1533   imap->transfer = FTPTRANSFER_BODY;
1534
1535   return result;
1536 }
1537
1538 /***********************************************************************
1539  *
1540  * imap_perform()
1541  *
1542  * This is the actual DO function for IMAP. Fetch or append a message, or do
1543  * other things according to the options previously setup.
1544  */
1545 static CURLcode imap_perform(struct connectdata *conn, bool *connected,
1546                              bool *dophase_done)
1547 {
1548   /* This is IMAP and no proxy */
1549   CURLcode result = CURLE_OK;
1550   struct SessionHandle *data = conn->data;
1551   struct IMAP *imap = data->req.protop;
1552   struct imap_conn *imapc = &conn->proto.imapc;
1553   bool selected = FALSE;
1554
1555   DEBUGF(infof(conn->data, "DO phase starts\n"));
1556
1557   if(conn->data->set.opt_no_body) {
1558     /* Requested no body means no transfer */
1559     imap->transfer = FTPTRANSFER_INFO;
1560   }
1561
1562   *dophase_done = FALSE; /* not done yet */
1563
1564   /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1565      has already been selected on this connection */
1566   if(imap->mailbox && imapc->mailbox &&
1567      !strcmp(imap->mailbox, imapc->mailbox) &&
1568      (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1569       !strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1570     selected = TRUE;
1571
1572   /* Start the first command in the DO phase */
1573   if(conn->data->set.upload)
1574     /* APPEND can be executed directly */
1575     result = imap_perform_append(conn);
1576   else if(imap->custom && (selected || !imap->mailbox))
1577     /* Custom command using the same mailbox or no mailbox */
1578     result = imap_perform_list(conn);
1579   else if(!imap->custom && selected && imap->uid)
1580     /* FETCH from the same mailbox */
1581     result = imap_perform_fetch(conn);
1582   else if(!imap->custom && selected && imap->query)
1583     /* SEARCH the current mailbox */
1584     result = imap_perform_search(conn);
1585   else if(imap->mailbox && !selected &&
1586          (imap->custom || imap->uid || imap->query))
1587     /* SELECT the mailbox */
1588     result = imap_perform_select(conn);
1589   else
1590     /* LIST */
1591     result = imap_perform_list(conn);
1592
1593   if(result)
1594     return result;
1595
1596   /* Run the state-machine */
1597   result = imap_multi_statemach(conn, dophase_done);
1598
1599   *connected = conn->bits.tcpconnect[FIRSTSOCKET];
1600
1601   if(*dophase_done)
1602     DEBUGF(infof(conn->data, "DO phase is complete\n"));
1603
1604   return result;
1605 }
1606
1607 /***********************************************************************
1608  *
1609  * imap_do()
1610  *
1611  * This function is registered as 'curl_do' function. It decodes the path
1612  * parts etc as a wrapper to the actual DO function (imap_perform).
1613  *
1614  * The input argument is already checked for validity.
1615  */
1616 static CURLcode imap_do(struct connectdata *conn, bool *done)
1617 {
1618   CURLcode result = CURLE_OK;
1619
1620   *done = FALSE; /* default to false */
1621
1622   /* Parse the URL path */
1623   result = imap_parse_url_path(conn);
1624   if(result)
1625     return result;
1626
1627   /* Parse the custom request */
1628   result = imap_parse_custom_request(conn);
1629   if(result)
1630     return result;
1631
1632   result = imap_regular_transfer(conn, done);
1633
1634   return result;
1635 }
1636
1637 /***********************************************************************
1638  *
1639  * imap_disconnect()
1640  *
1641  * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1642  * resources. BLOCKING.
1643  */
1644 static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection)
1645 {
1646   struct imap_conn *imapc = &conn->proto.imapc;
1647
1648   /* We cannot send quit unconditionally. If this connection is stale or
1649      bad in any way, sending quit and waiting around here will make the
1650      disconnect wait in vain and cause more problems than we need to. */
1651
1652   /* The IMAP session may or may not have been allocated/setup at this
1653      point! */
1654   if(!dead_connection && imapc->pp.conn && imapc->pp.conn->bits.protoconnstart)
1655     if(!imap_perform_logout(conn))
1656       (void)imap_block_statemach(conn); /* ignore errors on LOGOUT */
1657
1658   /* Disconnect from the server */
1659   Curl_pp_disconnect(&imapc->pp);
1660
1661   /* Cleanup the SASL module */
1662   Curl_sasl_cleanup(conn, imapc->sasl.authused);
1663
1664   /* Cleanup our connection based variables */
1665   Curl_safefree(imapc->mailbox);
1666   Curl_safefree(imapc->mailbox_uidvalidity);
1667
1668   return CURLE_OK;
1669 }
1670
1671 /* Call this when the DO phase has completed */
1672 static CURLcode imap_dophase_done(struct connectdata *conn, bool connected)
1673 {
1674   struct IMAP *imap = conn->data->req.protop;
1675
1676   (void)connected;
1677
1678   if(imap->transfer != FTPTRANSFER_BODY)
1679     /* no data to transfer */
1680     Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
1681
1682   return CURLE_OK;
1683 }
1684
1685 /* Called from multi.c while DOing */
1686 static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done)
1687 {
1688   CURLcode result = imap_multi_statemach(conn, dophase_done);
1689
1690   if(result)
1691     DEBUGF(infof(conn->data, "DO phase failed\n"));
1692   else if(*dophase_done) {
1693     result = imap_dophase_done(conn, FALSE /* not connected */);
1694
1695     DEBUGF(infof(conn->data, "DO phase is complete\n"));
1696   }
1697
1698   return result;
1699 }
1700
1701 /***********************************************************************
1702  *
1703  * imap_regular_transfer()
1704  *
1705  * The input argument is already checked for validity.
1706  *
1707  * Performs all commands done before a regular transfer between a local and a
1708  * remote host.
1709  */
1710 static CURLcode imap_regular_transfer(struct connectdata *conn,
1711                                       bool *dophase_done)
1712 {
1713   CURLcode result = CURLE_OK;
1714   bool connected = FALSE;
1715   struct SessionHandle *data = conn->data;
1716
1717   /* Make sure size is unknown at this point */
1718   data->req.size = -1;
1719
1720   /* Set the progress data */
1721   Curl_pgrsSetUploadCounter(data, 0);
1722   Curl_pgrsSetDownloadCounter(data, 0);
1723   Curl_pgrsSetUploadSize(data, -1);
1724   Curl_pgrsSetDownloadSize(data, -1);
1725
1726   /* Carry out the perform */
1727   result = imap_perform(conn, &connected, dophase_done);
1728
1729   /* Perform post DO phase operations if necessary */
1730   if(!result && *dophase_done)
1731     result = imap_dophase_done(conn, connected);
1732
1733   return result;
1734 }
1735
1736 static CURLcode imap_setup_connection(struct connectdata *conn)
1737 {
1738   struct SessionHandle *data = conn->data;
1739
1740   /* Initialise the IMAP layer */
1741   CURLcode result = imap_init(conn);
1742   if(result)
1743     return result;
1744
1745   /* Clear the TLS upgraded flag */
1746   conn->tls_upgraded = FALSE;
1747
1748   /* Set up the proxy if necessary */
1749   if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) {
1750     /* Unless we have asked to tunnel IMAP operations through the proxy, we
1751        switch and use HTTP operations only */
1752 #ifndef CURL_DISABLE_HTTP
1753     if(conn->handler == &Curl_handler_imap)
1754       conn->handler = &Curl_handler_imap_proxy;
1755     else {
1756 #ifdef USE_SSL
1757       conn->handler = &Curl_handler_imaps_proxy;
1758 #else
1759       failf(data, "IMAPS not supported!");
1760       return CURLE_UNSUPPORTED_PROTOCOL;
1761 #endif
1762     }
1763
1764     /* set it up as an HTTP connection instead */
1765     return conn->handler->setup_connection(conn);
1766 #else
1767     failf(data, "IMAP over http proxy requires HTTP support built-in!");
1768     return CURLE_UNSUPPORTED_PROTOCOL;
1769 #endif
1770   }
1771
1772   data->state.path++;   /* don't include the initial slash */
1773
1774   return CURLE_OK;
1775 }
1776
1777 /***********************************************************************
1778  *
1779  * imap_sendf()
1780  *
1781  * Sends the formated string as an IMAP command to the server.
1782  *
1783  * Designed to never block.
1784  */
1785 static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...)
1786 {
1787   CURLcode result = CURLE_OK;
1788   struct imap_conn *imapc = &conn->proto.imapc;
1789   char *taggedfmt;
1790   va_list ap;
1791
1792   DEBUGASSERT(fmt);
1793
1794   /* Calculate the next command ID wrapping at 3 digits */
1795   imapc->cmdid = (imapc->cmdid + 1) % 1000;
1796
1797   /* Calculate the tag based on the connection ID and command ID */
1798   snprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1799            'A' + curlx_sltosi(conn->connection_id % 26), imapc->cmdid);
1800
1801   /* Prefix the format with the tag */
1802   taggedfmt = aprintf("%s %s", imapc->resptag, fmt);
1803   if(!taggedfmt)
1804     return CURLE_OUT_OF_MEMORY;
1805
1806   /* Send the data with the tag */
1807   va_start(ap, fmt);
1808   result = Curl_pp_vsendf(&imapc->pp, taggedfmt, ap);
1809   va_end(ap);
1810
1811   free(taggedfmt);
1812
1813   return result;
1814 }
1815
1816 /***********************************************************************
1817  *
1818  * imap_atom()
1819  *
1820  * Checks the input string for characters that need escaping and returns an
1821  * atom ready for sending to the server.
1822  *
1823  * The returned string needs to be freed.
1824  *
1825  */
1826 static char *imap_atom(const char *str, bool escape_only)
1827 {
1828   const char atom_specials[] = "(){ %*]";
1829   const char *p1;
1830   char *p2;
1831   size_t backsp_count = 0;
1832   size_t quote_count = 0;
1833   bool others_exists = FALSE;
1834   size_t newlen = 0;
1835   char *newstr = NULL;
1836
1837   if(!str)
1838     return NULL;
1839
1840   /* Look for "atom-specials", counting the backslash and quote characters as
1841      these will need escapping */
1842   p1 = str;
1843   while(*p1) {
1844     if(*p1 == '\\')
1845       backsp_count++;
1846     else if(*p1 == '"')
1847       quote_count++;
1848     else if(!escape_only) {
1849       const char *p3 = atom_specials;
1850
1851       while(*p3 && !others_exists) {
1852         if(*p1 == *p3)
1853           others_exists = TRUE;
1854
1855         p3++;
1856       }
1857     }
1858
1859     p1++;
1860   }
1861
1862   /* Does the input contain any "atom-special" characters? */
1863   if(!backsp_count && !quote_count && !others_exists)
1864     return strdup(str);
1865
1866   /* Calculate the new string length */
1867   newlen = strlen(str) + backsp_count + quote_count + (others_exists ? 2 : 0);
1868
1869   /* Allocate the new string */
1870   newstr = (char *) malloc((newlen + 1) * sizeof(char));
1871   if(!newstr)
1872     return NULL;
1873
1874   /* Surround the string in quotes if necessary */
1875   p2 = newstr;
1876   if(others_exists) {
1877     newstr[0] = '"';
1878     newstr[newlen - 1] = '"';
1879     p2++;
1880   }
1881
1882   /* Copy the string, escaping backslash and quote characters along the way */
1883   p1 = str;
1884   while(*p1) {
1885     if(*p1 == '\\' || *p1 == '"') {
1886       *p2 = '\\';
1887       p2++;
1888     }
1889
1890    *p2 = *p1;
1891
1892     p1++;
1893     p2++;
1894   }
1895
1896   /* Terminate the string */
1897   newstr[newlen] = '\0';
1898
1899   return newstr;
1900 }
1901
1902 /***********************************************************************
1903  *
1904  * imap_is_bchar()
1905  *
1906  * Portable test of whether the specified char is a "bchar" as defined in the
1907  * grammar of RFC-5092.
1908  */
1909 static bool imap_is_bchar(char ch)
1910 {
1911   switch(ch) {
1912     /* bchar */
1913     case ':': case '@': case '/':
1914     /* bchar -> achar */
1915     case '&': case '=':
1916     /* bchar -> achar -> uchar -> unreserved */
1917     case '0': case '1': case '2': case '3': case '4': case '5': case '6':
1918     case '7': case '8': case '9':
1919     case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
1920     case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
1921     case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
1922     case 'V': case 'W': case 'X': case 'Y': case 'Z':
1923     case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
1924     case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
1925     case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
1926     case 'v': case 'w': case 'x': case 'y': case 'z':
1927     case '-': case '.': case '_': case '~':
1928     /* bchar -> achar -> uchar -> sub-delims-sh */
1929     case '!': case '$': case '\'': case '(': case ')': case '*':
1930     case '+': case ',':
1931     /* bchar -> achar -> uchar -> pct-encoded */
1932     case '%': /* HEXDIG chars are already included above */
1933       return true;
1934
1935     default:
1936       return false;
1937   }
1938 }
1939
1940 /***********************************************************************
1941  *
1942  * imap_parse_url_options()
1943  *
1944  * Parse the URL login options.
1945  */
1946 static CURLcode imap_parse_url_options(struct connectdata *conn)
1947 {
1948   CURLcode result = CURLE_OK;
1949   struct imap_conn *imapc = &conn->proto.imapc;
1950   const char *ptr = conn->options;
1951
1952   imapc->sasl.resetprefs = TRUE;
1953
1954   while(!result && ptr && *ptr) {
1955     const char *key = ptr;
1956     const char *value;
1957
1958     while(*ptr && *ptr != '=')
1959         ptr++;
1960
1961     value = ptr + 1;
1962
1963     while(*ptr && *ptr != ';')
1964       ptr++;
1965
1966     if(strnequal(key, "AUTH=", 5))
1967       result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1968                                                value, ptr - value);
1969     else
1970       result = CURLE_URL_MALFORMAT;
1971
1972     if(*ptr == ';')
1973       ptr++;
1974   }
1975
1976   switch(imapc->sasl.prefmech) {
1977   case SASL_AUTH_NONE:
1978     imapc->preftype = IMAP_TYPE_NONE;
1979     break;
1980   case SASL_AUTH_DEFAULT:
1981     imapc->preftype = IMAP_TYPE_ANY;
1982     break;
1983   default:
1984     imapc->preftype = IMAP_TYPE_SASL;
1985     break;
1986   }
1987
1988   return result;
1989 }
1990
1991 /***********************************************************************
1992  *
1993  * imap_parse_url_path()
1994  *
1995  * Parse the URL path into separate path components.
1996  *
1997  */
1998 static CURLcode imap_parse_url_path(struct connectdata *conn)
1999 {
2000   /* The imap struct is already initialised in imap_connect() */
2001   CURLcode result = CURLE_OK;
2002   struct SessionHandle *data = conn->data;
2003   struct IMAP *imap = data->req.protop;
2004   const char *begin = data->state.path;
2005   const char *ptr = begin;
2006
2007   /* See how much of the URL is a valid path and decode it */
2008   while(imap_is_bchar(*ptr))
2009     ptr++;
2010
2011   if(ptr != begin) {
2012     /* Remove the trailing slash if present */
2013     const char *end = ptr;
2014     if(end > begin && end[-1] == '/')
2015       end--;
2016
2017     result = Curl_urldecode(data, begin, end - begin, &imap->mailbox, NULL,
2018                             TRUE);
2019     if(result)
2020       return result;
2021   }
2022   else
2023     imap->mailbox = NULL;
2024
2025   /* There can be any number of parameters in the form ";NAME=VALUE" */
2026   while(*ptr == ';') {
2027     char *name;
2028     char *value;
2029     size_t valuelen;
2030
2031     /* Find the length of the name parameter */
2032     begin = ++ptr;
2033     while(*ptr && *ptr != '=')
2034       ptr++;
2035
2036     if(!*ptr)
2037       return CURLE_URL_MALFORMAT;
2038
2039     /* Decode the name parameter */
2040     result = Curl_urldecode(data, begin, ptr - begin, &name, NULL, TRUE);
2041     if(result)
2042       return result;
2043
2044     /* Find the length of the value parameter */
2045     begin = ++ptr;
2046     while(imap_is_bchar(*ptr))
2047       ptr++;
2048
2049     /* Decode the value parameter */
2050     result = Curl_urldecode(data, begin, ptr - begin, &value, &valuelen, TRUE);
2051     if(result) {
2052       free(name);
2053       return result;
2054     }
2055
2056     DEBUGF(infof(conn->data, "IMAP URL parameter '%s' = '%s'\n", name, value));
2057
2058     /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2059        PARTIAL) stripping of the trailing slash character if it is present.
2060
2061        Note: Unknown parameters trigger a URL_MALFORMAT error. */
2062     if(Curl_raw_equal(name, "UIDVALIDITY") && !imap->uidvalidity) {
2063       if(valuelen > 0 && value[valuelen - 1] == '/')
2064         value[valuelen - 1] = '\0';
2065
2066       imap->uidvalidity = value;
2067       value = NULL;
2068     }
2069     else if(Curl_raw_equal(name, "UID") && !imap->uid) {
2070       if(valuelen > 0 && value[valuelen - 1] == '/')
2071         value[valuelen - 1] = '\0';
2072
2073       imap->uid = value;
2074       value = NULL;
2075     }
2076     else if(Curl_raw_equal(name, "SECTION") && !imap->section) {
2077       if(valuelen > 0 && value[valuelen - 1] == '/')
2078         value[valuelen - 1] = '\0';
2079
2080       imap->section = value;
2081       value = NULL;
2082     }
2083     else if(Curl_raw_equal(name, "PARTIAL") && !imap->partial) {
2084       if(valuelen > 0 && value[valuelen - 1] == '/')
2085         value[valuelen - 1] = '\0';
2086
2087       imap->partial = value;
2088       value = NULL;
2089     }
2090     else {
2091       free(name);
2092       free(value);
2093
2094       return CURLE_URL_MALFORMAT;
2095     }
2096
2097     free(name);
2098     free(value);
2099   }
2100
2101   /* Does the URL contain a query parameter? Only valid when we have a mailbox
2102      and no UID as per RFC-5092 */
2103   if(imap->mailbox && !imap->uid && *ptr == '?') {
2104     /* Find the length of the query parameter */
2105     begin = ++ptr;
2106     while(imap_is_bchar(*ptr))
2107       ptr++;
2108
2109     /* Decode the query parameter */
2110     result = Curl_urldecode(data, begin, ptr - begin, &imap->query, NULL,
2111                             TRUE);
2112     if(result)
2113       return result;
2114   }
2115
2116   /* Any extra stuff at the end of the URL is an error */
2117   if(*ptr)
2118     return CURLE_URL_MALFORMAT;
2119
2120   return CURLE_OK;
2121 }
2122
2123 /***********************************************************************
2124  *
2125  * imap_parse_custom_request()
2126  *
2127  * Parse the custom request.
2128  */
2129 static CURLcode imap_parse_custom_request(struct connectdata *conn)
2130 {
2131   CURLcode result = CURLE_OK;
2132   struct SessionHandle *data = conn->data;
2133   struct IMAP *imap = data->req.protop;
2134   const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2135
2136   if(custom) {
2137     /* URL decode the custom request */
2138     result = Curl_urldecode(data, custom, 0, &imap->custom, NULL, TRUE);
2139
2140     /* Extract the parameters if specified */
2141     if(!result) {
2142       const char *params = imap->custom;
2143
2144       while(*params && *params != ' ')
2145         params++;
2146
2147       if(*params) {
2148         imap->custom_params = strdup(params);
2149         imap->custom[params - imap->custom] = '\0';
2150
2151         if(!imap->custom_params)
2152           result = CURLE_OUT_OF_MEMORY;
2153       }
2154     }
2155   }
2156
2157   return result;
2158 }
2159
2160 #endif /* CURL_DISABLE_IMAP */