xref: /curl/lib/imap.c (revision c294f9cb)
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 don't */
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 don't */
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 &= ~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,
818                       "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}",
819                       mailbox, data->state.infilesize);
820 
821   free(mailbox);
822 
823   if(!result)
824     imap_state(data, IMAP_APPEND);
825 
826   return result;
827 }
828 
829 /***********************************************************************
830  *
831  * imap_perform_search()
832  *
833  * Sends a SEARCH command.
834  */
imap_perform_search(struct Curl_easy * data)835 static CURLcode imap_perform_search(struct Curl_easy *data)
836 {
837   CURLcode result = CURLE_OK;
838   struct IMAP *imap = data->req.p.imap;
839 
840   /* Check we have a query string */
841   if(!imap->query) {
842     failf(data, "Cannot SEARCH without a query string.");
843     return CURLE_URL_MALFORMAT;
844   }
845 
846   /* Send the SEARCH command */
847   result = imap_sendf(data, "SEARCH %s", imap->query);
848 
849   if(!result)
850     imap_state(data, IMAP_SEARCH);
851 
852   return result;
853 }
854 
855 /***********************************************************************
856  *
857  * imap_perform_logout()
858  *
859  * Performs the logout action prior to sclose() being called.
860  */
imap_perform_logout(struct Curl_easy * data)861 static CURLcode imap_perform_logout(struct Curl_easy *data)
862 {
863   /* Send the LOGOUT command */
864   CURLcode result = imap_sendf(data, "LOGOUT");
865 
866   if(!result)
867     imap_state(data, IMAP_LOGOUT);
868 
869   return result;
870 }
871 
872 /* For the initial server greeting */
imap_state_servergreet_resp(struct Curl_easy * data,int imapcode,imapstate instate)873 static CURLcode imap_state_servergreet_resp(struct Curl_easy *data,
874                                             int imapcode,
875                                             imapstate instate)
876 {
877   struct connectdata *conn = data->conn;
878   (void)instate; /* no use for this yet */
879 
880   if(imapcode == IMAP_RESP_PREAUTH) {
881     /* PREAUTH */
882     struct imap_conn *imapc = &conn->proto.imapc;
883     imapc->preauth = TRUE;
884     infof(data, "PREAUTH connection, already authenticated");
885   }
886   else if(imapcode != IMAP_RESP_OK) {
887     failf(data, "Got unexpected imap-server response");
888     return CURLE_WEIRD_SERVER_REPLY;
889   }
890 
891   return imap_perform_capability(data, conn);
892 }
893 
894 /* For CAPABILITY responses */
imap_state_capability_resp(struct Curl_easy * data,int imapcode,imapstate instate)895 static CURLcode imap_state_capability_resp(struct Curl_easy *data,
896                                            int imapcode,
897                                            imapstate instate)
898 {
899   CURLcode result = CURLE_OK;
900   struct connectdata *conn = data->conn;
901   struct imap_conn *imapc = &conn->proto.imapc;
902   const char *line = Curl_dyn_ptr(&imapc->pp.recvbuf);
903 
904   (void)instate; /* no use for this yet */
905 
906   /* Do we have a untagged response? */
907   if(imapcode == '*') {
908     line += 2;
909 
910     /* Loop through the data line */
911     for(;;) {
912       size_t wordlen;
913       while(*line &&
914             (*line == ' ' || *line == '\t' ||
915               *line == '\r' || *line == '\n')) {
916 
917         line++;
918       }
919 
920       if(!*line)
921         break;
922 
923       /* Extract the word */
924       for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
925             line[wordlen] != '\t' && line[wordlen] != '\r' &&
926             line[wordlen] != '\n';)
927         wordlen++;
928 
929       /* Does the server support the STARTTLS capability? */
930       if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
931         imapc->tls_supported = TRUE;
932 
933       /* Has the server explicitly disabled clear text authentication? */
934       else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
935         imapc->login_disabled = TRUE;
936 
937       /* Does the server support the SASL-IR capability? */
938       else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
939         imapc->ir_supported = TRUE;
940 
941       /* Do we have a SASL based authentication mechanism? */
942       else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
943         size_t llen;
944         unsigned short mechbit;
945 
946         line += 5;
947         wordlen -= 5;
948 
949         /* Test the word for a matching authentication mechanism */
950         mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
951         if(mechbit && llen == wordlen)
952           imapc->sasl.authmechs |= mechbit;
953       }
954 
955       line += wordlen;
956     }
957   }
958   else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
959     /* PREAUTH is not compatible with STARTTLS. */
960     if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
961       /* Switch to TLS connection now */
962       result = imap_perform_starttls(data);
963     }
964     else if(data->set.use_ssl <= CURLUSESSL_TRY)
965       result = imap_perform_authentication(data, conn);
966     else {
967       failf(data, "STARTTLS not available.");
968       result = CURLE_USE_SSL_FAILED;
969     }
970   }
971   else
972     result = imap_perform_authentication(data, conn);
973 
974   return result;
975 }
976 
977 /* For STARTTLS responses */
imap_state_starttls_resp(struct Curl_easy * data,int imapcode,imapstate instate)978 static CURLcode imap_state_starttls_resp(struct Curl_easy *data,
979                                          int imapcode,
980                                          imapstate instate)
981 {
982   CURLcode result = CURLE_OK;
983   struct connectdata *conn = data->conn;
984 
985   (void)instate; /* no use for this yet */
986 
987   /* Pipelining in response is forbidden. */
988   if(data->conn->proto.imapc.pp.overflow)
989     return CURLE_WEIRD_SERVER_REPLY;
990 
991   if(imapcode != IMAP_RESP_OK) {
992     if(data->set.use_ssl != CURLUSESSL_TRY) {
993       failf(data, "STARTTLS denied");
994       result = CURLE_USE_SSL_FAILED;
995     }
996     else
997       result = imap_perform_authentication(data, conn);
998   }
999   else
1000     result = imap_perform_upgrade_tls(data, conn);
1001 
1002   return result;
1003 }
1004 
1005 /* For SASL authentication responses */
imap_state_auth_resp(struct Curl_easy * data,struct connectdata * conn,int imapcode,imapstate instate)1006 static CURLcode imap_state_auth_resp(struct Curl_easy *data,
1007                                      struct connectdata *conn,
1008                                      int imapcode,
1009                                      imapstate instate)
1010 {
1011   CURLcode result = CURLE_OK;
1012   struct imap_conn *imapc = &conn->proto.imapc;
1013   saslprogress progress;
1014 
1015   (void)instate; /* no use for this yet */
1016 
1017   result = Curl_sasl_continue(&imapc->sasl, data, imapcode, &progress);
1018   if(!result)
1019     switch(progress) {
1020     case SASL_DONE:
1021       imap_state(data, IMAP_STOP);  /* Authenticated */
1022       break;
1023     case SASL_IDLE:            /* No mechanism left after cancellation */
1024       if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
1025         /* Perform clear text authentication */
1026         result = imap_perform_login(data, conn);
1027       else {
1028         failf(data, "Authentication cancelled");
1029         result = CURLE_LOGIN_DENIED;
1030       }
1031       break;
1032     default:
1033       break;
1034     }
1035 
1036   return result;
1037 }
1038 
1039 /* For LOGIN responses */
imap_state_login_resp(struct Curl_easy * data,int imapcode,imapstate instate)1040 static CURLcode imap_state_login_resp(struct Curl_easy *data,
1041                                       int imapcode,
1042                                       imapstate instate)
1043 {
1044   CURLcode result = CURLE_OK;
1045   (void)instate; /* no use for this yet */
1046 
1047   if(imapcode != IMAP_RESP_OK) {
1048     failf(data, "Access denied. %c", imapcode);
1049     result = CURLE_LOGIN_DENIED;
1050   }
1051   else
1052     /* End of connect phase */
1053     imap_state(data, IMAP_STOP);
1054 
1055   return result;
1056 }
1057 
1058 /* For LIST and SEARCH responses */
imap_state_listsearch_resp(struct Curl_easy * data,int imapcode,imapstate instate)1059 static CURLcode imap_state_listsearch_resp(struct Curl_easy *data,
1060                                            int imapcode,
1061                                            imapstate instate)
1062 {
1063   CURLcode result = CURLE_OK;
1064   char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf);
1065   size_t len = data->conn->proto.imapc.pp.nfinal;
1066 
1067   (void)instate; /* No use for this yet */
1068 
1069   if(imapcode == '*')
1070     result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
1071   else if(imapcode != IMAP_RESP_OK)
1072     result = CURLE_QUOTE_ERROR;
1073   else
1074     /* End of DO phase */
1075     imap_state(data, IMAP_STOP);
1076 
1077   return result;
1078 }
1079 
1080 /* For SELECT responses */
imap_state_select_resp(struct Curl_easy * data,int imapcode,imapstate instate)1081 static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode,
1082                                        imapstate instate)
1083 {
1084   CURLcode result = CURLE_OK;
1085   struct connectdata *conn = data->conn;
1086   struct IMAP *imap = data->req.p.imap;
1087   struct imap_conn *imapc = &conn->proto.imapc;
1088   const char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf);
1089 
1090   (void)instate; /* no use for this yet */
1091 
1092   if(imapcode == '*') {
1093     /* See if this is an UIDVALIDITY response */
1094     if(checkprefix("OK [UIDVALIDITY ", line + 2)) {
1095       size_t len = 0;
1096       const char *p = &line[2] + strlen("OK [UIDVALIDITY ");
1097       while((len < 20) && p[len] && ISDIGIT(p[len]))
1098         len++;
1099       if(len && (p[len] == ']')) {
1100         struct dynbuf uid;
1101         Curl_dyn_init(&uid, 20);
1102         if(Curl_dyn_addn(&uid, p, len))
1103           return CURLE_OUT_OF_MEMORY;
1104         Curl_safefree(imapc->mailbox_uidvalidity);
1105         imapc->mailbox_uidvalidity = Curl_dyn_ptr(&uid);
1106       }
1107     }
1108   }
1109   else if(imapcode == IMAP_RESP_OK) {
1110     /* Check if the UIDVALIDITY has been specified and matches */
1111     if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1112        !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1113       failf(data, "Mailbox UIDVALIDITY has changed");
1114       result = CURLE_REMOTE_FILE_NOT_FOUND;
1115     }
1116     else {
1117       /* Note the currently opened mailbox on this connection */
1118       DEBUGASSERT(!imapc->mailbox);
1119       imapc->mailbox = strdup(imap->mailbox);
1120       if(!imapc->mailbox)
1121         return CURLE_OUT_OF_MEMORY;
1122 
1123       if(imap->custom)
1124         result = imap_perform_list(data);
1125       else if(imap->query)
1126         result = imap_perform_search(data);
1127       else
1128         result = imap_perform_fetch(data);
1129     }
1130   }
1131   else {
1132     failf(data, "Select failed");
1133     result = CURLE_LOGIN_DENIED;
1134   }
1135 
1136   return result;
1137 }
1138 
1139 /* For the (first line of the) FETCH responses */
imap_state_fetch_resp(struct Curl_easy * data,struct connectdata * conn,int imapcode,imapstate instate)1140 static CURLcode imap_state_fetch_resp(struct Curl_easy *data,
1141                                       struct connectdata *conn, int imapcode,
1142                                       imapstate instate)
1143 {
1144   CURLcode result = CURLE_OK;
1145   struct imap_conn *imapc = &conn->proto.imapc;
1146   struct pingpong *pp = &imapc->pp;
1147   const char *ptr = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf);
1148   size_t len = data->conn->proto.imapc.pp.nfinal;
1149   bool parsed = FALSE;
1150   curl_off_t size = 0;
1151 
1152   (void)instate; /* no use for this yet */
1153 
1154   if(imapcode != '*') {
1155     Curl_pgrsSetDownloadSize(data, -1);
1156     imap_state(data, IMAP_STOP);
1157     return CURLE_REMOTE_FILE_NOT_FOUND;
1158   }
1159 
1160   /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1161      the continuation data contained within the curly brackets */
1162   ptr = memchr(ptr, '{', len);
1163   if(ptr) {
1164     char *endptr;
1165     if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size) &&
1166        (endptr - ptr > 1 && *endptr == '}'))
1167       parsed = TRUE;
1168   }
1169 
1170   if(parsed) {
1171     infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download",
1172           size);
1173     Curl_pgrsSetDownloadSize(data, size);
1174 
1175     if(pp->overflow) {
1176       /* At this point there is a data in the receive buffer that is body
1177          content, send it as body and then skip it. Do note that there may
1178          even be additional "headers" after the body. */
1179       size_t chunk = pp->overflow;
1180 
1181       /* keep only the overflow */
1182       Curl_dyn_tail(&pp->recvbuf, chunk);
1183       pp->nfinal = 0; /* done */
1184 
1185       if(chunk > (size_t)size)
1186         /* The conversion from curl_off_t to size_t is always fine here */
1187         chunk = (size_t)size;
1188 
1189       if(!chunk) {
1190         /* no size, we're done with the data */
1191         imap_state(data, IMAP_STOP);
1192         return CURLE_OK;
1193       }
1194       result = Curl_client_write(data, CLIENTWRITE_BODY,
1195                                  Curl_dyn_ptr(&pp->recvbuf), chunk);
1196       if(result)
1197         return result;
1198 
1199       infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU
1200             " bytes are left for transfer", chunk, size - chunk);
1201 
1202       /* Have we used the entire overflow or just part of it?*/
1203       if(pp->overflow > chunk) {
1204         /* remember the remaining trailing overflow data */
1205         pp->overflow -= chunk;
1206         Curl_dyn_tail(&pp->recvbuf, pp->overflow);
1207       }
1208       else {
1209         pp->overflow = 0; /* handled */
1210         /* Free the cache */
1211         Curl_dyn_reset(&pp->recvbuf);
1212       }
1213     }
1214 
1215     if(data->req.bytecount == size)
1216       /* The entire data is already transferred! */
1217       Curl_xfer_setup(data, -1, -1, FALSE, -1);
1218     else {
1219       /* IMAP download */
1220       data->req.maxdownload = size;
1221       /* force a recv/send check of this connection, as the data might've been
1222        read off the socket already */
1223       data->state.select_bits = CURL_CSELECT_IN;
1224       Curl_xfer_setup(data, FIRSTSOCKET, size, FALSE, -1);
1225     }
1226   }
1227   else {
1228     /* We don't know how to parse this line */
1229     failf(data, "Failed to parse FETCH response.");
1230     result = CURLE_WEIRD_SERVER_REPLY;
1231   }
1232 
1233   /* End of DO phase */
1234   imap_state(data, IMAP_STOP);
1235 
1236   return result;
1237 }
1238 
1239 /* For final FETCH responses performed after the download */
imap_state_fetch_final_resp(struct Curl_easy * data,int imapcode,imapstate instate)1240 static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data,
1241                                             int imapcode,
1242                                             imapstate instate)
1243 {
1244   CURLcode result = CURLE_OK;
1245 
1246   (void)instate; /* No use for this yet */
1247 
1248   if(imapcode != IMAP_RESP_OK)
1249     result = CURLE_WEIRD_SERVER_REPLY;
1250   else
1251     /* End of DONE phase */
1252     imap_state(data, IMAP_STOP);
1253 
1254   return result;
1255 }
1256 
1257 /* For APPEND responses */
imap_state_append_resp(struct Curl_easy * data,int imapcode,imapstate instate)1258 static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode,
1259                                        imapstate instate)
1260 {
1261   CURLcode result = CURLE_OK;
1262   (void)instate; /* No use for this yet */
1263 
1264   if(imapcode != '+') {
1265     result = CURLE_UPLOAD_FAILED;
1266   }
1267   else {
1268     /* Set the progress upload size */
1269     Curl_pgrsSetUploadSize(data, data->state.infilesize);
1270 
1271     /* IMAP upload */
1272     Curl_xfer_setup(data, -1, -1, FALSE, FIRSTSOCKET);
1273 
1274     /* End of DO phase */
1275     imap_state(data, IMAP_STOP);
1276   }
1277 
1278   return result;
1279 }
1280 
1281 /* For final APPEND responses performed after the upload */
imap_state_append_final_resp(struct Curl_easy * data,int imapcode,imapstate instate)1282 static CURLcode imap_state_append_final_resp(struct Curl_easy *data,
1283                                              int imapcode,
1284                                              imapstate instate)
1285 {
1286   CURLcode result = CURLE_OK;
1287 
1288   (void)instate; /* No use for this yet */
1289 
1290   if(imapcode != IMAP_RESP_OK)
1291     result = CURLE_UPLOAD_FAILED;
1292   else
1293     /* End of DONE phase */
1294     imap_state(data, IMAP_STOP);
1295 
1296   return result;
1297 }
1298 
imap_statemachine(struct Curl_easy * data,struct connectdata * conn)1299 static CURLcode imap_statemachine(struct Curl_easy *data,
1300                                   struct connectdata *conn)
1301 {
1302   CURLcode result = CURLE_OK;
1303   int imapcode;
1304   struct imap_conn *imapc = &conn->proto.imapc;
1305   struct pingpong *pp = &imapc->pp;
1306   size_t nread = 0;
1307   (void)data;
1308 
1309   /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1310   if(imapc->state == IMAP_UPGRADETLS)
1311     return imap_perform_upgrade_tls(data, conn);
1312 
1313   /* Flush any data that needs to be sent */
1314   if(pp->sendleft)
1315     return Curl_pp_flushsend(data, pp);
1316 
1317   do {
1318     /* Read the response from the server */
1319     result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &imapcode, &nread);
1320     if(result)
1321       return result;
1322 
1323     /* Was there an error parsing the response line? */
1324     if(imapcode == -1)
1325       return CURLE_WEIRD_SERVER_REPLY;
1326 
1327     if(!imapcode)
1328       break;
1329 
1330     /* We have now received a full IMAP server response */
1331     switch(imapc->state) {
1332     case IMAP_SERVERGREET:
1333       result = imap_state_servergreet_resp(data, imapcode, imapc->state);
1334       break;
1335 
1336     case IMAP_CAPABILITY:
1337       result = imap_state_capability_resp(data, imapcode, imapc->state);
1338       break;
1339 
1340     case IMAP_STARTTLS:
1341       result = imap_state_starttls_resp(data, imapcode, imapc->state);
1342       break;
1343 
1344     case IMAP_AUTHENTICATE:
1345       result = imap_state_auth_resp(data, conn, imapcode, imapc->state);
1346       break;
1347 
1348     case IMAP_LOGIN:
1349       result = imap_state_login_resp(data, imapcode, imapc->state);
1350       break;
1351 
1352     case IMAP_LIST:
1353     case IMAP_SEARCH:
1354       result = imap_state_listsearch_resp(data, imapcode, imapc->state);
1355       break;
1356 
1357     case IMAP_SELECT:
1358       result = imap_state_select_resp(data, imapcode, imapc->state);
1359       break;
1360 
1361     case IMAP_FETCH:
1362       result = imap_state_fetch_resp(data, conn, imapcode, imapc->state);
1363       break;
1364 
1365     case IMAP_FETCH_FINAL:
1366       result = imap_state_fetch_final_resp(data, imapcode, imapc->state);
1367       break;
1368 
1369     case IMAP_APPEND:
1370       result = imap_state_append_resp(data, imapcode, imapc->state);
1371       break;
1372 
1373     case IMAP_APPEND_FINAL:
1374       result = imap_state_append_final_resp(data, imapcode, imapc->state);
1375       break;
1376 
1377     case IMAP_LOGOUT:
1378     default:
1379       /* internal error */
1380       imap_state(data, IMAP_STOP);
1381       break;
1382     }
1383   } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1384 
1385   return result;
1386 }
1387 
1388 /* Called repeatedly until done from multi.c */
imap_multi_statemach(struct Curl_easy * data,bool * done)1389 static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
1390 {
1391   CURLcode result = CURLE_OK;
1392   struct connectdata *conn = data->conn;
1393   struct imap_conn *imapc = &conn->proto.imapc;
1394 
1395   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1396     bool ssldone = FALSE;
1397     result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
1398     imapc->ssldone = ssldone;
1399     if(result || !ssldone)
1400       return result;
1401   }
1402 
1403   result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE);
1404   *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1405 
1406   return result;
1407 }
1408 
imap_block_statemach(struct Curl_easy * data,struct connectdata * conn,bool disconnecting)1409 static CURLcode imap_block_statemach(struct Curl_easy *data,
1410                                      struct connectdata *conn,
1411                                      bool disconnecting)
1412 {
1413   CURLcode result = CURLE_OK;
1414   struct imap_conn *imapc = &conn->proto.imapc;
1415 
1416   while(imapc->state != IMAP_STOP && !result)
1417     result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting);
1418 
1419   return result;
1420 }
1421 
1422 /* Allocate and initialize the struct IMAP for the current Curl_easy if
1423    required */
imap_init(struct Curl_easy * data)1424 static CURLcode imap_init(struct Curl_easy *data)
1425 {
1426   CURLcode result = CURLE_OK;
1427   struct IMAP *imap;
1428 
1429   imap = data->req.p.imap = calloc(1, sizeof(struct IMAP));
1430   if(!imap)
1431     result = CURLE_OUT_OF_MEMORY;
1432 
1433   return result;
1434 }
1435 
1436 /* For the IMAP "protocol connect" and "doing" phases only */
imap_getsock(struct Curl_easy * data,struct connectdata * conn,curl_socket_t * socks)1437 static int imap_getsock(struct Curl_easy *data,
1438                         struct connectdata *conn,
1439                         curl_socket_t *socks)
1440 {
1441   return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks);
1442 }
1443 
1444 /***********************************************************************
1445  *
1446  * imap_connect()
1447  *
1448  * This function should do everything that is to be considered a part of the
1449  * connection phase.
1450  *
1451  * The variable 'done' points to will be TRUE if the protocol-layer connect
1452  * phase is done when this function returns, or FALSE if not.
1453  */
imap_connect(struct Curl_easy * data,bool * done)1454 static CURLcode imap_connect(struct Curl_easy *data, bool *done)
1455 {
1456   CURLcode result = CURLE_OK;
1457   struct connectdata *conn = data->conn;
1458   struct imap_conn *imapc = &conn->proto.imapc;
1459   struct pingpong *pp = &imapc->pp;
1460 
1461   *done = FALSE; /* default to not done yet */
1462 
1463   /* We always support persistent connections in IMAP */
1464   connkeep(conn, "IMAP default");
1465 
1466   PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp);
1467 
1468   /* Set the default preferred authentication type and mechanism */
1469   imapc->preftype = IMAP_TYPE_ANY;
1470   Curl_sasl_init(&imapc->sasl, data, &saslimap);
1471 
1472   Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD);
1473   Curl_pp_init(pp);
1474 
1475   /* Parse the URL options */
1476   result = imap_parse_url_options(conn);
1477   if(result)
1478     return result;
1479 
1480   /* Start off waiting for the server greeting response */
1481   imap_state(data, IMAP_SERVERGREET);
1482 
1483   /* Start off with an response id of '*' */
1484   strcpy(imapc->resptag, "*");
1485 
1486   result = imap_multi_statemach(data, done);
1487 
1488   return result;
1489 }
1490 
1491 /***********************************************************************
1492  *
1493  * imap_done()
1494  *
1495  * The DONE function. This does what needs to be done after a single DO has
1496  * performed.
1497  *
1498  * Input argument is already checked for validity.
1499  */
imap_done(struct Curl_easy * data,CURLcode status,bool premature)1500 static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
1501                           bool premature)
1502 {
1503   CURLcode result = CURLE_OK;
1504   struct connectdata *conn = data->conn;
1505   struct IMAP *imap = data->req.p.imap;
1506 
1507   (void)premature;
1508 
1509   if(!imap)
1510     return CURLE_OK;
1511 
1512   if(status) {
1513     connclose(conn, "IMAP done with bad status"); /* marked for closure */
1514     result = status;         /* use the already set error code */
1515   }
1516   else if(!data->set.connect_only && !imap->custom &&
1517           (imap->uid || imap->mindex || data->state.upload ||
1518           IS_MIME_POST(data))) {
1519     /* Handle responses after FETCH or APPEND transfer has finished */
1520 
1521     if(!data->state.upload && !IS_MIME_POST(data))
1522       imap_state(data, IMAP_FETCH_FINAL);
1523     else {
1524       /* End the APPEND command first by sending an empty line */
1525       result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", "");
1526       if(!result)
1527         imap_state(data, IMAP_APPEND_FINAL);
1528     }
1529 
1530     /* Run the state-machine */
1531     if(!result)
1532       result = imap_block_statemach(data, conn, FALSE);
1533   }
1534 
1535   /* Cleanup our per-request based variables */
1536   Curl_safefree(imap->mailbox);
1537   Curl_safefree(imap->uidvalidity);
1538   Curl_safefree(imap->uid);
1539   Curl_safefree(imap->mindex);
1540   Curl_safefree(imap->section);
1541   Curl_safefree(imap->partial);
1542   Curl_safefree(imap->query);
1543   Curl_safefree(imap->custom);
1544   Curl_safefree(imap->custom_params);
1545 
1546   /* Clear the transfer mode for the next request */
1547   imap->transfer = PPTRANSFER_BODY;
1548 
1549   return result;
1550 }
1551 
1552 /***********************************************************************
1553  *
1554  * imap_perform()
1555  *
1556  * This is the actual DO function for IMAP. Fetch or append a message, or do
1557  * other things according to the options previously setup.
1558  */
imap_perform(struct Curl_easy * data,bool * connected,bool * dophase_done)1559 static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
1560                              bool *dophase_done)
1561 {
1562   /* This is IMAP and no proxy */
1563   CURLcode result = CURLE_OK;
1564   struct connectdata *conn = data->conn;
1565   struct IMAP *imap = data->req.p.imap;
1566   struct imap_conn *imapc = &conn->proto.imapc;
1567   bool selected = FALSE;
1568 
1569   DEBUGF(infof(data, "DO phase starts"));
1570 
1571   if(data->req.no_body) {
1572     /* Requested no body means no transfer */
1573     imap->transfer = PPTRANSFER_INFO;
1574   }
1575 
1576   *dophase_done = FALSE; /* not done yet */
1577 
1578   /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1579      has already been selected on this connection */
1580   if(imap->mailbox && imapc->mailbox &&
1581      strcasecompare(imap->mailbox, imapc->mailbox) &&
1582      (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1583       strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1584     selected = TRUE;
1585 
1586   /* Start the first command in the DO phase */
1587   if(data->state.upload || IS_MIME_POST(data))
1588     /* APPEND can be executed directly */
1589     result = imap_perform_append(data);
1590   else if(imap->custom && (selected || !imap->mailbox))
1591     /* Custom command using the same mailbox or no mailbox */
1592     result = imap_perform_list(data);
1593   else if(!imap->custom && selected && (imap->uid || imap->mindex))
1594     /* FETCH from the same mailbox */
1595     result = imap_perform_fetch(data);
1596   else if(!imap->custom && selected && imap->query)
1597     /* SEARCH the current mailbox */
1598     result = imap_perform_search(data);
1599   else if(imap->mailbox && !selected &&
1600          (imap->custom || imap->uid || imap->mindex || imap->query))
1601     /* SELECT the mailbox */
1602     result = imap_perform_select(data);
1603   else
1604     /* LIST */
1605     result = imap_perform_list(data);
1606 
1607   if(result)
1608     return result;
1609 
1610   /* Run the state-machine */
1611   result = imap_multi_statemach(data, dophase_done);
1612 
1613   *connected = Curl_conn_is_connected(conn, FIRSTSOCKET);
1614 
1615   if(*dophase_done)
1616     DEBUGF(infof(data, "DO phase is complete"));
1617 
1618   return result;
1619 }
1620 
1621 /***********************************************************************
1622  *
1623  * imap_do()
1624  *
1625  * This function is registered as 'curl_do' function. It decodes the path
1626  * parts etc as a wrapper to the actual DO function (imap_perform).
1627  *
1628  * The input argument is already checked for validity.
1629  */
imap_do(struct Curl_easy * data,bool * done)1630 static CURLcode imap_do(struct Curl_easy *data, bool *done)
1631 {
1632   CURLcode result = CURLE_OK;
1633   *done = FALSE; /* default to false */
1634 
1635   /* Parse the URL path */
1636   result = imap_parse_url_path(data);
1637   if(result)
1638     return result;
1639 
1640   /* Parse the custom request */
1641   result = imap_parse_custom_request(data);
1642   if(result)
1643     return result;
1644 
1645   result = imap_regular_transfer(data, done);
1646 
1647   return result;
1648 }
1649 
1650 /***********************************************************************
1651  *
1652  * imap_disconnect()
1653  *
1654  * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1655  * resources. BLOCKING.
1656  */
imap_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead_connection)1657 static CURLcode imap_disconnect(struct Curl_easy *data,
1658                                 struct connectdata *conn, bool dead_connection)
1659 {
1660   struct imap_conn *imapc = &conn->proto.imapc;
1661   (void)data;
1662 
1663   /* We cannot send quit unconditionally. If this connection is stale or
1664      bad in any way, sending quit and waiting around here will make the
1665      disconnect wait in vain and cause more problems than we need to. */
1666 
1667   /* The IMAP session may or may not have been allocated/setup at this
1668      point! */
1669   if(!dead_connection && conn->bits.protoconnstart) {
1670     if(!imap_perform_logout(data))
1671       (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */
1672   }
1673 
1674   /* Disconnect from the server */
1675   Curl_pp_disconnect(&imapc->pp);
1676   Curl_dyn_free(&imapc->dyn);
1677 
1678   /* Cleanup the SASL module */
1679   Curl_sasl_cleanup(conn, imapc->sasl.authused);
1680 
1681   /* Cleanup our connection based variables */
1682   Curl_safefree(imapc->mailbox);
1683   Curl_safefree(imapc->mailbox_uidvalidity);
1684 
1685   return CURLE_OK;
1686 }
1687 
1688 /* Call this when the DO phase has completed */
imap_dophase_done(struct Curl_easy * data,bool connected)1689 static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected)
1690 {
1691   struct IMAP *imap = data->req.p.imap;
1692 
1693   (void)connected;
1694 
1695   if(imap->transfer != PPTRANSFER_BODY)
1696     /* no data to transfer */
1697     Curl_xfer_setup(data, -1, -1, FALSE, -1);
1698 
1699   return CURLE_OK;
1700 }
1701 
1702 /* Called from multi.c while DOing */
imap_doing(struct Curl_easy * data,bool * dophase_done)1703 static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
1704 {
1705   CURLcode result = imap_multi_statemach(data, dophase_done);
1706 
1707   if(result)
1708     DEBUGF(infof(data, "DO phase failed"));
1709   else if(*dophase_done) {
1710     result = imap_dophase_done(data, FALSE /* not connected */);
1711 
1712     DEBUGF(infof(data, "DO phase is complete"));
1713   }
1714 
1715   return result;
1716 }
1717 
1718 /***********************************************************************
1719  *
1720  * imap_regular_transfer()
1721  *
1722  * The input argument is already checked for validity.
1723  *
1724  * Performs all commands done before a regular transfer between a local and a
1725  * remote host.
1726  */
imap_regular_transfer(struct Curl_easy * data,bool * dophase_done)1727 static CURLcode imap_regular_transfer(struct Curl_easy *data,
1728                                       bool *dophase_done)
1729 {
1730   CURLcode result = CURLE_OK;
1731   bool connected = FALSE;
1732 
1733   /* Make sure size is unknown at this point */
1734   data->req.size = -1;
1735 
1736   /* Set the progress data */
1737   Curl_pgrsSetUploadCounter(data, 0);
1738   Curl_pgrsSetDownloadCounter(data, 0);
1739   Curl_pgrsSetUploadSize(data, -1);
1740   Curl_pgrsSetDownloadSize(data, -1);
1741 
1742   /* Carry out the perform */
1743   result = imap_perform(data, &connected, dophase_done);
1744 
1745   /* Perform post DO phase operations if necessary */
1746   if(!result && *dophase_done)
1747     result = imap_dophase_done(data, connected);
1748 
1749   return result;
1750 }
1751 
imap_setup_connection(struct Curl_easy * data,struct connectdata * conn)1752 static CURLcode imap_setup_connection(struct Curl_easy *data,
1753                                       struct connectdata *conn)
1754 {
1755   /* Initialise the IMAP layer */
1756   CURLcode result = imap_init(data);
1757   if(result)
1758     return result;
1759 
1760   /* Clear the TLS upgraded flag */
1761   conn->bits.tls_upgraded = FALSE;
1762 
1763   return CURLE_OK;
1764 }
1765 
1766 /***********************************************************************
1767  *
1768  * imap_sendf()
1769  *
1770  * Sends the formatted string as an IMAP command to the server.
1771  *
1772  * Designed to never block.
1773  */
imap_sendf(struct Curl_easy * data,const char * fmt,...)1774 static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...)
1775 {
1776   CURLcode result = CURLE_OK;
1777   struct imap_conn *imapc = &data->conn->proto.imapc;
1778 
1779   DEBUGASSERT(fmt);
1780 
1781   /* Calculate the tag based on the connection ID and command ID */
1782   msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1783             'A' + curlx_sltosi((long)(data->conn->connection_id % 26)),
1784             ++imapc->cmdid);
1785 
1786   /* start with a blank buffer */
1787   Curl_dyn_reset(&imapc->dyn);
1788 
1789   /* append tag + space + fmt */
1790   result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
1791   if(!result) {
1792     va_list ap;
1793     va_start(ap, fmt);
1794 #ifdef __clang__
1795 #pragma clang diagnostic push
1796 #pragma clang diagnostic ignored "-Wformat-nonliteral"
1797 #endif
1798     result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap);
1799 #ifdef __clang__
1800 #pragma clang diagnostic pop
1801 #endif
1802     va_end(ap);
1803   }
1804   return result;
1805 }
1806 
1807 /***********************************************************************
1808  *
1809  * imap_atom()
1810  *
1811  * Checks the input string for characters that need escaping and returns an
1812  * atom ready for sending to the server.
1813  *
1814  * The returned string needs to be freed.
1815  *
1816  */
imap_atom(const char * str,bool escape_only)1817 static char *imap_atom(const char *str, bool escape_only)
1818 {
1819   struct dynbuf line;
1820   size_t nclean;
1821   size_t len;
1822 
1823   if(!str)
1824     return NULL;
1825 
1826   len = strlen(str);
1827   nclean = strcspn(str, "() {%*]\\\"");
1828   if(len == nclean)
1829     /* nothing to escape, return a strdup */
1830     return strdup(str);
1831 
1832   Curl_dyn_init(&line, 2000);
1833 
1834   if(!escape_only && Curl_dyn_addn(&line, "\"", 1))
1835     return NULL;
1836 
1837   while(*str) {
1838     if((*str == '\\' || *str == '"') &&
1839        Curl_dyn_addn(&line, "\\", 1))
1840       return NULL;
1841     if(Curl_dyn_addn(&line, str, 1))
1842       return NULL;
1843     str++;
1844   }
1845 
1846   if(!escape_only && Curl_dyn_addn(&line, "\"", 1))
1847     return NULL;
1848 
1849   return Curl_dyn_ptr(&line);
1850 }
1851 
1852 /***********************************************************************
1853  *
1854  * imap_is_bchar()
1855  *
1856  * Portable test of whether the specified char is a "bchar" as defined in the
1857  * grammar of RFC-5092.
1858  */
imap_is_bchar(char ch)1859 static bool imap_is_bchar(char ch)
1860 {
1861   /* Performing the alnum check with this macro is faster because of ASCII
1862      arithmetic */
1863   if(ISALNUM(ch))
1864     return true;
1865 
1866   switch(ch) {
1867     /* bchar */
1868     case ':': case '@': case '/':
1869     /* bchar -> achar */
1870     case '&': case '=':
1871     /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */
1872     case '-': case '.': case '_': case '~':
1873     /* bchar -> achar -> uchar -> sub-delims-sh */
1874     case '!': case '$': case '\'': case '(': case ')': case '*':
1875     case '+': case ',':
1876     /* bchar -> achar -> uchar -> pct-encoded */
1877     case '%': /* HEXDIG chars are already included above */
1878       return true;
1879 
1880     default:
1881       return false;
1882   }
1883 }
1884 
1885 /***********************************************************************
1886  *
1887  * imap_parse_url_options()
1888  *
1889  * Parse the URL login options.
1890  */
imap_parse_url_options(struct connectdata * conn)1891 static CURLcode imap_parse_url_options(struct connectdata *conn)
1892 {
1893   CURLcode result = CURLE_OK;
1894   struct imap_conn *imapc = &conn->proto.imapc;
1895   const char *ptr = conn->options;
1896   bool prefer_login = false;
1897 
1898   while(!result && ptr && *ptr) {
1899     const char *key = ptr;
1900     const char *value;
1901 
1902     while(*ptr && *ptr != '=')
1903       ptr++;
1904 
1905     value = ptr + 1;
1906 
1907     while(*ptr && *ptr != ';')
1908       ptr++;
1909 
1910     if(strncasecompare(key, "AUTH=+LOGIN", 11)) {
1911       /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */
1912       prefer_login = true;
1913       imapc->sasl.prefmech = SASL_AUTH_NONE;
1914     }
1915     else if(strncasecompare(key, "AUTH=", 5)) {
1916       prefer_login = false;
1917       result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1918                                                value, ptr - value);
1919     }
1920     else {
1921       prefer_login = false;
1922       result = CURLE_URL_MALFORMAT;
1923     }
1924 
1925     if(*ptr == ';')
1926       ptr++;
1927   }
1928 
1929   if(prefer_login)
1930     imapc->preftype = IMAP_TYPE_CLEARTEXT;
1931   else {
1932     switch(imapc->sasl.prefmech) {
1933     case SASL_AUTH_NONE:
1934       imapc->preftype = IMAP_TYPE_NONE;
1935       break;
1936     case SASL_AUTH_DEFAULT:
1937       imapc->preftype = IMAP_TYPE_ANY;
1938       break;
1939     default:
1940       imapc->preftype = IMAP_TYPE_SASL;
1941       break;
1942     }
1943   }
1944 
1945   return result;
1946 }
1947 
1948 /***********************************************************************
1949  *
1950  * imap_parse_url_path()
1951  *
1952  * Parse the URL path into separate path components.
1953  *
1954  */
imap_parse_url_path(struct Curl_easy * data)1955 static CURLcode imap_parse_url_path(struct Curl_easy *data)
1956 {
1957   /* The imap struct is already initialised in imap_connect() */
1958   CURLcode result = CURLE_OK;
1959   struct IMAP *imap = data->req.p.imap;
1960   const char *begin = &data->state.up.path[1]; /* skip leading slash */
1961   const char *ptr = begin;
1962 
1963   /* See how much of the URL is a valid path and decode it */
1964   while(imap_is_bchar(*ptr))
1965     ptr++;
1966 
1967   if(ptr != begin) {
1968     /* Remove the trailing slash if present */
1969     const char *end = ptr;
1970     if(end > begin && end[-1] == '/')
1971       end--;
1972 
1973     result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
1974                             REJECT_CTRL);
1975     if(result)
1976       return result;
1977   }
1978   else
1979     imap->mailbox = NULL;
1980 
1981   /* There can be any number of parameters in the form ";NAME=VALUE" */
1982   while(*ptr == ';') {
1983     char *name;
1984     char *value;
1985     size_t valuelen;
1986 
1987     /* Find the length of the name parameter */
1988     begin = ++ptr;
1989     while(*ptr && *ptr != '=')
1990       ptr++;
1991 
1992     if(!*ptr)
1993       return CURLE_URL_MALFORMAT;
1994 
1995     /* Decode the name parameter */
1996     result = Curl_urldecode(begin, ptr - begin, &name, NULL,
1997                             REJECT_CTRL);
1998     if(result)
1999       return result;
2000 
2001     /* Find the length of the value parameter */
2002     begin = ++ptr;
2003     while(imap_is_bchar(*ptr))
2004       ptr++;
2005 
2006     /* Decode the value parameter */
2007     result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
2008                             REJECT_CTRL);
2009     if(result) {
2010       free(name);
2011       return result;
2012     }
2013 
2014     DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
2015 
2016     /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2017        PARTIAL) stripping of the trailing slash character if it is present.
2018 
2019        Note: Unknown parameters trigger a URL_MALFORMAT error. */
2020     if(strcasecompare(name, "UIDVALIDITY") && !imap->uidvalidity) {
2021       if(valuelen > 0 && value[valuelen - 1] == '/')
2022         value[valuelen - 1] = '\0';
2023 
2024       imap->uidvalidity = value;
2025       value = NULL;
2026     }
2027     else if(strcasecompare(name, "UID") && !imap->uid) {
2028       if(valuelen > 0 && value[valuelen - 1] == '/')
2029         value[valuelen - 1] = '\0';
2030 
2031       imap->uid = value;
2032       value = NULL;
2033     }
2034     else if(strcasecompare(name, "MAILINDEX") && !imap->mindex) {
2035       if(valuelen > 0 && value[valuelen - 1] == '/')
2036         value[valuelen - 1] = '\0';
2037 
2038       imap->mindex = value;
2039       value = NULL;
2040     }
2041     else if(strcasecompare(name, "SECTION") && !imap->section) {
2042       if(valuelen > 0 && value[valuelen - 1] == '/')
2043         value[valuelen - 1] = '\0';
2044 
2045       imap->section = value;
2046       value = NULL;
2047     }
2048     else if(strcasecompare(name, "PARTIAL") && !imap->partial) {
2049       if(valuelen > 0 && value[valuelen - 1] == '/')
2050         value[valuelen - 1] = '\0';
2051 
2052       imap->partial = value;
2053       value = NULL;
2054     }
2055     else {
2056       free(name);
2057       free(value);
2058 
2059       return CURLE_URL_MALFORMAT;
2060     }
2061 
2062     free(name);
2063     free(value);
2064   }
2065 
2066   /* Does the URL contain a query parameter? Only valid when we have a mailbox
2067      and no UID as per RFC-5092 */
2068   if(imap->mailbox && !imap->uid && !imap->mindex) {
2069     /* Get the query parameter, URL decoded */
2070     (void)curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
2071                        CURLU_URLDECODE);
2072   }
2073 
2074   /* Any extra stuff at the end of the URL is an error */
2075   if(*ptr)
2076     return CURLE_URL_MALFORMAT;
2077 
2078   return CURLE_OK;
2079 }
2080 
2081 /***********************************************************************
2082  *
2083  * imap_parse_custom_request()
2084  *
2085  * Parse the custom request.
2086  */
imap_parse_custom_request(struct Curl_easy * data)2087 static CURLcode imap_parse_custom_request(struct Curl_easy *data)
2088 {
2089   CURLcode result = CURLE_OK;
2090   struct IMAP *imap = data->req.p.imap;
2091   const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2092 
2093   if(custom) {
2094     /* URL decode the custom request */
2095     result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
2096 
2097     /* Extract the parameters if specified */
2098     if(!result) {
2099       const char *params = imap->custom;
2100 
2101       while(*params && *params != ' ')
2102         params++;
2103 
2104       if(*params) {
2105         imap->custom_params = strdup(params);
2106         imap->custom[params - imap->custom] = '\0';
2107 
2108         if(!imap->custom_params)
2109           result = CURLE_OUT_OF_MEMORY;
2110       }
2111     }
2112   }
2113 
2114   return result;
2115 }
2116 
2117 #endif /* CURL_DISABLE_IMAP */
2118