Coverage Report

Created: 2026-11-12 08:28

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