Coverage Report

Created: 2025-12-14 06:23

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