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