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