VirtualBox

source: vbox/trunk/src/libs/curl-7.87.0/lib/imap.c@ 98326

Last change on this file since 98326 was 98326, checked in by vboxsync, 2 years ago

curl-7.87.0: Applied and adjusted our curl changes to 7.83.1. bugref:10356

  • Property svn:eol-style set to native
File size: 59.8 KB
Line 
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#include <curl/curl.h>
60#include "urldata.h"
61#include "sendf.h"
62#include "hostip.h"
63#include "progress.h"
64#include "transfer.h"
65#include "escape.h"
66#include "http.h" /* for HTTP proxy tunnel stuff */
67#include "socks.h"
68#include "imap.h"
69#include "mime.h"
70#include "strtoofft.h"
71#include "strcase.h"
72#include "vtls/vtls.h"
73#include "cfilters.h"
74#include "connect.h"
75#include "select.h"
76#include "multiif.h"
77#include "url.h"
78#include "bufref.h"
79#include "curl_sasl.h"
80#include "warnless.h"
81#include "curl_ctype.h"
82
83/* The last 3 #include files should be in this order */
84#include "curl_printf.h"
85#include "curl_memory.h"
86#include "memdebug.h"
87
88/* Local API functions */
89static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *done);
90static CURLcode imap_do(struct Curl_easy *data, bool *done);
91static CURLcode imap_done(struct Curl_easy *data, CURLcode status,
92 bool premature);
93static CURLcode imap_connect(struct Curl_easy *data, bool *done);
94static CURLcode imap_disconnect(struct Curl_easy *data,
95 struct connectdata *conn, bool dead);
96static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done);
97static int imap_getsock(struct Curl_easy *data, struct connectdata *conn,
98 curl_socket_t *socks);
99static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done);
100static CURLcode imap_setup_connection(struct Curl_easy *data,
101 struct connectdata *conn);
102static char *imap_atom(const char *str, bool escape_only);
103static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...);
104static CURLcode imap_parse_url_options(struct connectdata *conn);
105static CURLcode imap_parse_url_path(struct Curl_easy *data);
106static CURLcode imap_parse_custom_request(struct Curl_easy *data);
107static CURLcode imap_perform_authenticate(struct Curl_easy *data,
108 const char *mech,
109 const struct bufref *initresp);
110static CURLcode imap_continue_authenticate(struct Curl_easy *data,
111 const char *mech,
112 const struct bufref *resp);
113static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
114 const char *mech);
115static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out);
116
117/*
118 * IMAP protocol handler.
119 */
120
121const struct Curl_handler Curl_handler_imap = {
122 "IMAP", /* scheme */
123 imap_setup_connection, /* setup_connection */
124 imap_do, /* do_it */
125 imap_done, /* done */
126 ZERO_NULL, /* do_more */
127 imap_connect, /* connect_it */
128 imap_multi_statemach, /* connecting */
129 imap_doing, /* doing */
130 imap_getsock, /* proto_getsock */
131 imap_getsock, /* doing_getsock */
132 ZERO_NULL, /* domore_getsock */
133 ZERO_NULL, /* perform_getsock */
134 imap_disconnect, /* disconnect */
135 ZERO_NULL, /* readwrite */
136 ZERO_NULL, /* connection_check */
137 ZERO_NULL, /* attach connection */
138 PORT_IMAP, /* defport */
139 CURLPROTO_IMAP, /* protocol */
140 CURLPROTO_IMAP, /* family */
141 PROTOPT_CLOSEACTION| /* flags */
142 PROTOPT_URLOPTIONS
143};
144
145#ifdef USE_SSL
146/*
147 * IMAPS protocol handler.
148 */
149
150const struct Curl_handler Curl_handler_imaps = {
151 "IMAPS", /* scheme */
152 imap_setup_connection, /* setup_connection */
153 imap_do, /* do_it */
154 imap_done, /* done */
155 ZERO_NULL, /* do_more */
156 imap_connect, /* connect_it */
157 imap_multi_statemach, /* connecting */
158 imap_doing, /* doing */
159 imap_getsock, /* proto_getsock */
160 imap_getsock, /* doing_getsock */
161 ZERO_NULL, /* domore_getsock */
162 ZERO_NULL, /* perform_getsock */
163 imap_disconnect, /* disconnect */
164 ZERO_NULL, /* readwrite */
165 ZERO_NULL, /* connection_check */
166 ZERO_NULL, /* attach connection */
167 PORT_IMAPS, /* defport */
168 CURLPROTO_IMAPS, /* protocol */
169 CURLPROTO_IMAP, /* family */
170 PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */
171 PROTOPT_URLOPTIONS
172};
173#endif
174
175#define IMAP_RESP_OK 1
176#define IMAP_RESP_NOT_OK 2
177#define IMAP_RESP_PREAUTH 3
178
179/* SASL parameters for the imap protocol */
180static const struct SASLproto saslimap = {
181 "imap", /* The service name */
182 imap_perform_authenticate, /* Send authentication command */
183 imap_continue_authenticate, /* Send authentication continuation */
184 imap_cancel_authenticate, /* Send authentication cancellation */
185 imap_get_message, /* Get SASL response message */
186 0, /* No maximum initial response length */
187 '+', /* Code received when continuation is expected */
188 IMAP_RESP_OK, /* Code to receive upon authentication success */
189 SASL_AUTH_DEFAULT, /* Default mechanisms */
190 SASL_FLAG_BASE64 /* Configuration flags */
191};
192
193
194#ifdef USE_SSL
195static void imap_to_imaps(struct connectdata *conn)
196{
197 /* Change the connection handler */
198 conn->handler = &Curl_handler_imaps;
199
200 /* Set the connection's upgraded to TLS flag */
201 conn->bits.tls_upgraded = TRUE;
202}
203#else
204#define imap_to_imaps(x) Curl_nop_stmt
205#endif
206
207/***********************************************************************
208 *
209 * imap_matchresp()
210 *
211 * Determines whether the untagged response is related to the specified
212 * command by checking if it is in format "* <command-name> ..." or
213 * "* <number> <command-name> ...".
214 *
215 * The "* " marker is assumed to have already been checked by the caller.
216 */
217static bool imap_matchresp(const char *line, size_t len, const char *cmd)
218{
219 const char *end = line + len;
220 size_t cmd_len = strlen(cmd);
221
222 /* Skip the untagged response marker */
223 line += 2;
224
225 /* Do we have a number after the marker? */
226 if(line < end && ISDIGIT(*line)) {
227 /* Skip the number */
228 do
229 line++;
230 while(line < end && ISDIGIT(*line));
231
232 /* Do we have the space character? */
233 if(line == end || *line != ' ')
234 return FALSE;
235
236 line++;
237 }
238
239 /* Does the command name match and is it followed by a space character or at
240 the end of line? */
241 if(line + cmd_len <= end && strncasecompare(line, cmd, cmd_len) &&
242 (line[cmd_len] == ' ' || line + cmd_len + 2 == end))
243 return TRUE;
244
245 return FALSE;
246}
247
248/***********************************************************************
249 *
250 * imap_endofresp()
251 *
252 * Checks whether the given string is a valid tagged, untagged or continuation
253 * response which can be processed by the response handler.
254 */
255static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn,
256 char *line, size_t len, int *resp)
257{
258 struct IMAP *imap = data->req.p.imap;
259 struct imap_conn *imapc = &conn->proto.imapc;
260 const char *id = imapc->resptag;
261 size_t id_len = strlen(id);
262
263 /* Do we have a tagged command response? */
264 if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') {
265 line += id_len + 1;
266 len -= id_len + 1;
267
268 if(len >= 2 && !memcmp(line, "OK", 2))
269 *resp = IMAP_RESP_OK;
270 else if(len >= 7 && !memcmp(line, "PREAUTH", 7))
271 *resp = IMAP_RESP_PREAUTH;
272 else
273 *resp = IMAP_RESP_NOT_OK;
274
275 return TRUE;
276 }
277
278 /* Do we have an untagged command response? */
279 if(len >= 2 && !memcmp("* ", line, 2)) {
280 switch(imapc->state) {
281 /* States which are interested in untagged responses */
282 case IMAP_CAPABILITY:
283 if(!imap_matchresp(line, len, "CAPABILITY"))
284 return FALSE;
285 break;
286
287 case IMAP_LIST:
288 if((!imap->custom && !imap_matchresp(line, len, "LIST")) ||
289 (imap->custom && !imap_matchresp(line, len, imap->custom) &&
290 (!strcasecompare(imap->custom, "STORE") ||
291 !imap_matchresp(line, len, "FETCH")) &&
292 !strcasecompare(imap->custom, "SELECT") &&
293 !strcasecompare(imap->custom, "EXAMINE") &&
294 !strcasecompare(imap->custom, "SEARCH") &&
295 !strcasecompare(imap->custom, "EXPUNGE") &&
296 !strcasecompare(imap->custom, "LSUB") &&
297 !strcasecompare(imap->custom, "UID") &&
298 !strcasecompare(imap->custom, "GETQUOTAROOT") &&
299 !strcasecompare(imap->custom, "NOOP")))
300 return FALSE;
301 break;
302
303 case IMAP_SELECT:
304 /* SELECT is special in that its untagged responses do not have a
305 common prefix so accept anything! */
306 break;
307
308 case IMAP_FETCH:
309 if(!imap_matchresp(line, len, "FETCH"))
310 return FALSE;
311 break;
312
313 case IMAP_SEARCH:
314 if(!imap_matchresp(line, len, "SEARCH"))
315 return FALSE;
316 break;
317
318 /* Ignore other untagged responses */
319 default:
320 return FALSE;
321 }
322
323 *resp = '*';
324 return TRUE;
325 }
326
327 /* Do we have a continuation response? This should be a + symbol followed by
328 a space and optionally some text as per RFC-3501 for the AUTHENTICATE and
329 APPEND commands and as outlined in Section 4. Examples of RFC-4959 but
330 some email servers ignore this and only send a single + instead. */
331 if(imap && !imap->custom && ((len == 3 && line[0] == '+') ||
332 (len >= 2 && !memcmp("+ ", line, 2)))) {
333 switch(imapc->state) {
334 /* States which are interested in continuation responses */
335 case IMAP_AUTHENTICATE:
336 case IMAP_APPEND:
337 *resp = '+';
338 break;
339
340 default:
341 failf(data, "Unexpected continuation response");
342 *resp = -1;
343 break;
344 }
345
346 return TRUE;
347 }
348
349 return FALSE; /* Nothing for us */
350}
351
352/***********************************************************************
353 *
354 * imap_get_message()
355 *
356 * Gets the authentication message from the response buffer.
357 */
358static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out)
359{
360 char *message = data->state.buffer;
361 size_t len = strlen(message);
362
363 if(len > 2) {
364 /* Find the start of the message */
365 len -= 2;
366 for(message += 2; *message == ' ' || *message == '\t'; message++, len--)
367 ;
368
369 /* Find the end of the message */
370 while(len--)
371 if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' &&
372 message[len] != '\t')
373 break;
374
375 /* Terminate the message */
376 message[++len] = '\0';
377 Curl_bufref_set(out, message, len, NULL);
378 }
379 else
380 /* junk input => zero length output */
381 Curl_bufref_set(out, "", 0, NULL);
382
383 return CURLE_OK;
384}
385
386/***********************************************************************
387 *
388 * state()
389 *
390 * This is the ONLY way to change IMAP state!
391 */
392static void state(struct Curl_easy *data, imapstate newstate)
393{
394 struct imap_conn *imapc = &data->conn->proto.imapc;
395#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
396 /* for debug purposes */
397 static const char * const names[]={
398 "STOP",
399 "SERVERGREET",
400 "CAPABILITY",
401 "STARTTLS",
402 "UPGRADETLS",
403 "AUTHENTICATE",
404 "LOGIN",
405 "LIST",
406 "SELECT",
407 "FETCH",
408 "FETCH_FINAL",
409 "APPEND",
410 "APPEND_FINAL",
411 "SEARCH",
412 "LOGOUT",
413 /* LAST */
414 };
415
416 if(imapc->state != newstate)
417 infof(data, "IMAP %p state change from %s to %s",
418 (void *)imapc, names[imapc->state], names[newstate]);
419#endif
420
421 imapc->state = newstate;
422}
423
424/***********************************************************************
425 *
426 * imap_perform_capability()
427 *
428 * Sends the CAPABILITY command in order to obtain a list of server side
429 * supported capabilities.
430 */
431static CURLcode imap_perform_capability(struct Curl_easy *data,
432 struct connectdata *conn)
433{
434 CURLcode result = CURLE_OK;
435 struct imap_conn *imapc = &conn->proto.imapc;
436 imapc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanisms yet */
437 imapc->sasl.authused = SASL_AUTH_NONE; /* Clear the auth. mechanism used */
438 imapc->tls_supported = FALSE; /* Clear the TLS capability */
439
440 /* Send the CAPABILITY command */
441 result = imap_sendf(data, "CAPABILITY");
442
443 if(!result)
444 state(data, IMAP_CAPABILITY);
445
446 return result;
447}
448
449/***********************************************************************
450 *
451 * imap_perform_starttls()
452 *
453 * Sends the STARTTLS command to start the upgrade to TLS.
454 */
455static CURLcode imap_perform_starttls(struct Curl_easy *data)
456{
457 /* Send the STARTTLS command */
458 CURLcode result = imap_sendf(data, "STARTTLS");
459
460 if(!result)
461 state(data, IMAP_STARTTLS);
462
463 return result;
464}
465
466/***********************************************************************
467 *
468 * imap_perform_upgrade_tls()
469 *
470 * Performs the upgrade to TLS.
471 */
472static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data,
473 struct connectdata *conn)
474{
475 /* Start the SSL connection */
476 struct imap_conn *imapc = &conn->proto.imapc;
477 CURLcode result;
478
479 if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
480 result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
481 if(result)
482 goto out;
483 }
484
485 result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone);
486 if(!result) {
487 if(imapc->state != IMAP_UPGRADETLS)
488 state(data, IMAP_UPGRADETLS);
489
490 if(imapc->ssldone) {
491 imap_to_imaps(conn);
492 result = imap_perform_capability(data, conn);
493 }
494 }
495out:
496 return result;
497}
498
499/***********************************************************************
500 *
501 * imap_perform_login()
502 *
503 * Sends a clear text LOGIN command to authenticate with.
504 */
505static CURLcode imap_perform_login(struct Curl_easy *data,
506 struct connectdata *conn)
507{
508 CURLcode result = CURLE_OK;
509 char *user;
510 char *passwd;
511
512 /* Check we have a username and password to authenticate with and end the
513 connect phase if we don't */
514 if(!data->state.aptr.user) {
515 state(data, IMAP_STOP);
516
517 return result;
518 }
519
520 /* Make sure the username and password are in the correct atom format */
521 user = imap_atom(conn->user, false);
522 passwd = imap_atom(conn->passwd, false);
523
524 /* Send the LOGIN command */
525 result = imap_sendf(data, "LOGIN %s %s", user ? user : "",
526 passwd ? passwd : "");
527
528 free(user);
529 free(passwd);
530
531 if(!result)
532 state(data, IMAP_LOGIN);
533
534 return result;
535}
536
537/***********************************************************************
538 *
539 * imap_perform_authenticate()
540 *
541 * Sends an AUTHENTICATE command allowing the client to login with the given
542 * SASL authentication mechanism.
543 */
544static CURLcode imap_perform_authenticate(struct Curl_easy *data,
545 const char *mech,
546 const struct bufref *initresp)
547{
548 CURLcode result = CURLE_OK;
549 const char *ir = (const char *) Curl_bufref_ptr(initresp);
550
551 if(ir) {
552 /* Send the AUTHENTICATE command with the initial response */
553 result = imap_sendf(data, "AUTHENTICATE %s %s", mech, ir);
554 }
555 else {
556 /* Send the AUTHENTICATE command */
557 result = imap_sendf(data, "AUTHENTICATE %s", mech);
558 }
559
560 return result;
561}
562
563/***********************************************************************
564 *
565 * imap_continue_authenticate()
566 *
567 * Sends SASL continuation data.
568 */
569static CURLcode imap_continue_authenticate(struct Curl_easy *data,
570 const char *mech,
571 const struct bufref *resp)
572{
573 struct imap_conn *imapc = &data->conn->proto.imapc;
574
575 (void)mech;
576
577 return Curl_pp_sendf(data, &imapc->pp,
578 "%s", (const char *) Curl_bufref_ptr(resp));
579}
580
581/***********************************************************************
582 *
583 * imap_cancel_authenticate()
584 *
585 * Sends SASL cancellation.
586 */
587static CURLcode imap_cancel_authenticate(struct Curl_easy *data,
588 const char *mech)
589{
590 struct imap_conn *imapc = &data->conn->proto.imapc;
591
592 (void)mech;
593
594 return Curl_pp_sendf(data, &imapc->pp, "*");
595}
596
597/***********************************************************************
598 *
599 * imap_perform_authentication()
600 *
601 * Initiates the authentication sequence, with the appropriate SASL
602 * authentication mechanism, falling back to clear text should a common
603 * mechanism not be available between the client and server.
604 */
605static CURLcode imap_perform_authentication(struct Curl_easy *data,
606 struct connectdata *conn)
607{
608 CURLcode result = CURLE_OK;
609 struct imap_conn *imapc = &conn->proto.imapc;
610 saslprogress progress;
611
612 /* Check if already authenticated OR if there is enough data to authenticate
613 with and end the connect phase if we don't */
614 if(imapc->preauth ||
615 !Curl_sasl_can_authenticate(&imapc->sasl, data)) {
616 state(data, IMAP_STOP);
617 return result;
618 }
619
620 /* Calculate the SASL login details */
621 result = Curl_sasl_start(&imapc->sasl, data, imapc->ir_supported, &progress);
622
623 if(!result) {
624 if(progress == SASL_INPROGRESS)
625 state(data, IMAP_AUTHENTICATE);
626 else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
627 /* Perform clear text authentication */
628 result = imap_perform_login(data, conn);
629 else {
630 /* Other mechanisms not supported */
631 infof(data, "No known authentication mechanisms supported");
632 result = CURLE_LOGIN_DENIED;
633 }
634 }
635
636 return result;
637}
638
639/***********************************************************************
640 *
641 * imap_perform_list()
642 *
643 * Sends a LIST command or an alternative custom request.
644 */
645static CURLcode imap_perform_list(struct Curl_easy *data)
646{
647 CURLcode result = CURLE_OK;
648 struct IMAP *imap = data->req.p.imap;
649
650 if(imap->custom)
651 /* Send the custom request */
652 result = imap_sendf(data, "%s%s", imap->custom,
653 imap->custom_params ? imap->custom_params : "");
654 else {
655 /* Make sure the mailbox is in the correct atom format if necessary */
656 char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true)
657 : strdup("");
658 if(!mailbox)
659 return CURLE_OUT_OF_MEMORY;
660
661 /* Send the LIST command */
662 result = imap_sendf(data, "LIST \"%s\" *", mailbox);
663
664 free(mailbox);
665 }
666
667 if(!result)
668 state(data, IMAP_LIST);
669
670 return result;
671}
672
673/***********************************************************************
674 *
675 * imap_perform_select()
676 *
677 * Sends a SELECT command to ask the server to change the selected mailbox.
678 */
679static CURLcode imap_perform_select(struct Curl_easy *data)
680{
681 CURLcode result = CURLE_OK;
682 struct connectdata *conn = data->conn;
683 struct IMAP *imap = data->req.p.imap;
684 struct imap_conn *imapc = &conn->proto.imapc;
685 char *mailbox;
686
687 /* Invalidate old information as we are switching mailboxes */
688 Curl_safefree(imapc->mailbox);
689 Curl_safefree(imapc->mailbox_uidvalidity);
690
691 /* Check we have a mailbox */
692 if(!imap->mailbox) {
693 failf(data, "Cannot SELECT without a mailbox.");
694 return CURLE_URL_MALFORMAT;
695 }
696
697 /* Make sure the mailbox is in the correct atom format */
698 mailbox = imap_atom(imap->mailbox, false);
699 if(!mailbox)
700 return CURLE_OUT_OF_MEMORY;
701
702 /* Send the SELECT command */
703 result = imap_sendf(data, "SELECT %s", mailbox);
704
705 free(mailbox);
706
707 if(!result)
708 state(data, IMAP_SELECT);
709
710 return result;
711}
712
713/***********************************************************************
714 *
715 * imap_perform_fetch()
716 *
717 * Sends a FETCH command to initiate the download of a message.
718 */
719static CURLcode imap_perform_fetch(struct Curl_easy *data)
720{
721 CURLcode result = CURLE_OK;
722 struct IMAP *imap = data->req.p.imap;
723 /* Check we have a UID */
724 if(imap->uid) {
725
726 /* Send the FETCH command */
727 if(imap->partial)
728 result = imap_sendf(data, "UID FETCH %s BODY[%s]<%s>",
729 imap->uid, imap->section ? imap->section : "",
730 imap->partial);
731 else
732 result = imap_sendf(data, "UID FETCH %s BODY[%s]",
733 imap->uid, imap->section ? imap->section : "");
734 }
735 else if(imap->mindex) {
736 /* Send the FETCH command */
737 if(imap->partial)
738 result = imap_sendf(data, "FETCH %s BODY[%s]<%s>",
739 imap->mindex, imap->section ? imap->section : "",
740 imap->partial);
741 else
742 result = imap_sendf(data, "FETCH %s BODY[%s]",
743 imap->mindex, imap->section ? imap->section : "");
744 }
745 else {
746 failf(data, "Cannot FETCH without a UID.");
747 return CURLE_URL_MALFORMAT;
748 }
749 if(!result)
750 state(data, IMAP_FETCH);
751
752 return result;
753}
754
755/***********************************************************************
756 *
757 * imap_perform_append()
758 *
759 * Sends an APPEND command to initiate the upload of a message.
760 */
761static CURLcode imap_perform_append(struct Curl_easy *data)
762{
763 CURLcode result = CURLE_OK;
764 struct IMAP *imap = data->req.p.imap;
765 char *mailbox;
766
767 /* Check we have a mailbox */
768 if(!imap->mailbox) {
769 failf(data, "Cannot APPEND without a mailbox.");
770 return CURLE_URL_MALFORMAT;
771 }
772
773 /* Prepare the mime data if some. */
774 if(data->set.mimepost.kind != MIMEKIND_NONE) {
775 /* Use the whole structure as data. */
776 data->set.mimepost.flags &= ~MIME_BODY_ONLY;
777
778 /* Add external headers and mime version. */
779 curl_mime_headers(&data->set.mimepost, data->set.headers, 0);
780 result = Curl_mime_prepare_headers(data, &data->set.mimepost, NULL,
781 NULL, MIMESTRATEGY_MAIL);
782
783 if(!result)
784 if(!Curl_checkheaders(data, STRCONST("Mime-Version")))
785 result = Curl_mime_add_header(&data->set.mimepost.curlheaders,
786 "Mime-Version: 1.0");
787
788 /* Make sure we will read the entire mime structure. */
789 if(!result)
790 result = Curl_mime_rewind(&data->set.mimepost);
791
792 if(result)
793 return result;
794
795 data->state.infilesize = Curl_mime_size(&data->set.mimepost);
796
797 /* Read from mime structure. */
798 data->state.fread_func = (curl_read_callback) Curl_mime_read;
799 data->state.in = (void *) &data->set.mimepost;
800 }
801
802 /* Check we know the size of the upload */
803 if(data->state.infilesize < 0) {
804 failf(data, "Cannot APPEND with unknown input file size");
805 return CURLE_UPLOAD_FAILED;
806 }
807
808 /* Make sure the mailbox is in the correct atom format */
809 mailbox = imap_atom(imap->mailbox, false);
810 if(!mailbox)
811 return CURLE_OUT_OF_MEMORY;
812
813 /* Send the APPEND command */
814 result = imap_sendf(data,
815 "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}",
816 mailbox, data->state.infilesize);
817
818 free(mailbox);
819
820 if(!result)
821 state(data, IMAP_APPEND);
822
823 return result;
824}
825
826/***********************************************************************
827 *
828 * imap_perform_search()
829 *
830 * Sends a SEARCH command.
831 */
832static CURLcode imap_perform_search(struct Curl_easy *data)
833{
834 CURLcode result = CURLE_OK;
835 struct IMAP *imap = data->req.p.imap;
836
837 /* Check we have a query string */
838 if(!imap->query) {
839 failf(data, "Cannot SEARCH without a query string.");
840 return CURLE_URL_MALFORMAT;
841 }
842
843 /* Send the SEARCH command */
844 result = imap_sendf(data, "SEARCH %s", imap->query);
845
846 if(!result)
847 state(data, IMAP_SEARCH);
848
849 return result;
850}
851
852/***********************************************************************
853 *
854 * imap_perform_logout()
855 *
856 * Performs the logout action prior to sclose() being called.
857 */
858static CURLcode imap_perform_logout(struct Curl_easy *data)
859{
860 /* Send the LOGOUT command */
861 CURLcode result = imap_sendf(data, "LOGOUT");
862
863 if(!result)
864 state(data, IMAP_LOGOUT);
865
866 return result;
867}
868
869/* For the initial server greeting */
870static CURLcode imap_state_servergreet_resp(struct Curl_easy *data,
871 int imapcode,
872 imapstate instate)
873{
874 struct connectdata *conn = data->conn;
875 (void)instate; /* no use for this yet */
876
877 if(imapcode == IMAP_RESP_PREAUTH) {
878 /* PREAUTH */
879 struct imap_conn *imapc = &conn->proto.imapc;
880 imapc->preauth = TRUE;
881 infof(data, "PREAUTH connection, already authenticated");
882 }
883 else if(imapcode != IMAP_RESP_OK) {
884 failf(data, "Got unexpected imap-server response");
885 return CURLE_WEIRD_SERVER_REPLY;
886 }
887
888 return imap_perform_capability(data, conn);
889}
890
891/* For CAPABILITY responses */
892static CURLcode imap_state_capability_resp(struct Curl_easy *data,
893 int imapcode,
894 imapstate instate)
895{
896 CURLcode result = CURLE_OK;
897 struct connectdata *conn = data->conn;
898 struct imap_conn *imapc = &conn->proto.imapc;
899 const char *line = data->state.buffer;
900
901 (void)instate; /* no use for this yet */
902
903 /* Do we have a untagged response? */
904 if(imapcode == '*') {
905 line += 2;
906
907 /* Loop through the data line */
908 for(;;) {
909 size_t wordlen;
910 while(*line &&
911 (*line == ' ' || *line == '\t' ||
912 *line == '\r' || *line == '\n')) {
913
914 line++;
915 }
916
917 if(!*line)
918 break;
919
920 /* Extract the word */
921 for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' &&
922 line[wordlen] != '\t' && line[wordlen] != '\r' &&
923 line[wordlen] != '\n';)
924 wordlen++;
925
926 /* Does the server support the STARTTLS capability? */
927 if(wordlen == 8 && !memcmp(line, "STARTTLS", 8))
928 imapc->tls_supported = TRUE;
929
930 /* Has the server explicitly disabled clear text authentication? */
931 else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13))
932 imapc->login_disabled = TRUE;
933
934 /* Does the server support the SASL-IR capability? */
935 else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7))
936 imapc->ir_supported = TRUE;
937
938 /* Do we have a SASL based authentication mechanism? */
939 else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) {
940 size_t llen;
941 unsigned short mechbit;
942
943 line += 5;
944 wordlen -= 5;
945
946 /* Test the word for a matching authentication mechanism */
947 mechbit = Curl_sasl_decode_mech(line, wordlen, &llen);
948 if(mechbit && llen == wordlen)
949 imapc->sasl.authmechs |= mechbit;
950 }
951
952 line += wordlen;
953 }
954 }
955 else if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
956 /* PREAUTH is not compatible with STARTTLS. */
957 if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
958 /* Switch to TLS connection now */
959 result = imap_perform_starttls(data);
960 }
961 else if(data->set.use_ssl <= CURLUSESSL_TRY)
962 result = imap_perform_authentication(data, conn);
963 else {
964 failf(data, "STARTTLS not available.");
965 result = CURLE_USE_SSL_FAILED;
966 }
967 }
968 else
969 result = imap_perform_authentication(data, conn);
970
971 return result;
972}
973
974/* For STARTTLS responses */
975static CURLcode imap_state_starttls_resp(struct Curl_easy *data,
976 int imapcode,
977 imapstate instate)
978{
979 CURLcode result = CURLE_OK;
980 struct connectdata *conn = data->conn;
981
982 (void)instate; /* no use for this yet */
983
984 /* Pipelining in response is forbidden. */
985 if(data->conn->proto.imapc.pp.cache_size)
986 return CURLE_WEIRD_SERVER_REPLY;
987
988 if(imapcode != IMAP_RESP_OK) {
989 if(data->set.use_ssl != CURLUSESSL_TRY) {
990 failf(data, "STARTTLS denied");
991 result = CURLE_USE_SSL_FAILED;
992 }
993 else
994 result = imap_perform_authentication(data, conn);
995 }
996 else
997 result = imap_perform_upgrade_tls(data, conn);
998
999 return result;
1000}
1001
1002/* For SASL authentication responses */
1003static CURLcode imap_state_auth_resp(struct Curl_easy *data,
1004 struct connectdata *conn,
1005 int imapcode,
1006 imapstate instate)
1007{
1008 CURLcode result = CURLE_OK;
1009 struct imap_conn *imapc = &conn->proto.imapc;
1010 saslprogress progress;
1011
1012 (void)instate; /* no use for this yet */
1013
1014 result = Curl_sasl_continue(&imapc->sasl, data, imapcode, &progress);
1015 if(!result)
1016 switch(progress) {
1017 case SASL_DONE:
1018 state(data, IMAP_STOP); /* Authenticated */
1019 break;
1020 case SASL_IDLE: /* No mechanism left after cancellation */
1021 if((!imapc->login_disabled) && (imapc->preftype & IMAP_TYPE_CLEARTEXT))
1022 /* Perform clear text authentication */
1023 result = imap_perform_login(data, conn);
1024 else {
1025 failf(data, "Authentication cancelled");
1026 result = CURLE_LOGIN_DENIED;
1027 }
1028 break;
1029 default:
1030 break;
1031 }
1032
1033 return result;
1034}
1035
1036/* For LOGIN responses */
1037static CURLcode imap_state_login_resp(struct Curl_easy *data,
1038 int imapcode,
1039 imapstate instate)
1040{
1041 CURLcode result = CURLE_OK;
1042 (void)instate; /* no use for this yet */
1043
1044 if(imapcode != IMAP_RESP_OK) {
1045 failf(data, "Access denied. %c", imapcode);
1046 result = CURLE_LOGIN_DENIED;
1047 }
1048 else
1049 /* End of connect phase */
1050 state(data, IMAP_STOP);
1051
1052 return result;
1053}
1054
1055/* For LIST and SEARCH responses */
1056static CURLcode imap_state_listsearch_resp(struct Curl_easy *data,
1057 int imapcode,
1058 imapstate instate)
1059{
1060 CURLcode result = CURLE_OK;
1061 char *line = data->state.buffer;
1062 size_t len = strlen(line);
1063
1064 (void)instate; /* No use for this yet */
1065
1066 if(imapcode == '*') {
1067 /* Temporarily add the LF character back and send as body to the client */
1068 line[len] = '\n';
1069 result = Curl_client_write(data, CLIENTWRITE_BODY, line, len + 1);
1070 line[len] = '\0';
1071 }
1072 else if(imapcode != IMAP_RESP_OK)
1073 result = CURLE_QUOTE_ERROR;
1074 else
1075 /* End of DO phase */
1076 state(data, IMAP_STOP);
1077
1078 return result;
1079}
1080
1081/* For SELECT responses */
1082static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode,
1083 imapstate instate)
1084{
1085 CURLcode result = CURLE_OK;
1086 struct connectdata *conn = data->conn;
1087 struct IMAP *imap = data->req.p.imap;
1088 struct imap_conn *imapc = &conn->proto.imapc;
1089 const char *line = data->state.buffer;
1090
1091 (void)instate; /* no use for this yet */
1092
1093 if(imapcode == '*') {
1094 /* See if this is an UIDVALIDITY response */
1095 char tmp[20];
1096 if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) {
1097 Curl_safefree(imapc->mailbox_uidvalidity);
1098 imapc->mailbox_uidvalidity = strdup(tmp);
1099 }
1100 }
1101 else if(imapcode == IMAP_RESP_OK) {
1102 /* Check if the UIDVALIDITY has been specified and matches */
1103 if(imap->uidvalidity && imapc->mailbox_uidvalidity &&
1104 !strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity)) {
1105 failf(data, "Mailbox UIDVALIDITY has changed");
1106 result = CURLE_REMOTE_FILE_NOT_FOUND;
1107 }
1108 else {
1109 /* Note the currently opened mailbox on this connection */
1110 imapc->mailbox = strdup(imap->mailbox);
1111
1112 if(imap->custom)
1113 result = imap_perform_list(data);
1114 else if(imap->query)
1115 result = imap_perform_search(data);
1116 else
1117 result = imap_perform_fetch(data);
1118 }
1119 }
1120 else {
1121 failf(data, "Select failed");
1122 result = CURLE_LOGIN_DENIED;
1123 }
1124
1125 return result;
1126}
1127
1128/* For the (first line of the) FETCH responses */
1129static CURLcode imap_state_fetch_resp(struct Curl_easy *data,
1130 struct connectdata *conn, int imapcode,
1131 imapstate instate)
1132{
1133 CURLcode result = CURLE_OK;
1134 struct imap_conn *imapc = &conn->proto.imapc;
1135 struct pingpong *pp = &imapc->pp;
1136 const char *ptr = data->state.buffer;
1137 bool parsed = FALSE;
1138 curl_off_t size = 0;
1139
1140 (void)instate; /* no use for this yet */
1141
1142 if(imapcode != '*') {
1143 Curl_pgrsSetDownloadSize(data, -1);
1144 state(data, IMAP_STOP);
1145 return CURLE_REMOTE_FILE_NOT_FOUND;
1146 }
1147
1148 /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse
1149 the continuation data contained within the curly brackets */
1150 while(*ptr && (*ptr != '{'))
1151 ptr++;
1152
1153 if(*ptr == '{') {
1154 char *endptr;
1155 if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size)) {
1156 if(endptr - ptr > 1 && endptr[0] == '}' &&
1157 endptr[1] == '\r' && endptr[2] == '\0')
1158 parsed = TRUE;
1159 }
1160 }
1161
1162 if(parsed) {
1163 infof(data, "Found %" CURL_FORMAT_CURL_OFF_T " bytes to download",
1164 size);
1165 Curl_pgrsSetDownloadSize(data, size);
1166
1167 if(pp->cache) {
1168 /* At this point there is a bunch of data in the header "cache" that is
1169 actually body content, send it as body and then skip it. Do note
1170 that there may even be additional "headers" after the body. */
1171 size_t chunk = pp->cache_size;
1172
1173 if(chunk > (size_t)size)
1174 /* The conversion from curl_off_t to size_t is always fine here */
1175 chunk = (size_t)size;
1176
1177 if(!chunk) {
1178 /* no size, we're done with the data */
1179 state(data, IMAP_STOP);
1180 return CURLE_OK;
1181 }
1182 result = Curl_client_write(data, CLIENTWRITE_BODY, pp->cache, chunk);
1183 if(result)
1184 return result;
1185
1186 data->req.bytecount += chunk;
1187
1188 infof(data, "Written %zu bytes, %" CURL_FORMAT_CURL_OFF_TU
1189 " bytes are left for transfer", chunk, size - chunk);
1190
1191 /* Have we used the entire cache or just part of it?*/
1192 if(pp->cache_size > chunk) {
1193 /* Only part of it so shrink the cache to fit the trailing data */
1194 memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk);
1195 pp->cache_size -= chunk;
1196 }
1197 else {
1198 /* Free the cache */
1199 Curl_safefree(pp->cache);
1200
1201 /* Reset the cache size */
1202 pp->cache_size = 0;
1203 }
1204 }
1205
1206 if(data->req.bytecount == size)
1207 /* The entire data is already transferred! */
1208 Curl_setup_transfer(data, -1, -1, FALSE, -1);
1209 else {
1210 /* IMAP download */
1211 data->req.maxdownload = size;
1212 /* force a recv/send check of this connection, as the data might've been
1213 read off the socket already */
1214 data->conn->cselect_bits = CURL_CSELECT_IN;
1215 Curl_setup_transfer(data, FIRSTSOCKET, size, FALSE, -1);
1216 }
1217 }
1218 else {
1219 /* We don't know how to parse this line */
1220 failf(data, "Failed to parse FETCH response.");
1221 result = CURLE_WEIRD_SERVER_REPLY;
1222 }
1223
1224 /* End of DO phase */
1225 state(data, IMAP_STOP);
1226
1227 return result;
1228}
1229
1230/* For final FETCH responses performed after the download */
1231static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data,
1232 int imapcode,
1233 imapstate instate)
1234{
1235 CURLcode result = CURLE_OK;
1236
1237 (void)instate; /* No use for this yet */
1238
1239 if(imapcode != IMAP_RESP_OK)
1240 result = CURLE_WEIRD_SERVER_REPLY;
1241 else
1242 /* End of DONE phase */
1243 state(data, IMAP_STOP);
1244
1245 return result;
1246}
1247
1248/* For APPEND responses */
1249static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode,
1250 imapstate instate)
1251{
1252 CURLcode result = CURLE_OK;
1253 (void)instate; /* No use for this yet */
1254
1255 if(imapcode != '+') {
1256 result = CURLE_UPLOAD_FAILED;
1257 }
1258 else {
1259 /* Set the progress upload size */
1260 Curl_pgrsSetUploadSize(data, data->state.infilesize);
1261
1262 /* IMAP upload */
1263 Curl_setup_transfer(data, -1, -1, FALSE, FIRSTSOCKET);
1264
1265 /* End of DO phase */
1266 state(data, IMAP_STOP);
1267 }
1268
1269 return result;
1270}
1271
1272/* For final APPEND responses performed after the upload */
1273static CURLcode imap_state_append_final_resp(struct Curl_easy *data,
1274 int imapcode,
1275 imapstate instate)
1276{
1277 CURLcode result = CURLE_OK;
1278
1279 (void)instate; /* No use for this yet */
1280
1281 if(imapcode != IMAP_RESP_OK)
1282 result = CURLE_UPLOAD_FAILED;
1283 else
1284 /* End of DONE phase */
1285 state(data, IMAP_STOP);
1286
1287 return result;
1288}
1289
1290static CURLcode imap_statemachine(struct Curl_easy *data,
1291 struct connectdata *conn)
1292{
1293 CURLcode result = CURLE_OK;
1294 curl_socket_t sock = conn->sock[FIRSTSOCKET];
1295 int imapcode;
1296 struct imap_conn *imapc = &conn->proto.imapc;
1297 struct pingpong *pp = &imapc->pp;
1298 size_t nread = 0;
1299 (void)data;
1300
1301 /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */
1302 if(imapc->state == IMAP_UPGRADETLS)
1303 return imap_perform_upgrade_tls(data, conn);
1304
1305 /* Flush any data that needs to be sent */
1306 if(pp->sendleft)
1307 return Curl_pp_flushsend(data, pp);
1308
1309 do {
1310 /* Read the response from the server */
1311 result = Curl_pp_readresp(data, sock, pp, &imapcode, &nread);
1312 if(result)
1313 return result;
1314
1315 /* Was there an error parsing the response line? */
1316 if(imapcode == -1)
1317 return CURLE_WEIRD_SERVER_REPLY;
1318
1319 if(!imapcode)
1320 break;
1321
1322 /* We have now received a full IMAP server response */
1323 switch(imapc->state) {
1324 case IMAP_SERVERGREET:
1325 result = imap_state_servergreet_resp(data, imapcode, imapc->state);
1326 break;
1327
1328 case IMAP_CAPABILITY:
1329 result = imap_state_capability_resp(data, imapcode, imapc->state);
1330 break;
1331
1332 case IMAP_STARTTLS:
1333 result = imap_state_starttls_resp(data, imapcode, imapc->state);
1334 break;
1335
1336 case IMAP_AUTHENTICATE:
1337 result = imap_state_auth_resp(data, conn, imapcode, imapc->state);
1338 break;
1339
1340 case IMAP_LOGIN:
1341 result = imap_state_login_resp(data, imapcode, imapc->state);
1342 break;
1343
1344 case IMAP_LIST:
1345 case IMAP_SEARCH:
1346 result = imap_state_listsearch_resp(data, imapcode, imapc->state);
1347 break;
1348
1349 case IMAP_SELECT:
1350 result = imap_state_select_resp(data, imapcode, imapc->state);
1351 break;
1352
1353 case IMAP_FETCH:
1354 result = imap_state_fetch_resp(data, conn, imapcode, imapc->state);
1355 break;
1356
1357 case IMAP_FETCH_FINAL:
1358 result = imap_state_fetch_final_resp(data, imapcode, imapc->state);
1359 break;
1360
1361 case IMAP_APPEND:
1362 result = imap_state_append_resp(data, imapcode, imapc->state);
1363 break;
1364
1365 case IMAP_APPEND_FINAL:
1366 result = imap_state_append_final_resp(data, imapcode, imapc->state);
1367 break;
1368
1369 case IMAP_LOGOUT:
1370 /* fallthrough, just stop! */
1371 default:
1372 /* internal error */
1373 state(data, IMAP_STOP);
1374 break;
1375 }
1376 } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp));
1377
1378 return result;
1379}
1380
1381/* Called repeatedly until done from multi.c */
1382static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
1383{
1384 CURLcode result = CURLE_OK;
1385 struct connectdata *conn = data->conn;
1386 struct imap_conn *imapc = &conn->proto.imapc;
1387
1388 if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
1389 result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &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->req.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 = Curl_conn_is_connected(conn, 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 /* Performing the alnum check with this macro is faster because of ASCII
1890 arithmetic */
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 */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette