Coverage Report

Created: 2026-04-12 06:59

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
10.1M
#define CURL_META_IMAP_EASY   "meta:proto:imap:easy"
79
/* meta key for storing protocol meta at connection */
80
10.1M
#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.70k
#define IMAP_RESP_OK       1
141
2.01k
#define IMAP_RESP_NOT_OK   2
142
2.30k
#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
6.13k
{
164
6.13k
  CURLcode result = CURLE_OK;
165
166
6.13k
  DEBUGASSERT(fmt);
167
168
  /* Calculate the tag based on the connection ID and command ID */
169
6.13k
  curl_msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
170
6.13k
                 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)),
171
6.13k
                 ++imapc->cmdid);
172
173
  /* start with a blank buffer */
174
6.13k
  curlx_dyn_reset(&imapc->dyn);
175
176
  /* append tag + space + fmt */
177
6.13k
  result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
178
6.13k
  if(!result) {
179
6.13k
    va_list ap;
180
6.13k
    va_start(ap, fmt);
181
6.13k
#ifdef __clang__
182
6.13k
#pragma clang diagnostic push
183
6.13k
#pragma clang diagnostic ignored "-Wformat-nonliteral"
184
6.13k
#endif
185
6.13k
    result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap);
186
6.13k
#ifdef __clang__
187
6.13k
#pragma clang diagnostic pop
188
6.13k
#endif
189
6.13k
    va_end(ap);
190
6.13k
  }
191
6.13k
  return result;
192
6.13k
}
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.72k
{
206
1.72k
  struct dynbuf line;
207
1.72k
  size_t nclean;
208
1.72k
  size_t len;
209
210
1.72k
  if(!str)
211
0
    return NULL;
212
213
1.72k
  len = strlen(str);
214
1.72k
  nclean = strcspn(str, "() {%*]\\\"");
215
1.72k
  if(len == nclean)
216
    /* nothing to escape, return a strdup */
217
1.23k
    return curlx_strdup(str);
218
219
490
  curlx_dyn_init(&line, 2000);
220
221
490
  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
222
0
    return NULL;
223
224
36.5k
  while(*str) {
225
36.0k
    if((*str == '\\' || *str == '"') &&
226
2.40k
       curlx_dyn_addn(&line, "\\", 1))
227
1
      return NULL;
228
36.0k
    if(curlx_dyn_addn(&line, str, 1))
229
11
      return NULL;
230
36.0k
    str++;
231
36.0k
  }
232
233
478
  if(!escape_only && curlx_dyn_addn(&line, "\"", 1))
234
1
    return NULL;
235
236
477
  return curlx_dyn_ptr(&line);
237
478
}
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
11.1k
{
244
11.1k
  const char *end = line + len;
245
11.1k
  bool in_quote = FALSE;
246
247
677k
  while(line < end) {
248
667k
    if(in_quote) {
249
296k
      if(*line == '\\' && (line + 1) < end) {
250
579
        line += 2;
251
579
        continue;
252
579
      }
253
296k
      if(*line == '"')
254
1.11k
        in_quote = FALSE;
255
296k
    }
256
370k
    else {
257
370k
      if(*line == '"')
258
1.29k
        in_quote = TRUE;
259
369k
      else if(*line == '{')
260
1.17k
        return line;
261
370k
    }
262
665k
    line++;
263
665k
  }
264
9.94k
  return NULL;
265
11.1k
}
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
58.8k
{
279
58.8k
  const char *end = line + len;
280
58.8k
  size_t cmd_len = strlen(cmd);
281
282
  /* Skip the untagged response marker */
283
58.8k
  line += 2;
284
285
  /* Do we have a number after the marker? */
286
58.8k
  if(line < end && ISDIGIT(*line)) {
287
    /* Skip the number */
288
16.0k
    do
289
26.8k
      line++;
290
26.8k
    while(line < end && ISDIGIT(*line));
291
292
    /* Do we have the space character? */
293
16.0k
    if(line == end || *line != ' ')
294
8.97k
      return FALSE;
295
296
7.05k
    line++;
297
7.05k
  }
298
299
  /* Does the command name match and is it followed by a space character or at
300
     the end of line? */
301
49.8k
  if(line + cmd_len <= end && curl_strnequal(line, cmd, cmd_len) &&
302
21.4k
     (line[cmd_len] == ' ' || line + cmd_len + 2 == end))
303
9.03k
    return TRUE;
304
305
40.8k
  return FALSE;
306
49.8k
}
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
396k
{
318
396k
  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
319
396k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
320
396k
  const char *id;
321
396k
  size_t id_len;
322
323
396k
  DEBUGASSERT(imapc);
324
396k
  DEBUGASSERT(imap);
325
396k
  if(!imapc || !imap)
326
0
    return FALSE;
327
328
  /* Do we have a tagged command response? */
329
396k
  id = imapc->resptag;
330
396k
  id_len = strlen(id);
331
396k
  if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') {
332
5.05k
    line += id_len + 1;
333
5.05k
    len -= id_len + 1;
334
335
5.05k
    if(len >= 2 && !memcmp(line, "OK", 2))
336
2.85k
      *resp = IMAP_RESP_OK;
337
2.19k
    else if(len >= 7 && !memcmp(line, "PREAUTH", 7))
338
176
      *resp = IMAP_RESP_PREAUTH;
339
2.01k
    else
340
2.01k
      *resp = IMAP_RESP_NOT_OK;
341
342
5.05k
    return TRUE;
343
5.05k
  }
344
345
  /* Do we have an untagged command response? */
346
391k
  if(len >= 2 && !memcmp("* ", line, 2)) {
347
86.5k
    switch(imapc->state) {
348
    /* States which are interested in untagged responses */
349
4.13k
    case IMAP_CAPABILITY:
350
4.13k
      if(!imap_matchresp(line, len, "CAPABILITY"))
351
4.13k
        return FALSE;
352
0
      break;
353
354
45.0k
    case IMAP_LIST:
355
45.0k
      if((!imap->custom && !imap_matchresp(line, len, "LIST")) ||
356
25.3k
         (imap->custom && !imap_matchresp(line, len, imap->custom) &&
357
17.7k
          (!curl_strequal(imap->custom, "STORE") ||
358
385
           !imap_matchresp(line, len, "FETCH")) &&
359
17.7k
          !curl_strequal(imap->custom, "SELECT") &&
360
17.4k
          !curl_strequal(imap->custom, "EXAMINE") &&
361
17.3k
          !curl_strequal(imap->custom, "SEARCH") &&
362
17.1k
          !curl_strequal(imap->custom, "EXPUNGE") &&
363
16.8k
          !curl_strequal(imap->custom, "LSUB") &&
364
16.5k
          !curl_strequal(imap->custom, "UID") &&
365
15.9k
          !curl_strequal(imap->custom, "GETQUOTAROOT") &&
366
15.9k
          !curl_strequal(imap->custom, "NOOP")))
367
35.2k
        return FALSE;
368
9.75k
      break;
369
370
24.4k
    case IMAP_SELECT:
371
      /* SELECT is special in that its untagged responses do not have a
372
         common prefix so accept anything! */
373
24.4k
      break;
374
375
5.11k
    case IMAP_FETCH:
376
5.11k
      if(!imap_matchresp(line, len, "FETCH"))
377
4.56k
        return FALSE;
378
547
      break;
379
380
4.18k
    case IMAP_SEARCH:
381
4.18k
      if(!imap_matchresp(line, len, "SEARCH"))
382
3.33k
        return FALSE;
383
842
      break;
384
385
    /* Ignore other untagged responses */
386
3.66k
    default:
387
3.66k
      return FALSE;
388
86.5k
    }
389
390
35.6k
    *resp = '*';
391
35.6k
    return TRUE;
392
86.5k
  }
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
304k
  if(!imap->custom && ((len == 3 && line[0] == '+') ||
399
256k
                       (len >= 2 && !memcmp("+ ", line, 2)))) {
400
526
    switch(imapc->state) {
401
    /* States which are interested in continuation responses */
402
0
    case IMAP_AUTHENTICATE:
403
503
    case IMAP_APPEND:
404
503
      *resp = '+';
405
503
      break;
406
407
23
    default:
408
23
      failf(data, "Unexpected continuation response");
409
23
      *resp = -1;
410
23
      break;
411
526
    }
412
413
526
    return TRUE;
414
526
  }
