Coverage Report

Created: 2025-11-23 06:13

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