Coverage Report

Created: 2026-10-15 06:19

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