415
416
304k
  return FALSE; /* Nothing for us */
417
304k
}
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
12.6k
{
469
12.6k
#if defined(DEBUGBUILD) && defined(CURLVERBOSE)
470
  /* for debug purposes */
471
12.6k
  static const char * const names[] = {
472
12.6k
    "STOP",
473
12.6k
    "SERVERGREET",
474
12.6k
    "CAPABILITY",
475
12.6k
    "STARTTLS",
476
12.6k
    "UPGRADETLS",
477
12.6k
    "AUTHENTICATE",
478
12.6k
    "LOGIN",
479
12.6k
    "LIST",
480
12.6k
    "SELECT",
481
12.6k
    "FETCH",
482
12.6k
    "FETCH_FINAL",
483
12.6k
    "APPEND",
484
12.6k
    "APPEND_FINAL",
485
12.6k
    "SEARCH",
486
12.6k
    "LOGOUT",
487
    /* LAST */
488
12.6k
  };
489
490
12.6k
  if(imapc->state != newstate)
491
12.6k
    infof(data, "IMAP %p state change from %s to %s",
492
12.6k
          (void *)imapc, names[imapc->state], names[newstate]);
493
#else
494
  (void)data;
495
#endif
496
12.6k
  imapc->state = newstate;
497
12.6k
}
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
2.10k
{
509
2.10k
  CURLcode result = CURLE_OK;
510
511
2.10k
  imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
512
2.10k
  imapc->sasl.authused = SASL_AUTH_NONE;  /* Clear the auth. mechanism used */
513
2.10k
  imapc->tls_supported = FALSE;           /* Clear the TLS capability */
514
515
  /* Send the CAPABILITY command */
516
2.10k
  result = imap_sendf(data, imapc, "CAPABILITY");
517
518
2.10k
  if(!result)
519
2.10k
    imap_state(data, imapc, IMAP_CAPABILITY);
520
521
2.10k
  return result;
522
2.10k
}
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
46
{
594
46
  CURLcode result = CURLE_OK;
595
46
  char *user;
596
46
  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
46
  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
46
  user = imap_atom(conn->user, FALSE);
608
46
  passwd = imap_atom(conn->passwd, FALSE);
609
610
  /* Send the LOGIN command */
611
46
  result = imap_sendf(data, imapc, "LOGIN %s %s", user ? user : "",
612
46
                      passwd ? passwd : "");
613
614
46
  curlx_free(user);
615
46
  curlx_free(passwd);
616
617
46
  if(!result)
618
46
    imap_state(data, imapc, IMAP_LOGIN);
619
620
46
  return result;
621
46
}
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
2.02k
{
701
2.02k
  CURLcode result = CURLE_OK;
702
2.02k
  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
2.02k
  if(imapc->preauth ||
707
1.98k
     !Curl_sasl_can_authenticate(&imapc->sasl, data)) {
708
1.98k
    imap_state(data, imapc, IMAP_STOP);
709
1.98k
    return result;
710
1.98k
  }
711
712
  /* Calculate the SASL login details */
713
47
  result = Curl_sasl_start(&imapc->sasl, data, (bool)imapc->ir_supported,
714
47
                           &progress);
715
716
47
  if(!result) {
717
47
    if(progress == SASL_INPROGRESS)
718
0
      imap_state(data, imapc, IMAP_AUTHENTICATE);
719
47
    else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
720
      /* Perform clear text authentication */
721
46
      result = imap_perform_login(data, imapc, data->conn);
722
1
    else
723
1
      result = Curl_sasl_is_blocked(&imapc->sasl, data);
724
47
  }
725
726
47
  return result;
727
2.02k
}
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
607
{
739
607
  CURLcode result = CURLE_OK;
740
741
607
  if(imap->custom)
742
    /* Send the custom request */
743
419
    result = imap_sendf(data, imapc, "%s%s", imap->custom,
744
419
                        imap->custom_params ? imap->custom_params : "");
745
188
  else {
746
    /* Make sure the mailbox is in the correct atom format if necessary */
747
188
    char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, TRUE)
748
188
                                  : curlx_strdup("");
749
188
    if(!mailbox)
750
3
      return CURLE_OUT_OF_MEMORY;
751
752
    /* Send the LIST command */
753
185
    result = imap_sendf(data, imapc, "LIST \"%s\" *", mailbox);
754
755
185
    curlx_free(mailbox);
756
185
  }
757
758
604
  if(!result)
759
604
    imap_state(data, imapc, IMAP_LIST);
760
761
604
  return result;
762
607
}
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
909
{
774
909
  CURLcode result = CURLE_OK;
775
909
  char *mailbox;
776
777
  /* Invalidate old information as we are switching mailboxes */
778
909
  curlx_safefree(imapc->mailbox);
779
909
  imapc->mb_uidvalidity_set = FALSE;
780
781
  /* Check we have a mailbox */
782
909
  if(!imap->mailbox) {
783
0
    failf(data, "Cannot SELECT without a mailbox.");
784
0
    return CURLE_URL_MALFORMAT;
785
0
  }
786
787
  /* Make sure the mailbox is in the correct atom format */
788
909
  mailbox = imap_atom(imap->mailbox, FALSE);
789
909
  if(!mailbox)
790
1
    return CURLE_OUT_OF_MEMORY;
791
792
  /* Send the SELECT command */
793
908
  result = imap_sendf(data, imapc, "SELECT %s", mailbox);
794
795
908
  curlx_free(mailbox);
796
797
908
  if(!result)
798
908
    imap_state(data, imapc, IMAP_SELECT);
799
800
908
  return result;
801
909
}
802
803
/***********************************************************************
804
 *
805
 * imap_perform_fetch()
806
 *
807
 * Sends a FETCH command to initiate the download of a message.
808
 */
809
static CURLcode imap_perform_fetch(struct Curl_easy *data,
810
                                   struct imap_conn *imapc,
811
                                   struct IMAP *imap)
812
600
{
813
600
  CURLcode result = CURLE_OK;
814
  /* Check we have a UID */
815
600
  if(imap->uid) {
816
817
    /* Send the FETCH command */
818
599
    if(imap->partial)
819
0
      result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]<%s>",
820
0
                          imap->uid, imap->section ? imap->section : "",
821
0
                          imap->partial);
822
599
    else
823
599
      result = imap_sendf(data, imapc, "UID FETCH %s BODY[%s]",
824
599
                          imap->uid, imap->section ? imap->section : "");
825
599
  }
826
1
  else if(imap->mindex) {
827
    /* Send the FETCH command */
828
1
    if(imap->partial)
829
0
      result = imap_sendf(data, imapc, "FETCH %s BODY[%s]<%s>",
830
0
                          imap->mindex, imap->section ? imap->section : "",
831
0
                          imap->partial);
832
1
    else
833
1
      result = imap_sendf(data, imapc, "FETCH %s BODY[%s]",
834
1
                          imap->mindex, imap->section ? imap->section : "");
835
1
  }
836
0
  else {
837
0
    failf(data, "Cannot FETCH without a UID.");
838
0
    return CURLE_URL_MALFORMAT;
839
0
  }
840
600
  if(!result)
841
600
    imap_state(data, imapc, IMAP_FETCH);
842
843
600
  return result;
844
600
}
845
846
/***********************************************************************
847
 *
848
 * imap_perform_append()
849
 *
850
 * Sends an APPEND command to initiate the upload of a message.
851
 */
852
static CURLcode imap_perform_append(struct Curl_easy *data,
853
                                    struct imap_conn *imapc,
854
                                    struct IMAP *imap)
