Coverage Report

Created: 2026-08-31 06:46

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