xref: /third_party/curl/lib/imap.c (revision 13498266)
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 */
86static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done);
87static CURLcode imap_do(struct Curl_easy *data, bool *done);
88static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
89                          bool premature);
90static CURLcode imap_connect(struct Curl_easy *data, bool *done);
91static CURLcode imap_disconnect(struct Curl_easy *data,
92                                struct connectdata *conn, bool dead);
93static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done);
94static int imap_getsock(struct Curl_easy *data, struct connectdata *conn,
95                        curl_socket_t *socks);
96static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done);
97static CURLcode imap_setup_connection(struct Curl_easy *data,
98                                      struct connectdata *conn);
99static char *imap_atom(const char *str, bool escape_only);
100static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...)
101  CURL_PRINTF(2, 3);
102static CURLcode imap_parse_url_options(struct connectdata *conn);
103static CURLcode imap_parse_url_path(struct Curl_easy *data);
104static CURLcode imap_parse_custom_request(struct Curl_easy *data);
105static CURLcode imap_perform_authenticate(struct Curl_easy *data,
106                                          const char *mech,
107                                          const struct bufref *initresp);
108static CURLcode imap_continue_authenticate(struct Curl_easy *data,
109                                           const char *mech,
110                                           const struct bufref *resp);
111static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
112                                         const char *mech);
113static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out);
114
115/*
116 * IMAP protocol handler.
117 */
118
119const 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
148const 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 */
178static 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
193static 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 */
215static 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 */
253static 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 */
356static 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 */
390static 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 */
429static 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 */
453static 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 */
470static 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  }
495out:
496  return result;
497}
498
499/***********************************************************************
500 *
501 * imap_perform_login()
502 *
503 * Sends a clear text LOGIN command to authenticate with.
504 */
505static 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 */
544static 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 */
569static 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 */
587static 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 */
605static 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 */
645static 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 */
679static 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 */
719static 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 */
761static 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 */
832static 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 */
858static 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 */
870static 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 */
892static 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 */
975static 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 */
1003static 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 */
1037static 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 */
1056static 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 */
1078static 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 */
1137static 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 */
1237static 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 */
1255static 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 */
1279static 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
1296static 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 */
1387static 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
1407static 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 */
1422static 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 */
1435static 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 */
1452static 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 */
1498static 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 */
1557static 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 */
1628static 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 */
1655static 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 */
1687static 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 */
1701static 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 */
1725static 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
1750static 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 */
1772static 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 */
1815static 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 */
1857static 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 */
1889static 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 */
1953static 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 */
2085static 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