Coverage Report

Created: 2026-08-13 07:42

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