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