855
590
{
856
590
  CURLcode result = CURLE_OK;
857
590
  char *mailbox;
858
590
  struct dynbuf flags;
859
860
  /* Check we have a mailbox */
861
590
  if(!imap->mailbox) {
862
2
    failf(data, "Cannot APPEND without a mailbox.");
863
2
    return CURLE_URL_MALFORMAT;
864
2
  }
865
866
588
#ifndef CURL_DISABLE_MIME
867
  /* Prepare the mime data if some. */
868
588
  if(IS_MIME_POST(data)) {
869
514
    curl_mimepart *postp = data->set.mimepostp;
870
871
    /* Use the whole structure as data. */
872
514
    postp->flags &= ~(unsigned int)MIME_BODY_ONLY;
873
874
    /* Add external headers and mime version. */
875
514
    curl_mime_headers(postp, data->set.headers, 0);
876
514
    result = Curl_mime_prepare_headers(data, postp, NULL,
877
514
                                       NULL, MIMESTRATEGY_MAIL);
878
879
514
    if(!result)
880
514
      if(!Curl_checkheaders(data, STRCONST("Mime-Version")))
881
514
        result = Curl_mime_add_header(&postp->curlheaders,
882
514
                                      "Mime-Version: 1.0");
883
884
514
    if(!result)
885
514
      result = Curl_creader_set_mime(data, postp);
886
514
    if(result)
887
0
      return result;
888
514
    data->state.infilesize = Curl_creader_client_length(data);
889
514
  }
890
74
  else
891
74
#endif
892
74
  {
893
74
    result = Curl_creader_set_fread(data, data->state.infilesize);
894
74
    if(result)
895
0
      return result;
896
74
  }
897
898
  /* Check we know the size of the upload */
899
588
  if(data->state.infilesize < 0) {
900
0
    failf(data, "Cannot APPEND with unknown input file size");
901
0
    return CURLE_UPLOAD_FAILED;
902
0
  }
903
904
  /* Make sure the mailbox is in the correct atom format */
905
588
  mailbox = imap_atom(imap->mailbox, FALSE);
906
588
  if(!mailbox)
907
1
    return CURLE_OUT_OF_MEMORY;
908
909
  /* Generate flags string and send the APPEND command */
910
587
  curlx_dyn_init(&flags, 100);
911
587
  if(data->set.upload_flags) {
912
587
    int i;
913
587
    struct ulbits ulflag[] = {
914
587
      {CURLULFLAG_ANSWERED, "Answered"},
915
587
      {CURLULFLAG_DELETED, "Deleted"},
916
587
      {CURLULFLAG_DRAFT, "Draft"},
917
587
      {CURLULFLAG_FLAGGED, "Flagged"},
918
587
      {CURLULFLAG_SEEN, "Seen"},
919
587
      {0, NULL}
920
587
    };
921
922
587
    result = CURLE_OUT_OF_MEMORY;
923
587
    if(curlx_dyn_add(&flags, " (")) {
924
0
      goto cleanup;
925
0
    }
926
927
3.52k
    for(i = 0; ulflag[i].bit; i++) {
928
2.93k
      if(data->set.upload_flags & ulflag[i].bit) {
929
587
        if((curlx_dyn_len(&flags) > 2 && curlx_dyn_add(&flags, " ")) ||
930
587
           curlx_dyn_add(&flags, "\\") ||
931
587
           curlx_dyn_add(&flags, ulflag[i].flag))
932
0
          goto cleanup;
933
587
      }
934
2.93k
    }
935
936
587
    if(curlx_dyn_add(&flags, ")"))
937
0
      goto cleanup;
938
587
  }
939
0
  else if(curlx_dyn_add(&flags, ""))
940
0
    goto cleanup;
941
942
587
  result = imap_sendf(data, imapc, "APPEND %s%s {%" FMT_OFF_T "}",
943
587
                      mailbox, curlx_dyn_ptr(&flags), data->state.infilesize);
944
945
587
cleanup:
946
587
  curlx_dyn_free(&flags);
947
587
  curlx_free(mailbox);
948
949
587
  if(!result)
950
587
    imap_state(data, imapc, IMAP_APPEND);
951
952
587
  return result;
953
587
}
954
955
/***********************************************************************
956
 *
957
 * imap_perform_search()
958
 *
959
 * Sends a SEARCH command.
960
 */
961
static CURLcode imap_perform_search(struct Curl_easy *data,
962
                                    struct imap_conn *imapc,
963
                                    struct IMAP *imap)
964
70
{
965
70
  CURLcode result = CURLE_OK;
966
967
  /* Check we have a query string */
968
70
  if(!imap->query) {
969
0
    failf(data, "Cannot SEARCH without a query string.");
970
0
    return CURLE_URL_MALFORMAT;
971
0
  }
972
973
  /* Send the SEARCH command */
974
70
  result = imap_sendf(data, imapc, "SEARCH %s", imap->query);
975
976
70
  if(!result)
977
70
    imap_state(data, imapc, IMAP_SEARCH);
978
979
70
  return result;
980
70
}
981
982
/***********************************************************************
983
 *
984
 * imap_perform_logout()
985
 *
986
 * Performs the logout action prior to sclose() being called.
987
 */
988
static CURLcode imap_perform_logout(struct Curl_easy *data,
989
                                    struct imap_conn *imapc)
990
1.21k
{
991
  /* Send the LOGOUT command */
992
1.21k
  CURLcode result = imap_sendf(data, imapc, "LOGOUT");
993
994
1.21k
  if(!result)
995
1.21k
    imap_state(data, imapc, IMAP_LOGOUT);
996
997
1.21k
  return result;
998
1.21k
}
999
1000
/* For the initial server greeting */
1001
static CURLcode imap_state_servergreet_resp(struct Curl_easy *data,
1002
                                            struct imap_conn *imapc,
1003
                                            int imapcode,
1004
                                            imapstate instate)
1005
2.12k
{
1006
2.12k
  (void)instate;
1007
1008
2.12k
  if(imapcode == IMAP_RESP_PREAUTH) {
1009
    /* PREAUTH */
1010
172
    imapc->preauth = TRUE;
1011
172
    infof(data, "PREAUTH connection, already authenticated");
1012
172
  }
1013
1.95k
  else if(imapcode != IMAP_RESP_OK) {
1014
21
    failf(data, "Got unexpected imap-server response");
1015
21
    return CURLE_WEIRD_SERVER_REPLY;
1016
21
  }
1017
1018
2.10k
  return imap_perform_capability(data, imapc);
1019
2.12k
}
1020
1021
/* For CAPABILITY responses */
1022
static CURLcode imap_state_capability_resp(struct Curl_easy *data,
1023
                                           struct imap_conn *imapc,
1024
                                           int imapcode,
1025
                                           imapstate instate)
1026
2.02k
{
1027
2.02k
  CURLcode result = CURLE_OK;
1028
2.02k
  const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf);
1029
1030
2.02k
  (void)instate;
1031
1032
  /* Do we have an untagged response? */
1033
2.02k
  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
2.02k
  else if(data->set.use_ssl && !Curl_xfer_is_secure(data)) {
1080
    /* PREAUTH is not compatible with STARTTLS. */
1081
2
    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
2
    else if(data->set.use_ssl <= CURLUSESSL_TRY)
1086
1
      result = imap_perform_authentication(data, imapc);
1087
1
    else {
1088
1
      failf(data, "STARTTLS not available.");
1089
1
      result = CURLE_USE_SSL_FAILED;
1090
1
    }
1091
2
  }
1092
2.02k
  else
1093
2.02k
    result = imap_perform_authentication(data, imapc);
1094
1095
2.02k
  return result;
