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