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