Coverage Report

Created: 2025-11-23 06:22

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