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