Coverage Report

Created: 2026-07-30 07:26

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