Coverage Report

Created: 2026-02-26 06:40

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