Coverage Report

Created: 2026-01-09 07:14

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