Coverage Report

Created: 2025-10-10 06:31

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