Coverage Report

Created: 2026-07-16 06:10

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