1096
2.02k
}
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
16
{
1165
16
  CURLcode result = CURLE_OK;
1166
16
  (void)instate;
1167
1168
16
  if(imapcode != IMAP_RESP_OK) {
1169
1
    failf(data, "Access denied. %c", imapcode);
1170
1
    result = CURLE_LOGIN_DENIED;
1171
1
  }
1172
15
  else
1173
    /* End of connect phase */
1174
15
    imap_state(data, imapc, IMAP_STOP);
1175
1176
16
  return result;
1177
16
}
1178
1179
/* Detect IMAP listings vs. downloading a single email */
1180
static bool is_custom_fetch_listing_match(const char *params)
1181
47
{
1182
  /* match " 1:* (FLAGS ..." or " 1,2,3 (FLAGS ..." */
1183
47
  if(*params++ != ' ')
1184
0
    return FALSE;
1185
1186
155
  while(ISDIGIT(*params)) {
1187
121
    params++;
1188
121
    if(*params == 0)
1189
13
      return FALSE;
1190
121
  }
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
10.5k
{
1200
  /* filter out "UID FETCH 1:* (FLAGS ..." queries to list emails */
1201
10.5k
  if(!imap->custom)
1202
1.92k
    return FALSE;
1203
8.66k
  else if(curl_strequal(imap->custom, "FETCH") && imap->custom_params) {
1204
47
    const char *p = imap->custom_params;
1205
47
    return is_custom_fetch_listing_match(p);
1206
47
  }
1207
8.62k
  else if(curl_strequal(imap->custom, "UID") && imap->custom_params) {
1208
311
    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
311
  }
1213
8.62k
  return FALSE;
1214
10.5k
}
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
10.6k
{
1222
10.6k
  CURLcode result = CURLE_OK;
1223
10.6k
  const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf);
1224
10.6k
  size_t len = imapc->pp.nfinal;
1225
10.6k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
1226
1227
10.6k
  DEBUGASSERT(imap);
1228
10.6k
  if(!imap)
1229
0
    return CURLE_FAILED_INIT;
1230
10.6k
  (void)instate;
1231
1232
10.6k
  if(imapcode == '*' && is_custom_fetch_listing(imap)) {
1233
    /* custom FETCH or UID FETCH for listing is not handled here */
1234
20
  }
1235
10.5k
  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
10.5k
    const char *cr = memchr(line, '\r', len);
1239
10.5k
    size_t line_len = cr ? (size_t)(cr - line) : len;
1240
10.5k
    const char *ptr = imap_find_literal(line, line_len);
1241
10.5k
    if(ptr) {
1242
657
      curl_off_t size = 0;
1243
657
      bool parsed = FALSE;
1244
657
      ptr++;
1245
657
      if(!curlx_str_number(&ptr, &size, CURL_OFF_T_MAX) &&
1246
423
         !curlx_str_single(&ptr, '}'))
1247
178
        parsed = TRUE;
1248
1249
657
      if(parsed) {
1250
178
        struct pingpong *pp = &imapc->pp;
1251
178
        size_t buffer_len = curlx_dyn_len(&pp->recvbuf);
1252
178
        size_t after_header = buffer_len - pp->nfinal;
1253
1254
        /* This is a literal response, setup to receive the body data */
1255
178
        infof(data, "Found %" FMT_OFF_T " bytes to download", size);
1256
1257
        /* First write the header line */
1258
178
        result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
1259
178
        if(result)
1260
1
          return result;
1261
1262
        /* Handle data already in buffer after the header line */
1263
177
        if(after_header > 0) {
1264
          /* There is already data in the buffer that is part of the literal
1265
             body or subsequent responses */
1266
172
          size_t chunk = after_header;
1267
1268
          /* Keep only the data after the header line */
1269
172
          curlx_dyn_tail(&pp->recvbuf, chunk);
1270
172
          pp->nfinal = 0; /* done */
1271
1272
          /* Limit chunk to the literal size */
1273
172
          if(chunk > (size_t)size)
1274
25
            chunk = (size_t)size;
1275
1276
172
          if(chunk) {
1277
            /* Write the literal body data */
1278
163
            result = Curl_client_write(data, CLIENTWRITE_BODY,
1279
163
                                       curlx_dyn_ptr(&pp->recvbuf), chunk);
1280
163
            if(result)
1281
2
              return result;
1282
163
          }
1283
1284
          /* Handle remaining data in buffer (either more literal data or
1285
             subsequent responses) */
1286
170
          if(after_header > chunk) {
1287
            /* Keep the data after the literal body */
1288
25
            pp->overflow = after_header - chunk;
1289
25
            curlx_dyn_tail(&pp->recvbuf, pp->overflow);
1290
25
          }
1291
145
          else {
1292
145
            pp->overflow = 0;
1293
145
            curlx_dyn_reset(&pp->recvbuf);
1294
145
          }
1295
170
        }
1296
5
        else {
1297
          /* No data in buffer yet, reset overflow */
1298
5
          pp->overflow = 0;
1299
5
        }
1300
1301
175
        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
2
          size = CURL_OFF_T_MAX;
1305
173
        else
1306
173
          size += len;
1307
1308
        /* Progress size includes both header line and literal body */
1309
175
        Curl_pgrsSetDownloadSize(data, size);
1310
1311
175
        if(data->req.bytecount == size)
1312
          /* All data already transferred (header + literal body) */
1313
4
          Curl_xfer_setup_nop(data);
1314
171
        else {
1315
          /* Setup to receive the literal body data.
1316
             maxdownload and transfer size include both header line and
1317
             literal body */
1318
171
          data->req.maxdownload = size;
1319
171
          Curl_xfer_setup_recv(data, FIRSTSOCKET, size);
1320
171
        }
1321
        /* End of DO phase */
1322
175
        imap_state(data, imapc, IMAP_STOP);
1323
175
      }
1324
479
      else {
1325
        /* Failed to parse literal, write the line */
1326
479
        result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
1327
479
      }
1328
657
    }
1329
9.91k
    else {
1330
      /* No literal, write the line as-is */
1331
9.91k
      result = Curl_client_write(data, CLIENTWRITE_BODY, line, len);
1332
9.91k
    }
1333
10.5k
  }
1334
25
  else if(imapcode != IMAP_RESP_OK)
1335
5
    result = CURLE_QUOTE_ERROR;
1336
20
  else
1337
    /* End of DO phase */
1338
20
    imap_state(data, imapc, IMAP_STOP);
1339
1340
10.6k
  return result;
1341
10.6k
}
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
25.3k
{
1350
25.3k
  CURLcode result = CURLE_OK;
1351
25.3k
  (void)instate;
1352
1353
25.3k
  if(imapcode == '*') {
1354
    /* See if this is an UIDVALIDITY response */
1355
24.4k
    const char *line = curlx_dyn_ptr(&imapc->pp.recvbuf);
1356
24.4k
    size_t len = curlx_dyn_len(&imapc->pp.recvbuf);
1357
24.4k
    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
24.4k
  }
1366
846
  else if(imapcode == IMAP_RESP_OK) {
1367
    /* Check if the UIDVALIDITY has been specified and matches */
1368
843
    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
843
    else {
1374
      /* Note the currently opened mailbox on this connection */
1375
843
      DEBUGASSERT(!imapc->mailbox);
1376
843
      imapc->mailbox = curlx_strdup(imap->mailbox);
1377
843
      if(!imapc->mailbox)
1378
0
        return CURLE_OUT_OF_MEMORY;
1379
1380
843
      if(imap->custom)
1381
173
        result = imap_perform_list(data, imapc, imap);
1382
670
      else if(imap->query)
1383
70
        result = imap_perform_search(data, imapc, imap);
1384
600
      else
1385
600
        result = imap_perform_fetch(data, imapc, imap);
1386
843
    }
1387
843
  }
1388
3
  else {
1389
3
    failf(data, "Select failed");
1390
3
    result = CURLE_LOGIN_DENIED;
1391
3
  }
1392
1393
25.3k
  return result;
1394
25.3k
}
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
549
{
1402
549
  CURLcode result = CURLE_OK;
1403
549
  struct pingpong *pp = &imapc->pp;
1404
549
  const char *ptr = curlx_dyn_ptr(&imapc->pp.recvbuf);
1405
549
  size_t len = imapc->pp.nfinal;
1406
549
  bool parsed = FALSE;
1407
549
  curl_off_t size = 0;
1408
1409
549
  (void)instate;
1410
1411
549
  if(imapcode != '*') {
1412
2
    Curl_pgrsSetDownloadSize(data, -1);
1413
2
    imap_state(data, imapc, IMAP_STOP);
1414
2
    return CURLE_REMOTE_FILE_NOT_FOUND;
1415
2
  }
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
547
  ptr = imap_find_literal(ptr, len);
1420
547
  if(ptr) {
1421
513
    ptr++;
1422
513
    if(!curlx_str_number(&ptr, &size, CURL_OFF_T_MAX) &&
1423
512
       !curlx_str_single(&ptr, '}'))
1424
511
      parsed = TRUE;
1425
513
  }
1426
1427
547
  if(parsed) {
1428
511
    infof(data, "Found %" FMT_OFF_T " bytes to download", size);
1429
511
    Curl_pgrsSetDownloadSize(data, size);
1430
1431
511
    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
278
      size_t chunk = pp->overflow;
1436
1437
      /* keep only the overflow */
1438
278
      curlx_dyn_tail(&pp->recvbuf, chunk);
1439
278
      pp->nfinal = 0; /* done */
1440
1441
278
      if(chunk > (size_t)size)
1442
        /* The conversion from curl_off_t to size_t is always fine here */
1443
13
        chunk = (size_t)size;
1444
1445
278
      if(!chunk) {
1446
        /* no size, we are done with the data */
1447
3
        imap_state(data, imapc, IMAP_STOP);
1448
3
        return CURLE_OK;
1449
3
      }
1450
275
      result = Curl_client_write(data, CLIENTWRITE_BODY,
1451
275
                                 curlx_dyn_ptr(&pp->recvbuf), chunk);
1452
275
      if(result)
1453
1
        return result;
1454
1455
274
      infof(data, "Written %zu bytes, %" FMT_OFF_TU
1456
274
            " bytes are left for transfer", chunk, size - chunk);
1457
1458
      /* Have we used the entire overflow or part of it?*/
1459
274
      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
265
      else {
1465
265
        pp->overflow = 0; /* handled */
1466
        /* Free the cache */
1467
265
        curlx_dyn_reset(&pp->recvbuf);
1468
265
      }
1469
274
    }
1470
1471
507
    if(data->req.bytecount == size)
1472
      /* The entire data is already transferred! */
1473
9
      Curl_xfer_setup_nop(data);
1474
498
    else {
1475
      /* IMAP download */
1476
498
      data->req.maxdownload = size;
1477
498
      Curl_xfer_setup_recv(data, FIRSTSOCKET, size);
1478
498
    }
1479
507
  }
