• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_UTSNAME_H
49 #include <sys/utsname.h>
50 #endif
51 #ifdef HAVE_NETDB_H
52 #include <netdb.h>
53 #endif
54 #ifdef __VMS
55 #include <in.h>
56 #include <inet.h>
57 #endif
58 
59 #include <curl/curl.h>
60 #include "urldata.h"
61 #include "sendf.h"
62 #include "hostip.h"
63 #include "progress.h"
64 #include "transfer.h"
65 #include "escape.h"
66 #include "http.h" /* for HTTP proxy tunnel stuff */
67 #include "socks.h"
68 #include "imap.h"
69 #include "mime.h"
70 #include "strtoofft.h"
71 #include "strcase.h"
72 #include "vtls/vtls.h"
73 #include "cfilters.h"
74 #include "connect.h"
75 #include "select.h"
76 #include "multiif.h"
77 #include "url.h"
78 #include "bufref.h"
79 #include "curl_sasl.h"
80 #include "warnless.h"
81 #include "curl_ctype.h"
82 
83 /* The last 3 #include files should be in this order */
84 #include "curl_printf.h"
85 #include "curl_memory.h"
86 #include "memdebug.h"
87 
88 /* Local API functions */
89 static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done);
90 static CURLcode imap_do(struct Curl_easy *data, bool *done);
91 static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
92                           bool premature);
93 static CURLcode imap_connect(struct Curl_easy *data, bool *done);
94 static CURLcode imap_disconnect(struct Curl_easy *data,
95                                 struct connectdata *conn, bool dead);
96 static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done);
97 static int imap_getsock(struct Curl_easy *data, struct connectdata *conn,
98                         curl_socket_t *socks);
99 static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done);
100 static CURLcode imap_setup_connection(struct Curl_easy *data,
101                                       struct connectdata *conn);
102 static char *imap_atom(const char *str, bool escape_only);
103 static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...);
104 static CURLcode imap_parse_url_options(struct connectdata *conn);
105 static CURLcode imap_parse_url_path(struct Curl_easy *data);
106 static CURLcode imap_parse_custom_request(struct Curl_easy *data);
107 static CURLcode imap_perform_authenticate(struct Curl_easy *data,
108                                           const char *mech,
109                                           const struct bufref *initresp);
110 static CURLcode imap_continue_authenticate(struct Curl_easy *data,
111                                            const char *mech,
112                                            const struct bufref *resp);
113 static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
114                                          const char *mech);
115 static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out);
116 
117 /*
118  * IMAP protocol handler.
119  */
120 
121 const struct Curl_handler Curl_handler_imap = {
122   "IMAP",                           /* scheme */
123   imap_setup_connection,            /* setup_connection */
124   imap_do,                          /* do_it */
125   imap_done,                        /* done */
126   ZERO_NULL,                        /* do_more */
127   imap_connect,                     /* connect_it */
128   imap_multi_statemach,             /* connecting */
129   imap_doing,                       /* doing */
130   imap_getsock,                     /* proto_getsock */
131   imap_getsock,                     /* doing_getsock */
132   ZERO_NULL,                        /* domore_getsock */
133   ZERO_NULL,                        /* perform_getsock */
134   imap_disconnect,                  /* disconnect */
135   ZERO_NULL,                        /* readwrite */
136   ZERO_NULL,                        /* connection_check */
137   ZERO_NULL,                        /* attach connection */
138   PORT_IMAP,                        /* defport */
139   CURLPROTO_IMAP,                   /* protocol */
140   CURLPROTO_IMAP,                   /* family */
141   PROTOPT_CLOSEACTION|              /* flags */
142   PROTOPT_URLOPTIONS
143 };
144 
145 #ifdef USE_SSL
146 /*
147  * IMAPS protocol handler.
148  */
149 
150 const struct Curl_handler Curl_handler_imaps = {
151   "IMAPS",                          /* scheme */
152   imap_setup_connection,            /* setup_connection */
153   imap_do,                          /* do_it */
154   imap_done,                        /* done */
155   ZERO_NULL,                        /* do_more */
156   imap_connect,                     /* connect_it */
157   imap_multi_statemach,             /* connecting */
158   imap_doing,                       /* doing */
159   imap_getsock,                     /* proto_getsock */
160   imap_getsock,                     /* doing_getsock */
161   ZERO_NULL,                        /* domore_getsock */
162   ZERO_NULL,                        /* perform_getsock */
163   imap_disconnect,                  /* disconnect */
164   ZERO_NULL,                        /* readwrite */
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 = data->state.buffer;
361   size_t len = strlen(message);
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  * state()
389  *
390  * This is the ONLY way to change IMAP state!
391  */
state(struct Curl_easy * data,imapstate newstate)392 static void 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     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     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       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     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     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     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       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     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     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     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   /* Prepare the mime data if some. */
776   if(data->set.mimepost.kind != MIMEKIND_NONE) {
777     /* Use the whole structure as data. */
778     data->set.mimepost.flags &= ~MIME_BODY_ONLY;
779 
780     /* Add external headers and mime version. */
781     curl_mime_headers(&data->set.mimepost, data->set.headers, 0);
782     result = Curl_mime_prepare_headers(data, &data->set.mimepost, NULL,
783                                        NULL, MIMESTRATEGY_MAIL);
784 
785     if(!result)
786       if(!Curl_checkheaders(data, STRCONST("Mime-Version")))
787         result = Curl_mime_add_header(&data->set.mimepost.curlheaders,
788                                       "Mime-Version: 1.0");
789 
790     /* Make sure we will read the entire mime structure. */
791     if(!result)
792       result = Curl_mime_rewind(&data->set.mimepost);
793 
794     if(result)
795       return result;
796 
797     data->state.infilesize = Curl_mime_size(&data->set.mimepost);
798 
799     /* Read from mime structure. */
800     data->state.fread_func = (curl_read_callback) Curl_mime_read;
801     data->state.in = (void *) &data->set.mimepost;
802   }
803 
804   /* Check we know the size of the upload */
805   if(data->state.infilesize < 0) {
806     failf(data, "Cannot APPEND with unknown input file size");
807     return CURLE_UPLOAD_FAILED;
808   }
809 
810   /* Make sure the mailbox is in the correct atom format */
811   mailbox = imap_atom(imap->mailbox, false);
812   if(!mailbox)
813     return CURLE_OUT_OF_MEMORY;
814 
815   /* Send the APPEND command */
816   result = imap_sendf(data,
817                       "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}",
818                       mailbox, data->state.infilesize);
819 
820   free(mailbox);
821 
822   if(!result)
823     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     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     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 = data->state.buffer;
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.cache_size)
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       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     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 = data->state.buffer;
1064   size_t len = strlen(line);
1065 
1066   (void)instate; /* No use for this yet */
1067 
1068   if(imapcode == '*') {
1069     /* Temporarily add the LF character back and send as body to the client */
1070     line[len] = '\n';
1071     result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1);
1072     line[len] = '\0';
1073   }
1074   else if(imapcode != IMAP_RESP_OK)
1075     result = CURLE_QUOTE_ERROR;
1076   else
1077     /* End of DO phase */
1078     state(data, IMAP_STOP);
1079 
1080   return result;
1081 }
1082 
1083 /* For SELECT responses */
imap_state_select_resp(struct Curl_easy * data,int imapcode,imapstate instate)1084 static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode,
1085                                        imapstate instate)
1086 {
1087   CURLcode result = CURLE_OK;
1088   struct connectdata *conn = data->conn;
1089   struct IMAP *imap = data->req.p.imap;
1090   struct imap_conn *imapc = &conn->proto.imapc;
1091   const char *line = data->state.buffer;
1092 
1093   (void)instate; /* no use for this yet */
1094 
1095   if(imapcode == '*') {
1096     /* See if this is an UIDVALIDITY response */
1097     char tmp[20];
1098     if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1099       Curl_safefree(imapc->mailbox_uidvalidity);
1100       imapc->mailbox_uidvalidity = strdup(tmp);
1101     }
1102   }
1103   else if(imapcode == IMAP_RESP_OK) {
1104     /* Check if the UIDVALIDITY has been specified and matches */
1105     if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1106        !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1107       failf(data, "Mailbox UIDVALIDITY has changed");
1108       result = CURLE_REMOTE_FILE_NOT_FOUND;
1109     }
1110     else {
1111       /* Note the currently opened mailbox on this connection */
1112       imapc->mailbox = strdup(imap->mailbox);
1113 
1114       if(imap->custom)
1115         result = imap_perform_list(data);
1116       else if(imap->query)
1117         result = imap_perform_search(data);
1118       else
1119         result = imap_perform_fetch(data);
1120     }
1121   }
1122   else {
1123     failf(data, "Select failed");
1124     result = CURLE_LOGIN_DENIED;
1125   }
1126 
1127   return result;
1128 }
1129 
1130 /* For the (first line of the) FETCH responses */
imap_state_fetch_resp(struct Curl_easy * data,struct connectdata * conn,int imapcode,imapstate instate)1131 static CURLcode imap_state_fetch_resp(struct Curl_easy *data,
1132                                       struct connectdata *conn, int imapcode,
1133                                       imapstate instate)
1134 {
1135   CURLcode result = CURLE_OK;
1136   struct imap_conn *imapc = &conn->proto.imapc;
1137   struct pingpong *pp = &imapc->pp;
1138   const char *ptr = data->state.buffer;
1139   bool parsed = FALSE;
1140   curl_off_t size = 0;
1141 
1142   (void)instate; /* no use for this yet */
1143 
1144   if(imapcode != '*') {
1145     Curl_pgrsSetDownloadSize(data, -1);
1146     state(data, IMAP_STOP);
1147     return CURLE_REMOTE_FILE_NOT_FOUND;
1148   }
1149 
1150   /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1151      the continuation data contained within the curly brackets */
1152   while(*ptr && (*ptr != '{'))
1153     ptr++;
1154 
1155   if(*ptr == '{') {
1156     char *endptr;
1157     if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) {
1158       if(endptr - ptr > 1 && endptr[0] == '}' &&
1159          endptr[1] == '\r' && endptr[2] == '\0')
1160         parsed = TRUE;
1161     }
1162   }
1163 
1164   if(parsed) {
1165     infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download",
1166           size);
1167     Curl_pgrsSetDownloadSize(data, size);
1168 
1169     if(pp->cache) {
1170       /* At this point there is a bunch of data in the header "cache" that is
1171          actually body content, send it as body and then skip it. Do note
1172          that there may even be additional "headers" after the body. */
1173       size_t chunk = pp->cache_size;
1174 
1175       if(chunk > (size_t)size)
1176         /* The conversion from curl_off_t to size_t is always fine here */
1177         chunk = (size_t)size;
1178 
1179       if(!chunk) {
1180         /* no size, we're done with the data */
1181         state(data, IMAP_STOP);
1182         return CURLE_OK;
1183       }
1184       result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk);
1185       if(result)
1186         return result;
1187 
1188       data->req.bytecount += chunk;
1189 
1190       infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU
1191             " bytes are left for transfer", chunk, size - chunk);
1192 
1193       /* Have we used the entire cache or just part of it?*/
1194       if(pp->cache_size > chunk) {
1195         /* Only part of it so shrink the cache to fit the trailing data */
1196         memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1197         pp->cache_size -= chunk;
1198       }
1199       else {
1200         /* Free the cache */
1201         Curl_safefree(pp->cache);
1202 
1203         /* Reset the cache size */
1204         pp->cache_size = 0;
1205       }
1206     }
1207 
1208     if(data->req.bytecount == size)
1209       /* The entire data is already transferred! */
1210       Curl_setup_transfer(data, -1, -1, FALSE, -1);
1211     else {
1212       /* IMAP download */
1213       data->req.maxdownload = size;
1214       /* force a recv/send check of this connection, as the data might've been
1215        read off the socket already */
1216       data->conn->cselect_bits = CURL_CSELECT_IN;
1217       Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1);
1218     }
1219   }
1220   else {
1221     /* We don't know how to parse this line */
1222     failf(data, "Failed to parse FETCH response.");
1223     result = CURLE_WEIRD_SERVER_REPLY;
1224   }
1225 
1226   /* End of DO phase */
1227   state(data, IMAP_STOP);
1228 
1229   return result;
1230 }
1231 
1232 /* For final FETCH responses performed after the download */
imap_state_fetch_final_resp(struct Curl_easy * data,int imapcode,imapstate instate)1233 static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data,
1234                                             int imapcode,
1235                                             imapstate instate)
1236 {
1237   CURLcode result = CURLE_OK;
1238 
1239   (void)instate; /* No use for this yet */
1240 
1241   if(imapcode != IMAP_RESP_OK)
1242     result = CURLE_WEIRD_SERVER_REPLY;
1243   else
1244     /* End of DONE phase */
1245     state(data, IMAP_STOP);
1246 
1247   return result;
1248 }
1249 
1250 /* For APPEND responses */
imap_state_append_resp(struct Curl_easy * data,int imapcode,imapstate instate)1251 static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode,
1252                                        imapstate instate)
1253 {
1254   CURLcode result = CURLE_OK;
1255   (void)instate; /* No use for this yet */
1256 
1257   if(imapcode != '+') {
1258     result = CURLE_UPLOAD_FAILED;
1259   }
1260   else {
1261     /* Set the progress upload size */
1262     Curl_pgrsSetUploadSize(data, data->state.infilesize);
1263 
1264     /* IMAP upload */
1265     Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET);
1266 
1267     /* End of DO phase */
1268     state(data, IMAP_STOP);
1269   }
1270 
1271   return result;
1272 }
1273 
1274 /* For final APPEND responses performed after the upload */
imap_state_append_final_resp(struct Curl_easy * data,int imapcode,imapstate instate)1275 static CURLcode imap_state_append_final_resp(struct Curl_easy *data,
1276                                              int imapcode,
1277                                              imapstate instate)
1278 {
1279   CURLcode result = CURLE_OK;
1280 
1281   (void)instate; /* No use for this yet */
1282 
1283   if(imapcode != IMAP_RESP_OK)
1284     result = CURLE_UPLOAD_FAILED;
1285   else
1286     /* End of DONE phase */
1287     state(data, IMAP_STOP);
1288 
1289   return result;
1290 }
1291 
imap_statemachine(struct Curl_easy * data,struct connectdata * conn)1292 static CURLcode imap_statemachine(struct Curl_easy *data,
1293                                   struct connectdata *conn)
1294 {
1295   CURLcode result = CURLE_OK;
1296   curl_socket_t sock = conn->sock[FIRSTSOCKET];
1297   int imapcode;
1298   struct imap_conn *imapc = &conn->proto.imapc;
1299   struct pingpong *pp = &imapc->pp;
1300   size_t nread = 0;
1301   (void)data;
1302 
1303   /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1304   if(imapc->state == IMAP_UPGRADETLS)
1305     return imap_perform_upgrade_tls(data, conn);
1306 
1307   /* Flush any data that needs to be sent */
1308   if(pp->sendleft)
1309     return Curl_pp_flushsend(data, pp);
1310 
1311   do {
1312     /* Read the response from the server */
1313     result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread);
1314     if(result)
1315       return result;
1316 
1317     /* Was there an error parsing the response line? */
1318     if(imapcode == -1)
1319       return CURLE_WEIRD_SERVER_REPLY;
1320 
1321     if(!imapcode)
1322       break;
1323 
1324     /* We have now received a full IMAP server response */
1325     switch(imapc->state) {
1326     case IMAP_SERVERGREET:
1327       result = imap_state_servergreet_resp(data, imapcode, imapc->state);
1328       break;
1329 
1330     case IMAP_CAPABILITY:
1331       result = imap_state_capability_resp(data, imapcode, imapc->state);
1332       break;
1333 
1334     case IMAP_STARTTLS:
1335       result = imap_state_starttls_resp(data, imapcode, imapc->state);
1336       break;
1337 
1338     case IMAP_AUTHENTICATE:
1339       result = imap_state_auth_resp(data, conn, imapcode, imapc->state);
1340       break;
1341 
1342     case IMAP_LOGIN:
1343       result = imap_state_login_resp(data, imapcode, imapc->state);
1344       break;
1345 
1346     case IMAP_LIST:
1347     case IMAP_SEARCH:
1348       result = imap_state_listsearch_resp(data, imapcode, imapc->state);
1349       break;
1350 
1351     case IMAP_SELECT:
1352       result = imap_state_select_resp(data, imapcode, imapc->state);
1353       break;
1354 
1355     case IMAP_FETCH:
1356       result = imap_state_fetch_resp(data, conn, imapcode, imapc->state);
1357       break;
1358 
1359     case IMAP_FETCH_FINAL:
1360       result = imap_state_fetch_final_resp(data, imapcode, imapc->state);
1361       break;
1362 
1363     case IMAP_APPEND:
1364       result = imap_state_append_resp(data, imapcode, imapc->state);
1365       break;
1366 
1367     case IMAP_APPEND_FINAL:
1368       result = imap_state_append_final_resp(data, imapcode, imapc->state);
1369       break;
1370 
1371     case IMAP_LOGOUT:
1372       /* fallthrough, just stop! */
1373     default:
1374       /* internal error */
1375       state(data, IMAP_STOP);
1376       break;
1377     }
1378   } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1379 
1380   return result;
1381 }
1382 
1383 /* Called repeatedly until done from multi.c */
imap_multi_statemach(struct Curl_easy * data,bool * done)1384 static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
1385 {
1386   CURLcode result = CURLE_OK;
1387   struct connectdata *conn = data->conn;
1388   struct imap_conn *imapc = &conn->proto.imapc;
1389 
1390   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1391     bool ssldone = FALSE;
1392     result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
1393     imapc->ssldone = ssldone;
1394     if(result || !ssldone)
1395       return result;
1396   }
1397 
1398   result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE);
1399   *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1400 
1401   return result;
1402 }
1403 
imap_block_statemach(struct Curl_easy * data,struct connectdata * conn,bool disconnecting)1404 static CURLcode imap_block_statemach(struct Curl_easy *data,
1405                                      struct connectdata *conn,
1406                                      bool disconnecting)
1407 {
1408   CURLcode result = CURLE_OK;
1409   struct imap_conn *imapc = &conn->proto.imapc;
1410 
1411   while(imapc->state != IMAP_STOP && !result)
1412     result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting);
1413 
1414   return result;
1415 }
1416 
1417 /* Allocate and initialize the struct IMAP for the current Curl_easy if
1418    required */
imap_init(struct Curl_easy * data)1419 static CURLcode imap_init(struct Curl_easy *data)
1420 {
1421   CURLcode result = CURLE_OK;
1422   struct IMAP *imap;
1423 
1424   imap = data->req.p.imap = calloc(sizeof(struct IMAP), 1);
1425   if(!imap)
1426     result = CURLE_OUT_OF_MEMORY;
1427 
1428   return result;
1429 }
1430 
1431 /* For the IMAP "protocol connect" and "doing" phases only */
imap_getsock(struct Curl_easy * data,struct connectdata * conn,curl_socket_t * socks)1432 static int imap_getsock(struct Curl_easy *data,
1433                         struct connectdata *conn,
1434                         curl_socket_t *socks)
1435 {
1436   return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks);
1437 }
1438 
1439 /***********************************************************************
1440  *
1441  * imap_connect()
1442  *
1443  * This function should do everything that is to be considered a part of the
1444  * connection phase.
1445  *
1446  * The variable 'done' points to will be TRUE if the protocol-layer connect
1447  * phase is done when this function returns, or FALSE if not.
1448  */
imap_connect(struct Curl_easy * data,bool * done)1449 static CURLcode imap_connect(struct Curl_easy *data, bool *done)
1450 {
1451   CURLcode result = CURLE_OK;
1452   struct connectdata *conn = data->conn;
1453   struct imap_conn *imapc = &conn->proto.imapc;
1454   struct pingpong *pp = &imapc->pp;
1455 
1456   *done = FALSE; /* default to not done yet */
1457 
1458   /* We always support persistent connections in IMAP */
1459   connkeep(conn, "IMAP default");
1460 
1461   PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp);
1462 
1463   /* Set the default preferred authentication type and mechanism */
1464   imapc->preftype = IMAP_TYPE_ANY;
1465   Curl_sasl_init(&imapc->sasl, data, &saslimap);
1466 
1467   Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD);
1468   /* Initialise the pingpong layer */
1469   Curl_pp_setup(pp);
1470   Curl_pp_init(data, pp);
1471 
1472   /* Parse the URL options */
1473   result = imap_parse_url_options(conn);
1474   if(result)
1475     return result;
1476 
1477   /* Start off waiting for the server greeting response */
1478   state(data, IMAP_SERVERGREET);
1479 
1480   /* Start off with an response id of '*' */
1481   strcpy(imapc->resptag, "*");
1482 
1483   result = imap_multi_statemach(data, done);
1484 
1485   return result;
1486 }
1487 
1488 /***********************************************************************
1489  *
1490  * imap_done()
1491  *
1492  * The DONE function. This does what needs to be done after a single DO has
1493  * performed.
1494  *
1495  * Input argument is already checked for validity.
1496  */
imap_done(struct Curl_easy * data,CURLcode status,bool premature)1497 static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
1498                           bool premature)
1499 {
1500   CURLcode result = CURLE_OK;
1501   struct connectdata *conn = data->conn;
1502   struct IMAP *imap = data->req.p.imap;
1503 
1504   (void)premature;
1505 
1506   if(!imap)
1507     return CURLE_OK;
1508 
1509   if(status) {
1510     connclose(conn, "IMAP done with bad status"); /* marked for closure */
1511     result = status;         /* use the already set error code */
1512   }
1513   else if(!data->set.connect_only && !imap->custom &&
1514           (imap->uid || imap->mindex || data->set.upload ||
1515           data->set.mimepost.kind != MIMEKIND_NONE)) {
1516     /* Handle responses after FETCH or APPEND transfer has finished */
1517 
1518     if(!data->set.upload && data->set.mimepost.kind == MIMEKIND_NONE)
1519       state(data, IMAP_FETCH_FINAL);
1520     else {
1521       /* End the APPEND command first by sending an empty line */
1522       result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", "");
1523       if(!result)
1524         state(data, IMAP_APPEND_FINAL);
1525     }
1526 
1527     /* Run the state-machine */
1528     if(!result)
1529       result = imap_block_statemach(data, conn, FALSE);
1530   }
1531 
1532   /* Cleanup our per-request based variables */
1533   Curl_safefree(imap->mailbox);
1534   Curl_safefree(imap->uidvalidity);
1535   Curl_safefree(imap->uid);
1536   Curl_safefree(imap->mindex);
1537   Curl_safefree(imap->section);
1538   Curl_safefree(imap->partial);
1539   Curl_safefree(imap->query);
1540   Curl_safefree(imap->custom);
1541   Curl_safefree(imap->custom_params);
1542 
1543   /* Clear the transfer mode for the next request */
1544   imap->transfer = PPTRANSFER_BODY;
1545 
1546   return result;
1547 }
1548 
1549 /***********************************************************************
1550  *
1551  * imap_perform()
1552  *
1553  * This is the actual DO function for IMAP. Fetch or append a message, or do
1554  * other things according to the options previously setup.
1555  */
imap_perform(struct Curl_easy * data,bool * connected,bool * dophase_done)1556 static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
1557                              bool *dophase_done)
1558 {
1559   /* This is IMAP and no proxy */
1560   CURLcode result = CURLE_OK;
1561   struct connectdata *conn = data->conn;
1562   struct IMAP *imap = data->req.p.imap;
1563   struct imap_conn *imapc = &conn->proto.imapc;
1564   bool selected = FALSE;
1565 
1566   DEBUGF(infof(data, "DO phase starts"));
1567 
1568   if(data->req.no_body) {
1569     /* Requested no body means no transfer */
1570     imap->transfer = PPTRANSFER_INFO;
1571   }
1572 
1573   *dophase_done = FALSE; /* not done yet */
1574 
1575   /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1576      has already been selected on this connection */
1577   if(imap->mailbox && imapc->mailbox &&
1578      strcasecompare(imap->mailbox, imapc->mailbox) &&
1579      (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1580       strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1581     selected = TRUE;
1582 
1583   /* Start the first command in the DO phase */
1584   if(data->set.upload || data->set.mimepost.kind != MIMEKIND_NONE)
1585     /* APPEND can be executed directly */
1586     result = imap_perform_append(data);
1587   else if(imap->custom && (selected || !imap->mailbox))
1588     /* Custom command using the same mailbox or no mailbox */
1589     result = imap_perform_list(data);
1590   else if(!imap->custom && selected && (imap->uid || imap->mindex))
1591     /* FETCH from the same mailbox */
1592     result = imap_perform_fetch(data);
1593   else if(!imap->custom && selected && imap->query)
1594     /* SEARCH the current mailbox */
1595     result = imap_perform_search(data);
1596   else if(imap->mailbox && !selected &&
1597          (imap->custom || imap->uid || imap->mindex || imap->query))
1598     /* SELECT the mailbox */
1599     result = imap_perform_select(data);
1600   else
1601     /* LIST */
1602     result = imap_perform_list(data);
1603 
1604   if(result)
1605     return result;
1606 
1607   /* Run the state-machine */
1608   result = imap_multi_statemach(data, dophase_done);
1609 
1610   *connected = Curl_conn_is_connected(conn, FIRSTSOCKET);
1611 
1612   if(*dophase_done)
1613     DEBUGF(infof(data, "DO phase is complete"));
1614 
1615   return result;
1616 }
1617 
1618 /***********************************************************************
1619  *
1620  * imap_do()
1621  *
1622  * This function is registered as 'curl_do' function. It decodes the path
1623  * parts etc as a wrapper to the actual DO function (imap_perform).
1624  *
1625  * The input argument is already checked for validity.
1626  */
imap_do(struct Curl_easy * data,bool * done)1627 static CURLcode imap_do(struct Curl_easy *data, bool *done)
1628 {
1629   CURLcode result = CURLE_OK;
1630   *done = FALSE; /* default to false */
1631 
1632   /* Parse the URL path */
1633   result = imap_parse_url_path(data);
1634   if(result)
1635     return result;
1636 
1637   /* Parse the custom request */
1638   result = imap_parse_custom_request(data);
1639   if(result)
1640     return result;
1641 
1642   result = imap_regular_transfer(data, done);
1643 
1644   return result;
1645 }
1646 
1647 /***********************************************************************
1648  *
1649  * imap_disconnect()
1650  *
1651  * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1652  * resources. BLOCKING.
1653  */
imap_disconnect(struct Curl_easy * data,struct connectdata * conn,bool dead_connection)1654 static CURLcode imap_disconnect(struct Curl_easy *data,
1655                                 struct connectdata *conn, bool dead_connection)
1656 {
1657   struct imap_conn *imapc = &conn->proto.imapc;
1658   (void)data;
1659 
1660   /* We cannot send quit unconditionally. If this connection is stale or
1661      bad in any way, sending quit and waiting around here will make the
1662      disconnect wait in vain and cause more problems than we need to. */
1663 
1664   /* The IMAP session may or may not have been allocated/setup at this
1665      point! */
1666   if(!dead_connection && conn->bits.protoconnstart) {
1667     if(!imap_perform_logout(data))
1668       (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */
1669   }
1670 
1671   /* Disconnect from the server */
1672   Curl_pp_disconnect(&imapc->pp);
1673   Curl_dyn_free(&imapc->dyn);
1674 
1675   /* Cleanup the SASL module */
1676   Curl_sasl_cleanup(conn, imapc->sasl.authused);
1677 
1678   /* Cleanup our connection based variables */
1679   Curl_safefree(imapc->mailbox);
1680   Curl_safefree(imapc->mailbox_uidvalidity);
1681 
1682   return CURLE_OK;
1683 }
1684 
1685 /* Call this when the DO phase has completed */
imap_dophase_done(struct Curl_easy * data,bool connected)1686 static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected)
1687 {
1688   struct IMAP *imap = data->req.p.imap;
1689 
1690   (void)connected;
1691 
1692   if(imap->transfer != PPTRANSFER_BODY)
1693     /* no data to transfer */
1694     Curl_setup_transfer(data, -1, -1, FALSE, -1);
1695 
1696   return CURLE_OK;
1697 }
1698 
1699 /* Called from multi.c while DOing */
imap_doing(struct Curl_easy * data,bool * dophase_done)1700 static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
1701 {
1702   CURLcode result = imap_multi_statemach(data, dophase_done);
1703 
1704   if(result)
1705     DEBUGF(infof(data, "DO phase failed"));
1706   else if(*dophase_done) {
1707     result = imap_dophase_done(data, FALSE /* not connected */);
1708 
1709     DEBUGF(infof(data, "DO phase is complete"));
1710   }
1711 
1712   return result;
1713 }
1714 
1715 /***********************************************************************
1716  *
1717  * imap_regular_transfer()
1718  *
1719  * The input argument is already checked for validity.
1720  *
1721  * Performs all commands done before a regular transfer between a local and a
1722  * remote host.
1723  */
imap_regular_transfer(struct Curl_easy * data,bool * dophase_done)1724 static CURLcode imap_regular_transfer(struct Curl_easy *data,
1725                                       bool *dophase_done)
1726 {
1727   CURLcode result = CURLE_OK;
1728   bool connected = FALSE;
1729 
1730   /* Make sure size is unknown at this point */
1731   data->req.size = -1;
1732 
1733   /* Set the progress data */
1734   Curl_pgrsSetUploadCounter(data, 0);
1735   Curl_pgrsSetDownloadCounter(data, 0);
1736   Curl_pgrsSetUploadSize(data, -1);
1737   Curl_pgrsSetDownloadSize(data, -1);
1738 
1739   /* Carry out the perform */
1740   result = imap_perform(data, &connected, dophase_done);
1741 
1742   /* Perform post DO phase operations if necessary */
1743   if(!result && *dophase_done)
1744     result = imap_dophase_done(data, connected);
1745 
1746   return result;
1747 }
1748 
imap_setup_connection(struct Curl_easy * data,struct connectdata * conn)1749 static CURLcode imap_setup_connection(struct Curl_easy *data,
1750                                       struct connectdata *conn)
1751 {
1752   /* Initialise the IMAP layer */
1753   CURLcode result = imap_init(data);
1754   if(result)
1755     return result;
1756 
1757   /* Clear the TLS upgraded flag */
1758   conn->bits.tls_upgraded = FALSE;
1759 
1760   return CURLE_OK;
1761 }
1762 
1763 /***********************************************************************
1764  *
1765  * imap_sendf()
1766  *
1767  * Sends the formatted string as an IMAP command to the server.
1768  *
1769  * Designed to never block.
1770  */
imap_sendf(struct Curl_easy * data,const char * fmt,...)1771 static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...)
1772 {
1773   CURLcode result = CURLE_OK;
1774   struct imap_conn *imapc = &data->conn->proto.imapc;
1775 
1776   DEBUGASSERT(fmt);
1777 
1778   /* Calculate the tag based on the connection ID and command ID */
1779   msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1780             'A' + curlx_sltosi(data->conn->connection_id % 26),
1781             ++imapc->cmdid);
1782 
1783   /* start with a blank buffer */
1784   Curl_dyn_reset(&imapc->dyn);
1785 
1786   /* append tag + space + fmt */
1787   result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
1788   if(!result) {
1789     va_list ap;
1790     va_start(ap, fmt);
1791     result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap);
1792     va_end(ap);
1793   }
1794   return result;
1795 }
1796 
1797 /***********************************************************************
1798  *
1799  * imap_atom()
1800  *
1801  * Checks the input string for characters that need escaping and returns an
1802  * atom ready for sending to the server.
1803  *
1804  * The returned string needs to be freed.
1805  *
1806  */
imap_atom(const char * str,bool escape_only)1807 static char *imap_atom(const char *str, bool escape_only)
1808 {
1809   /* !checksrc! disable PARENBRACE 1 */
1810   const char atom_specials[] = "(){ %*]";
1811   const char *p1;
1812   char *p2;
1813   size_t backsp_count = 0;
1814   size_t quote_count = 0;
1815   bool others_exists = FALSE;
1816   size_t newlen = 0;
1817   char *newstr = NULL;
1818 
1819   if(!str)
1820     return NULL;
1821 
1822   /* Look for "atom-specials", counting the backslash and quote characters as
1823      these will need escaping */
1824   p1 = str;
1825   while(*p1) {
1826     if(*p1 == '\\')
1827       backsp_count++;
1828     else if(*p1 == '"')
1829       quote_count++;
1830     else if(!escape_only) {
1831       const char *p3 = atom_specials;
1832 
1833       while(*p3 && !others_exists) {
1834         if(*p1 == *p3)
1835           others_exists = TRUE;
1836 
1837         p3++;
1838       }
1839     }
1840 
1841     p1++;
1842   }
1843 
1844   /* Does the input contain any "atom-special" characters? */
1845   if(!backsp_count && !quote_count && !others_exists)
1846     return strdup(str);
1847 
1848   /* Calculate the new string length */
1849   newlen = strlen(str) + backsp_count + quote_count + (escape_only ? 0 : 2);
1850 
1851   /* Allocate the new string */
1852   newstr = (char *) malloc((newlen + 1) * sizeof(char));
1853   if(!newstr)
1854     return NULL;
1855 
1856   /* Surround the string in quotes if necessary */
1857   p2 = newstr;
1858   if(!escape_only) {
1859     newstr[0] = '"';
1860     newstr[newlen - 1] = '"';
1861     p2++;
1862   }
1863 
1864   /* Copy the string, escaping backslash and quote characters along the way */
1865   p1 = str;
1866   while(*p1) {
1867     if(*p1 == '\\' || *p1 == '"') {
1868       *p2 = '\\';
1869       p2++;
1870     }
1871 
1872    *p2 = *p1;
1873 
1874     p1++;
1875     p2++;
1876   }
1877 
1878   /* Terminate the string */
1879   newstr[newlen] = '\0';
1880 
1881   return newstr;
1882 }
1883 
1884 /***********************************************************************
1885  *
1886  * imap_is_bchar()
1887  *
1888  * Portable test of whether the specified char is a "bchar" as defined in the
1889  * grammar of RFC-5092.
1890  */
imap_is_bchar(char ch)1891 static bool imap_is_bchar(char ch)
1892 {
1893   /* Performing the alnum check with this macro is faster because of ASCII
1894      arithmetic */
1895   if(ISALNUM(ch))
1896     return true;
1897 
1898   switch(ch) {
1899     /* bchar */
1900     case ':': case '@': case '/':
1901     /* bchar -> achar */
1902     case '&': case '=':
1903     /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */
1904     case '-': case '.': case '_': case '~':
1905     /* bchar -> achar -> uchar -> sub-delims-sh */
1906     case '!': case '$': case '\'': case '(': case ')': case '*':
1907     case '+': case ',':
1908     /* bchar -> achar -> uchar -> pct-encoded */
1909     case '%': /* HEXDIG chars are already included above */
1910       return true;
1911 
1912     default:
1913       return false;
1914   }
1915 }
1916 
1917 /***********************************************************************
1918  *
1919  * imap_parse_url_options()
1920  *
1921  * Parse the URL login options.
1922  */
imap_parse_url_options(struct connectdata * conn)1923 static CURLcode imap_parse_url_options(struct connectdata *conn)
1924 {
1925   CURLcode result = CURLE_OK;
1926   struct imap_conn *imapc = &conn->proto.imapc;
1927   const char *ptr = conn->options;
1928 
1929   while(!result && ptr && *ptr) {
1930     const char *key = ptr;
1931     const char *value;
1932 
1933     while(*ptr && *ptr != '=')
1934         ptr++;
1935 
1936     value = ptr + 1;
1937 
1938     while(*ptr && *ptr != ';')
1939       ptr++;
1940 
1941     if(strncasecompare(key, "AUTH=", 5))
1942       result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1943                                                value, ptr - value);
1944     else
1945       result = CURLE_URL_MALFORMAT;
1946 
1947     if(*ptr == ';')
1948       ptr++;
1949   }
1950 
1951   switch(imapc->sasl.prefmech) {
1952   case SASL_AUTH_NONE:
1953     imapc->preftype = IMAP_TYPE_NONE;
1954     break;
1955   case SASL_AUTH_DEFAULT:
1956     imapc->preftype = IMAP_TYPE_ANY;
1957     break;
1958   default:
1959     imapc->preftype = IMAP_TYPE_SASL;
1960     break;
1961   }
1962 
1963   return result;
1964 }
1965 
1966 /***********************************************************************
1967  *
1968  * imap_parse_url_path()
1969  *
1970  * Parse the URL path into separate path components.
1971  *
1972  */
imap_parse_url_path(struct Curl_easy * data)1973 static CURLcode imap_parse_url_path(struct Curl_easy *data)
1974 {
1975   /* The imap struct is already initialised in imap_connect() */
1976   CURLcode result = CURLE_OK;
1977   struct IMAP *imap = data->req.p.imap;
1978   const char *begin = &data->state.up.path[1]; /* skip leading slash */
1979   const char *ptr = begin;
1980 
1981   /* See how much of the URL is a valid path and decode it */
1982   while(imap_is_bchar(*ptr))
1983     ptr++;
1984 
1985   if(ptr != begin) {
1986     /* Remove the trailing slash if present */
1987     const char *end = ptr;
1988     if(end > begin && end[-1] == '/')
1989       end--;
1990 
1991     result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
1992                             REJECT_CTRL);
1993     if(result)
1994       return result;
1995   }
1996   else
1997     imap->mailbox = NULL;
1998 
1999   /* There can be any number of parameters in the form ";NAME=VALUE" */
2000   while(*ptr == ';') {
2001     char *name;
2002     char *value;
2003     size_t valuelen;
2004 
2005     /* Find the length of the name parameter */
2006     begin = ++ptr;
2007     while(*ptr && *ptr != '=')
2008       ptr++;
2009 
2010     if(!*ptr)
2011       return CURLE_URL_MALFORMAT;
2012 
2013     /* Decode the name parameter */
2014     result = Curl_urldecode(begin, ptr - begin, &name, NULL,
2015                             REJECT_CTRL);
2016     if(result)
2017       return result;
2018 
2019     /* Find the length of the value parameter */
2020     begin = ++ptr;
2021     while(imap_is_bchar(*ptr))
2022       ptr++;
2023 
2024     /* Decode the value parameter */
2025     result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
2026                             REJECT_CTRL);
2027     if(result) {
2028       free(name);
2029       return result;
2030     }
2031 
2032     DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
2033 
2034     /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2035        PARTIAL) stripping of the trailing slash character if it is present.
2036 
2037        Note: Unknown parameters trigger a URL_MALFORMAT error. */
2038     if(strcasecompare(name, "UIDVALIDITY") && !imap->uidvalidity) {
2039       if(valuelen > 0 && value[valuelen - 1] == '/')
2040         value[valuelen - 1] = '\0';
2041 
2042       imap->uidvalidity = value;
2043       value = NULL;
2044     }
2045     else if(strcasecompare(name, "UID") && !imap->uid) {
2046       if(valuelen > 0 && value[valuelen - 1] == '/')
2047         value[valuelen - 1] = '\0';
2048 
2049       imap->uid = value;
2050       value = NULL;
2051     }
2052     else if(strcasecompare(name, "MAILINDEX") && !imap->mindex) {
2053       if(valuelen > 0 && value[valuelen - 1] == '/')
2054         value[valuelen - 1] = '\0';
2055 
2056       imap->mindex = value;
2057       value = NULL;
2058     }
2059     else if(strcasecompare(name, "SECTION") && !imap->section) {
2060       if(valuelen > 0 && value[valuelen - 1] == '/')
2061         value[valuelen - 1] = '\0';
2062 
2063       imap->section = value;
2064       value = NULL;
2065     }
2066     else if(strcasecompare(name, "PARTIAL") && !imap->partial) {
2067       if(valuelen > 0 && value[valuelen - 1] == '/')
2068         value[valuelen - 1] = '\0';
2069 
2070       imap->partial = value;
2071       value = NULL;
2072     }
2073     else {
2074       free(name);
2075       free(value);
2076 
2077       return CURLE_URL_MALFORMAT;
2078     }
2079 
2080     free(name);
2081     free(value);
2082   }
2083 
2084   /* Does the URL contain a query parameter? Only valid when we have a mailbox
2085      and no UID as per RFC-5092 */
2086   if(imap->mailbox && !imap->uid && !imap->mindex) {
2087     /* Get the query parameter, URL decoded */
2088     (void)curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
2089                        CURLU_URLDECODE);
2090   }
2091 
2092   /* Any extra stuff at the end of the URL is an error */
2093   if(*ptr)
2094     return CURLE_URL_MALFORMAT;
2095 
2096   return CURLE_OK;
2097 }
2098 
2099 /***********************************************************************
2100  *
2101  * imap_parse_custom_request()
2102  *
2103  * Parse the custom request.
2104  */
imap_parse_custom_request(struct Curl_easy * data)2105 static CURLcode imap_parse_custom_request(struct Curl_easy *data)
2106 {
2107   CURLcode result = CURLE_OK;
2108   struct IMAP *imap = data->req.p.imap;
2109   const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2110 
2111   if(custom) {
2112     /* URL decode the custom request */
2113     result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
2114 
2115     /* Extract the parameters if specified */
2116     if(!result) {
2117       const char *params = imap->custom;
2118 
2119       while(*params && *params != ' ')
2120         params++;
2121 
2122       if(*params) {
2123         imap->custom_params = strdup(params);
2124         imap->custom[params - imap->custom] = '\0';
2125 
2126         if(!imap->custom_params)
2127           result = CURLE_OUT_OF_MEMORY;
2128       }
2129     }
2130   }
2131 
2132   return result;
2133 }
2134 
2135 #endif /* CURL_DISABLE_IMAP */
2136