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