1480
36
  else {
1481
    /* We do not know how to parse this line */
1482
36
    failf(data, "Failed to parse FETCH response.");
1483
36
    result = CURLE_WEIRD_SERVER_REPLY;
1484
36
  }
1485
1486
  /* End of DO phase */
1487
543
  imap_state(data, imapc, IMAP_STOP);
1488
1489
543
  return result;
1490
547
}
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
2
{
1498
2
  CURLcode result = CURLE_OK;
1499
1500
2
  (void)instate;
1501
1502
2
  if(imapcode != IMAP_RESP_OK)
1503
1
    result = CURLE_WEIRD_SERVER_REPLY;
1504
1
  else
1505
    /* End of DONE phase */
1506
1
    imap_state(data, imapc, IMAP_STOP);
1507
1508
2
  return result;
1509
2
}
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
504
{
1517
504
  CURLcode result = CURLE_OK;
1518
504
  (void)instate;
1519
1520
504
  if(imapcode != '+') {
1521
1
    result = CURLE_UPLOAD_FAILED;
1522
1
  }
1523
503
  else {
1524
    /* Set the progress upload size */
1525
503
    Curl_pgrsSetUploadSize(data, data->state.infilesize);
1526
1527
    /* IMAP upload */
1528
503
    Curl_xfer_setup_send(data, FIRSTSOCKET);
1529
1530
    /* End of DO phase */
1531
503
    imap_state(data, imapc, IMAP_STOP);
1532
503
  }
1533
1534
504
  return result;
1535
504
}
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
6
{
1543
6
  CURLcode result = CURLE_OK;
1544
1545
6
  (void)instate;
1546
1547
6
  if(imapcode != IMAP_RESP_OK)
1548
1
    result = CURLE_UPLOAD_FAILED;
1549
5
  else
1550
    /* End of DONE phase */
1551
5
    imap_state(data, imapc, IMAP_STOP);
1552
1553
6
  return result;
1554
6
}
1555
1556
static CURLcode imap_pp_statemachine(struct Curl_easy *data,
1557
                                     struct connectdata *conn)
1558
9.70M
{
1559
9.70M
  CURLcode result = CURLE_OK;
1560
9.70M
  int imapcode;
1561
9.70M
  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
1562
9.70M
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
1563
9.70M
  struct pingpong *pp;
1564
9.70M
  size_t nread = 0;
1565
1566
9.70M
  if(!imapc || !imap)
1567
1.16k
    return CURLE_FAILED_INIT;
1568
9.70M
  pp = &imapc->pp;
1569
  /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1570
9.70M
upgrade_tls:
1571
9.70M
  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
9.70M
  if(pp->sendleft)
1579
9.69M
    return Curl_pp_flushsend(data, pp);
1580
1581
47.4k
  do {
1582
    /* Read the response from the server */
1583
47.4k
    result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &imapcode, &nread);
1584
47.4k
    if(result)
1585
1.66k
      return result;
1586
1587
    /* Was there an error parsing the response line? */
1588
45.7k
    if(imapcode == -1)
1589
23
      return CURLE_WEIRD_SERVER_REPLY;
1590
1591
45.7k
    if(!imapcode)
1592
4.58k
      break;
1593
1594
    /* We have now received a full IMAP server response */
1595
41.1k
    switch(imapc->state) {
1596
2.12k
    case IMAP_SERVERGREET:
1597
2.12k
      result = imap_state_servergreet_resp(data, imapc,
1598
2.12k
                                           imapcode, imapc->state);
1599
2.12k
      break;
1600
1601
2.02k
    case IMAP_CAPABILITY:
1602
2.02k
      result = imap_state_capability_resp(data, imapc, imapcode, imapc->state);
1603
2.02k
      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
16
    case IMAP_LOGIN:
1618
16
      result = imap_state_login_resp(data, imapc, imapcode, imapc->state);
1619
16
      break;
1620
1621
9.77k
    case IMAP_LIST:
1622
10.6k
    case IMAP_SEARCH:
1623
10.6k
      result = imap_state_listsearch_resp(data, imapc, imapcode, imapc->state);
1624
10.6k
      break;
1625
1626
25.3k
    case IMAP_SELECT:
1627
25.3k
      result = imap_state_select_resp(data, imapc, imap,
1628
25.3k
                                      imapcode, imapc->state);
1629
25.3k
      break;
1630
1631
549
    case IMAP_FETCH:
1632
549
      result = imap_state_fetch_resp(data, imapc, imapcode, imapc->state);
1633
549
      break;
1634
1635
2
    case IMAP_FETCH_FINAL:
1636
2
      result = imap_state_fetch_final_resp(data, imapc,
1637
2
                                           imapcode, imapc->state);
1638
2
      break;
1639
1640
504
    case IMAP_APPEND:
1641
504
      result = imap_state_append_resp(data, imapc, imapcode, imapc->state);
1642
504
      break;
1643
1644
6
    case IMAP_APPEND_FINAL:
1645
6
      result = imap_state_append_final_resp(data, imapc,
1646
6
                                            imapcode, imapc->state);
1647
6
      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
41.1k
    }
1655
41.1k
  } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1656
1657
8.01k
  return result;
1658
9.69k
}
1659
1660
/* Called repeatedly until done from multi.c */
1661
static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
1662
9.12k
{
1663
9.12k
  CURLcode result = CURLE_OK;
1664
9.12k
  struct imap_conn *imapc =
1665
9.12k
    Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN);
1666
1667
9.12k
  *done = FALSE;
1668
9.12k
  if(!imapc)
1669
0
    return CURLE_FAILED_INIT;
1670
9.12k
  result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE);
1671
9.12k
  *done = (imapc->state == IMAP_STOP);
1672
1673
9.12k
  return result;
1674
9.12k
}
1675
1676
static CURLcode imap_block_statemach(struct Curl_easy *data,
1677
                                     struct imap_conn *imapc,
1678
                                     bool disconnecting)
1679
1.85k
{
1680
1.85k
  CURLcode result = CURLE_OK;
1681
1682
9.69M
  while(imapc->state != IMAP_STOP && !result)
1683
9.69M
    result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting);
1684
1685
1.85k
  return result;
1686
1.85k
}
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.55k
{
1692
3.55k
  struct imap_conn *imapc =
1693
3.55k
    Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN);
1694
3.55k
  return imapc ? Curl_pp_pollset(data, &imapc->pp, ps) : CURLE_OK;
1695
3.55k
}
1696
1697
static void imap_easy_reset(struct IMAP *imap)
1698
9.81k
{
1699
9.81k
  curlx_safefree(imap->mailbox);
1700
9.81k
  curlx_safefree(imap->uid);
1701
9.81k
  curlx_safefree(imap->mindex);
1702
9.81k
  curlx_safefree(imap->section);
1703
9.81k
  curlx_safefree(imap->partial);
1704
9.81k
  curlx_safefree(imap->query);
1705
9.81k
  curlx_safefree(imap->custom);
1706
9.81k
  curlx_safefree(imap->custom_params);
1707
9.81k
  imap->uidvalidity_set = FALSE;
1708
  /* Clear the transfer mode for the next request */
1709
9.81k
  imap->transfer = PPTRANSFER_BODY;
1710
9.81k
}
1711
1712
/***********************************************************************
1713
 *
1714
 * imap_is_bchar()
1715
 *
1716
 * Portable test of whether the specified char is a "bchar" as defined in the
1717
 * grammar of RFC-5092.
1718
 */
