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