1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <[email protected]>, 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_UTSNAME_H
49#include <sys/utsname.h>
50#endif
51#ifdef HAVE_NETDB_H
52#include <netdb.h>
53#endif
54#ifdef __VMS
55#include <in.h>
56#include <inet.h>
57#endif
58
59#if (defined(NETWARE) && defined(__NOVELL_LIBC__))
60#undef in_addr_t
61#define in_addr_t unsigned long
62#endif
63
64#include <curl/curl.h>
65#include "urldata.h"
66#include "sendf.h"
67#include "hostip.h"
68#include "progress.h"
69#include "transfer.h"
70#include "escape.h"
71#include "http.h" /* for HTTP proxy tunnel stuff */
72#include "socks.h"
73#include "imap.h"
74#include "mime.h"
75#include "strtoofft.h"
76#include "strcase.h"
77#include "vtls/vtls.h"
78#include "connect.h"
79#include "select.h"
80#include "multiif.h"
81#include "url.h"
82#include "strcase.h"
83#include "bufref.h"
84#include "curl_sasl.h"
85#include "warnless.h"
86#include "curl_ctype.h"
87
88/* The last 3 #include files should be in this order */
89#include "curl_printf.h"
90#include "curl_memory.h"
91#include "memdebug.h"
92
93/* Local API functions */
94static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done);
95static CURLcode imap_do(struct Curl_easy *data, bool *done);
96static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
97 bool premature);
98static CURLcode imap_connect(struct Curl_easy *data, bool *done);
99static CURLcode imap_disconnect(struct Curl_easy *data,
100 struct connectdata *conn, bool dead);
101static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done);
102static int imap_getsock(struct Curl_easy *data, struct connectdata *conn,
103 curl_socket_t *socks);
104static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done);
105static CURLcode imap_setup_connection(struct Curl_easy *data,
106 struct connectdata *conn);
107static char *imap_atom(const char *str, bool escape_only);
108static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...);
109static CURLcode imap_parse_url_options(struct connectdata *conn);
110static CURLcode imap_parse_url_path(struct Curl_easy *data);
111static CURLcode imap_parse_custom_request(struct Curl_easy *data);
112static CURLcode imap_perform_authenticate(struct Curl_easy *data,
113 const char *mech,
114 const struct bufref *initresp);
115static CURLcode imap_continue_authenticate(struct Curl_easy *data,
116 const char *mech,
117 const struct bufref *resp);
118static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
119 const char *mech);
120static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out);
121
122/*
123 * IMAP protocol handler.
124 */
125
126const struct Curl_handler Curl_handler_imap = {
127 "IMAP", /* scheme */
128 imap_setup_connection, /* setup_connection */
129 imap_do, /* do_it */
130 imap_done, /* done */
131 ZERO_NULL, /* do_more */
132 imap_connect, /* connect_it */
133 imap_multi_statemach, /* connecting */
134 imap_doing, /* doing */
135 imap_getsock, /* proto_getsock */
136 imap_getsock, /* doing_getsock */
137 ZERO_NULL, /* domore_getsock */
138 ZERO_NULL, /* perform_getsock */
139 imap_disconnect, /* disconnect */
140 ZERO_NULL, /* readwrite */
141 ZERO_NULL, /* connection_check */
142 ZERO_NULL, /* attach connection */
143 PORT_IMAP, /* defport */
144 CURLPROTO_IMAP, /* protocol */
145 CURLPROTO_IMAP, /* family */
146 PROTOPT_CLOSEACTION| /* flags */
147 PROTOPT_URLOPTIONS
148};
149
150#ifdef USE_SSL
151/*
152 * IMAPS protocol handler.
153 */
154
155const struct Curl_handler Curl_handler_imaps = {
156 "IMAPS", /* scheme */
157 imap_setup_connection, /* setup_connection */
158 imap_do, /* do_it */
159 imap_done, /* done */
160 ZERO_NULL, /* do_more */
161 imap_connect, /* connect_it */
162 imap_multi_statemach, /* connecting */
163 imap_doing, /* doing */
164 imap_getsock, /* proto_getsock */
165 imap_getsock, /* doing_getsock */
166 ZERO_NULL, /* domore_getsock */
167 ZERO_NULL, /* perform_getsock */
168 imap_disconnect, /* disconnect */
169 ZERO_NULL, /* readwrite */
170 ZERO_NULL, /* connection_check */
171 ZERO_NULL, /* attach connection */
172 PORT_IMAPS, /* defport */
173 CURLPROTO_IMAPS, /* protocol */
174 CURLPROTO_IMAP, /* family */
175 PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
176 PROTOPT_URLOPTIONS
177};
178#endif
179
180#define IMAP_RESP_OK 1
181#define IMAP_RESP_NOT_OK 2
182#define IMAP_RESP_PREAUTH 3
183
184/* SASL parameters for the imap protocol */
185static const struct SASLproto saslimap = {
186 "imap", /* The service name */
187 imap_perform_authenticate, /* Send authentication command */
188 imap_continue_authenticate, /* Send authentication continuation */
189 imap_cancel_authenticate, /* Send authentication cancellation */
190 imap_get_message, /* Get SASL response message */
191 0, /* No maximum initial response length */
192 '+', /* Code received when continuation is expected */
193 IMAP_RESP_OK, /* Code to receive upon authentication success */
194 SASL_AUTH_DEFAULT, /* Default mechanisms */
195 SASL_FLAG_BASE64 /* Configuration flags */
196};
197
198
199#ifdef USE_SSL
200static void imap_to_imaps(struct connectdata *conn)
201{
202 /* Change the connection handler */
203 conn->handler = &Curl_handler_imaps;
204
205 /* Set the connection's upgraded to TLS flag */
206 conn->bits.tls_upgraded = TRUE;
207}
208#else
209#define imap_to_imaps(x) Curl_nop_stmt
210#endif
211
212/***********************************************************************
213 *
214 * imap_matchresp()
215 *
216 * Determines whether the untagged response is related to the specified
217 * command by checking if it is in format "* <command-name> ..." or
218 * "* <number> <command-name> ...".
219 *
220 * The "* " marker is assumed to have already been checked by the caller.
221 */
222static bool imap_matchresp(const char *line, size_t len, const char *cmd)
223{
224 const char *end = line + len;
225 size_t cmd_len = strlen(cmd);
226
227 /* Skip the untagged response marker */
228 line += 2;
229
230 /* Do we have a number after the marker? */
231 if(line < end && ISDIGIT(*line)) {
232 /* Skip the number */
233 do
234 line++;
235 while(line < end && ISDIGIT(*line));
236
237 /* Do we have the space character? */
238 if(line == end || *line != ' ')
239 return FALSE;
240
241 line++;
242 }
243
244 /* Does the command name match and is it followed by a space character or at
245 the end of line? */
246 if(line + cmd_len <= end && strncasecompare(line, cmd, cmd_len) &&
247 (line[cmd_len] == ' ' || line + cmd_len + 2 == end))
248 return TRUE;
249
250 return FALSE;
251}
252
253/***********************************************************************
254 *
255 * imap_endofresp()
256 *
257 * Checks whether the given string is a valid tagged, untagged or continuation
258 * response which can be processed by the response handler.
259 */
260static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn,
261 char *line, size_t len, int *resp)
262{
263 struct IMAP *imap = data->req.p.imap;
264 struct imap_conn *imapc = &conn->proto.imapc;
265 const char *id = imapc->resptag;
266 size_t id_len = strlen(id);
267
268 /* Do we have a tagged command response? */
269 if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') {
270 line += id_len + 1;
271 len -= id_len + 1;
272
273 if(len >= 2 && !memcmp(line, "OK", 2))
274 *resp = IMAP_RESP_OK;
275 else if(len >= 7 && !memcmp(line, "PREAUTH", 7))
276 *resp = IMAP_RESP_PREAUTH;
277 else
278 *resp = IMAP_RESP_NOT_OK;
279
280 return TRUE;
281 }
282
283 /* Do we have an untagged command response? */
284 if(len >= 2 && !memcmp("* ", line, 2)) {
285 switch(imapc->state) {
286 /* States which are interested in untagged responses */
287 case IMAP_CAPABILITY:
288 if(!imap_matchresp(line, len, "CAPABILITY"))
289 return FALSE;
290 break;
291
292 case IMAP_LIST:
293 if((!imap->custom && !imap_matchresp(line, len, "LIST")) ||
294 (imap->custom && !imap_matchresp(line, len, imap->custom) &&
295 (!strcasecompare(imap->custom, "STORE") ||
296 !imap_matchresp(line, len, "FETCH")) &&
297 !strcasecompare(imap->custom, "SELECT") &&
298 !strcasecompare(imap->custom, "EXAMINE") &&
299 !strcasecompare(imap->custom, "SEARCH") &&
300 !strcasecompare(imap->custom, "EXPUNGE") &&
301 !strcasecompare(imap->custom, "LSUB") &&
302 !strcasecompare(imap->custom, "UID") &&
303 !strcasecompare(imap->custom, "GETQUOTAROOT") &&
304 !strcasecompare(imap->custom, "NOOP")))
305 return FALSE;
306 break;
307
308 case IMAP_SELECT:
309 /* SELECT is special in that its untagged responses do not have a
310 common prefix so accept anything! */
311 break;
312
313 case IMAP_FETCH:
314 if(!imap_matchresp(line, len, "FETCH"))
315 return FALSE;
316 break;
317
318 case IMAP_SEARCH:
319 if(!imap_matchresp(line, len, "SEARCH"))
320 return FALSE;
321 break;
322
323 /* Ignore other untagged responses */
324 default:
325 return FALSE;
326 }
327
328 *resp = '*';
329 return TRUE;
330 }
331
332 /* Do we have a continuation response? This should be a + symbol followed by
333 a space and optionally some text as per RFC-3501 for the AUTHENTICATE and
334 APPEND commands and as outlined in Section 4. Examples of RFC-4959 but
335 some email servers ignore this and only send a single + instead. */
336 if(imap && !imap->custom && ((len == 3 && line[0] == '+') ||
337 (len >= 2 && !memcmp("+ ", line, 2)))) {
338 switch(imapc->state) {
339 /* States which are interested in continuation responses */
340 case IMAP_AUTHENTICATE:
341 case IMAP_APPEND:
342 *resp = '+';
343 break;
344
345 default:
346 failf(data, "Unexpected continuation response");
347 *resp = -1;
348 break;
349 }
350
351 return TRUE;
352 }
353
354 return FALSE; /* Nothing for us */
355}
356
357/***********************************************************************
358 *
359 * imap_get_message()
360 *
361 * Gets the authentication message from the response buffer.
362 */
363static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out)
364{
365 char *message = data->state.buffer;
366 size_t len = strlen(message);
367
368 if(len > 2) {
369 /* Find the start of the message */
370 len -= 2;
371 for(message += 2; *message == ' ' || *message == '\t'; message++, len--)
372 ;
373
374 /* Find the end of the message */
375 while(len--)
376 if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
377 message[len] != '\t')
378 break;
379
380 /* Terminate the message */
381 message[++len] = '\0';
382 Curl_bufref_set(out, message, len, NULL);
383 }
384 else
385 /* junk input => zero length output */
386 Curl_bufref_set(out, "", 0, NULL);
387
388 return CURLE_OK;
389}
390
391/***********************************************************************
392 *
393 * state()
394 *
395 * This is the ONLY way to change IMAP state!
396 */
397static void state(struct Curl_easy *data, imapstate newstate)
398{
399 struct imap_conn *imapc = &data->conn->proto.imapc;
400#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
401 /* for debug purposes */
402 static const char * const names[]={
403 "STOP",
404 "SERVERGREET",
405 "CAPABILITY",
406 "STARTTLS",
407 "UPGRADETLS",
408 "AUTHENTICATE",
409 "LOGIN",
410 "LIST",
411 "SELECT",
412 "FETCH",
413 "FETCH_FINAL",
414 "APPEND",
415 "APPEND_FINAL",
416 "SEARCH",
417 "LOGOUT",
418 /* LAST */
419 };
420
421 if(imapc->state != newstate)
422 infof(data, "IMAP %p state change from %s to %s",
423 (void *)imapc, names[imapc->state], names[newstate]);
424#endif
425
426 imapc->state = newstate;
427}
428
429/***********************************************************************
430 *
431 * imap_perform_capability()
432 *
433 * Sends the CAPABILITY command in order to obtain a list of server side
434 * supported capabilities.
435 */
436static CURLcode imap_perform_capability(struct Curl_easy *data,
437 struct connectdata *conn)
438{
439 CURLcode result = CURLE_OK;
440 struct imap_conn *imapc = &conn->proto.imapc;
441 imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
442 imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */
443 imapc->tls_supported = FALSE; /* Clear the TLS capability */
444
445 /* Send the CAPABILITY command */
446 result = imap_sendf(data, "CAPABILITY");
447
448 if(!result)
449 state(data, IMAP_CAPABILITY);
450
451 return result;
452}
453
454/***********************************************************************
455 *
456 * imap_perform_starttls()
457 *
458 * Sends the STARTTLS command to start the upgrade to TLS.
459 */
460static CURLcode imap_perform_starttls(struct Curl_easy *data)
461{
462 /* Send the STARTTLS command */
463 CURLcode result = imap_sendf(data, "STARTTLS");
464
465 if(!result)
466 state(data, IMAP_STARTTLS);
467
468 return result;
469}
470
471/***********************************************************************
472 *
473 * imap_perform_upgrade_tls()
474 *
475 * Performs the upgrade to TLS.
476 */
477static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data,
478 struct connectdata *conn)
479{
480 /* Start the SSL connection */
481 struct imap_conn *imapc = &conn->proto.imapc;
482 CURLcode result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
483 FIRSTSOCKET, &imapc->ssldone);
484
485 if(!result) {
486 if(imapc->state != IMAP_UPGRADETLS)
487 state(data, IMAP_UPGRADETLS);
488
489 if(imapc->ssldone) {
490 imap_to_imaps(conn);
491 result = imap_perform_capability(data, conn);
492 }
493 }
494
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 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 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 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 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 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 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 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->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 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 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 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 && !conn->ssl[FIRSTSOCKET].use) {
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 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 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 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 char tmp[20];
1095 if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1096 Curl_safefree(imapc->mailbox_uidvalidity);
1097 imapc->mailbox_uidvalidity = strdup(tmp);
1098 }
1099 }
1100 else if(imapcode == IMAP_RESP_OK) {
1101 /* Check if the UIDVALIDITY has been specified and matches */
1102 if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1103 !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1104 failf(data, "Mailbox UIDVALIDITY has changed");
1105 result = CURLE_REMOTE_FILE_NOT_FOUND;
1106 }
1107 else {
1108 /* Note the currently opened mailbox on this connection */
1109 imapc->mailbox = strdup(imap->mailbox);
1110
1111 if(imap->custom)
1112 result = imap_perform_list(data);
1113 else if(imap->query)
1114 result = imap_perform_search(data);
1115 else
1116 result = imap_perform_fetch(data);
1117 }
1118 }
1119 else {
1120 failf(data, "Select failed");
1121 result = CURLE_LOGIN_DENIED;
1122 }
1123
1124 return result;
1125}
1126
1127/* For the (first line of the) FETCH responses */
1128static CURLcode imap_state_fetch_resp(struct Curl_easy *data,
1129 struct connectdata *conn, int imapcode,
1130 imapstate instate)
1131{
1132 CURLcode result = CURLE_OK;
1133 struct imap_conn *imapc = &conn->proto.imapc;
1134 struct pingpong *pp = &imapc->pp;
1135 const char *ptr = data->state.buffer;
1136 bool parsed = FALSE;
1137 curl_off_t size = 0;
1138
1139 (void)instate; /* no use for this yet */
1140
1141 if(imapcode != '*') {
1142 Curl_pgrsSetDownloadSize(data, -1);
1143 state(data, IMAP_STOP);
1144 return CURLE_REMOTE_FILE_NOT_FOUND;
1145 }
1146
1147 /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1148 the continuation data contained within the curly brackets */
1149 while(*ptr && (*ptr != '{'))
1150 ptr++;
1151
1152 if(*ptr == '{') {
1153 char *endptr;
1154 if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) {
1155 if(endptr - ptr > 1 && endptr[0] == '}' &&
1156 endptr[1] == '\r' && endptr[2] == '\0')
1157 parsed = TRUE;
1158 }
1159 }
1160
1161 if(parsed) {
1162 infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download",
1163 size);
1164 Curl_pgrsSetDownloadSize(data, size);
1165
1166 if(pp->cache) {
1167 /* At this point there is a bunch of data in the header "cache" that is
1168 actually body content, send it as body and then skip it. Do note
1169 that there may even be additional "headers" after the body. */
1170 size_t chunk = pp->cache_size;
1171
1172 if(chunk > (size_t)size)
1173 /* The conversion from curl_off_t to size_t is always fine here */
1174 chunk = (size_t)size;
1175
1176 if(!chunk) {
1177 /* no size, we're done with the data */
1178 state(data, IMAP_STOP);
1179 return CURLE_OK;
1180 }
1181 result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk);
1182 if(result)
1183 return result;
1184
1185 data->req.bytecount += chunk;
1186
1187 infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU
1188 " bytes are left for transfer", chunk, size - chunk);
1189
1190 /* Have we used the entire cache or just part of it?*/
1191 if(pp->cache_size > chunk) {
1192 /* Only part of it so shrink the cache to fit the trailing data */
1193 memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1194 pp->cache_size -= chunk;
1195 }
1196 else {
1197 /* Free the cache */
1198 Curl_safefree(pp->cache);
1199
1200 /* Reset the cache size */
1201 pp->cache_size = 0;
1202 }
1203 }
1204
1205 if(data->req.bytecount == size)
1206 /* The entire data is already transferred! */
1207 Curl_setup_transfer(data, -1, -1, FALSE, -1);
1208 else {
1209 /* IMAP download */
1210 data->req.maxdownload = size;
1211 /* force a recv/send check of this connection, as the data might've been
1212 read off the socket already */
1213 data->conn->cselect_bits = CURL_CSELECT_IN;
1214 Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1);
1215 }
1216 }
1217 else {
1218 /* We don't know how to parse this line */
1219 failf(data, "Failed to parse FETCH response.");
1220 result = CURLE_WEIRD_SERVER_REPLY;
1221 }
1222
1223 /* End of DO phase */
1224 state(data, IMAP_STOP);
1225
1226 return result;
1227}
1228
1229/* For final FETCH responses performed after the download */
1230static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data,
1231 int imapcode,
1232 imapstate instate)
1233{
1234 CURLcode result = CURLE_OK;
1235
1236 (void)instate; /* No use for this yet */
1237
1238 if(imapcode != IMAP_RESP_OK)
1239 result = CURLE_WEIRD_SERVER_REPLY;
1240 else
1241 /* End of DONE phase */
1242 state(data, IMAP_STOP);
1243
1244 return result;
1245}
1246
1247/* For APPEND responses */
1248static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode,
1249 imapstate instate)
1250{
1251 CURLcode result = CURLE_OK;
1252 (void)instate; /* No use for this yet */
1253
1254 if(imapcode != '+') {
1255 result = CURLE_UPLOAD_FAILED;
1256 }
1257 else {
1258 /* Set the progress upload size */
1259 Curl_pgrsSetUploadSize(data, data->state.infilesize);
1260
1261 /* IMAP upload */
1262 Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET);
1263
1264 /* End of DO phase */
1265 state(data, IMAP_STOP);
1266 }
1267
1268 return result;
1269}
1270
1271/* For final APPEND responses performed after the upload */
1272static CURLcode imap_state_append_final_resp(struct Curl_easy *data,
1273 int imapcode,
1274 imapstate instate)
1275{
1276 CURLcode result = CURLE_OK;
1277
1278 (void)instate; /* No use for this yet */
1279
1280 if(imapcode != IMAP_RESP_OK)
1281 result = CURLE_UPLOAD_FAILED;
1282 else
1283 /* End of DONE phase */
1284 state(data, IMAP_STOP);
1285
1286 return result;
1287}
1288
1289static CURLcode imap_statemachine(struct Curl_easy *data,
1290 struct connectdata *conn)
1291{
1292 CURLcode result = CURLE_OK;
1293 curl_socket_t sock = conn->sock[FIRSTSOCKET];
1294 int imapcode;
1295 struct imap_conn *imapc = &conn->proto.imapc;
1296 struct pingpong *pp = &imapc->pp;
1297 size_t nread = 0;
1298 (void)data;
1299
1300 /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1301 if(imapc->state == IMAP_UPGRADETLS)
1302 return imap_perform_upgrade_tls(data, conn);
1303
1304 /* Flush any data that needs to be sent */
1305 if(pp->sendleft)
1306 return Curl_pp_flushsend(data, pp);
1307
1308 do {
1309 /* Read the response from the server */
1310 result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread);
1311 if(result)
1312 return result;
1313
1314 /* Was there an error parsing the response line? */
1315 if(imapcode == -1)
1316 return CURLE_WEIRD_SERVER_REPLY;
1317
1318 if(!imapcode)
1319 break;
1320
1321 /* We have now received a full IMAP server response */
1322 switch(imapc->state) {
1323 case IMAP_SERVERGREET:
1324 result = imap_state_servergreet_resp(data, imapcode, imapc->state);
1325 break;
1326
1327 case IMAP_CAPABILITY:
1328 result = imap_state_capability_resp(data, imapcode, imapc->state);
1329 break;
1330
1331 case IMAP_STARTTLS:
1332 result = imap_state_starttls_resp(data, imapcode, imapc->state);
1333 break;
1334
1335 case IMAP_AUTHENTICATE:
1336 result = imap_state_auth_resp(data, conn, imapcode, imapc->state);
1337 break;
1338
1339 case IMAP_LOGIN:
1340 result = imap_state_login_resp(data, imapcode, imapc->state);
1341 break;
1342
1343 case IMAP_LIST:
1344 case IMAP_SEARCH:
1345 result = imap_state_listsearch_resp(data, imapcode, imapc->state);
1346 break;
1347
1348 case IMAP_SELECT:
1349 result = imap_state_select_resp(data, imapcode, imapc->state);
1350 break;
1351
1352 case IMAP_FETCH:
1353 result = imap_state_fetch_resp(data, conn, imapcode, imapc->state);
1354 break;
1355
1356 case IMAP_FETCH_FINAL:
1357 result = imap_state_fetch_final_resp(data, imapcode, imapc->state);
1358 break;
1359
1360 case IMAP_APPEND:
1361 result = imap_state_append_resp(data, imapcode, imapc->state);
1362 break;
1363
1364 case IMAP_APPEND_FINAL:
1365 result = imap_state_append_final_resp(data, imapcode, imapc->state);
1366 break;
1367
1368 case IMAP_LOGOUT:
1369 /* fallthrough, just stop! */
1370 default:
1371 /* internal error */
1372 state(data, IMAP_STOP);
1373 break;
1374 }
1375 } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1376
1377 return result;
1378}
1379
1380/* Called repeatedly until done from multi.c */
1381static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
1382{
1383 CURLcode result = CURLE_OK;
1384 struct connectdata *conn = data->conn;
1385 struct imap_conn *imapc = &conn->proto.imapc;
1386
1387 if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1388 result = Curl_ssl_connect_nonblocking(data, conn, FALSE,
1389 FIRSTSOCKET, &imapc->ssldone);
1390 if(result || !imapc->ssldone)
1391 return result;
1392 }
1393
1394 result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE);
1395 *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE;
1396
1397 return result;
1398}
1399
1400static CURLcode imap_block_statemach(struct Curl_easy *data,
1401 struct connectdata *conn,
1402 bool disconnecting)
1403{
1404 CURLcode result = CURLE_OK;
1405 struct imap_conn *imapc = &conn->proto.imapc;
1406
1407 while(imapc->state != IMAP_STOP && !result)
1408 result = Curl_pp_statemach(data, &imapc->pp, TRUE, disconnecting);
1409
1410 return result;
1411}
1412
1413/* Allocate and initialize the struct IMAP for the current Curl_easy if
1414 required */
1415static CURLcode imap_init(struct Curl_easy *data)
1416{
1417 CURLcode result = CURLE_OK;
1418 struct IMAP *imap;
1419
1420 imap = data->req.p.imap = calloc(sizeof(struct IMAP), 1);
1421 if(!imap)
1422 result = CURLE_OUT_OF_MEMORY;
1423
1424 return result;
1425}
1426
1427/* For the IMAP "protocol connect" and "doing" phases only */
1428static int imap_getsock(struct Curl_easy *data,
1429 struct connectdata *conn,
1430 curl_socket_t *socks)
1431{
1432 return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks);
1433}
1434
1435/***********************************************************************
1436 *
1437 * imap_connect()
1438 *
1439 * This function should do everything that is to be considered a part of the
1440 * connection phase.
1441 *
1442 * The variable 'done' points to will be TRUE if the protocol-layer connect
1443 * phase is done when this function returns, or FALSE if not.
1444 */
1445static CURLcode imap_connect(struct Curl_easy *data, bool *done)
1446{
1447 CURLcode result = CURLE_OK;
1448 struct connectdata *conn = data->conn;
1449 struct imap_conn *imapc = &conn->proto.imapc;
1450 struct pingpong *pp = &imapc->pp;
1451
1452 *done = FALSE; /* default to not done yet */
1453
1454 /* We always support persistent connections in IMAP */
1455 connkeep(conn, "IMAP default");
1456
1457 PINGPONG_SETUP(pp, imap_statemachine, imap_endofresp);
1458
1459 /* Set the default preferred authentication type and mechanism */
1460 imapc->preftype = IMAP_TYPE_ANY;
1461 Curl_sasl_init(&imapc->sasl, data, &saslimap);
1462
1463 Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD);
1464 /* Initialise the pingpong layer */
1465 Curl_pp_setup(pp);
1466 Curl_pp_init(data, pp);
1467
1468 /* Parse the URL options */
1469 result = imap_parse_url_options(conn);
1470 if(result)
1471 return result;
1472
1473 /* Start off waiting for the server greeting response */
1474 state(data, IMAP_SERVERGREET);
1475
1476 /* Start off with an response id of '*' */
1477 strcpy(imapc->resptag, "*");
1478
1479 result = imap_multi_statemach(data, done);
1480
1481 return result;
1482}
1483
1484/***********************************************************************
1485 *
1486 * imap_done()
1487 *
1488 * The DONE function. This does what needs to be done after a single DO has
1489 * performed.
1490 *
1491 * Input argument is already checked for validity.
1492 */
1493static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
1494 bool premature)
1495{
1496 CURLcode result = CURLE_OK;
1497 struct connectdata *conn = data->conn;
1498 struct IMAP *imap = data->req.p.imap;
1499
1500 (void)premature;
1501
1502 if(!imap)
1503 return CURLE_OK;
1504
1505 if(status) {
1506 connclose(conn, "IMAP done with bad status"); /* marked for closure */
1507 result = status; /* use the already set error code */
1508 }
1509 else if(!data->set.connect_only && !imap->custom &&
1510 (imap->uid || imap->mindex || data->set.upload ||
1511 data->set.mimepost.kind != MIMEKIND_NONE)) {
1512 /* Handle responses after FETCH or APPEND transfer has finished */
1513
1514 if(!data->set.upload && data->set.mimepost.kind == MIMEKIND_NONE)
1515 state(data, IMAP_FETCH_FINAL);
1516 else {
1517 /* End the APPEND command first by sending an empty line */
1518 result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", "");
1519 if(!result)
1520 state(data, IMAP_APPEND_FINAL);
1521 }
1522
1523 /* Run the state-machine */
1524 if(!result)
1525 result = imap_block_statemach(data, conn, FALSE);
1526 }
1527
1528 /* Cleanup our per-request based variables */
1529 Curl_safefree(imap->mailbox);
1530 Curl_safefree(imap->uidvalidity);
1531 Curl_safefree(imap->uid);
1532 Curl_safefree(imap->mindex);
1533 Curl_safefree(imap->section);
1534 Curl_safefree(imap->partial);
1535 Curl_safefree(imap->query);
1536 Curl_safefree(imap->custom);
1537 Curl_safefree(imap->custom_params);
1538
1539 /* Clear the transfer mode for the next request */
1540 imap->transfer = PPTRANSFER_BODY;
1541
1542 return result;
1543}
1544
1545/***********************************************************************
1546 *
1547 * imap_perform()
1548 *
1549 * This is the actual DO function for IMAP. Fetch or append a message, or do
1550 * other things according to the options previously setup.
1551 */
1552static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
1553 bool *dophase_done)
1554{
1555 /* This is IMAP and no proxy */
1556 CURLcode result = CURLE_OK;
1557 struct connectdata *conn = data->conn;
1558 struct IMAP *imap = data->req.p.imap;
1559 struct imap_conn *imapc = &conn->proto.imapc;
1560 bool selected = FALSE;
1561
1562 DEBUGF(infof(data, "DO phase starts"));
1563
1564 if(data->set.opt_no_body) {
1565 /* Requested no body means no transfer */
1566 imap->transfer = PPTRANSFER_INFO;
1567 }
1568
1569 *dophase_done = FALSE; /* not done yet */
1570
1571 /* Determine if the requested mailbox (with the same UIDVALIDITY if set)
1572 has already been selected on this connection */
1573 if(imap->mailbox && imapc->mailbox &&
1574 strcasecompare(imap->mailbox, imapc->mailbox) &&
1575 (!imap->uidvalidity || !imapc->mailbox_uidvalidity ||
1576 strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)))
1577 selected = TRUE;
1578
1579 /* Start the first command in the DO phase */
1580 if(data->set.upload || data->set.mimepost.kind != MIMEKIND_NONE)
1581 /* APPEND can be executed directly */
1582 result = imap_perform_append(data);
1583 else if(imap->custom && (selected || !imap->mailbox))
1584 /* Custom command using the same mailbox or no mailbox */
1585 result = imap_perform_list(data);
1586 else if(!imap->custom && selected && (imap->uid || imap->mindex))
1587 /* FETCH from the same mailbox */
1588 result = imap_perform_fetch(data);
1589 else if(!imap->custom && selected && imap->query)
1590 /* SEARCH the current mailbox */
1591 result = imap_perform_search(data);
1592 else if(imap->mailbox && !selected &&
1593 (imap->custom || imap->uid || imap->mindex || imap->query))
1594 /* SELECT the mailbox */
1595 result = imap_perform_select(data);
1596 else
1597 /* LIST */
1598 result = imap_perform_list(data);
1599
1600 if(result)
1601 return result;
1602
1603 /* Run the state-machine */
1604 result = imap_multi_statemach(data, dophase_done);
1605
1606 *connected = conn->bits.tcpconnect[FIRSTSOCKET];
1607
1608 if(*dophase_done)
1609 DEBUGF(infof(data, "DO phase is complete"));
1610
1611 return result;
1612}
1613
1614/***********************************************************************
1615 *
1616 * imap_do()
1617 *
1618 * This function is registered as 'curl_do' function. It decodes the path
1619 * parts etc as a wrapper to the actual DO function (imap_perform).
1620 *
1621 * The input argument is already checked for validity.
1622 */
1623static CURLcode imap_do(struct Curl_easy *data, bool *done)
1624{
1625 CURLcode result = CURLE_OK;
1626 *done = FALSE; /* default to false */
1627
1628 /* Parse the URL path */
1629 result = imap_parse_url_path(data);
1630 if(result)
1631 return result;
1632
1633 /* Parse the custom request */
1634 result = imap_parse_custom_request(data);
1635 if(result)
1636 return result;
1637
1638 result = imap_regular_transfer(data, done);
1639
1640 return result;
1641}
1642
1643/***********************************************************************
1644 *
1645 * imap_disconnect()
1646 *
1647 * Disconnect from an IMAP server. Cleanup protocol-specific per-connection
1648 * resources. BLOCKING.
1649 */
1650static CURLcode imap_disconnect(struct Curl_easy *data,
1651 struct connectdata *conn, bool dead_connection)
1652{
1653 struct imap_conn *imapc = &conn->proto.imapc;
1654 (void)data;
1655
1656 /* We cannot send quit unconditionally. If this connection is stale or
1657 bad in any way, sending quit and waiting around here will make the
1658 disconnect wait in vain and cause more problems than we need to. */
1659
1660 /* The IMAP session may or may not have been allocated/setup at this
1661 point! */
1662 if(!dead_connection && conn->bits.protoconnstart) {
1663 if(!imap_perform_logout(data))
1664 (void)imap_block_statemach(data, conn, TRUE); /* ignore errors */
1665 }
1666
1667 /* Disconnect from the server */
1668 Curl_pp_disconnect(&imapc->pp);
1669 Curl_dyn_free(&imapc->dyn);
1670
1671 /* Cleanup the SASL module */
1672 Curl_sasl_cleanup(conn, imapc->sasl.authused);
1673
1674 /* Cleanup our connection based variables */
1675 Curl_safefree(imapc->mailbox);
1676 Curl_safefree(imapc->mailbox_uidvalidity);
1677
1678 return CURLE_OK;
1679}
1680
1681/* Call this when the DO phase has completed */
1682static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected)
1683{
1684 struct IMAP *imap = data->req.p.imap;
1685
1686 (void)connected;
1687
1688 if(imap->transfer != PPTRANSFER_BODY)
1689 /* no data to transfer */
1690 Curl_setup_transfer(data, -1, -1, FALSE, -1);
1691
1692 return CURLE_OK;
1693}
1694
1695/* Called from multi.c while DOing */
1696static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done)
1697{
1698 CURLcode result = imap_multi_statemach(data, dophase_done);
1699
1700 if(result)
1701 DEBUGF(infof(data, "DO phase failed"));
1702 else if(*dophase_done) {
1703 result = imap_dophase_done(data, FALSE /* not connected */);
1704
1705 DEBUGF(infof(data, "DO phase is complete"));
1706 }
1707
1708 return result;
1709}
1710
1711/***********************************************************************
1712 *
1713 * imap_regular_transfer()
1714 *
1715 * The input argument is already checked for validity.
1716 *
1717 * Performs all commands done before a regular transfer between a local and a
1718 * remote host.
1719 */
1720static CURLcode imap_regular_transfer(struct Curl_easy *data,
1721 bool *dophase_done)
1722{
1723 CURLcode result = CURLE_OK;
1724 bool connected = FALSE;
1725
1726 /* Make sure size is unknown at this point */
1727 data->req.size = -1;
1728
1729 /* Set the progress data */
1730 Curl_pgrsSetUploadCounter(data, 0);
1731 Curl_pgrsSetDownloadCounter(data, 0);
1732 Curl_pgrsSetUploadSize(data, -1);
1733 Curl_pgrsSetDownloadSize(data, -1);
1734
1735 /* Carry out the perform */
1736 result = imap_perform(data, &connected, dophase_done);
1737
1738 /* Perform post DO phase operations if necessary */
1739 if(!result && *dophase_done)
1740 result = imap_dophase_done(data, connected);
1741
1742 return result;
1743}
1744
1745static CURLcode imap_setup_connection(struct Curl_easy *data,
1746 struct connectdata *conn)
1747{
1748 /* Initialise the IMAP layer */
1749 CURLcode result = imap_init(data);
1750 if(result)
1751 return result;
1752
1753 /* Clear the TLS upgraded flag */
1754 conn->bits.tls_upgraded = FALSE;
1755
1756 return CURLE_OK;
1757}
1758
1759/***********************************************************************
1760 *
1761 * imap_sendf()
1762 *
1763 * Sends the formatted string as an IMAP command to the server.
1764 *
1765 * Designed to never block.
1766 */
1767static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...)
1768{
1769 CURLcode result = CURLE_OK;
1770 struct imap_conn *imapc = &data->conn->proto.imapc;
1771
1772 DEBUGASSERT(fmt);
1773
1774 /* Calculate the tag based on the connection ID and command ID */
1775 msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
1776 'A' + curlx_sltosi(data->conn->connection_id % 26),
1777 (++imapc->cmdid)%1000);
1778
1779 /* start with a blank buffer */
1780 Curl_dyn_reset(&imapc->dyn);
1781
1782 /* append tag + space + fmt */
1783 result = Curl_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt);
1784 if(!result) {
1785 va_list ap;
1786 va_start(ap, fmt);
1787 result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap);
1788 va_end(ap);
1789 }
1790 return result;
1791}
1792
1793/***********************************************************************
1794 *
1795 * imap_atom()
1796 *
1797 * Checks the input string for characters that need escaping and returns an
1798 * atom ready for sending to the server.
1799 *
1800 * The returned string needs to be freed.
1801 *
1802 */
1803static char *imap_atom(const char *str, bool escape_only)
1804{
1805 /* !checksrc! disable PARENBRACE 1 */
1806 const char atom_specials[] = "(){ %*]";
1807 const char *p1;
1808 char *p2;
1809 size_t backsp_count = 0;
1810 size_t quote_count = 0;
1811 bool others_exists = FALSE;
1812 size_t newlen = 0;
1813 char *newstr = NULL;
1814
1815 if(!str)
1816 return NULL;
1817
1818 /* Look for "atom-specials", counting the backslash and quote characters as
1819 these will need escaping */
1820 p1 = str;
1821 while(*p1) {
1822 if(*p1 == '\\')
1823 backsp_count++;
1824 else if(*p1 == '"')
1825 quote_count++;
1826 else if(!escape_only) {
1827 const char *p3 = atom_specials;
1828
1829 while(*p3 && !others_exists) {
1830 if(*p1 == *p3)
1831 others_exists = TRUE;
1832
1833 p3++;
1834 }
1835 }
1836
1837 p1++;
1838 }
1839
1840 /* Does the input contain any "atom-special" characters? */
1841 if(!backsp_count && !quote_count && !others_exists)
1842 return strdup(str);
1843
1844 /* Calculate the new string length */
1845 newlen = strlen(str) + backsp_count + quote_count + (escape_only ? 0 : 2);
1846
1847 /* Allocate the new string */
1848 newstr = (char *) malloc((newlen + 1) * sizeof(char));
1849 if(!newstr)
1850 return NULL;
1851
1852 /* Surround the string in quotes if necessary */
1853 p2 = newstr;
1854 if(!escape_only) {
1855 newstr[0] = '"';
1856 newstr[newlen - 1] = '"';
1857 p2++;
1858 }
1859
1860 /* Copy the string, escaping backslash and quote characters along the way */
1861 p1 = str;
1862 while(*p1) {
1863 if(*p1 == '\\' || *p1 == '"') {
1864 *p2 = '\\';
1865 p2++;
1866 }
1867
1868 *p2 = *p1;
1869
1870 p1++;
1871 p2++;
1872 }
1873
1874 /* Terminate the string */
1875 newstr[newlen] = '\0';
1876
1877 return newstr;
1878}
1879
1880/***********************************************************************
1881 *
1882 * imap_is_bchar()
1883 *
1884 * Portable test of whether the specified char is a "bchar" as defined in the
1885 * grammar of RFC-5092.
1886 */
1887static bool imap_is_bchar(char ch)
1888{
1889 /* Peforming the alnum check with this macro is faster because of ASCII
1890 artihmetic */
1891 if(ISALNUM(ch))
1892 return true;
1893
1894 switch(ch) {
1895 /* bchar */
1896 case ':': case '@': case '/':
1897 /* bchar -> achar */
1898 case '&': case '=':
1899 /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */
1900 case '-': case '.': case '_': case '~':
1901 /* bchar -> achar -> uchar -> sub-delims-sh */
1902 case '!': case '$': case '\'': case '(': case ')': case '*':
1903 case '+': case ',':
1904 /* bchar -> achar -> uchar -> pct-encoded */
1905 case '%': /* HEXDIG chars are already included above */
1906 return true;
1907
1908 default:
1909 return false;
1910 }
1911}
1912
1913/***********************************************************************
1914 *
1915 * imap_parse_url_options()
1916 *
1917 * Parse the URL login options.
1918 */
1919static CURLcode imap_parse_url_options(struct connectdata *conn)
1920{
1921 CURLcode result = CURLE_OK;
1922 struct imap_conn *imapc = &conn->proto.imapc;
1923 const char *ptr = conn->options;
1924
1925 while(!result && ptr && *ptr) {
1926 const char *key = ptr;
1927 const char *value;
1928
1929 while(*ptr && *ptr != '=')
1930 ptr++;
1931
1932 value = ptr + 1;
1933
1934 while(*ptr && *ptr != ';')
1935 ptr++;
1936
1937 if(strncasecompare(key, "AUTH=", 5))
1938 result = Curl_sasl_parse_url_auth_option(&imapc->sasl,
1939 value, ptr - value);
1940 else
1941 result = CURLE_URL_MALFORMAT;
1942
1943 if(*ptr == ';')
1944 ptr++;
1945 }
1946
1947 switch(imapc->sasl.prefmech) {
1948 case SASL_AUTH_NONE:
1949 imapc->preftype = IMAP_TYPE_NONE;
1950 break;
1951 case SASL_AUTH_DEFAULT:
1952 imapc->preftype = IMAP_TYPE_ANY;
1953 break;
1954 default:
1955 imapc->preftype = IMAP_TYPE_SASL;
1956 break;
1957 }
1958
1959 return result;
1960}
1961
1962/***********************************************************************
1963 *
1964 * imap_parse_url_path()
1965 *
1966 * Parse the URL path into separate path components.
1967 *
1968 */
1969static CURLcode imap_parse_url_path(struct Curl_easy *data)
1970{
1971 /* The imap struct is already initialised in imap_connect() */
1972 CURLcode result = CURLE_OK;
1973 struct IMAP *imap = data->req.p.imap;
1974 const char *begin = &data->state.up.path[1]; /* skip leading slash */
1975 const char *ptr = begin;
1976
1977 /* See how much of the URL is a valid path and decode it */
1978 while(imap_is_bchar(*ptr))
1979 ptr++;
1980
1981 if(ptr != begin) {
1982 /* Remove the trailing slash if present */
1983 const char *end = ptr;
1984 if(end > begin && end[-1] == '/')
1985 end--;
1986
1987 result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL,
1988 REJECT_CTRL);
1989 if(result)
1990 return result;
1991 }
1992 else
1993 imap->mailbox = NULL;
1994
1995 /* There can be any number of parameters in the form ";NAME=VALUE" */
1996 while(*ptr == ';') {
1997 char *name;
1998 char *value;
1999 size_t valuelen;
2000
2001 /* Find the length of the name parameter */
2002 begin = ++ptr;
2003 while(*ptr && *ptr != '=')
2004 ptr++;
2005
2006 if(!*ptr)
2007 return CURLE_URL_MALFORMAT;
2008
2009 /* Decode the name parameter */
2010 result = Curl_urldecode(begin, ptr - begin, &name, NULL,
2011 REJECT_CTRL);
2012 if(result)
2013 return result;
2014
2015 /* Find the length of the value parameter */
2016 begin = ++ptr;
2017 while(imap_is_bchar(*ptr))
2018 ptr++;
2019
2020 /* Decode the value parameter */
2021 result = Curl_urldecode(begin, ptr - begin, &value, &valuelen,
2022 REJECT_CTRL);
2023 if(result) {
2024 free(name);
2025 return result;
2026 }
2027
2028 DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
2029
2030 /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and
2031 PARTIAL) stripping of the trailing slash character if it is present.
2032
2033 Note: Unknown parameters trigger a URL_MALFORMAT error. */
2034 if(strcasecompare(name, "UIDVALIDITY") && !imap->uidvalidity) {
2035 if(valuelen > 0 && value[valuelen - 1] == '/')
2036 value[valuelen - 1] = '\0';
2037
2038 imap->uidvalidity = value;
2039 value = NULL;
2040 }
2041 else if(strcasecompare(name, "UID") && !imap->uid) {
2042 if(valuelen > 0 && value[valuelen - 1] == '/')
2043 value[valuelen - 1] = '\0';
2044
2045 imap->uid = value;
2046 value = NULL;
2047 }
2048 else if(strcasecompare(name, "MAILINDEX") && !imap->mindex) {
2049 if(valuelen > 0 && value[valuelen - 1] == '/')
2050 value[valuelen - 1] = '\0';
2051
2052 imap->mindex = value;
2053 value = NULL;
2054 }
2055 else if(strcasecompare(name, "SECTION") && !imap->section) {
2056 if(valuelen > 0 && value[valuelen - 1] == '/')
2057 value[valuelen - 1] = '\0';
2058
2059 imap->section = value;
2060 value = NULL;
2061 }
2062 else if(strcasecompare(name, "PARTIAL") && !imap->partial) {
2063 if(valuelen > 0 && value[valuelen - 1] == '/')
2064 value[valuelen - 1] = '\0';
2065
2066 imap->partial = value;
2067 value = NULL;
2068 }
2069 else {
2070 free(name);
2071 free(value);
2072
2073 return CURLE_URL_MALFORMAT;
2074 }
2075
2076 free(name);
2077 free(value);
2078 }
2079
2080 /* Does the URL contain a query parameter? Only valid when we have a mailbox
2081 and no UID as per RFC-5092 */
2082 if(imap->mailbox && !imap->uid && !imap->mindex) {
2083 /* Get the query parameter, URL decoded */
2084 (void)curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query,
2085 CURLU_URLDECODE);
2086 }
2087
2088 /* Any extra stuff at the end of the URL is an error */
2089 if(*ptr)
2090 return CURLE_URL_MALFORMAT;
2091
2092 return CURLE_OK;
2093}
2094
2095/***********************************************************************
2096 *
2097 * imap_parse_custom_request()
2098 *
2099 * Parse the custom request.
2100 */
2101static CURLcode imap_parse_custom_request(struct Curl_easy *data)
2102{
2103 CURLcode result = CURLE_OK;
2104 struct IMAP *imap = data->req.p.imap;
2105 const char *custom = data->set.str[STRING_CUSTOMREQUEST];
2106
2107 if(custom) {
2108 /* URL decode the custom request */
2109 result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL);
2110
2111 /* Extract the parameters if specified */
2112 if(!result) {
2113 const char *params = imap->custom;
2114
2115 while(*params && *params != ' ')
2116 params++;
2117
2118 if(*params) {
2119 imap->custom_params = strdup(params);
2120 imap->custom[params - imap->custom] = '\0';
2121
2122 if(!imap->custom_params)
2123 result = CURLE_OUT_OF_MEMORY;
2124 }
2125 }
2126 }
2127
2128 return result;
2129}
2130
2131#endif /* CURL_DISABLE_IMAP */
2132