1719
static bool imap_is_bchar(char ch)
1720
131k
{
1721
  /* Performing the alnum check first with macro is faster because of ASCII
1722
     arithmetic */
1723
131k
  return ch && (ISALNUM(ch) || strchr(":@/&=-._~!$\'()*+,%", ch));
1724
131k
}
1725
1726
/***********************************************************************
1727
 *
1728
 * imap_parse_url_options()
1729
 *
1730
 * Parse the URL login options.
1731
 */
1732
static CURLcode imap_parse_url_options(struct connectdata *conn,
1733
                                       struct imap_conn *imapc)
1734
2.69k
{
1735
2.69k
  CURLcode result = CURLE_OK;
1736
2.69k
  const char *ptr = conn->options;
1737
2.69k
  bool prefer_login = FALSE;
1738
1739
2.75k
  while(!result && ptr && *ptr) {
1740
54
    const char *key = ptr;
1741
54
    const char *value;
1742
1743
437
    while(*ptr && *ptr != '=')
1744
383
      ptr++;
1745
1746
54
    value = ptr + 1;
1747
1748
436
    while(*ptr && *ptr != ';')
1749
382
      ptr++;
1750
1751
54
    if(curl_strnequal(key, "AUTH=+LOGIN", 11)) {
1752
      /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */
1753
0
      prefer_login = TRUE;
1754
0
      imapc->sasl.prefmech = SASL_AUTH_NONE;
1755
0
    }
1756
54
    else if(curl_strnequal(key, "AUTH=", 5)) {
1757
31
      prefer_login = FALSE;
1758
31
      result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1759
31
                                               value, ptr - value);
1760
31
    }
1761
23
    else {
1762
23
      prefer_login = FALSE;
1763
23
      result = CURLE_URL_MALFORMAT;
1764
23
    }
1765
1766
54
    if(*ptr == ';')
1767
17
      ptr++;
1768
54
  }
1769
1770
2.69k
  if(prefer_login)
1771
0
    imapc->preftype = IMAP_TYPE_CLEARTEXT;
1772
2.69k
  else {
1773
2.69k
    switch(imapc->sasl.prefmech) {
1774
11
    case SASL_AUTH_NONE:
1775
11
      imapc->preftype = IMAP_TYPE_NONE;
1776
11
      break;
1777
2.66k
    case SASL_AUTH_DEFAULT:
1778
2.66k
      imapc->preftype = IMAP_TYPE_ANY;
1779
2.66k
      break;
1780
23
    default:
1781
23
      imapc->preftype = IMAP_TYPE_SASL;
1782
23
      break;
1783
2.69k
    }
1784
2.69k
  }
1785
1786
2.69k
  return result;
1787
2.69k
}
1788
1789
/***********************************************************************
1790
 *
1791
 * imap_parse_url_path()
1792
 *
1793
 * Parse the URL path into separate path components.
1794
 *
1795
 */
1796
static CURLcode imap_parse_url_path(struct Curl_easy *data,
1797
                                    struct IMAP *imap)
1798
1.99k
{
1799
  /* The imap struct is already initialised in imap_connect() */
1800
1.99k
  CURLcode result = CURLE_OK;
1801
1.99k
  const char *begin = &data->state.up.path[1]; /* skip leading slash */
1802
1.99k
  const char *ptr = begin;
1803
1804
  /* See how much of the URL is a valid path and decode it */
1805
77.3k
  while(imap_is_bchar(*ptr))
1806
75.3k
    ptr++;
1807
1808
1.99k
  if(ptr != begin) {
1809
    /* Remove the trailing slash if present */
1810
1.66k
    const char *end = ptr;
1811
1.66k
    if(end > begin && end[-1] == '/')
1812
66
      end--;
1813
1814
1.66k
    result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
1815
1.66k
                            REJECT_CTRL);
1816
1.66k
    if(result)
1817
1
      return result;
1818
1.66k
  }
1819
326
  else
1820
326
    imap->mailbox = NULL;
1821
1822
  /* There can be any number of parameters in the form ";NAME=VALUE" */
1823
2.81k
  while(*ptr == ';') {
1824
871
    char *name;
1825
871
    char *value;
1826
871
    size_t valuelen;
1827
1828
    /* Find the length of the name parameter */
1829
871
    begin = ++ptr;
1830
9.30k
    while(*ptr && *ptr != '=')
1831
8.43k
      ptr++;
1832
1833
871
    if(!*ptr)
1834
7
      return CURLE_URL_MALFORMAT;
1835
1836
    /* Decode the name parameter */
1837
864
    result = Curl_urldecode(begin, ptr - begin, &name, NULL,
1838
864
                            REJECT_CTRL);
1839
864
    if(result)
1840
1
      return result;
1841
1842
    /* Find the length of the value parameter */
1843
863
    begin = ++ptr;
1844
54.3k
    while(imap_is_bchar(*ptr))
1845
53.4k
      ptr++;
1846
1847
    /* Decode the value parameter */
1848
863
    result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
1849
863
                            REJECT_CTRL);
1850
863
    if(result) {
1851
1
      curlx_free(name);
1852
1
      return result;
1853
1
    }
1854
1855
862
    DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
1856
1857
    /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION
1858
       and PARTIAL) stripping of the trailing slash character if it is
1859
       present.
1860
1861
       Note: Unknown parameters trigger a URL_MALFORMAT error. */
1862
862
    if(valuelen > 0 && value[valuelen - 1] == '/')
1863
48
      value[valuelen - 1] = '\0';
1864
862
    if(valuelen) {
1865
852
      if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) {
1866
0
        curl_off_t num;
1867
0
        const char *p = (const char *)value;
1868
0
        if(!curlx_str_number(&p, &num, UINT_MAX)) {
1869
0
          imap->uidvalidity = (unsigned int)num;
1870
0
          imap->uidvalidity_set = TRUE;
1871
0
        }
1872
0
        curlx_free(value);
1873
0
      }
1874
852
      else if(curl_strequal(name, "UID") && !imap->uid) {
1875
808
        imap->uid = value;
1876
808
      }
1877
44
      else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) {
1878
3
        imap->mindex = value;
1879
3
      }
1880
41
      else if(curl_strequal(name, "SECTION") && !imap->section) {
1881
0
        imap->section = value;
1882
0
      }
1883
41
      else if(curl_strequal(name, "PARTIAL") && !imap->partial) {
1884
0
        imap->partial = value;
1885
0
      }
1886
41
      else {
1887
41
        curlx_free(name);
1888
41
        curlx_free(value);
1889
41
        return CURLE_URL_MALFORMAT;
1890
41
      }
1891
852
    }
1892
10
    else
1893
      /* blank? */
1894
10
      curlx_free(value);
1895
821
    curlx_free(name);
1896
821
  }
1897
1898
  /* Does the URL contain a query parameter? Only valid when we have a mailbox
1899
     and no UID as per RFC-5092 */
1900
1.94k
  if(imap->mailbox && !imap->uid && !imap->mindex) {
1901
    /* Get the query parameter, URL decoded */
1902
839
    CURLUcode uc = curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
1903
839
                                CURLU_URLDECODE);
1904
839
    if(uc == CURLUE_OUT_OF_MEMORY)
1905
0
      return CURLE_OUT_OF_MEMORY;
1906
839
  }
1907
1908
  /* Any extra stuff at the end of the URL is an error */
1909
1.94k
  if(*ptr)
1910
10
    return CURLE_URL_MALFORMAT;
1911
1912
1.93k
  return CURLE_OK;
1913
1.94k
}
1914
1915
/***********************************************************************
1916
 *
1917
 * imap_parse_custom_request()
1918
 *
1919
 * Parse the custom request.
1920
 */
1921
static CURLcode imap_parse_custom_request(struct Curl_easy *data,
1922
                                          struct IMAP *imap)
