Coverage Report

Created: 2025-10-30 06:17

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