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