1923
1.93k
{
1924
1.93k
  CURLcode result = CURLE_OK;
1925
1.93k
  const char *custom = data->set.str[STRING_CUSTOMREQUEST];
1926
1927
1.93k
  if(custom) {
1928
    /* URL decode the custom request */
1929
429
    result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
1930
1931
    /* Extract the parameters if specified */
1932
429
    if(!result) {
1933
428
      const char *params = imap->custom;
1934
1935
1.17k
      while(*params && *params != ' ')
1936
750
        params++;
1937
1938
428
      if(*params) {
1939
70
        imap->custom_params = curlx_strdup(params);
1940
70
        imap->custom[params - imap->custom] = '\0';
1941
1942
70
        if(!imap->custom_params)
1943
0
          result = CURLE_OUT_OF_MEMORY;
1944
70
      }
1945
428
    }
1946
429
  }
1947
1948
1.93k
  return result;
1949
1.93k
}
1950
1951
/***********************************************************************
1952
 *
1953
 * imap_connect()
1954
 *
1955
 * This function should do everything that is to be considered a part of the
1956
 * connection phase.
1957
 *
1958
 * The variable 'done' points to will be TRUE if the protocol-layer connect
1959
 * phase is done when this function returns, or FALSE if not.
1960
 */
1961
static CURLcode imap_connect(struct Curl_easy *data, bool *done)
1962
2.69k
{
1963
2.69k
  struct imap_conn *imapc =
1964
2.69k
    Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN);
1965
2.69k
  CURLcode result = CURLE_OK;
1966
1967
2.69k
  *done = FALSE; /* default to not done yet */
1968
2.69k
  if(!imapc)
1969
0
    return CURLE_FAILED_INIT;
1970
1971
  /* Parse the URL options */
1972
2.69k
  result = imap_parse_url_options(data->conn, imapc);
1973
2.69k
  if(result)
1974
36
    return result;
1975
1976
  /* Start off waiting for the server greeting response */
1977
2.66k
  imap_state(data, imapc, IMAP_SERVERGREET);
1978
1979
  /* Start off with an response id of '*' */
1980
2.66k
  curlx_strcopy(imapc->resptag, sizeof(imapc->resptag), STRCONST("*"));
1981
1982
2.66k
  result = imap_multi_statemach(data, done);
1983
1984
2.66k
  return result;
1985
2.69k
}
1986
1987
/***********************************************************************
1988
 *
1989
 * imap_done()
1990
 *
1991
 * The DONE function. This does what needs to be done after a single DO has
1992
 * performed.
1993
 *
1994
 * Input argument is already checked for validity.
1995
 */
1996
static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
1997
                          bool premature)
1998
2.69k
{
1999
2.69k
  CURLcode result = CURLE_OK;
2000
2.69k
  struct connectdata *conn = data->conn;
2001
2.69k
  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
2002
2.69k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
2003
2004
2.69k
  (void)premature;
2005
2006
2.69k
  if(!imapc)
2007
0
    return CURLE_FAILED_INIT;
2008
2.69k
  if(!imap)
2009
0
    return CURLE_OK;
2010
2011
2.69k
  if(status) {
2012
1.96k
    connclose(conn, "IMAP done with bad status"); /* marked for closure */
2013
1.96k
    result = status;         /* use the already set error code */
2014
1.96k
  }
2015
738
  else if(!data->set.connect_only &&
2016
737
          ((!imap->custom && (imap->uid || imap->mindex)) ||
2017
512
           (imap->custom && data->req.maxdownload > 0) ||
2018
640
           data->state.upload || IS_MIME_POST(data))) {
2019
    /* Handle responses after FETCH or APPEND transfer has finished.
2020
       For custom commands, check if we set up a download which indicates
2021
       a FETCH-like command with literal data. */
2022
2023
640
    if(!data->state.upload && !IS_MIME_POST(data))
2024
116
      imap_state(data, imapc, IMAP_FETCH_FINAL);
2025
524
    else {
2026
      /* End the APPEND command first by sending an empty line */
2027
524
      result = Curl_pp_sendf(data, &imapc->pp, "%s", "");
2028
524
      if(!result)
2029
524
        imap_state(data, imapc, IMAP_APPEND_FINAL);
2030
524
    }
2031
2032
    /* Run the state-machine */
2033
640
    if(!result)
2034
640
      result = imap_block_statemach(data, imapc, FALSE);
2035
640
  }
2036
2037
2.69k
  imap_easy_reset(imap);
2038
2.69k
  return result;
2039
2.69k
}
2040
2041
/***********************************************************************
2042
 *
2043
 * imap_perform()
2044
 *
2045
 * This is the actual DO function for IMAP. Fetch or append a message, or do
2046
 * other things according to the options previously setup.
2047
 */
2048
static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
2049
                             bool *dophase_done)
2050
1.93k
{
2051
  /* This is IMAP and no proxy */
2052
1.93k
  CURLcode result = CURLE_OK;
2053
1.93k
  struct connectdata *conn = data->conn;
2054
1.93k
  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
2055
1.93k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
2056
1.93k
  bool selected = FALSE;
2057
2058
1.93k
  DEBUGF(infof(data, "DO phase starts"));
2059
1.93k
  if(!imapc || !imap)
2060
0
    return CURLE_FAILED_INIT;
2061
2062
1.93k
  if(data->req.no_body) {
2063
    /* Requested no body means no transfer */
2064
16
    imap->transfer = PPTRANSFER_INFO;
2065
16
  }
2066
2067
1.93k
  *dophase_done = FALSE; /* not done yet */
2068
2069
  /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
2070
     has already been selected on this connection */
2071
1.93k
  if(imap->mailbox && imapc->mailbox &&
2072
0
     curl_strequal(imap->mailbox, imapc->mailbox) &&
2073
0
     (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set ||
2074
0
      (imap->uidvalidity == imapc->mb_uidvalidity)))
2075
0
    selected = TRUE;
2076
2077
  /* Start the first command in the DO phase */
2078
1.93k
  if(data->state.upload || IS_MIME_POST(data))
2079
    /* APPEND can be executed directly */
2080
590
    result = imap_perform_append(data, imapc, imap);
2081
1.34k
  else if(imap->custom && (selected || !imap->mailbox))
2082
    /* Custom command using the same mailbox or no mailbox */
2083
246
    result = imap_perform_list(data, imapc, imap);
2084
1.09k
  else if(!imap->custom && selected && (imap->uid || imap->mindex))
2085
    /* FETCH from the same mailbox */
2086
0
    result = imap_perform_fetch(data, imapc, imap);
2087
1.09k
  else if(!imap->custom && selected && imap->query)
2088
    /* SEARCH the current mailbox */
2089
0
    result = imap_perform_search(data, imapc, imap);
2090
1.09k
  else if(imap->mailbox && !selected &&
2091
1.04k
          (imap->custom || imap->uid || imap->mindex || imap->query))
2092
    /* SELECT the mailbox */
2093
909
    result = imap_perform_select(data, imapc, imap);
2094
188
  else
2095
    /* LIST */
2096
188
    result = imap_perform_list(data, imapc, imap);
2097
2098
1.93k
  if(result)
2099
7
    return result;
2100
2101
  /* Run the state-machine */
2102
1.92k
  result = imap_multi_statemach(data, dophase_done);
2103
2104
1.92k
  *connected = Curl_conn_is_connected(conn, FIRSTSOCKET);
2105
2106
1.92k
  if(*dophase_done)
2107
1.12k
    DEBUGF(infof(data, "DO phase is complete"));
2108
2109
1.92k
  return result;
2110
1.93k
}
2111
2112
/* Call this when the DO phase has completed */
2113
static CURLcode imap_dophase_done(struct Curl_easy *data,
2114
                                  struct IMAP *imap,
2115
                                  bool connected)
2116
1.20k
{
2117
1.20k
  (void)connected;
2118
2119
1.20k
  if(imap->transfer != PPTRANSFER_BODY)
2120
    /* no data to transfer */
2121
4
    Curl_xfer_setup_nop(data);
2122
2123
1.20k
  return CURLE_OK;
2124
1.20k
}
2125
2126
/***********************************************************************
2127
 *
2128
 * imap_regular_transfer()
2129
 *
2130
 * The input argument is already checked for validity.
2131
 *
2132
 * Performs all commands done before a regular transfer between a local and a
2133
 * remote host.
2134
 */
