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