2135
static CURLcode imap_regular_transfer(struct Curl_easy *data,
2136
                                      struct IMAP *imap,
2137
                                      bool *dophase_done)
2138
1.93k
{
2139
1.93k
  CURLcode result = CURLE_OK;
2140
1.93k
  bool connected = FALSE;
2141
2142
  /* Make sure size is unknown at this point */
2143
1.93k
  data->req.size = -1;
2144
2145
  /* Set the progress data */
2146
1.93k
  Curl_pgrsReset(data);
2147
2148
  /* Carry out the perform */
2149
1.93k
  result = imap_perform(data, &connected, dophase_done);
2150
2151
  /* Perform post DO phase operations if necessary */
2152
1.93k
  if(!result && *dophase_done)
2153
1.09k
    result = imap_dophase_done(data, imap, connected);
2154
2155
1.93k
  return result;
2156
1.93k
}
2157
2158
/***********************************************************************
2159
 *
2160
 * imap_do()
2161
 *
2162
 * This function is registered as 'curl_do' function. It decodes the path
2163
 * parts etc as a wrapper to the actual DO function (imap_perform).
2164
 *
2165
 * The input argument is already checked for validity.
2166
 */
2167
static CURLcode imap_do(struct Curl_easy *data, bool *done)
2168
1.99k
{
2169
1.99k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
2170
1.99k
  CURLcode result = CURLE_OK;
2171
1.99k
  *done = FALSE; /* default to false */
2172
2173
1.99k
  if(!imap)
2174
0
    return CURLE_FAILED_INIT;
2175
  /* Parse the URL path */
2176
1.99k
  result = imap_parse_url_path(data, imap);
2177
1.99k
  if(result)
2178
61
    return result;
2179
2180
  /* Parse the custom request */
2181
1.93k
  result = imap_parse_custom_request(data, imap);
2182
1.93k
  if(result)
2183
1
    return result;
2184
2185
1.93k
  result = imap_regular_transfer(data, imap, done);
2186
2187
1.93k
  return result;
2188
1.93k
}
2189
2190
/***********************************************************************
2191
 *
2192
 * imap_disconnect()
2193
 *
2194
 * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
2195
 * resources. BLOCKING.
2196
 */
2197
static CURLcode imap_disconnect(struct Curl_easy *data,
2198
                                struct connectdata *conn, bool dead_connection)
2199
7.14k
{
2200
7.14k
  struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN);
2201
2202
7.14k
  if(imapc) {
2203
    /* We cannot send quit unconditionally. If this connection is stale or
2204
       bad in any way (pingpong has pending data to send),
2205
       sending quit and waiting around here will make the
2206
       disconnect wait in vain and cause more problems than we need to. */
2207
7.12k
    if(!dead_connection && conn->bits.protoconnstart &&
2208
1.22k
       !Curl_pp_needs_flush(data, &imapc->pp)) {
2209
1.21k
      if(!imap_perform_logout(data, imapc))
2210
1.21k
        (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */
2211
1.21k
    }
2212
7.12k
  }
2213
7.14k
  return CURLE_OK;
2214
7.14k
}
2215
2216
/* Called from multi.c while DOing */
2217
static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
2218
3.96k
{
2219
3.96k
  struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY);
2220
3.96k
  CURLcode result;
2221
2222
3.96k
  if(!imap)
2223
0
    return CURLE_FAILED_INIT;
2224
2225
3.96k
  result = imap_multi_statemach(data, dophase_done);
2226
3.96k
  if(result)
2227
569
    DEBUGF(infof(data, "DO phase failed"));
2228
3.39k
  else if(*dophase_done) {
2229
115
    result = imap_dophase_done(data, imap, FALSE /* not connected */);
2230
2231
115
    DEBUGF(infof(data, "DO phase is complete"));
2232
115
  }
2233
2234
3.96k
  return result;
2235
3.96k
}
2236
2237
static void imap_easy_dtor(void *key, size_t klen, void *entry)
2238
7.12k
{
2239
7.12k
  struct IMAP *imap = entry;
2240
7.12k
  (void)key;
2241
7.12k
  (void)klen;
2242
7.12k
  imap_easy_reset(imap);
2243
7.12k
  curlx_free(imap);
2244
7.12k
}
2245
2246
static void imap_conn_dtor(void *key, size_t klen, void *entry)
2247
7.12k
{
2248
7.12k
  struct imap_conn *imapc = entry;
2249
7.12k
  (void)key;
2250
7.12k
  (void)klen;
2251
7.12k
  Curl_pp_disconnect(&imapc->pp);
2252
7.12k
  curlx_dyn_free(&imapc->dyn);
2253
7.12k
  curlx_safefree(imapc->mailbox);
2254
7.12k
  curlx_free(imapc);
2255
7.12k
}
2256
2257
/* SASL parameters for the imap protocol */
2258
static const struct SASLproto saslimap = {
2259
  "imap",                     /* The service name */
2260
  imap_perform_authenticate,  /* Send authentication command */
2261
  imap_continue_authenticate, /* Send authentication continuation */
2262
  imap_cancel_authenticate,   /* Send authentication cancellation */
2263
  imap_get_message,           /* Get SASL response message */
2264
  0,                          /* No maximum initial response length */
2265
  '+',                        /* Code received when continuation is expected */
2266
  IMAP_RESP_OK,               /* Code to receive upon authentication success */
2267
  SASL_AUTH_DEFAULT,          /* Default mechanisms */
2268
  SASL_FLAG_BASE64            /* Configuration flags */
2269
};
2270
2271
static CURLcode imap_setup_connection(struct Curl_easy *data,
2272
                                      struct connectdata *conn)
2273
7.12k
{
2274
7.12k
  struct imap_conn *imapc;
2275
7.12k
  struct pingpong *pp;
2276
7.12k
  struct IMAP *imap;
2277
2278
7.12k
  imapc = curlx_calloc(1, sizeof(*imapc));
2279
7.12k
  if(!imapc)
2280
0
    return CURLE_OUT_OF_MEMORY;
2281
2282
7.12k
  pp = &imapc->pp;
2283
7.12k
  PINGPONG_SETUP(pp, imap_pp_statemachine, imap_endofresp);
2284
2285
  /* Set the default preferred authentication type and mechanism */
2286
7.12k
  imapc->preftype = IMAP_TYPE_ANY;
2287
7.12k
  Curl_sasl_init(&imapc->sasl, data, &saslimap);
2288
2289
7.12k
  curlx_dyn_init(&imapc->dyn, DYN_IMAP_CMD);
2290
7.12k
  Curl_pp_init(pp, Curl_pgrs_now(data));
2291
2292
7.12k
  if(Curl_conn_meta_set(conn, CURL_META_IMAP_CONN, imapc, imap_conn_dtor))
2293
0
    return CURLE_OUT_OF_MEMORY;
2294
2295
7.12k
  imap = curlx_calloc(1, sizeof(struct IMAP));
2296
7.12k
  if(!imap ||
2297
7.12k
     Curl_meta_set(data, CURL_META_IMAP_EASY, imap, imap_easy_dtor))
2298
0
    return CURLE_OUT_OF_MEMORY;
2299
2300
7.12k
  return CURLE_OK;
2301
7.12k
}
2302
2303
/*
2304
 * IMAP protocol.
2305
 */
2306
const struct Curl_protocol Curl_protocol_imap = {
2307
  imap_setup_connection,            /* setup_connection */
2308
  imap_do,                          /* do_it */
2309
  imap_done,                        /* done */
2310
  ZERO_NULL,                        /* do_more */
2311
  imap_connect,                     /* connect_it */
2312
  imap_multi_statemach,             /* connecting */
2313
  imap_doing,                       /* doing */
2314
  imap_pollset,                     /* proto_pollset */
2315
  imap_pollset,                     /* doing_pollset */
2316
  ZERO_NULL,                        /* domore_pollset */
2317
  ZERO_NULL,                        /* perform_pollset */
2318
  imap_disconnect,                  /* disconnect */
2319
  ZERO_NULL,                        /* write_resp */
2320
  ZERO_NULL,                        /* write_resp_hd */
2321
  ZERO_NULL,                        /* connection_is_dead */
2322
  ZERO_NULL,                        /* attach connection */
2323
  ZERO_NULL,                        /* follow */
2324
};
2325
2326
#endif /* CURL_DISABLE_IMAP */