From: Viktor Szakats Date: Sun, 21 Dec 2025 15:33:06 +0000 (+0100) Subject: lib: reorder protocol functions to avoid forward declarations (email) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ded1e3c6e9c1ad78c1ca04a44b09d73c488070d;p=thirdparty%2Fcurl.git lib: reorder protocol functions to avoid forward declarations (email) For protocols: imap, pop3, smtp. Move protocol hander table to the end of sources, rearrange static functions is reverse dependency order as necessary. Closes #20275 --- diff --git a/lib/imap.c b/lib/imap.c index 8fe87d9932..76b78a12a8 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -137,128 +137,101 @@ struct IMAP { BIT(uidvalidity_set); }; -/* Local API functions */ -static CURLcode imap_regular_transfer(struct Curl_easy *data, - struct IMAP *imap, - bool *done); -static CURLcode imap_do(struct Curl_easy *data, bool *done); -static CURLcode imap_done(struct Curl_easy *data, CURLcode status, - bool premature); -static CURLcode imap_connect(struct Curl_easy *data, bool *done); -static CURLcode imap_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); -static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done); -static CURLcode imap_pollset(struct Curl_easy *data, - struct easy_pollset *ps); -static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done); -static CURLcode imap_setup_connection(struct Curl_easy *data, - struct connectdata *conn); -static char *imap_atom(const char *str, bool escape_only); +#define IMAP_RESP_OK 1 +#define IMAP_RESP_NOT_OK 2 +#define IMAP_RESP_PREAUTH 3 + +struct ulbits { + int bit; + const char *flag; +}; + +/*********************************************************************** + * + * imap_sendf() + * + * Sends the formatted string as an IMAP command to the server. + * + * Designed to never block. + */ static CURLcode imap_sendf(struct Curl_easy *data, struct imap_conn *imapc, - const char *fmt, ...) CURL_PRINTF(3, 4); -static CURLcode imap_parse_url_options(struct connectdata *conn, - struct imap_conn *imapc); -static CURLcode imap_parse_url_path(struct Curl_easy *data, - struct IMAP *imap); -static CURLcode imap_parse_custom_request(struct Curl_easy *data, - struct IMAP *imap); -static CURLcode imap_perform_authenticate(struct Curl_easy *data, - const char *mech, - const struct bufref *initresp); -static CURLcode imap_continue_authenticate(struct Curl_easy *data, - const char *mech, - const struct bufref *resp); -static CURLcode imap_cancel_authenticate(struct Curl_easy *data, - const char *mech); -static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out); -static void imap_easy_reset(struct IMAP *imap); + const char *fmt, ...) +{ + CURLcode result = CURLE_OK; -/* - * IMAP protocol handler. - */ + DEBUGASSERT(fmt); -const struct Curl_handler Curl_handler_imap = { - "imap", /* scheme */ - imap_setup_connection, /* setup_connection */ - imap_do, /* do_it */ - imap_done, /* done */ - ZERO_NULL, /* do_more */ - imap_connect, /* connect_it */ - imap_multi_statemach, /* connecting */ - imap_doing, /* doing */ - imap_pollset, /* proto_pollset */ - imap_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - imap_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_IMAP, /* defport */ - CURLPROTO_IMAP, /* protocol */ - CURLPROTO_IMAP, /* family */ - PROTOPT_CLOSEACTION | /* flags */ - PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | - PROTOPT_CONN_REUSE -}; + /* Calculate the tag based on the connection ID and command ID */ + curl_msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", + 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)), + ++imapc->cmdid); -#ifdef USE_SSL -/* - * IMAPS protocol handler. - */ + /* start with a blank buffer */ + curlx_dyn_reset(&imapc->dyn); -const struct Curl_handler Curl_handler_imaps = { - "imaps", /* scheme */ - imap_setup_connection, /* setup_connection */ - imap_do, /* do_it */ - imap_done, /* done */ - ZERO_NULL, /* do_more */ - imap_connect, /* connect_it */ - imap_multi_statemach, /* connecting */ - imap_doing, /* doing */ - imap_pollset, /* proto_pollset */ - imap_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - imap_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_IMAPS, /* defport */ - CURLPROTO_IMAPS, /* protocol */ - CURLPROTO_IMAP, /* family */ - PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ - PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE -}; + /* append tag + space + fmt */ + result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); + if(!result) { + va_list ap; + va_start(ap, fmt); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic pop #endif + va_end(ap); + } + return result; +} -#define IMAP_RESP_OK 1 -#define IMAP_RESP_NOT_OK 2 -#define IMAP_RESP_PREAUTH 3 +/*********************************************************************** + * + * imap_atom() + * + * Checks the input string for characters that need escaping and returns an + * atom ready for sending to the server. + * + * The returned string needs to be freed. + * + */ +static char *imap_atom(const char *str, bool escape_only) +{ + struct dynbuf line; + size_t nclean; + size_t len; -/* SASL parameters for the imap protocol */ -static const struct SASLproto saslimap = { - "imap", /* The service name */ - imap_perform_authenticate, /* Send authentication command */ - imap_continue_authenticate, /* Send authentication continuation */ - imap_cancel_authenticate, /* Send authentication cancellation */ - imap_get_message, /* Get SASL response message */ - 0, /* No maximum initial response length */ - '+', /* Code received when continuation is expected */ - IMAP_RESP_OK, /* Code to receive upon authentication success */ - SASL_AUTH_DEFAULT, /* Default mechanisms */ - SASL_FLAG_BASE64 /* Configuration flags */ -}; + if(!str) + return NULL; -struct ulbits { - int bit; - const char *flag; -}; + len = strlen(str); + nclean = strcspn(str, "() {%*]\\\""); + if(len == nclean) + /* nothing to escape, return a strdup */ + return curlx_strdup(str); + + curlx_dyn_init(&line, 2000); + + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) + return NULL; + + while(*str) { + if((*str == '\\' || *str == '"') && + curlx_dyn_addn(&line, "\\", 1)) + return NULL; + if(curlx_dyn_addn(&line, str, 1)) + return NULL; + str++; + } + + if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) + return NULL; + + return curlx_dyn_ptr(&line); +} /*********************************************************************** * @@ -1650,261 +1623,465 @@ static CURLcode imap_pollset(struct Curl_easy *data, return imapc ? Curl_pp_pollset(data, &imapc->pp, ps) : CURLE_OK; } +static void imap_easy_reset(struct IMAP *imap) +{ + Curl_safefree(imap->mailbox); + Curl_safefree(imap->uid); + Curl_safefree(imap->mindex); + Curl_safefree(imap->section); + Curl_safefree(imap->partial); + Curl_safefree(imap->query); + Curl_safefree(imap->custom); + Curl_safefree(imap->custom_params); + /* Clear the transfer mode for the next request */ + imap->transfer = PPTRANSFER_BODY; +} + /*********************************************************************** * - * imap_connect() - * - * This function should do everything that is to be considered a part of the - * connection phase. + * imap_is_bchar() * - * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE if not. + * Portable test of whether the specified char is a "bchar" as defined in the + * grammar of RFC-5092. */ -static CURLcode imap_connect(struct Curl_easy *data, bool *done) +static bool imap_is_bchar(char ch) { - struct imap_conn *imapc = - Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); - CURLcode result = CURLE_OK; - - *done = FALSE; /* default to not done yet */ - if(!imapc) - return CURLE_FAILED_INIT; - - /* Parse the URL options */ - result = imap_parse_url_options(data->conn, imapc); - if(result) - return result; - - /* Start off waiting for the server greeting response */ - imap_state(data, imapc, IMAP_SERVERGREET); - - /* Start off with an response id of '*' */ - curlx_strcopy(imapc->resptag, sizeof(imapc->resptag), "*", 1); + /* Performing the alnum check with this macro is faster because of ASCII + arithmetic */ + if(ISALNUM(ch)) + return TRUE; - result = imap_multi_statemach(data, done); + switch(ch) { + /* bchar */ + case ':': + case '@': + case '/': + /* bchar -> achar */ + case '&': + case '=': + /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */ + case '-': + case '.': + case '_': + case '~': + /* bchar -> achar -> uchar -> sub-delims-sh */ + case '!': + case '$': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + /* bchar -> achar -> uchar -> pct-encoded */ + case '%': /* HEXDIG chars are already included above */ + return TRUE; - return result; + default: + return FALSE; + } } /*********************************************************************** * - * imap_done() + * imap_parse_url_options() * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. + * Parse the URL login options. */ -static CURLcode imap_done(struct Curl_easy *data, CURLcode status, - bool premature) +static CURLcode imap_parse_url_options(struct connectdata *conn, + struct imap_conn *imapc) { CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); - struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + const char *ptr = conn->options; + bool prefer_login = FALSE; - (void)premature; + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; - if(!imapc) - return CURLE_FAILED_INIT; - if(!imap) - return CURLE_OK; + while(*ptr && *ptr != '=') + ptr++; - if(status) { - connclose(conn, "IMAP done with bad status"); /* marked for closure */ - result = status; /* use the already set error code */ - } - else if(!data->set.connect_only && - ((!imap->custom && (imap->uid || imap->mindex)) || - (imap->custom && data->req.maxdownload > 0) || - data->state.upload || IS_MIME_POST(data))) { - /* Handle responses after FETCH or APPEND transfer has finished. - For custom commands, check if we set up a download which indicates - a FETCH-like command with literal data. */ + value = ptr + 1; - if(!data->state.upload && !IS_MIME_POST(data)) - imap_state(data, imapc, IMAP_FETCH_FINAL); + while(*ptr && *ptr != ';') + ptr++; + + if(curl_strnequal(key, "AUTH=+LOGIN", 11)) { + /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */ + prefer_login = TRUE; + imapc->sasl.prefmech = SASL_AUTH_NONE; + } + else if(curl_strnequal(key, "AUTH=", 5)) { + prefer_login = FALSE; + result = Curl_sasl_parse_url_auth_option(&imapc->sasl, + value, ptr - value); + } else { - /* End the APPEND command first by sending an empty line */ - result = Curl_pp_sendf(data, &imapc->pp, "%s", ""); - if(!result) - imap_state(data, imapc, IMAP_APPEND_FINAL); + prefer_login = FALSE; + result = CURLE_URL_MALFORMAT; } - /* Run the state-machine */ - if(!result) - result = imap_block_statemach(data, imapc, FALSE); + if(*ptr == ';') + ptr++; + } + + if(prefer_login) + imapc->preftype = IMAP_TYPE_CLEARTEXT; + else { + switch(imapc->sasl.prefmech) { + case SASL_AUTH_NONE: + imapc->preftype = IMAP_TYPE_NONE; + break; + case SASL_AUTH_DEFAULT: + imapc->preftype = IMAP_TYPE_ANY; + break; + default: + imapc->preftype = IMAP_TYPE_SASL; + break; + } } - imap_easy_reset(imap); return result; } /*********************************************************************** * - * imap_perform() + * imap_parse_url_path() + * + * Parse the URL path into separate path components. * - * This is the actual DO function for IMAP. Fetch or append a message, or do - * other things according to the options previously setup. */ -static CURLcode imap_perform(struct Curl_easy *data, bool *connected, - bool *dophase_done) +static CURLcode imap_parse_url_path(struct Curl_easy *data, + struct IMAP *imap) { - /* This is IMAP and no proxy */ + /* The imap struct is already initialised in imap_connect() */ CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); - struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); - bool selected = FALSE; + const char *begin = &data->state.up.path[1]; /* skip leading slash */ + const char *ptr = begin; - DEBUGF(infof(data, "DO phase starts")); - if(!imapc || !imap) - return CURLE_FAILED_INIT; + /* See how much of the URL is a valid path and decode it */ + while(imap_is_bchar(*ptr)) + ptr++; - if(data->req.no_body) { - /* Requested no body means no transfer */ - imap->transfer = PPTRANSFER_INFO; + if(ptr != begin) { + /* Remove the trailing slash if present */ + const char *end = ptr; + if(end > begin && end[-1] == '/') + end--; + + result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL, + REJECT_CTRL); + if(result) + return result; } + else + imap->mailbox = NULL; - *dophase_done = FALSE; /* not done yet */ + /* There can be any number of parameters in the form ";NAME=VALUE" */ + while(*ptr == ';') { + char *name; + char *value; + size_t valuelen; - /* Determine if the requested mailbox (with the same UIDVALIDITY if set) - has already been selected on this connection */ - if(imap->mailbox && imapc->mailbox && - curl_strequal(imap->mailbox, imapc->mailbox) && - (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set || - (imap->uidvalidity == imapc->mb_uidvalidity))) - selected = TRUE; + /* Find the length of the name parameter */ + begin = ++ptr; + while(*ptr && *ptr != '=') + ptr++; - /* Start the first command in the DO phase */ - if(data->state.upload || IS_MIME_POST(data)) - /* APPEND can be executed directly */ - result = imap_perform_append(data, imapc, imap); - else if(imap->custom && (selected || !imap->mailbox)) - /* Custom command using the same mailbox or no mailbox */ - result = imap_perform_list(data, imapc, imap); - else if(!imap->custom && selected && (imap->uid || imap->mindex)) - /* FETCH from the same mailbox */ - result = imap_perform_fetch(data, imapc, imap); - else if(!imap->custom && selected && imap->query) - /* SEARCH the current mailbox */ - result = imap_perform_search(data, imapc, imap); - else if(imap->mailbox && !selected && - (imap->custom || imap->uid || imap->mindex || imap->query)) - /* SELECT the mailbox */ - result = imap_perform_select(data, imapc, imap); - else - /* LIST */ - result = imap_perform_list(data, imapc, imap); + if(!*ptr) + return CURLE_URL_MALFORMAT; - if(result) - return result; + /* Decode the name parameter */ + result = Curl_urldecode(begin, ptr - begin, &name, NULL, + REJECT_CTRL); + if(result) + return result; - /* Run the state-machine */ - result = imap_multi_statemach(data, dophase_done); + /* Find the length of the value parameter */ + begin = ++ptr; + while(imap_is_bchar(*ptr)) + ptr++; - *connected = Curl_conn_is_connected(conn, FIRSTSOCKET); + /* Decode the value parameter */ + result = Curl_urldecode(begin, ptr - begin, &value, &valuelen, + REJECT_CTRL); + if(result) { + curlx_free(name); + return result; + } - if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete")); + DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value)); - return result; + /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION + and PARTIAL) stripping of the trailing slash character if it is + present. + + Note: Unknown parameters trigger a URL_MALFORMAT error. */ + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + if(valuelen) { + if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) { + curl_off_t num; + const char *p = (const char *)value; + if(!curlx_str_number(&p, &num, UINT_MAX)) { + imap->uidvalidity = (unsigned int)num; + imap->uidvalidity_set = TRUE; + } + curlx_free(value); + } + else if(curl_strequal(name, "UID") && !imap->uid) { + imap->uid = value; + } + else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) { + imap->mindex = value; + } + else if(curl_strequal(name, "SECTION") && !imap->section) { + imap->section = value; + } + else if(curl_strequal(name, "PARTIAL") && !imap->partial) { + imap->partial = value; + } + else { + curlx_free(name); + curlx_free(value); + return CURLE_URL_MALFORMAT; + } + } + else + /* blank? */ + curlx_free(value); + curlx_free(name); + } + + /* Does the URL contain a query parameter? Only valid when we have a mailbox + and no UID as per RFC-5092 */ + if(imap->mailbox && !imap->uid && !imap->mindex) { + /* Get the query parameter, URL decoded */ + CURLUcode uc = curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query, + CURLU_URLDECODE); + if(uc == CURLUE_OUT_OF_MEMORY) + return CURLE_OUT_OF_MEMORY; + } + + /* Any extra stuff at the end of the URL is an error */ + if(*ptr) + return CURLE_URL_MALFORMAT; + + return CURLE_OK; } /*********************************************************************** * - * imap_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (imap_perform). + * imap_parse_custom_request() * - * The input argument is already checked for validity. + * Parse the custom request. */ -static CURLcode imap_do(struct Curl_easy *data, bool *done) +static CURLcode imap_parse_custom_request(struct Curl_easy *data, + struct IMAP *imap) { - struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); CURLcode result = CURLE_OK; - *done = FALSE; /* default to false */ + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; - if(!imap) - return CURLE_FAILED_INIT; - /* Parse the URL path */ - result = imap_parse_url_path(data, imap); - if(result) - return result; + if(custom) { + /* URL decode the custom request */ + result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL); - /* Parse the custom request */ - result = imap_parse_custom_request(data, imap); + /* Extract the parameters if specified */ + if(!result) { + const char *params = imap->custom; + + while(*params && *params != ' ') + params++; + + if(*params) { + imap->custom_params = curlx_strdup(params); + imap->custom[params - imap->custom] = '\0'; + + if(!imap->custom_params) + result = CURLE_OUT_OF_MEMORY; + } + } + } + + return result; +} + +/*********************************************************************** + * + * imap_connect() + * + * This function should do everything that is to be considered a part of the + * connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE if not. + */ +static CURLcode imap_connect(struct Curl_easy *data, bool *done) +{ + struct imap_conn *imapc = + Curl_conn_meta_get(data->conn, CURL_META_IMAP_CONN); + CURLcode result = CURLE_OK; + + *done = FALSE; /* default to not done yet */ + if(!imapc) + return CURLE_FAILED_INIT; + + /* Parse the URL options */ + result = imap_parse_url_options(data->conn, imapc); if(result) return result; - result = imap_regular_transfer(data, imap, done); + /* Start off waiting for the server greeting response */ + imap_state(data, imapc, IMAP_SERVERGREET); + + /* Start off with an response id of '*' */ + curlx_strcopy(imapc->resptag, sizeof(imapc->resptag), "*", 1); + + result = imap_multi_statemach(data, done); return result; } /*********************************************************************** * - * imap_disconnect() + * imap_done() * - * Disconnect from an IMAP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. */ -static CURLcode imap_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection) +static CURLcode imap_done(struct Curl_easy *data, CURLcode status, + bool premature) { + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); - (void)data; - if(imapc) { - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way (pingpong has pending data to send), - sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. */ - if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &imapc->pp)) { - if(!imap_perform_logout(data, imapc)) - (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ - } + (void)premature; + + if(!imapc) + return CURLE_FAILED_INIT; + if(!imap) + return CURLE_OK; + + if(status) { + connclose(conn, "IMAP done with bad status"); /* marked for closure */ + result = status; /* use the already set error code */ } - return CURLE_OK; -} + else if(!data->set.connect_only && + ((!imap->custom && (imap->uid || imap->mindex)) || + (imap->custom && data->req.maxdownload > 0) || + data->state.upload || IS_MIME_POST(data))) { + /* Handle responses after FETCH or APPEND transfer has finished. + For custom commands, check if we set up a download which indicates + a FETCH-like command with literal data. */ -/* Call this when the DO phase has completed */ -static CURLcode imap_dophase_done(struct Curl_easy *data, - struct IMAP *imap, - bool connected) -{ - (void)connected; + if(!data->state.upload && !IS_MIME_POST(data)) + imap_state(data, imapc, IMAP_FETCH_FINAL); + else { + /* End the APPEND command first by sending an empty line */ + result = Curl_pp_sendf(data, &imapc->pp, "%s", ""); + if(!result) + imap_state(data, imapc, IMAP_APPEND_FINAL); + } - if(imap->transfer != PPTRANSFER_BODY) - /* no data to transfer */ - Curl_xfer_setup_nop(data); + /* Run the state-machine */ + if(!result) + result = imap_block_statemach(data, imapc, FALSE); + } - return CURLE_OK; + imap_easy_reset(imap); + return result; } -/* Called from multi.c while DOing */ -static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) +/*********************************************************************** + * + * imap_perform() + * + * This is the actual DO function for IMAP. Fetch or append a message, or do + * other things according to the options previously setup. + */ +static CURLcode imap_perform(struct Curl_easy *data, bool *connected, + bool *dophase_done) { + /* This is IMAP and no proxy */ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); - CURLcode result; + bool selected = FALSE; - if(!imap) + DEBUGF(infof(data, "DO phase starts")); + if(!imapc || !imap) return CURLE_FAILED_INIT; - result = imap_multi_statemach(data, dophase_done); + if(data->req.no_body) { + /* Requested no body means no transfer */ + imap->transfer = PPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Determine if the requested mailbox (with the same UIDVALIDITY if set) + has already been selected on this connection */ + if(imap->mailbox && imapc->mailbox && + curl_strequal(imap->mailbox, imapc->mailbox) && + (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set || + (imap->uidvalidity == imapc->mb_uidvalidity))) + selected = TRUE; + + /* Start the first command in the DO phase */ + if(data->state.upload || IS_MIME_POST(data)) + /* APPEND can be executed directly */ + result = imap_perform_append(data, imapc, imap); + else if(imap->custom && (selected || !imap->mailbox)) + /* Custom command using the same mailbox or no mailbox */ + result = imap_perform_list(data, imapc, imap); + else if(!imap->custom && selected && (imap->uid || imap->mindex)) + /* FETCH from the same mailbox */ + result = imap_perform_fetch(data, imapc, imap); + else if(!imap->custom && selected && imap->query) + /* SEARCH the current mailbox */ + result = imap_perform_search(data, imapc, imap); + else if(imap->mailbox && !selected && + (imap->custom || imap->uid || imap->mindex || imap->query)) + /* SELECT the mailbox */ + result = imap_perform_select(data, imapc, imap); + else + /* LIST */ + result = imap_perform_list(data, imapc, imap); + if(result) - DEBUGF(infof(data, "DO phase failed")); - else if(*dophase_done) { - result = imap_dophase_done(data, imap, FALSE /* not connected */); + return result; + + /* Run the state-machine */ + result = imap_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(conn, FIRSTSOCKET); + if(*dophase_done) DEBUGF(infof(data, "DO phase is complete")); - } return result; } +/* Call this when the DO phase has completed */ +static CURLcode imap_dophase_done(struct Curl_easy *data, + struct IMAP *imap, + bool connected) +{ + (void)connected; + + if(imap->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_xfer_setup_nop(data); + + return CURLE_OK; +} + /*********************************************************************** * * imap_regular_transfer() @@ -1937,31 +2114,97 @@ static CURLcode imap_regular_transfer(struct Curl_easy *data, return result; } -static void imap_easy_reset(struct IMAP *imap) +/*********************************************************************** + * + * imap_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (imap_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode imap_do(struct Curl_easy *data, bool *done) { - Curl_safefree(imap->mailbox); - Curl_safefree(imap->uid); - Curl_safefree(imap->mindex); - Curl_safefree(imap->section); - Curl_safefree(imap->partial); - Curl_safefree(imap->query); - Curl_safefree(imap->custom); - Curl_safefree(imap->custom_params); - /* Clear the transfer mode for the next request */ - imap->transfer = PPTRANSFER_BODY; -} + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + CURLcode result = CURLE_OK; + *done = FALSE; /* default to false */ -static void imap_easy_dtor(void *key, size_t klen, void *entry) -{ - struct IMAP *imap = entry; - (void)key; - (void)klen; - imap_easy_reset(imap); - curlx_free(imap); + if(!imap) + return CURLE_FAILED_INIT; + /* Parse the URL path */ + result = imap_parse_url_path(data, imap); + if(result) + return result; + + /* Parse the custom request */ + result = imap_parse_custom_request(data, imap); + if(result) + return result; + + result = imap_regular_transfer(data, imap, done); + + return result; } -static void imap_conn_dtor(void *key, size_t klen, void *entry) -{ +/*********************************************************************** + * + * imap_disconnect() + * + * Disconnect from an IMAP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode imap_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) +{ + struct imap_conn *imapc = Curl_conn_meta_get(conn, CURL_META_IMAP_CONN); + + (void)data; + if(imapc) { + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way (pingpong has pending data to send), + sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ + if(!dead_connection && conn->bits.protoconnstart && + !Curl_pp_needs_flush(data, &imapc->pp)) { + if(!imap_perform_logout(data, imapc)) + (void)imap_block_statemach(data, imapc, TRUE); /* ignore errors */ + } + } + return CURLE_OK; +} + +/* Called from multi.c while DOing */ +static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) +{ + struct IMAP *imap = Curl_meta_get(data, CURL_META_IMAP_EASY); + CURLcode result; + + if(!imap) + return CURLE_FAILED_INIT; + + result = imap_multi_statemach(data, dophase_done); + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = imap_dophase_done(data, imap, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete")); + } + + return result; +} + +static void imap_easy_dtor(void *key, size_t klen, void *entry) +{ + struct IMAP *imap = entry; + (void)key; + (void)klen; + imap_easy_reset(imap); + curlx_free(imap); +} + +static void imap_conn_dtor(void *key, size_t klen, void *entry) +{ struct imap_conn *imapc = entry; (void)key; (void)klen; @@ -1971,6 +2214,20 @@ static void imap_conn_dtor(void *key, size_t klen, void *entry) curlx_free(imapc); } +/* SASL parameters for the imap protocol */ +static const struct SASLproto saslimap = { + "imap", /* The service name */ + imap_perform_authenticate, /* Send authentication command */ + imap_continue_authenticate, /* Send authentication continuation */ + imap_cancel_authenticate, /* Send authentication cancellation */ + imap_get_message, /* Get SASL response message */ + 0, /* No maximum initial response length */ + '+', /* Code received when continuation is expected */ + IMAP_RESP_OK, /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + static CURLcode imap_setup_connection(struct Curl_easy *data, struct connectdata *conn) { @@ -2003,361 +2260,65 @@ static CURLcode imap_setup_connection(struct Curl_easy *data, return CURLE_OK; } -/*********************************************************************** - * - * imap_sendf() - * - * Sends the formatted string as an IMAP command to the server. - * - * Designed to never block. - */ -static CURLcode imap_sendf(struct Curl_easy *data, - struct imap_conn *imapc, - const char *fmt, ...) -{ - CURLcode result = CURLE_OK; - - DEBUGASSERT(fmt); - - /* Calculate the tag based on the connection ID and command ID */ - curl_msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", - 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)), - ++imapc->cmdid); - - /* start with a blank buffer */ - curlx_dyn_reset(&imapc->dyn); - - /* append tag + space + fmt */ - result = curlx_dyn_addf(&imapc->dyn, "%s %s", imapc->resptag, fmt); - if(!result) { - va_list ap; - va_start(ap, fmt); -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wformat-nonliteral" -#endif - result = Curl_pp_vsendf(data, &imapc->pp, curlx_dyn_ptr(&imapc->dyn), ap); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - va_end(ap); - } - return result; -} - -/*********************************************************************** - * - * imap_atom() - * - * Checks the input string for characters that need escaping and returns an - * atom ready for sending to the server. - * - * The returned string needs to be freed. - * - */ -static char *imap_atom(const char *str, bool escape_only) -{ - struct dynbuf line; - size_t nclean; - size_t len; - - if(!str) - return NULL; - - len = strlen(str); - nclean = strcspn(str, "() {%*]\\\""); - if(len == nclean) - /* nothing to escape, return a strdup */ - return curlx_strdup(str); - - curlx_dyn_init(&line, 2000); - - if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) - return NULL; - - while(*str) { - if((*str == '\\' || *str == '"') && - curlx_dyn_addn(&line, "\\", 1)) - return NULL; - if(curlx_dyn_addn(&line, str, 1)) - return NULL; - str++; - } - - if(!escape_only && curlx_dyn_addn(&line, "\"", 1)) - return NULL; - - return curlx_dyn_ptr(&line); -} - -/*********************************************************************** - * - * imap_is_bchar() - * - * Portable test of whether the specified char is a "bchar" as defined in the - * grammar of RFC-5092. - */ -static bool imap_is_bchar(char ch) -{ - /* Performing the alnum check with this macro is faster because of ASCII - arithmetic */ - if(ISALNUM(ch)) - return TRUE; - - switch(ch) { - /* bchar */ - case ':': - case '@': - case '/': - /* bchar -> achar */ - case '&': - case '=': - /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */ - case '-': - case '.': - case '_': - case '~': - /* bchar -> achar -> uchar -> sub-delims-sh */ - case '!': - case '$': - case '\'': - case '(': - case ')': - case '*': - case '+': - case ',': - /* bchar -> achar -> uchar -> pct-encoded */ - case '%': /* HEXDIG chars are already included above */ - return TRUE; - - default: - return FALSE; - } -} - -/*********************************************************************** - * - * imap_parse_url_options() - * - * Parse the URL login options. - */ -static CURLcode imap_parse_url_options(struct connectdata *conn, - struct imap_conn *imapc) -{ - CURLcode result = CURLE_OK; - const char *ptr = conn->options; - bool prefer_login = FALSE; - - while(!result && ptr && *ptr) { - const char *key = ptr; - const char *value; - - while(*ptr && *ptr != '=') - ptr++; - - value = ptr + 1; - - while(*ptr && *ptr != ';') - ptr++; - - if(curl_strnequal(key, "AUTH=+LOGIN", 11)) { - /* User prefers plaintext LOGIN over any SASL, including SASL LOGIN */ - prefer_login = TRUE; - imapc->sasl.prefmech = SASL_AUTH_NONE; - } - else if(curl_strnequal(key, "AUTH=", 5)) { - prefer_login = FALSE; - result = Curl_sasl_parse_url_auth_option(&imapc->sasl, - value, ptr - value); - } - else { - prefer_login = FALSE; - result = CURLE_URL_MALFORMAT; - } - - if(*ptr == ';') - ptr++; - } - - if(prefer_login) - imapc->preftype = IMAP_TYPE_CLEARTEXT; - else { - switch(imapc->sasl.prefmech) { - case SASL_AUTH_NONE: - imapc->preftype = IMAP_TYPE_NONE; - break; - case SASL_AUTH_DEFAULT: - imapc->preftype = IMAP_TYPE_ANY; - break; - default: - imapc->preftype = IMAP_TYPE_SASL; - break; - } - } - - return result; -} - -/*********************************************************************** - * - * imap_parse_url_path() - * - * Parse the URL path into separate path components. - * +/* + * IMAP protocol handler. */ -static CURLcode imap_parse_url_path(struct Curl_easy *data, - struct IMAP *imap) -{ - /* The imap struct is already initialised in imap_connect() */ - CURLcode result = CURLE_OK; - const char *begin = &data->state.up.path[1]; /* skip leading slash */ - const char *ptr = begin; - - /* See how much of the URL is a valid path and decode it */ - while(imap_is_bchar(*ptr)) - ptr++; - - if(ptr != begin) { - /* Remove the trailing slash if present */ - const char *end = ptr; - if(end > begin && end[-1] == '/') - end--; - - result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL, - REJECT_CTRL); - if(result) - return result; - } - else - imap->mailbox = NULL; - - /* There can be any number of parameters in the form ";NAME=VALUE" */ - while(*ptr == ';') { - char *name; - char *value; - size_t valuelen; - - /* Find the length of the name parameter */ - begin = ++ptr; - while(*ptr && *ptr != '=') - ptr++; - - if(!*ptr) - return CURLE_URL_MALFORMAT; - - /* Decode the name parameter */ - result = Curl_urldecode(begin, ptr - begin, &name, NULL, - REJECT_CTRL); - if(result) - return result; - - /* Find the length of the value parameter */ - begin = ++ptr; - while(imap_is_bchar(*ptr)) - ptr++; - - /* Decode the value parameter */ - result = Curl_urldecode(begin, ptr - begin, &value, &valuelen, - REJECT_CTRL); - if(result) { - curlx_free(name); - return result; - } - - DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value)); - - /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION - and PARTIAL) stripping of the trailing slash character if it is - present. - - Note: Unknown parameters trigger a URL_MALFORMAT error. */ - if(valuelen > 0 && value[valuelen - 1] == '/') - value[valuelen - 1] = '\0'; - if(valuelen) { - if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) { - curl_off_t num; - const char *p = (const char *)value; - if(!curlx_str_number(&p, &num, UINT_MAX)) { - imap->uidvalidity = (unsigned int)num; - imap->uidvalidity_set = TRUE; - } - curlx_free(value); - } - else if(curl_strequal(name, "UID") && !imap->uid) { - imap->uid = value; - } - else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) { - imap->mindex = value; - } - else if(curl_strequal(name, "SECTION") && !imap->section) { - imap->section = value; - } - else if(curl_strequal(name, "PARTIAL") && !imap->partial) { - imap->partial = value; - } - else { - curlx_free(name); - curlx_free(value); - return CURLE_URL_MALFORMAT; - } - } - else - /* blank? */ - curlx_free(value); - curlx_free(name); - } - - /* Does the URL contain a query parameter? Only valid when we have a mailbox - and no UID as per RFC-5092 */ - if(imap->mailbox && !imap->uid && !imap->mindex) { - /* Get the query parameter, URL decoded */ - CURLUcode uc = curl_url_get(data->state.uh, CURLUPART_QUERY, &imap->query, - CURLU_URLDECODE); - if(uc == CURLUE_OUT_OF_MEMORY) - return CURLE_OUT_OF_MEMORY; - } - - /* Any extra stuff at the end of the URL is an error */ - if(*ptr) - return CURLE_URL_MALFORMAT; - - return CURLE_OK; -} +const struct Curl_handler Curl_handler_imap = { + "imap", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_pollset, /* proto_pollset */ + imap_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_IMAP, /* defport */ + CURLPROTO_IMAP, /* protocol */ + CURLPROTO_IMAP, /* family */ + PROTOPT_CLOSEACTION | /* flags */ + PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | + PROTOPT_CONN_REUSE +}; -/*********************************************************************** - * - * imap_parse_custom_request() - * - * Parse the custom request. +#ifdef USE_SSL +/* + * IMAPS protocol handler. */ -static CURLcode imap_parse_custom_request(struct Curl_easy *data, - struct IMAP *imap) -{ - CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; - - if(custom) { - /* URL decode the custom request */ - result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL); - - /* Extract the parameters if specified */ - if(!result) { - const char *params = imap->custom; - - while(*params && *params != ' ') - params++; - - if(*params) { - imap->custom_params = curlx_strdup(params); - imap->custom[params - imap->custom] = '\0'; - - if(!imap->custom_params) - result = CURLE_OUT_OF_MEMORY; - } - } - } - - return result; -} +const struct Curl_handler Curl_handler_imaps = { + "imaps", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_pollset, /* proto_pollset */ + imap_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_IMAPS, /* defport */ + CURLPROTO_IMAPS, /* protocol */ + CURLPROTO_IMAP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE +}; +#endif #endif /* CURL_DISABLE_IMAP */ diff --git a/lib/pop3.c b/lib/pop3.c index 8e2c8aa32e..122b34a274 100644 --- a/lib/pop3.c +++ b/lib/pop3.c @@ -135,111 +135,6 @@ struct pop3_conn { BIT(tls_supported); /* StartTLS capability supported by server */ }; -/* Local API functions */ -static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *done); -static CURLcode pop3_do(struct Curl_easy *data, bool *done); -static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, - bool premature); -static CURLcode pop3_connect(struct Curl_easy *data, bool *done); -static CURLcode pop3_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); -static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done); -static CURLcode pop3_pollset(struct Curl_easy *data, - struct easy_pollset *ps); -static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done); -static CURLcode pop3_setup_connection(struct Curl_easy *data, - struct connectdata *conn); -static CURLcode pop3_parse_url_options(struct connectdata *conn); -static CURLcode pop3_parse_url_path(struct Curl_easy *data); -static CURLcode pop3_parse_custom_request(struct Curl_easy *data); -static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech, - const struct bufref *initresp); -static CURLcode pop3_continue_auth(struct Curl_easy *data, const char *mech, - const struct bufref *resp); -static CURLcode pop3_cancel_auth(struct Curl_easy *data, const char *mech); -static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out); - -/* This function scans the body after the end-of-body and writes everything - * until the end is found */ -static CURLcode pop3_write(struct Curl_easy *data, - const char *str, size_t nread, bool is_eos); - -/* - * POP3 protocol handler. - */ - -const struct Curl_handler Curl_handler_pop3 = { - "pop3", /* scheme */ - pop3_setup_connection, /* setup_connection */ - pop3_do, /* do_it */ - pop3_done, /* done */ - ZERO_NULL, /* do_more */ - pop3_connect, /* connect_it */ - pop3_multi_statemach, /* connecting */ - pop3_doing, /* doing */ - pop3_pollset, /* proto_pollset */ - pop3_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - pop3_disconnect, /* disconnect */ - pop3_write, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_POP3, /* defport */ - CURLPROTO_POP3, /* protocol */ - CURLPROTO_POP3, /* family */ - PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ - PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE -}; - -#ifdef USE_SSL -/* - * POP3S protocol handler. - */ - -const struct Curl_handler Curl_handler_pop3s = { - "pop3s", /* scheme */ - pop3_setup_connection, /* setup_connection */ - pop3_do, /* do_it */ - pop3_done, /* done */ - ZERO_NULL, /* do_more */ - pop3_connect, /* connect_it */ - pop3_multi_statemach, /* connecting */ - pop3_doing, /* doing */ - pop3_pollset, /* proto_pollset */ - pop3_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - pop3_disconnect, /* disconnect */ - pop3_write, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_POP3S, /* defport */ - CURLPROTO_POP3S, /* protocol */ - CURLPROTO_POP3, /* family */ - PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ - PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE -}; -#endif - -/* SASL parameters for the pop3 protocol */ -static const struct SASLproto saslpop3 = { - "pop", /* The service name */ - pop3_perform_auth, /* Send authentication command */ - pop3_continue_auth, /* Send authentication continuation */ - pop3_cancel_auth, /* Send authentication cancellation */ - pop3_get_message, /* Get SASL response message */ - 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ - '*', /* Code received when continuation is expected */ - '+', /* Code to receive upon authentication success */ - SASL_AUTH_DEFAULT, /* Default mechanisms */ - SASL_FLAG_BASE64 /* Configuration flags */ -}; - struct pop3_cmd { const char *name; unsigned short nlen; @@ -268,6 +163,105 @@ static const struct pop3_cmd pop3cmds[] = { { "XTND", 4, TRUE, TRUE }, }; +/*********************************************************************** + * + * pop3_parse_url_options() + * + * Parse the URL login options. + */ +static CURLcode pop3_parse_url_options(struct connectdata *conn) +{ + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + CURLcode result = CURLE_OK; + const char *ptr = conn->options; + + if(!pop3c) + return CURLE_FAILED_INIT; + + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; + + while(*ptr && *ptr != '=') + ptr++; + + value = ptr + 1; + + while(*ptr && *ptr != ';') + ptr++; + + if(curl_strnequal(key, "AUTH=", 5)) { + result = Curl_sasl_parse_url_auth_option(&pop3c->sasl, + value, ptr - value); + + if(result && curl_strnequal(value, "+APOP", ptr - value)) { + pop3c->preftype = POP3_TYPE_APOP; + pop3c->sasl.prefmech = SASL_AUTH_NONE; + result = CURLE_OK; + } + } + else + result = CURLE_URL_MALFORMAT; + + if(*ptr == ';') + ptr++; + } + + if(pop3c->preftype != POP3_TYPE_APOP) + switch(pop3c->sasl.prefmech) { + case SASL_AUTH_NONE: + pop3c->preftype = POP3_TYPE_NONE; + break; + case SASL_AUTH_DEFAULT: + pop3c->preftype = POP3_TYPE_ANY; + break; + default: + pop3c->preftype = POP3_TYPE_SASL; + break; + } + + return result; +} + +/*********************************************************************** + * + * pop3_parse_url_path() + * + * Parse the URL path into separate path components. + */ +static CURLcode pop3_parse_url_path(struct Curl_easy *data) +{ + /* The POP3 struct is already initialised in pop3_connect() */ + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + const char *path = &data->state.up.path[1]; /* skip leading path */ + + if(!pop3) + return CURLE_FAILED_INIT; + /* URL decode the path for the message ID */ + return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); +} + +/*********************************************************************** + * + * pop3_parse_custom_request() + * + * Parse the custom request. + */ +static CURLcode pop3_parse_custom_request(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + + if(!pop3) + return CURLE_FAILED_INIT; + /* URL decode the custom request */ + if(custom) + result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); + + return result; +} + /* Return iff a command is defined as "multi-line" (RFC 1939), * has a response terminated by a last line with a '.'. */ @@ -1070,59 +1064,199 @@ static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code, return result; } -/* For command responses */ -static CURLcode pop3_state_command_resp(struct Curl_easy *data, - int pop3code, - pop3state instate) +/*********************************************************************** + * + * pop3_write() + * + * This function scans the body after the end-of-body and writes everything + * until the end is found. + */ +static CURLcode pop3_write(struct Curl_easy *data, const char *str, + size_t nread, bool is_eos) { + /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; struct connectdata *conn = data->conn; - struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); - struct pingpong *pp; + bool strip_dot = FALSE; + size_t last = 0; + size_t i; + (void)is_eos; - (void)instate; - if(!pop3 || !pop3c) + if(!pop3c) return CURLE_FAILED_INIT; - pp = &pop3c->pp; - if(pop3code != '+') { - pop3_state(data, POP3_STOP); - return CURLE_WEIRD_SERVER_REPLY; - } - - /* This 'OK' line ends with a CR LF pair which is the two first bytes of the - EOB string so count this is two matching bytes. This is necessary to make - the code detect the EOB if the only data than comes now is %2e CR LF like - when there is no body to return. */ - pop3c->eob = 2; - - /* But since this initial CR LF pair is not part of the actual body, we set - the strip counter here so that these bytes will not be delivered. */ - pop3c->strip = 2; + /* Search through the buffer looking for the end-of-body marker which is + 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches + the eob so the server will have prefixed it with an extra dot which we + need to strip out. Additionally the marker could of course be spread out + over 5 different data chunks. */ + for(i = 0; i < nread; i++) { + size_t prev = pop3c->eob; - if(pop3->transfer == PPTRANSFER_BODY) { - /* POP3 download */ - Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); + switch(str[i]) { + case 0x0d: + if(pop3c->eob == 0) { + pop3c->eob++; - if(pp->overflow) { - /* The recv buffer contains data that is actually body content so send - it as such. Note that there may even be additional "headers" after - the body */ + if(i) { + /* Write out the body part that did not match */ + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], + i - last); - /* keep only the overflow */ - curlx_dyn_tail(&pp->recvbuf, pp->overflow); - pp->nfinal = 0; /* done */ + if(result) + return result; - if(!data->req.no_body) { - result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf), - curlx_dyn_len(&pp->recvbuf), FALSE); - if(result) - return result; + last = i; + } } - - /* reset the buffer */ - curlx_dyn_reset(&pp->recvbuf); + else if(pop3c->eob == 3) + pop3c->eob++; + else + /* If the character match was not at position 0 or 3 then restart the + pattern matching */ + pop3c->eob = 1; + break; + + case 0x0a: + if(pop3c->eob == 1 || pop3c->eob == 4) + pop3c->eob++; + else + /* If the character match was not at position 1 or 4 then start the + search again */ + pop3c->eob = 0; + break; + + case 0x2e: + if(pop3c->eob == 2) + pop3c->eob++; + else if(pop3c->eob == 3) { + /* We have an extra dot after the CRLF which we need to strip off */ + strip_dot = TRUE; + pop3c->eob = 0; + } + else + /* If the character match was not at position 2 then start the search + again */ + pop3c->eob = 0; + break; + + default: + pop3c->eob = 0; + break; + } + + /* Did we have a partial match which has subsequently failed? */ + if(prev && prev >= pop3c->eob) { + /* Strip can only be non-zero for the first mismatch after CRLF and + then both prev and strip are equal and nothing will be output below */ + while(prev && pop3c->strip) { + prev--; + pop3c->strip--; + } + + if(prev) { + /* If the partial match was the CRLF and dot then only write the CRLF + as the server would have inserted the dot */ + if(strip_dot && prev - 1 > 0) { + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, + prev - 1); + } + else if(!strip_dot) { + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, + prev); + } + else { + result = CURLE_OK; + } + + if(result) + return result; + + last = i; + strip_dot = FALSE; + } + } + } + + if(pop3c->eob == POP3_EOB_LEN) { + /* We have a full match so the transfer is done, however we must transfer + the CRLF at the start of the EOB as this is considered to be part of the + message as per RFC-1939, sect. 3 */ + result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2); + + k->keepon &= ~KEEP_RECV; + pop3c->eob = 0; + + return result; + } + + if(pop3c->eob) + /* While EOB is matching nothing should be output */ + return CURLE_OK; + + if(nread - last) { + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], + nread - last); + } + + return result; +} + +/* For command responses */ +static CURLcode pop3_state_command_resp(struct Curl_easy *data, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); + struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); + struct pingpong *pp; + + (void)instate; + if(!pop3 || !pop3c) + return CURLE_FAILED_INIT; + + pp = &pop3c->pp; + if(pop3code != '+') { + pop3_state(data, POP3_STOP); + return CURLE_WEIRD_SERVER_REPLY; + } + + /* This 'OK' line ends with a CR LF pair which is the two first bytes of the + EOB string so count this is two matching bytes. This is necessary to make + the code detect the EOB if the only data than comes now is %2e CR LF like + when there is no body to return. */ + pop3c->eob = 2; + + /* But since this initial CR LF pair is not part of the actual body, we set + the strip counter here so that these bytes will not be delivered. */ + pop3c->strip = 2; + + if(pop3->transfer == PPTRANSFER_BODY) { + /* POP3 download */ + Curl_xfer_setup_recv(data, FIRSTSOCKET, -1); + + if(pp->overflow) { + /* The recv buffer contains data that is actually body content so send + it as such. Note that there may even be additional "headers" after + the body */ + + /* keep only the overflow */ + curlx_dyn_tail(&pp->recvbuf, pp->overflow); + pp->nfinal = 0; /* done */ + + if(!data->req.no_body) { + result = pop3_write(data, curlx_dyn_ptr(&pp->recvbuf), + curlx_dyn_len(&pp->recvbuf), FALSE); + if(result) + return result; + } + + /* reset the buffer */ + curlx_dyn_reset(&pp->recvbuf); pp->overflow = 0; } } @@ -1264,6 +1398,20 @@ static CURLcode pop3_pollset(struct Curl_easy *data, return pop3c ? Curl_pp_pollset(data, &pop3c->pp, ps) : CURLE_OK; } +/* SASL parameters for the pop3 protocol */ +static const struct SASLproto saslpop3 = { + "pop", /* The service name */ + pop3_perform_auth, /* Send authentication command */ + pop3_continue_auth, /* Send authentication continuation */ + pop3_cancel_auth, /* Send authentication cancellation */ + pop3_get_message, /* Get SASL response message */ + 255 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + '*', /* Code received when continuation is expected */ + '+', /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + /*********************************************************************** * * pop3_connect() @@ -1382,6 +1530,46 @@ static CURLcode pop3_perform(struct Curl_easy *data, bool *connected, return result; } +/* Call this when the DO phase has completed */ +static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected) +{ + (void)data; + (void)connected; + + return CURLE_OK; +} + +/*********************************************************************** + * + * pop3_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode pop3_regular_transfer(struct Curl_easy *data, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + /* Set the progress data */ + Curl_pgrsReset(data); + + /* Carry out the perform */ + result = pop3_perform(data, &connected, dophase_done); + + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) + result = pop3_dophase_done(data, connected); + + return result; +} + /*********************************************************************** * * pop3_do() @@ -1446,15 +1634,6 @@ static CURLcode pop3_disconnect(struct Curl_easy *data, return CURLE_OK; } -/* Call this when the DO phase has completed */ -static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected) -{ - (void)data; - (void)connected; - - return CURLE_OK; -} - /* Called from multi.c while DOing */ static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) { @@ -1471,37 +1650,6 @@ static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) return result; } -/*********************************************************************** - * - * pop3_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - */ -static CURLcode pop3_regular_transfer(struct Curl_easy *data, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - - /* Make sure size is unknown at this point */ - data->req.size = -1; - - /* Set the progress data */ - Curl_pgrsReset(data); - - /* Carry out the perform */ - result = pop3_perform(data, &connected, dophase_done); - - /* Perform post DO phase operations if necessary */ - if(!result && *dophase_done) - result = pop3_dophase_done(data, connected); - - return result; -} - static void pop3_easy_dtor(void *key, size_t klen, void *entry) { struct POP3 *pop3 = entry; @@ -1542,243 +1690,64 @@ static CURLcode pop3_setup_connection(struct Curl_easy *data, return CURLE_OK; } -/*********************************************************************** - * - * pop3_parse_url_options() - * - * Parse the URL login options. +/* + * POP3 protocol handler. */ -static CURLcode pop3_parse_url_options(struct connectdata *conn) -{ - struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); - CURLcode result = CURLE_OK; - const char *ptr = conn->options; - - if(!pop3c) - return CURLE_FAILED_INIT; - - while(!result && ptr && *ptr) { - const char *key = ptr; - const char *value; - - while(*ptr && *ptr != '=') - ptr++; - - value = ptr + 1; - - while(*ptr && *ptr != ';') - ptr++; - - if(curl_strnequal(key, "AUTH=", 5)) { - result = Curl_sasl_parse_url_auth_option(&pop3c->sasl, - value, ptr - value); - - if(result && curl_strnequal(value, "+APOP", ptr - value)) { - pop3c->preftype = POP3_TYPE_APOP; - pop3c->sasl.prefmech = SASL_AUTH_NONE; - result = CURLE_OK; - } - } - else - result = CURLE_URL_MALFORMAT; - - if(*ptr == ';') - ptr++; - } - - if(pop3c->preftype != POP3_TYPE_APOP) - switch(pop3c->sasl.prefmech) { - case SASL_AUTH_NONE: - pop3c->preftype = POP3_TYPE_NONE; - break; - case SASL_AUTH_DEFAULT: - pop3c->preftype = POP3_TYPE_ANY; - break; - default: - pop3c->preftype = POP3_TYPE_SASL; - break; - } - - return result; -} - -/*********************************************************************** - * - * pop3_parse_url_path() - * - * Parse the URL path into separate path components. - */ -static CURLcode pop3_parse_url_path(struct Curl_easy *data) -{ - /* The POP3 struct is already initialised in pop3_connect() */ - struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); - const char *path = &data->state.up.path[1]; /* skip leading path */ - - if(!pop3) - return CURLE_FAILED_INIT; - /* URL decode the path for the message ID */ - return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); -} - -/*********************************************************************** - * - * pop3_parse_custom_request() - * - * Parse the custom request. - */ -static CURLcode pop3_parse_custom_request(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct POP3 *pop3 = Curl_meta_get(data, CURL_META_POP3_EASY); - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; - - if(!pop3) - return CURLE_FAILED_INIT; - /* URL decode the custom request */ - if(custom) - result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); - - return result; -} +const struct Curl_handler Curl_handler_pop3 = { + "pop3", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_pollset, /* proto_pollset */ + pop3_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + pop3_disconnect, /* disconnect */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_POP3, /* defport */ + CURLPROTO_POP3, /* protocol */ + CURLPROTO_POP3, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ + PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE +}; -/*********************************************************************** - * - * pop3_write() - * - * This function scans the body after the end-of-body and writes everything - * until the end is found. +#ifdef USE_SSL +/* + * POP3S protocol handler. */ -static CURLcode pop3_write(struct Curl_easy *data, const char *str, - size_t nread, bool is_eos) -{ - /* This code could be made into a special function in the handler struct */ - CURLcode result = CURLE_OK; - struct SingleRequest *k = &data->req; - struct connectdata *conn = data->conn; - struct pop3_conn *pop3c = Curl_conn_meta_get(conn, CURL_META_POP3_CONN); - bool strip_dot = FALSE; - size_t last = 0; - size_t i; - (void)is_eos; - - if(!pop3c) - return CURLE_FAILED_INIT; - - /* Search through the buffer looking for the end-of-body marker which is - 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches - the eob so the server will have prefixed it with an extra dot which we - need to strip out. Additionally the marker could of course be spread out - over 5 different data chunks. */ - for(i = 0; i < nread; i++) { - size_t prev = pop3c->eob; - - switch(str[i]) { - case 0x0d: - if(pop3c->eob == 0) { - pop3c->eob++; - - if(i) { - /* Write out the body part that did not match */ - result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], - i - last); - - if(result) - return result; - - last = i; - } - } - else if(pop3c->eob == 3) - pop3c->eob++; - else - /* If the character match was not at position 0 or 3 then restart the - pattern matching */ - pop3c->eob = 1; - break; - - case 0x0a: - if(pop3c->eob == 1 || pop3c->eob == 4) - pop3c->eob++; - else - /* If the character match was not at position 1 or 4 then start the - search again */ - pop3c->eob = 0; - break; - - case 0x2e: - if(pop3c->eob == 2) - pop3c->eob++; - else if(pop3c->eob == 3) { - /* We have an extra dot after the CRLF which we need to strip off */ - strip_dot = TRUE; - pop3c->eob = 0; - } - else - /* If the character match was not at position 2 then start the search - again */ - pop3c->eob = 0; - break; - - default: - pop3c->eob = 0; - break; - } - - /* Did we have a partial match which has subsequently failed? */ - if(prev && prev >= pop3c->eob) { - /* Strip can only be non-zero for the first mismatch after CRLF and - then both prev and strip are equal and nothing will be output below */ - while(prev && pop3c->strip) { - prev--; - pop3c->strip--; - } - - if(prev) { - /* If the partial match was the CRLF and dot then only write the CRLF - as the server would have inserted the dot */ - if(strip_dot && prev - 1 > 0) { - result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, - prev - 1); - } - else if(!strip_dot) { - result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, - prev); - } - else { - result = CURLE_OK; - } - - if(result) - return result; - - last = i; - strip_dot = FALSE; - } - } - } - - if(pop3c->eob == POP3_EOB_LEN) { - /* We have a full match so the transfer is done, however we must transfer - the CRLF at the start of the EOB as this is considered to be part of the - message as per RFC-1939, sect. 3 */ - result = Curl_client_write(data, CLIENTWRITE_BODY, POP3_EOB, 2); - - k->keepon &= ~KEEP_RECV; - pop3c->eob = 0; - - return result; - } - - if(pop3c->eob) - /* While EOB is matching nothing should be output */ - return CURLE_OK; - - if(nread - last) { - result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], - nread - last); - } - - return result; -} +const struct Curl_handler Curl_handler_pop3s = { + "pop3s", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_pollset, /* proto_pollset */ + pop3_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + pop3_disconnect, /* disconnect */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_POP3S, /* defport */ + CURLPROTO_POP3S, /* protocol */ + CURLPROTO_POP3, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE +}; +#endif #endif /* CURL_DISABLE_POP3 */ diff --git a/lib/smtp.c b/lib/smtp.c index f3f3fe52d5..431113550d 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -136,163 +136,38 @@ struct SMTP { BIT(trailing_crlf); /* Specifies if the trailing CRLF is present */ }; -/* Local API functions */ -static CURLcode smtp_regular_transfer(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - bool *done); -static CURLcode smtp_do(struct Curl_easy *data, bool *done); -static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, - bool premature); -static CURLcode smtp_connect(struct Curl_easy *data, bool *done); -static CURLcode smtp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead); -static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done); -static CURLcode smtp_pollset(struct Curl_easy *data, - struct easy_pollset *ps); -static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done); -static CURLcode smtp_setup_connection(struct Curl_easy *data, - struct connectdata *conn); -static CURLcode smtp_parse_url_options(struct connectdata *conn, - struct smtp_conn *smtpc); -static CURLcode smtp_parse_url_path(struct Curl_easy *data, - struct smtp_conn *smtpc); -static CURLcode smtp_parse_custom_request(struct Curl_easy *data, - struct SMTP *smtp); -static CURLcode smtp_parse_address(const char *fqma, - char **address, struct hostname *host, - const char **suffix); -static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech, - const struct bufref *initresp); -static CURLcode smtp_continue_auth(struct Curl_easy *data, const char *mech, - const struct bufref *resp); -static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech); -static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out); -static CURLcode cr_eob_add(struct Curl_easy *data); - -/* - * SMTP protocol handler. - */ - -const struct Curl_handler Curl_handler_smtp = { - "smtp", /* scheme */ - smtp_setup_connection, /* setup_connection */ - smtp_do, /* do_it */ - smtp_done, /* done */ - ZERO_NULL, /* do_more */ - smtp_connect, /* connect_it */ - smtp_multi_statemach, /* connecting */ - smtp_doing, /* doing */ - smtp_pollset, /* proto_pollset */ - smtp_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - smtp_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_SMTP, /* defport */ - CURLPROTO_SMTP, /* protocol */ - CURLPROTO_SMTP, /* family */ - PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ - PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE -}; - -#ifdef USE_SSL -/* - * SMTPS protocol handler. - */ - -const struct Curl_handler Curl_handler_smtps = { - "smtps", /* scheme */ - smtp_setup_connection, /* setup_connection */ - smtp_do, /* do_it */ - smtp_done, /* done */ - ZERO_NULL, /* do_more */ - smtp_connect, /* connect_it */ - smtp_multi_statemach, /* connecting */ - smtp_doing, /* doing */ - smtp_pollset, /* proto_pollset */ - smtp_pollset, /* doing_pollset */ - ZERO_NULL, /* domore_pollset */ - ZERO_NULL, /* perform_pollset */ - smtp_disconnect, /* disconnect */ - ZERO_NULL, /* write_resp */ - ZERO_NULL, /* write_resp_hd */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - ZERO_NULL, /* follow */ - PORT_SMTPS, /* defport */ - CURLPROTO_SMTPS, /* protocol */ - CURLPROTO_SMTP, /* family */ - PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ - PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE -}; -#endif - -/* SASL parameters for the smtp protocol */ -static const struct SASLproto saslsmtp = { - "smtp", /* The service name */ - smtp_perform_auth, /* Send authentication command */ - smtp_continue_auth, /* Send authentication continuation */ - smtp_cancel_auth, /* Cancel authentication */ - smtp_get_message, /* Get SASL response message */ - 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ - 334, /* Code received when continuation is expected */ - 235, /* Code to receive upon authentication success */ - SASL_AUTH_DEFAULT, /* Default mechanisms */ - SASL_FLAG_BASE64 /* Configuration flags */ -}; - /*********************************************************************** * - * smtp_endofresp() + * smtp_parse_url_options() * - * Checks for an ending SMTP status code at the start of the given string, but - * also detects various capabilities from the EHLO response including the - * supported authentication mechanisms. + * Parse the URL login options. */ -static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, - const char *line, size_t len, int *resp) +static CURLcode smtp_parse_url_options(struct connectdata *conn, + struct smtp_conn *smtpc) { - struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); - bool result = FALSE; - (void)data; + CURLcode result = CURLE_OK; + const char *ptr = conn->options; - DEBUGASSERT(smtpc); - if(!smtpc) - return FALSE; + while(!result && ptr && *ptr) { + const char *key = ptr; + const char *value; - /* Nothing for us */ - if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) - return FALSE; + while(*ptr && *ptr != '=') + ptr++; - /* Do we have a command response? This should be the response code followed - by a space and optionally some text as per RFC-5321 and as outlined in - Section 4. Examples of RFC-4954 but some email servers ignore this and - only send the response code instead as per Section 4.2. */ - if(line[3] == ' ' || len == 5) { - char tmpline[6]; - curl_off_t code; - const char *p = tmpline; - result = TRUE; - memcpy(tmpline, line, (len == 5 ? 5 : 3)); - tmpline[len == 5 ? 5 : 3] = 0; - if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999)) - return FALSE; - *resp = (int)code; + value = ptr + 1; - /* Make sure real server never sends internal value */ - if(*resp == 1) - *resp = 0; - } - /* Do we have a multiline (continuation) response? */ - else if(line[3] == '-' && - (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) { - result = TRUE; - *resp = 1; /* Internal response code */ + while(*ptr && *ptr != ';') + ptr++; + + if(curl_strnequal(key, "AUTH=", 5)) + result = Curl_sasl_parse_url_auth_option(&smtpc->sasl, + value, ptr - value); + else + result = CURLE_URL_MALFORMAT; + + if(*ptr == ';') + ptr++; } return result; @@ -300,1785 +175,1874 @@ static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, /*********************************************************************** * - * smtp_get_message() + * smtp_parse_url_path() * - * Gets the authentication message from the response buffer. + * Parse the URL path into separate path components. */ -static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) +static CURLcode smtp_parse_url_path(struct Curl_easy *data, + struct smtp_conn *smtpc) { - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - char *message; - size_t len; - - if(!smtpc) - return CURLE_FAILED_INIT; - - message = curlx_dyn_ptr(&smtpc->pp.recvbuf); - len = smtpc->pp.nfinal; - if(len > 4) { - /* Find the start of the message */ - len -= 4; - for(message += 4; *message == ' ' || *message == '\t'; message++, len--) - ; - - /* Find the end of the message */ - while(len--) - if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && - message[len] != '\t') - break; + /* The SMTP struct is already initialised in smtp_connect() */ + const char *path = &data->state.up.path[1]; /* skip leading path */ + char localhost[HOSTNAME_MAX + 1]; - /* Terminate the message */ - message[++len] = '\0'; - Curl_bufref_set(out, message, len, NULL); + /* Calculate the path if necessary */ + if(!*path) { + if(!Curl_gethostname(localhost, sizeof(localhost))) + path = localhost; + else + path = "localhost"; } - else - /* junk input => zero length output */ - Curl_bufref_set(out, "", 0, NULL); - return CURLE_OK; + /* URL decode the path and use it as the domain in our EHLO */ + return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL); } /*********************************************************************** * - * smtp_state() + * smtp_parse_custom_request() * - * This is the ONLY way to change SMTP state! + * Parse the custom request. */ -static void smtp_state(struct Curl_easy *data, - struct smtp_conn *smtpc, - smtpstate newstate) +static CURLcode smtp_parse_custom_request(struct Curl_easy *data, + struct SMTP *smtp) { -#ifndef CURL_DISABLE_VERBOSE_STRINGS - /* for debug purposes */ - static const char * const names[] = { - "STOP", - "SERVERGREET", - "EHLO", - "HELO", - "STARTTLS", - "UPGRADETLS", - "AUTH", - "COMMAND", - "MAIL", - "RCPT", - "DATA", - "POSTDATA", - "QUIT", - /* LAST */ - }; + CURLcode result = CURLE_OK; + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; - if(smtpc->state != newstate) - CURL_TRC_SMTP(data, "state change from %s to %s", - names[smtpc->state], names[newstate]); -#else - (void)data; -#endif + /* URL decode the custom request */ + if(custom) + result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL); - smtpc->state = newstate; + return result; } /*********************************************************************** * - * smtp_perform_ehlo() + * smtp_parse_address() * - * Sends the EHLO command to not only initialise communication with the ESMTP - * server but to also obtain a list of server side supported capabilities. - */ -static CURLcode smtp_perform_ehlo(struct Curl_easy *data, - struct smtp_conn *smtpc) -{ - CURLcode result = CURLE_OK; - - smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */ - smtpc->sasl.authused = SASL_AUTH_NONE; /* Clear the authentication mechanism - used for esmtp connections */ - smtpc->tls_supported = FALSE; /* Clear the TLS capability */ - smtpc->auth_supported = FALSE; /* Clear the AUTH capability */ - - /* Send the EHLO command */ - result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); - - if(!result) - smtp_state(data, smtpc, SMTP_EHLO); - - return result; -} - -/*********************************************************************** + * Parse the fully qualified mailbox address into a local address part and the + * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if + * necessary. * - * smtp_perform_helo() + * Parameters: * - * Sends the HELO command to initialise communication with the SMTP server. - */ -static CURLcode smtp_perform_helo(struct Curl_easy *data, - struct smtp_conn *smtpc) -{ - CURLcode result = CURLE_OK; - - smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used - in smtp connections */ - - /* Send the HELO command */ - result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); - - if(!result) - smtp_state(data, smtpc, SMTP_HELO); - - return result; -} - -/*********************************************************************** + * conn [in] - The connection handle. + * fqma [in] - The fully qualified mailbox address (which may or + * may not contain UTF-8 characters). + * address [in/out] - A new allocated buffer which holds the local + * address part of the mailbox. This buffer must be + * free'ed by the caller. + * host [in/out] - The hostname structure that holds the original, + * and optionally encoded, hostname. + * Curl_free_idnconverted_hostname() must be called + * once the caller has finished with the structure. * - * smtp_perform_starttls() + * Returns CURLE_OK on success. * - * Sends the STLS command to start the upgrade to TLS. - */ -static CURLcode smtp_perform_starttls(struct Curl_easy *data, - struct smtp_conn *smtpc) -{ - /* Send the STARTTLS command */ - CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS"); - - if(!result) - smtp_state(data, smtpc, SMTP_STARTTLS); - - return result; -} - -/*********************************************************************** + * Notes: * - * smtp_perform_upgrade_tls() + * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor + * that conversion then we shall return success. This allow the caller to send + * the data to the server as a U-label (as per RFC-6531 sect. 3.2). * - * Performs the upgrade to TLS. + * If an mailbox '@' separator cannot be located then the mailbox is considered + * to be either a local mailbox or an invalid mailbox (depending on what the + * calling function deems it to be) then the input will simply be returned in + * the address part with the hostname being NULL. */ -static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, - struct smtp_conn *smtpc) +static CURLcode smtp_parse_address(const char *fqma, char **address, + struct hostname *host, const char **suffix) { -#ifdef USE_SSL - /* Start the SSL connection */ - struct connectdata *conn = data->conn; - CURLcode result; - bool ssldone = FALSE; + CURLcode result = CURLE_OK; + size_t length; + char *addressend; - DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS); - if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { - result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); - if(result) - goto out; - /* Change the connection handler and SMTP state */ - conn->handler = &Curl_handler_smtps; - } + /* Duplicate the fully qualified email address so we can manipulate it, + ensuring it does not contain the delimiters if specified */ + char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); + if(!dup) + return CURLE_OUT_OF_MEMORY; - DEBUGASSERT(!smtpc->ssldone); - result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); - DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d", - result, ssldone)); - if(!result && ssldone) { - smtpc->ssldone = ssldone; - /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */ - result = smtp_perform_ehlo(data, smtpc); + if(fqma[0] != '<') { + length = strlen(dup); + if(length) { + if(dup[length - 1] == '>') + dup[length - 1] = '\0'; + } + } + else { + addressend = strrchr(dup, '>'); + if(addressend) { + *addressend = '\0'; + *suffix = addressend + 1; + } } -out: - return result; -#else - (void)data; - (void)smtpc; - return CURLE_NOT_BUILT_IN; -#endif -} -/*********************************************************************** - * - * smtp_perform_auth() - * - * Sends an AUTH command allowing the client to login with the given SASL - * authentication mechanism. - */ -static CURLcode smtp_perform_auth(struct Curl_easy *data, - const char *mech, - const struct bufref *initresp) -{ - CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - const char *ir = Curl_bufref_ptr(initresp); + /* Extract the hostname from the address (if we can) */ + host->name = strpbrk(dup, "@"); + if(host->name) { + *host->name = '\0'; + host->name = host->name + 1; - if(!smtpc) - return CURLE_FAILED_INIT; + /* Attempt to convert the hostname to IDN ACE */ + (void)Curl_idnconvert_hostname(host); - if(ir) { /* AUTH ... */ - /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); - } - else { - /* Send the AUTH command */ - result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech); + /* If Curl_idnconvert_hostname() fails then we shall attempt to continue + and send the hostname using UTF-8 rather than as 7-bit ACE (which is + our preference) */ } + /* Extract the local address from the mailbox */ + *address = dup; + return result; } -/*********************************************************************** - * - * smtp_continue_auth() - * - * Sends SASL continuation data. - */ -static CURLcode smtp_continue_auth(struct Curl_easy *data, - const char *mech, - const struct bufref *resp) -{ - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); +struct cr_eob_ctx { + struct Curl_creader super; + struct bufq buf; + size_t n_eob; /* how many EOB bytes we matched so far */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(processed_eos); /* we read and processed an EOS */ + BIT(eos); /* we have returned an EOS */ +}; - (void)mech; - if(!smtpc) - return CURLE_FAILED_INIT; - return Curl_pp_sendf(data, &smtpc->pp, "%s", Curl_bufref_ptr(resp)); +static CURLcode cr_eob_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + /* The first char we read is the first on a line, as if we had + * read CRLF just before */ + ctx->n_eob = 2; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; } -/*********************************************************************** - * - * smtp_cancel_auth() - * - * Sends SASL cancellation. - */ -static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) +static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) { - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - - (void)mech; - if(!smtpc) - return CURLE_FAILED_INIT; - return Curl_pp_sendf(data, &smtpc->pp, "*"); + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); } -/*********************************************************************** - * - * smtp_perform_authentication() - * - * Initiates the authentication sequence, with the appropriate SASL - * authentication mechanism. - */ -static CURLcode smtp_perform_authentication(struct Curl_easy *data, - struct smtp_conn *smtpc) +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_FIND_LEN 3 + +/* client reader doing SMTP End-Of-Body escaping. */ +static CURLcode cr_eob_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) { + struct cr_eob_ctx *ctx = reader->ctx; CURLcode result = CURLE_OK; - saslprogress progress; - - /* Check we have enough data to authenticate with, and the - server supports authentication, and end the connect phase if not */ - if(!smtpc->auth_supported || - !Curl_sasl_can_authenticate(&smtpc->sasl, data)) { - smtp_state(data, smtpc, SMTP_STOP); - return result; - } + size_t nread, i, start, n; + bool eos; - /* Calculate the SASL login details */ - result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress); + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* Get more and convert it when needed */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + CURL_TRC_SMTP(data, "cr_eob_read, next_read(len=%zu) -> %d, %zu eos=%d", + blen, result, nread, eos); + if(result) + return result; - if(!result) { - if(progress == SASL_INPROGRESS) - smtp_state(data, smtpc, SMTP_AUTH); - else - result = Curl_sasl_is_blocked(&smtpc->sasl, data); - } - - return result; -} - -/*********************************************************************** - * - * smtp_perform_command() - * - * Sends an SMTP based command. - */ -static CURLcode smtp_perform_command(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp) -{ - CURLcode result = CURLE_OK; - - if(smtp->rcpt) { - /* We notify the server we are sending UTF-8 data if a) it supports the - SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or hostname parts. This is regardless of - whether the hostname is encoded using IDN ACE */ - bool utf8 = FALSE; + ctx->read_eos = eos; + if(nread) { + if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { + /* not in the middle of a match, no EOB start found, just pass */ + *pnread = nread; + *peos = FALSE; + return CURLE_OK; + } + /* scan for EOB (continuation) and convert */ + for(i = start = 0; i < nread; ++i) { + if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { + /* matched the EOB prefix and seeing additional char, add '.' */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); + if(result) + return result; + ctx->n_eob = 0; + start = i; + if(data->state.infilesize > 0) + data->state.infilesize++; + } - if((!smtp->custom) || (!smtp->custom[0])) { - char *address = NULL; - struct hostname host = { NULL, NULL, NULL, NULL }; - const char *suffix = ""; + if(buf[i] != SMTP_EOB[ctx->n_eob]) + ctx->n_eob = 0; - /* Parse the mailbox to verify into the local address and hostname - parts, converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(smtp->rcpt->data, - &address, &host, &suffix); - if(result) - return result; + if(buf[i] == SMTP_EOB[ctx->n_eob]) { + /* matching another char of the EOB */ + ++ctx->n_eob; + } + } - /* Establish whether we should report SMTPUTF8 to the server for this - mailbox as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (smtpc->utf8_supported) && - ((host.encalloc) || (!Curl_is_ASCII_name(address)) || - (!Curl_is_ASCII_name(host.name))); + /* add any remainder to buf */ + if(start < nread) { + result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); + if(result) + return result; + } + } + } - /* Send the VRFY command (Note: The hostname part may be absent when the - host is a local system) */ - result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s", - address, - host.name ? "@" : "", - host.name ? host.name : "", - utf8 ? " SMTPUTF8" : ""); + *peos = FALSE; - Curl_free_idnconverted_hostname(&host); - curlx_free(address); + if(ctx->read_eos && !ctx->processed_eos) { + /* if we last matched a CRLF or if the data was empty, add ".\r\n" + * to end the body. If we sent something and it did not end with "\r\n", + * add "\r\n.\r\n" to end the body */ + const char *eob = SMTP_EOB; + CURL_TRC_SMTP(data, "auto-ending mail body with '\\r\\n.\\r\\n'"); + switch(ctx->n_eob) { + case 2: + /* seen a CRLF at the end, just add the remainder */ + eob = &SMTP_EOB[2]; + break; + case 3: + /* ended with '\r\n.', we should escape the last '.' */ + eob = "." SMTP_EOB; + break; + default: + break; } - else { - /* Establish whether we should report that we support SMTPUTF8 for EXPN - commands to the server as per RFC-6531 sect. 3.1 point 6 */ - utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN")); + result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); + if(result) + return result; + ctx->processed_eos = TRUE; + } - /* Send the custom recipient based command such as the EXPN command */ - result = Curl_pp_sendf(data, &smtpc->pp, - "%s %s%s", smtp->custom, - smtp->rcpt->data, - utf8 ? " SMTPUTF8" : ""); - } + if(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); } else - /* Send the non-recipient based command such as HELP */ - result = Curl_pp_sendf(data, &smtpc->pp, "%s", - smtp->custom && smtp->custom[0] != '\0' ? - smtp->custom : "HELP"); + *pnread = 0; + + if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + CURL_TRC_SMTP(data, "mail body complete, returning EOS"); + ctx->eos = TRUE; + } + *peos = ctx->eos; + DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", + blen, result, *pnread, *peos)); + return result; +} + +static curl_off_t cr_eob_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + +static const struct Curl_crtype cr_eob = { + "cr-smtp-eob", + cr_eob_init, + cr_eob_read, + cr_eob_close, + Curl_creader_def_needs_rewind, + cr_eob_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_cntrl, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_eob_ctx) +}; + +static CURLcode cr_eob_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + result = Curl_creader_create(&reader, data, &cr_eob, CURL_CR_CONTENT_ENCODE); if(!result) - smtp_state(data, smtpc, SMTP_COMMAND); + result = Curl_creader_add(data, reader); + if(result && reader) + Curl_creader_free(data, reader); return result; } /*********************************************************************** * - * smtp_perform_mail() + * smtp_endofresp() * - * Sends an MAIL command to initiate the upload of a message. + * Checks for an ending SMTP status code at the start of the given string, but + * also detects various capabilities from the EHLO response including the + * supported authentication mechanisms. */ -static CURLcode smtp_perform_mail(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp) +static bool smtp_endofresp(struct Curl_easy *data, struct connectdata *conn, + const char *line, size_t len, int *resp) { - char *from = NULL; - char *auth = NULL; - char *size = NULL; - CURLcode result = CURLE_OK; - - /* We notify the server we are sending UTF-8 data if a) it supports the - SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in - either the local address or hostname parts. This is regardless of - whether the hostname is encoded using IDN ACE */ - bool utf8 = FALSE; - - /* Calculate the FROM parameter */ - if(data->set.str[STRING_MAIL_FROM]) { - char *address = NULL; - struct hostname host = { NULL, NULL, NULL, NULL }; - const char *suffix = ""; - - /* Parse the FROM mailbox into the local address and hostname parts, - converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], - &address, &host, &suffix); - if(result) - goto out; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + bool result = FALSE; + (void)data; - /* Establish whether we should report SMTPUTF8 to the server for this - mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ - utf8 = (smtpc->utf8_supported) && - ((host.encalloc) || (!Curl_is_ASCII_name(address)) || - (!Curl_is_ASCII_name(host.name))); + DEBUGASSERT(smtpc); + if(!smtpc) + return FALSE; - if(host.name) { - from = curl_maprintf("<%s@%s>%s", address, host.name, suffix); + /* Nothing for us */ + if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) + return FALSE; - Curl_free_idnconverted_hostname(&host); - } - else - /* An invalid mailbox was provided but we will simply let the server - worry about that and reply with a 501 error */ - from = curl_maprintf("<%s>%s", address, suffix); + /* Do we have a command response? This should be the response code followed + by a space and optionally some text as per RFC-5321 and as outlined in + Section 4. Examples of RFC-4954 but some email servers ignore this and + only send the response code instead as per Section 4.2. */ + if(line[3] == ' ' || len == 5) { + char tmpline[6]; + curl_off_t code; + const char *p = tmpline; + result = TRUE; + memcpy(tmpline, line, (len == 5 ? 5 : 3)); + tmpline[len == 5 ? 5 : 3] = 0; + if(curlx_str_number(&p, &code, len == 5 ? 99999 : 999)) + return FALSE; + *resp = (int)code; - curlx_free(address); + /* Make sure real server never sends internal value */ + if(*resp == 1) + *resp = 0; } - else - /* Null reverse-path, RFC-5321, sect. 3.6.3 */ - from = curlx_strdup("<>"); - - if(!from) { - result = CURLE_OUT_OF_MEMORY; - goto out; + /* Do we have a multiline (continuation) response? */ + else if(line[3] == '-' && + (smtpc->state == SMTP_EHLO || smtpc->state == SMTP_COMMAND)) { + result = TRUE; + *resp = 1; /* Internal response code */ } - /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { - if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { - char *address = NULL; - struct hostname host = { NULL, NULL, NULL, NULL }; - const char *suffix = ""; - - /* Parse the AUTH mailbox into the local address and hostname parts, - converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], - &address, &host, &suffix); - if(result) - goto out; - - /* Establish whether we should report SMTPUTF8 to the server for this - mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ - if((!utf8) && (smtpc->utf8_supported) && - ((host.encalloc) || (!Curl_is_ASCII_name(address)) || - (!Curl_is_ASCII_name(host.name)))) - utf8 = TRUE; - - if(host.name) { - auth = curl_maprintf("<%s@%s>%s", address, host.name, suffix); - - Curl_free_idnconverted_hostname(&host); - } - else - /* An invalid mailbox was provided but we will simply let the server - worry about it */ - auth = curl_maprintf("<%s>%s", address, suffix); - curlx_free(address); - } - else - /* Empty AUTH, RFC-2554, sect. 5 */ - auth = curlx_strdup("<>"); - - if(!auth) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } + return result; +} -#ifndef CURL_DISABLE_MIME - /* Prepare the mime data if some. */ - if(IS_MIME_POST(data)) { - curl_mimepart *postp = data->set.mimepostp; +/*********************************************************************** + * + * smtp_get_message() + * + * Gets the authentication message from the response buffer. + */ +static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) +{ + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + char *message; + size_t len; - /* Use the whole structure as data. */ - postp->flags &= ~(unsigned int)MIME_BODY_ONLY; + if(!smtpc) + return CURLE_FAILED_INIT; - /* Add external headers and mime version. */ - curl_mime_headers(postp, data->set.headers, 0); - result = Curl_mime_prepare_headers(data, postp, NULL, - NULL, MIMESTRATEGY_MAIL); + message = curlx_dyn_ptr(&smtpc->pp.recvbuf); + len = smtpc->pp.nfinal; + if(len > 4) { + /* Find the start of the message */ + len -= 4; + for(message += 4; *message == ' ' || *message == '\t'; message++, len--) + ; - if(!result) - if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) - result = Curl_mime_add_header(&postp->curlheaders, - "Mime-Version: 1.0"); + /* Find the end of the message */ + while(len--) + if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && + message[len] != '\t') + break; - if(!result) - result = Curl_creader_set_mime(data, postp); - if(result) - goto out; - data->state.infilesize = Curl_creader_total_length(data); + /* Terminate the message */ + message[++len] = '\0'; + Curl_bufref_set(out, message, len, NULL); } else -#endif - { - result = Curl_creader_set_fread(data, data->state.infilesize); - if(result) - goto out; - } - - /* Calculate the optional SIZE parameter */ - if(smtpc->size_supported && data->state.infilesize > 0) { - size = curl_maprintf("%" FMT_OFF_T, data->state.infilesize); + /* junk input => zero length output */ + Curl_bufref_set(out, "", 0, NULL); - if(!size) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } + return CURLE_OK; +} - /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8 - based address then quickly scan through the recipient list and check if - any there do, as we need to correctly identify our support for SMTPUTF8 - in the envelope, as per RFC-6531 sect. 3.4 */ - if(smtpc->utf8_supported && !utf8) { - struct curl_slist *rcpt = smtp->rcpt; +/*********************************************************************** + * + * smtp_state() + * + * This is the ONLY way to change SMTP state! + */ +static void smtp_state(struct Curl_easy *data, + struct smtp_conn *smtpc, + smtpstate newstate) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "EHLO", + "HELO", + "STARTTLS", + "UPGRADETLS", + "AUTH", + "COMMAND", + "MAIL", + "RCPT", + "DATA", + "POSTDATA", + "QUIT", + /* LAST */ + }; - while(rcpt && !utf8) { - /* Does the hostname contain non-ASCII characters? */ - if(!Curl_is_ASCII_name(rcpt->data)) - utf8 = TRUE; + if(smtpc->state != newstate) + CURL_TRC_SMTP(data, "state change from %s to %s", + names[smtpc->state], names[newstate]); +#else + (void)data; +#endif - rcpt = rcpt->next; - } - } + smtpc->state = newstate; +} - /* Add the client reader doing STMP EOB escaping */ - result = cr_eob_add(data); - if(result) - goto out; +/*********************************************************************** + * + * smtp_perform_ehlo() + * + * Sends the EHLO command to not only initialise communication with the ESMTP + * server but to also obtain a list of server side supported capabilities. + */ +static CURLcode smtp_perform_ehlo(struct Curl_easy *data, + struct smtp_conn *smtpc) +{ + CURLcode result = CURLE_OK; - /* Send the MAIL command */ - result = Curl_pp_sendf(data, &smtpc->pp, - "MAIL FROM:%s%s%s%s%s%s", - from, /* Mandatory */ - auth ? " AUTH=" : "", /* Optional on AUTH support */ - auth ? auth : "", /* */ - size ? " SIZE=" : "", /* Optional on SIZE support */ - size ? size : "", /* */ - utf8 ? " SMTPUTF8" /* Internationalised mailbox */ - : ""); /* included in our envelope */ + smtpc->sasl.authmechs = SASL_AUTH_NONE; /* No known auth. mechanism yet */ + smtpc->sasl.authused = SASL_AUTH_NONE; /* Clear the authentication mechanism + used for esmtp connections */ + smtpc->tls_supported = FALSE; /* Clear the TLS capability */ + smtpc->auth_supported = FALSE; /* Clear the AUTH capability */ -out: - curlx_free(from); - curlx_free(auth); - curlx_free(size); + /* Send the EHLO command */ + result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); if(!result) - smtp_state(data, smtpc, SMTP_MAIL); + smtp_state(data, smtpc, SMTP_EHLO); return result; } /*********************************************************************** * - * smtp_perform_rcpt_to() + * smtp_perform_helo() * - * Sends a RCPT TO command for a given recipient as part of the message upload - * process. + * Sends the HELO command to initialise communication with the SMTP server. */ -static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp) +static CURLcode smtp_perform_helo(struct Curl_easy *data, + struct smtp_conn *smtpc) { CURLcode result = CURLE_OK; - char *address = NULL; - struct hostname host = { NULL, NULL, NULL, NULL }; - const char *suffix = ""; - /* Parse the recipient mailbox into the local address and hostname parts, - converting the hostname to an IDN A-label if necessary */ - result = smtp_parse_address(smtp->rcpt->data, - &address, &host, &suffix); - if(result) - return result; - - /* Send the RCPT TO command */ - if(host.name) - result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>%s", - address, host.name, suffix); - else - /* An invalid mailbox was provided but we will simply let the server worry - about that and reply with a 501 error */ - result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>%s", - address, suffix); + smtpc->sasl.authused = SASL_AUTH_NONE; /* No authentication mechanism used + in smtp connections */ - Curl_free_idnconverted_hostname(&host); - curlx_free(address); + /* Send the HELO command */ + result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); if(!result) - smtp_state(data, smtpc, SMTP_RCPT); + smtp_state(data, smtpc, SMTP_HELO); return result; } /*********************************************************************** * - * smtp_perform_quit() + * smtp_perform_starttls() * - * Performs the quit action prior to sclose() being called. + * Sends the STLS command to start the upgrade to TLS. */ -static CURLcode smtp_perform_quit(struct Curl_easy *data, - struct smtp_conn *smtpc) +static CURLcode smtp_perform_starttls(struct Curl_easy *data, + struct smtp_conn *smtpc) { - /* Send the QUIT command */ - CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT"); + /* Send the STARTTLS command */ + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "STARTTLS"); if(!result) - smtp_state(data, smtpc, SMTP_QUIT); + smtp_state(data, smtpc, SMTP_STARTTLS); return result; } -/* For the initial server greeting */ -static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - (void)instate; - - if(smtpcode / 100 != 2) { - failf(data, "Got unexpected smtp-server response: %d", smtpcode); - result = CURLE_WEIRD_SERVER_REPLY; +/*********************************************************************** + * + * smtp_perform_upgrade_tls() + * + * Performs the upgrade to TLS. + */ +static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data, + struct smtp_conn *smtpc) +{ +#ifdef USE_SSL + /* Start the SSL connection */ + struct connectdata *conn = data->conn; + CURLcode result; + bool ssldone = FALSE; + + DEBUGASSERT(smtpc->state == SMTP_UPGRADETLS); + if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + goto out; + /* Change the connection handler and SMTP state */ + conn->handler = &Curl_handler_smtps; } - else - result = smtp_perform_ehlo(data, smtpc); + DEBUGASSERT(!smtpc->ssldone); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone); + DEBUGF(infof(data, "smtp_perform_upgrade_tls, connect -> %d, %d", + result, ssldone)); + if(!result && ssldone) { + smtpc->ssldone = ssldone; + /* perform EHLO now, changes smtp->state out of SMTP_UPGRADETLS */ + result = smtp_perform_ehlo(data, smtpc); + } +out: return result; +#else + (void)data; + (void)smtpc; + return CURLE_NOT_BUILT_IN; +#endif } -/* For STARTTLS responses */ -static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) +/*********************************************************************** + * + * smtp_perform_auth() + * + * Sends an AUTH command allowing the client to login with the given SASL + * authentication mechanism. + */ +static CURLcode smtp_perform_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *initresp) { CURLcode result = CURLE_OK; - (void)instate; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + const char *ir = Curl_bufref_ptr(initresp); - /* Pipelining in response is forbidden. */ - if(smtpc->pp.overflow) - return CURLE_WEIRD_SERVER_REPLY; + if(!smtpc) + return CURLE_FAILED_INIT; - if(smtpcode != 220) { - if(data->set.use_ssl != CURLUSESSL_TRY) { - failf(data, "STARTTLS denied, code %d", smtpcode); - result = CURLE_USE_SSL_FAILED; - } - else - result = smtp_perform_authentication(data, smtpc); + if(ir) { /* AUTH ... */ + /* Send the AUTH command with the initial response */ + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); + } + else { + /* Send the AUTH command */ + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech); } - else - smtp_state(data, smtpc, SMTP_UPGRADETLS); return result; } -/* For EHLO responses */ -static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) +/*********************************************************************** + * + * smtp_continue_auth() + * + * Sends SASL continuation data. + */ +static CURLcode smtp_continue_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) { - CURLcode result = CURLE_OK; - const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); - size_t len = smtpc->pp.nfinal; - - (void)instate; - - if(smtpcode / 100 != 2 && smtpcode != 1) { - if(data->set.use_ssl <= CURLUSESSL_TRY || - Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) - result = smtp_perform_helo(data, smtpc); - else { - failf(data, "Remote access denied: %d", smtpcode); - result = CURLE_REMOTE_ACCESS_DENIED; - } - } - else if(len >= 4) { - line += 4; - len -= 4; - - /* Does the server support the STARTTLS capability? */ - if(len >= 8 && curl_strnequal(line, "STARTTLS", 8)) - smtpc->tls_supported = TRUE; - - /* Does the server support the SIZE capability? */ - else if(len >= 4 && curl_strnequal(line, "SIZE", 4)) - smtpc->size_supported = TRUE; - - /* Does the server support the UTF-8 capability? */ - else if(len >= 8 && curl_strnequal(line, "SMTPUTF8", 8)) - smtpc->utf8_supported = TRUE; - - /* Does the server support authentication? */ - else if(len >= 5 && curl_strnequal(line, "AUTH ", 5)) { - smtpc->auth_supported = TRUE; - - /* Advance past the AUTH keyword */ - line += 5; - len -= 5; - - /* Loop through the data line */ - for(;;) { - size_t llen; - size_t wordlen; - unsigned short mechbit; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - while(len && - (*line == ' ' || *line == '\t' || - *line == '\r' || *line == '\n')) { + (void)mech; + if(!smtpc) + return CURLE_FAILED_INIT; + return Curl_pp_sendf(data, &smtpc->pp, "%s", Curl_bufref_ptr(resp)); +} - line++; - len--; - } +/*********************************************************************** + * + * smtp_cancel_auth() + * + * Sends SASL cancellation. + */ +static CURLcode smtp_cancel_auth(struct Curl_easy *data, const char *mech) +{ + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - if(!len) - break; + (void)mech; + if(!smtpc) + return CURLE_FAILED_INIT; + return Curl_pp_sendf(data, &smtpc->pp, "*"); +} - /* Extract the word */ - for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && - line[wordlen] != '\t' && line[wordlen] != '\r' && - line[wordlen] != '\n';) - wordlen++; +/*********************************************************************** + * + * smtp_perform_authentication() + * + * Initiates the authentication sequence, with the appropriate SASL + * authentication mechanism. + */ +static CURLcode smtp_perform_authentication(struct Curl_easy *data, + struct smtp_conn *smtpc) +{ + CURLcode result = CURLE_OK; + saslprogress progress; - /* Test the word for a matching authentication mechanism */ - mechbit = Curl_sasl_decode_mech(line, wordlen, &llen); - if(mechbit && llen == wordlen) - smtpc->sasl.authmechs |= mechbit; + /* Check we have enough data to authenticate with, and the + server supports authentication, and end the connect phase if not */ + if(!smtpc->auth_supported || + !Curl_sasl_can_authenticate(&smtpc->sasl, data)) { + smtp_state(data, smtpc, SMTP_STOP); + return result; + } - line += wordlen; - len -= wordlen; - } - } + /* Calculate the SASL login details */ + result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress); - if(smtpcode != 1) { - if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { - /* We do not have an SSL/TLS connection yet, but SSL is requested */ - if(smtpc->tls_supported) - /* Switch to TLS connection now */ - result = smtp_perform_starttls(data, smtpc); - else if(data->set.use_ssl == CURLUSESSL_TRY) - /* Fallback and carry on with authentication */ - result = smtp_perform_authentication(data, smtpc); - else { - failf(data, "STARTTLS not supported."); - result = CURLE_USE_SSL_FAILED; - } - } - else - result = smtp_perform_authentication(data, smtpc); - } - } - else { - failf(data, "Unexpectedly short EHLO response"); - result = CURLE_WEIRD_SERVER_REPLY; + if(!result) { + if(progress == SASL_INPROGRESS) + smtp_state(data, smtpc, SMTP_AUTH); + else + result = Curl_sasl_is_blocked(&smtpc->sasl, data); } return result; } -/* For HELO responses */ -static CURLcode smtp_state_helo_resp(struct Curl_easy *data, +/*********************************************************************** + * + * smtp_perform_command() + * + * Sends an SMTP based command. + */ +static CURLcode smtp_perform_command(struct Curl_easy *data, struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) + struct SMTP *smtp) { CURLcode result = CURLE_OK; - (void)instate; - if(smtpcode / 100 != 2) { - failf(data, "Remote access denied: %d", smtpcode); - result = CURLE_REMOTE_ACCESS_DENIED; - } - else - /* End of connect phase */ - smtp_state(data, smtpc, SMTP_STOP); + if(smtp->rcpt) { + /* We notify the server we are sending UTF-8 data if a) it supports the + SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ + bool utf8 = FALSE; - return result; -} + if((!smtp->custom) || (!smtp->custom[0])) { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + const char *suffix = ""; -/* For SASL authentication responses */ -static CURLcode smtp_state_auth_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - saslprogress progress; + /* Parse the mailbox to verify into the local address and hostname + parts, converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(smtp->rcpt->data, + &address, &host, &suffix); + if(result) + return result; - (void)instate; + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 6 */ + utf8 = (smtpc->utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name))); - result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress); - if(!result) - switch(progress) { - case SASL_DONE: - smtp_state(data, smtpc, SMTP_STOP); /* Authenticated */ - break; - case SASL_IDLE: /* No mechanism left after cancellation */ - failf(data, "Authentication cancelled"); - result = CURLE_LOGIN_DENIED; - break; - default: - break; + /* Send the VRFY command (Note: The hostname part may be absent when the + host is a local system) */ + result = Curl_pp_sendf(data, &smtpc->pp, "VRFY %s%s%s%s", + address, + host.name ? "@" : "", + host.name ? host.name : "", + utf8 ? " SMTPUTF8" : ""); + + Curl_free_idnconverted_hostname(&host); + curlx_free(address); + } + else { + /* Establish whether we should report that we support SMTPUTF8 for EXPN + commands to the server as per RFC-6531 sect. 3.1 point 6 */ + utf8 = (smtpc->utf8_supported) && (!strcmp(smtp->custom, "EXPN")); + + /* Send the custom recipient based command such as the EXPN command */ + result = Curl_pp_sendf(data, &smtpc->pp, + "%s %s%s", smtp->custom, + smtp->rcpt->data, + utf8 ? " SMTPUTF8" : ""); } + } + else + /* Send the non-recipient based command such as HELP */ + result = Curl_pp_sendf(data, &smtpc->pp, "%s", + smtp->custom && smtp->custom[0] != '\0' ? + smtp->custom : "HELP"); + + if(!result) + smtp_state(data, smtpc, SMTP_COMMAND); return result; } -/* For command responses */ -static CURLcode smtp_state_command_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - int smtpcode, - smtpstate instate) +/*********************************************************************** + * + * smtp_perform_mail() + * + * Sends an MAIL command to initiate the upload of a message. + */ +static CURLcode smtp_perform_mail(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp) { + char *from = NULL; + char *auth = NULL; + char *size = NULL; CURLcode result = CURLE_OK; - char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); - size_t len = smtpc->pp.nfinal; - (void)instate; + /* We notify the server we are sending UTF-8 data if a) it supports the + SMTPUTF8 extension and b) The mailbox contains UTF-8 characters, in + either the local address or hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ + bool utf8 = FALSE; - if((smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 553 && smtpcode != 1) || - (!smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 1)) { - failf(data, "Command failed: %d", smtpcode); - result = CURLE_WEIRD_SERVER_REPLY; - } - else { - if(!data->req.no_body) - result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); + /* Calculate the FROM parameter */ + if(data->set.str[STRING_MAIL_FROM]) { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + const char *suffix = ""; - if(!result && (smtpcode != 1)) { - if(smtp->rcpt) { - smtp->rcpt = smtp->rcpt->next; + /* Parse the FROM mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], + &address, &host, &suffix); + if(result) + goto out; - if(smtp->rcpt) { - /* Send the next command */ - result = smtp_perform_command(data, smtpc, smtp); - } - else - /* End of DO phase */ - smtp_state(data, smtpc, SMTP_STOP); - } - else - /* End of DO phase */ - smtp_state(data, smtpc, SMTP_STOP); - } - } + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ + utf8 = (smtpc->utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name))); - return result; -} + if(host.name) { + from = curl_maprintf("<%s@%s>%s", address, host.name, suffix); -/* For MAIL responses */ -static CURLcode smtp_state_mail_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - (void)instate; + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we will simply let the server + worry about that and reply with a 501 error */ + from = curl_maprintf("<%s>%s", address, suffix); - if(smtpcode / 100 != 2) { - failf(data, "MAIL failed: %d", smtpcode); - result = CURLE_SEND_ERROR; + curlx_free(address); } else - /* Start the RCPT TO command */ - result = smtp_perform_rcpt_to(data, smtpc, smtp); + /* Null reverse-path, RFC-5321, sect. 3.6.3 */ + from = curlx_strdup("<>"); - return result; -} + if(!from) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } -/* For RCPT responses */ -static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - bool is_smtp_err = FALSE; - bool is_smtp_blocking_err = FALSE; + /* Calculate the optional AUTH parameter */ + if(data->set.str[STRING_MAIL_AUTH] && smtpc->sasl.authused) { + if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + const char *suffix = ""; - (void)instate; + /* Parse the AUTH mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], + &address, &host, &suffix); + if(result) + goto out; - is_smtp_err = (smtpcode / 100 != 2); + /* Establish whether we should report SMTPUTF8 to the server for this + mailbox as per RFC-6531 sect. 3.1 point 4 and sect. 3.4 */ + if((!utf8) && (smtpc->utf8_supported) && + ((host.encalloc) || (!Curl_is_ASCII_name(address)) || + (!Curl_is_ASCII_name(host.name)))) + utf8 = TRUE; - /* If there is multiple RCPT TO to be issued, it is possible to ignore errors - and proceed with only the valid addresses. */ - is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails); + if(host.name) { + auth = curl_maprintf("<%s@%s>%s", address, host.name, suffix); - if(is_smtp_err) { - /* Remembering the last failure which we can report if all "RCPT TO" have - failed and we cannot proceed. */ - smtp->rcpt_last_error = smtpcode; + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we will simply let the server + worry about it */ + auth = curl_maprintf("<%s>%s", address, suffix); + curlx_free(address); + } + else + /* Empty AUTH, RFC-2554, sect. 5 */ + auth = curlx_strdup("<>"); - if(is_smtp_blocking_err) { - failf(data, "RCPT failed: %d", smtpcode); - result = CURLE_SEND_ERROR; + if(!auth) { + result = CURLE_OUT_OF_MEMORY; + goto out; } } - else { - /* Some RCPT TO commands have succeeded. */ - smtp->rcpt_had_ok = TRUE; - } - if(!is_smtp_blocking_err) { - smtp->rcpt = smtp->rcpt->next; +#ifndef CURL_DISABLE_MIME + /* Prepare the mime data if some. */ + if(IS_MIME_POST(data)) { + curl_mimepart *postp = data->set.mimepostp; - if(smtp->rcpt) - /* Send the next RCPT TO command */ - result = smtp_perform_rcpt_to(data, smtpc, smtp); - else { - /* We were not able to issue a successful RCPT TO command while going - over recipients (potentially multiple). Sending back last error. */ - if(!smtp->rcpt_had_ok) { - failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error); - result = CURLE_SEND_ERROR; - } - else { - /* Send the DATA command */ - result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA"); + /* Use the whole structure as data. */ + postp->flags &= ~(unsigned int)MIME_BODY_ONLY; - if(!result) - smtp_state(data, smtpc, SMTP_DATA); - } - } - } + /* Add external headers and mime version. */ + curl_mime_headers(postp, data->set.headers, 0); + result = Curl_mime_prepare_headers(data, postp, NULL, + NULL, MIMESTRATEGY_MAIL); + + if(!result) + if(!Curl_checkheaders(data, STRCONST("Mime-Version"))) + result = Curl_mime_add_header(&postp->curlheaders, + "Mime-Version: 1.0"); + + if(!result) + result = Curl_creader_set_mime(data, postp); + if(result) + goto out; + data->state.infilesize = Curl_creader_total_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + goto out; + } + + /* Calculate the optional SIZE parameter */ + if(smtpc->size_supported && data->state.infilesize > 0) { + size = curl_maprintf("%" FMT_OFF_T, data->state.infilesize); + + if(!size) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + /* If the mailboxes in the FROM and AUTH parameters do not include a UTF-8 + based address then quickly scan through the recipient list and check if + any there do, as we need to correctly identify our support for SMTPUTF8 + in the envelope, as per RFC-6531 sect. 3.4 */ + if(smtpc->utf8_supported && !utf8) { + struct curl_slist *rcpt = smtp->rcpt; + + while(rcpt && !utf8) { + /* Does the hostname contain non-ASCII characters? */ + if(!Curl_is_ASCII_name(rcpt->data)) + utf8 = TRUE; + + rcpt = rcpt->next; + } + } + + /* Add the client reader doing STMP EOB escaping */ + result = cr_eob_add(data); + if(result) + goto out; + + /* Send the MAIL command */ + result = Curl_pp_sendf(data, &smtpc->pp, + "MAIL FROM:%s%s%s%s%s%s", + from, /* Mandatory */ + auth ? " AUTH=" : "", /* Optional on AUTH support */ + auth ? auth : "", /* */ + size ? " SIZE=" : "", /* Optional on SIZE support */ + size ? size : "", /* */ + utf8 ? " SMTPUTF8" /* Internationalised mailbox */ + : ""); /* included in our envelope */ + +out: + curlx_free(from); + curlx_free(auth); + curlx_free(size); + + if(!result) + smtp_state(data, smtpc, SMTP_MAIL); return result; } -/* For DATA response */ -static CURLcode smtp_state_data_resp(struct Curl_easy *data, +/*********************************************************************** + * + * smtp_perform_rcpt_to() + * + * Sends a RCPT TO command for a given recipient as part of the message upload + * process. + */ +static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, struct smtp_conn *smtpc, - int smtpcode, - smtpstate instate) + struct SMTP *smtp) { CURLcode result = CURLE_OK; - (void)instate; + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + const char *suffix = ""; - if(smtpcode != 354) { - failf(data, "DATA failed: %d", smtpcode); - result = CURLE_SEND_ERROR; - } - else { - /* Set the progress upload size */ - Curl_pgrsSetUploadSize(data, data->state.infilesize); + /* Parse the recipient mailbox into the local address and hostname parts, + converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(smtp->rcpt->data, + &address, &host, &suffix); + if(result) + return result; - /* SMTP upload */ - Curl_xfer_setup_send(data, FIRSTSOCKET); + /* Send the RCPT TO command */ + if(host.name) + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s@%s>%s", + address, host.name, suffix); + else + /* An invalid mailbox was provided but we will simply let the server worry + about that and reply with a 501 error */ + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>%s", + address, suffix); - /* End of DO phase */ - smtp_state(data, smtpc, SMTP_STOP); + Curl_free_idnconverted_hostname(&host); + curlx_free(address); + + if(!result) + smtp_state(data, smtpc, SMTP_RCPT); + + return result; +} + +/*********************************************************************** + * + * smtp_perform_quit() + * + * Performs the quit action prior to sclose() being called. + */ +static CURLcode smtp_perform_quit(struct Curl_easy *data, + struct smtp_conn *smtpc) +{ + /* Send the QUIT command */ + CURLcode result = Curl_pp_sendf(data, &smtpc->pp, "%s", "QUIT"); + + if(!result) + smtp_state(data, smtpc, SMTP_QUIT); + + return result; +} + +/* For the initial server greeting */ +static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; + + if(smtpcode / 100 != 2) { + failf(data, "Got unexpected smtp-server response: %d", smtpcode); + result = CURLE_WEIRD_SERVER_REPLY; } + else + result = smtp_perform_ehlo(data, smtpc); return result; } -/* For POSTDATA responses, which are received after the entire DATA - part has been sent to the server */ -static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, +/* For STARTTLS responses */ +static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, struct smtp_conn *smtpc, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - (void)instate; - if(smtpcode != 250) - result = CURLE_WEIRD_SERVER_REPLY; + /* Pipelining in response is forbidden. */ + if(smtpc->pp.overflow) + return CURLE_WEIRD_SERVER_REPLY; - /* End of DONE phase */ - smtp_state(data, smtpc, SMTP_STOP); + if(smtpcode != 220) { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied, code %d", smtpcode); + result = CURLE_USE_SSL_FAILED; + } + else + result = smtp_perform_authentication(data, smtpc); + } + else + smtp_state(data, smtpc, SMTP_UPGRADETLS); return result; } -static CURLcode smtp_pp_statemachine(struct Curl_easy *data, - struct connectdata *conn) +/* For EHLO responses */ +static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, + smtpstate instate) { CURLcode result = CURLE_OK; - int smtpcode; - struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); - struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); - size_t nread = 0; + const char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; - if(!smtpc || !smtp) - return CURLE_FAILED_INIT; + (void)instate; - /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ -upgrade_tls: - if(smtpc->state == SMTP_UPGRADETLS) { - result = smtp_perform_upgrade_tls(data, smtpc); - if(result || (smtpc->state == SMTP_UPGRADETLS)) - return result; + if(smtpcode / 100 != 2 && smtpcode != 1) { + if(data->set.use_ssl <= CURLUSESSL_TRY || + Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) + result = smtp_perform_helo(data, smtpc); + else { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } } + else if(len >= 4) { + line += 4; + len -= 4; - /* Flush any data that needs to be sent */ - if(smtpc->pp.sendleft) - return Curl_pp_flushsend(data, &smtpc->pp); - - do { - /* Read the response from the server */ - result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp, - &smtpcode, &nread); - if(result) - return result; + /* Does the server support the STARTTLS capability? */ + if(len >= 8 && curl_strnequal(line, "STARTTLS", 8)) + smtpc->tls_supported = TRUE; - /* Store the latest response for later retrieval if necessary */ - if(smtpc->state != SMTP_QUIT && smtpcode != 1) - data->info.httpcode = smtpcode; + /* Does the server support the SIZE capability? */ + else if(len >= 4 && curl_strnequal(line, "SIZE", 4)) + smtpc->size_supported = TRUE; - if(!smtpcode) - break; + /* Does the server support the UTF-8 capability? */ + else if(len >= 8 && curl_strnequal(line, "SMTPUTF8", 8)) + smtpc->utf8_supported = TRUE; - /* We have now received a full SMTP server response */ - switch(smtpc->state) { - case SMTP_SERVERGREET: - result = smtp_state_servergreet_resp(data, smtpc, - smtpcode, smtpc->state); - break; + /* Does the server support authentication? */ + else if(len >= 5 && curl_strnequal(line, "AUTH ", 5)) { + smtpc->auth_supported = TRUE; - case SMTP_EHLO: - result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state); - break; + /* Advance past the AUTH keyword */ + line += 5; + len -= 5; - case SMTP_HELO: - result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state); - break; + /* Loop through the data line */ + for(;;) { + size_t llen; + size_t wordlen; + unsigned short mechbit; - case SMTP_STARTTLS: - result = smtp_state_starttls_resp(data, smtpc, smtpcode, smtpc->state); - /* During UPGRADETLS, leave the read loop as we need to connect - * (e.g. TLS handshake) before we continue sending/receiving. */ - if(!result && (smtpc->state == SMTP_UPGRADETLS)) - goto upgrade_tls; - break; - - case SMTP_AUTH: - result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state); - break; + while(len && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { - case SMTP_COMMAND: - result = smtp_state_command_resp(data, smtpc, smtp, - smtpcode, smtpc->state); - break; + line++; + len--; + } - case SMTP_MAIL: - result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state); - break; + if(!len) + break; - case SMTP_RCPT: - result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state); - break; + /* Extract the word */ + for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; - case SMTP_DATA: - result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state); - break; + /* Test the word for a matching authentication mechanism */ + mechbit = Curl_sasl_decode_mech(line, wordlen, &llen); + if(mechbit && llen == wordlen) + smtpc->sasl.authmechs |= mechbit; - case SMTP_POSTDATA: - result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state); - break; + line += wordlen; + len -= wordlen; + } + } - case SMTP_QUIT: - default: - /* internal error */ - smtp_state(data, smtpc, SMTP_STOP); - break; + if(smtpcode != 1) { + if(data->set.use_ssl && !Curl_conn_is_ssl(data->conn, FIRSTSOCKET)) { + /* We do not have an SSL/TLS connection yet, but SSL is requested */ + if(smtpc->tls_supported) + /* Switch to TLS connection now */ + result = smtp_perform_starttls(data, smtpc); + else if(data->set.use_ssl == CURLUSESSL_TRY) + /* Fallback and carry on with authentication */ + result = smtp_perform_authentication(data, smtpc); + else { + failf(data, "STARTTLS not supported."); + result = CURLE_USE_SSL_FAILED; + } + } + else + result = smtp_perform_authentication(data, smtpc); } - } while(!result && smtpc->state != SMTP_STOP && - Curl_pp_moredata(&smtpc->pp)); + } + else { + failf(data, "Unexpectedly short EHLO response"); + result = CURLE_WEIRD_SERVER_REPLY; + } return result; } -/* Called repeatedly until done from multi.c */ -static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) +/* For HELO responses */ +static CURLcode smtp_state_helo_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, + smtpstate instate) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + (void)instate; - *done = FALSE; - if(!smtpc) - return CURLE_FAILED_INIT; + if(smtpcode / 100 != 2) { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + else + /* End of connect phase */ + smtp_state(data, smtpc, SMTP_STOP); - result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); - *done = (smtpc->state == SMTP_STOP); return result; } -static CURLcode smtp_block_statemach(struct Curl_easy *data, +/* For SASL authentication responses */ +static CURLcode smtp_state_auth_resp(struct Curl_easy *data, struct smtp_conn *smtpc, - bool disconnecting) + int smtpcode, + smtpstate instate) { CURLcode result = CURLE_OK; + saslprogress progress; - while(smtpc->state != SMTP_STOP && !result) - result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting); + (void)instate; - return result; -} + result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress); + if(!result) + switch(progress) { + case SASL_DONE: + smtp_state(data, smtpc, SMTP_STOP); /* Authenticated */ + break; + case SASL_IDLE: /* No mechanism left after cancellation */ + failf(data, "Authentication cancelled"); + result = CURLE_LOGIN_DENIED; + break; + default: + break; + } -/* For the SMTP "protocol connect" and "doing" phases only */ -static CURLcode smtp_pollset(struct Curl_easy *data, - struct easy_pollset *ps) -{ - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - return smtpc ? Curl_pp_pollset(data, &smtpc->pp, ps) : CURLE_OK; + return result; } -/*********************************************************************** - * - * smtp_connect() - * - * This function should do everything that is to be considered a part of - * the connection phase. - * - * The variable pointed to by 'done' will be TRUE if the protocol-layer - * connect phase is done when this function returns, or FALSE if not. - */ -static CURLcode smtp_connect(struct Curl_easy *data, bool *done) +/* For command responses */ +static CURLcode smtp_state_command_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, + smtpstate instate) { - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; + char *line = curlx_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; - *done = FALSE; /* default to not done yet */ - if(!smtpc) - return CURLE_FAILED_INIT; - - PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp); + (void)instate; - /* Initialize the SASL storage */ - Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); + if((smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 553 && smtpcode != 1) || + (!smtp->rcpt && smtpcode / 100 != 2 && smtpcode != 1)) { + failf(data, "Command failed: %d", smtpcode); + result = CURLE_WEIRD_SERVER_REPLY; + } + else { + if(!data->req.no_body) + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); - /* Initialise the pingpong layer */ - Curl_pp_init(&smtpc->pp, Curl_pgrs_now(data)); + if(!result && (smtpcode != 1)) { + if(smtp->rcpt) { + smtp->rcpt = smtp->rcpt->next; - /* Parse the URL options */ - result = smtp_parse_url_options(data->conn, smtpc); - if(result) - return result; + if(smtp->rcpt) { + /* Send the next command */ + result = smtp_perform_command(data, smtpc, smtp); + } + else + /* End of DO phase */ + smtp_state(data, smtpc, SMTP_STOP); + } + else + /* End of DO phase */ + smtp_state(data, smtpc, SMTP_STOP); + } + } - /* Parse the URL path */ - result = smtp_parse_url_path(data, smtpc); - if(result) - return result; + return result; +} - /* Start off waiting for the server greeting response */ - smtp_state(data, smtpc, SMTP_SERVERGREET); +/* For MAIL responses */ +static CURLcode smtp_state_mail_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + (void)instate; - result = smtp_multi_statemach(data, done); + if(smtpcode / 100 != 2) { + failf(data, "MAIL failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + } + else + /* Start the RCPT TO command */ + result = smtp_perform_rcpt_to(data, smtpc, smtp); return result; } -/*********************************************************************** - * - * smtp_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, - bool premature) +/* For RCPT responses */ +static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + int smtpcode, + smtpstate instate) { - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); + bool is_smtp_err = FALSE; + bool is_smtp_blocking_err = FALSE; - (void)premature; + (void)instate; - if(!smtpc) - return CURLE_FAILED_INIT; - if(!smtp) - return CURLE_OK; + is_smtp_err = (smtpcode / 100 != 2); - /* Cleanup our per-request based variables */ - Curl_safefree(smtp->custom); + /* If there is multiple RCPT TO to be issued, it is possible to ignore errors + and proceed with only the valid addresses. */ + is_smtp_blocking_err = (is_smtp_err && !data->set.mail_rcpt_allowfails); - if(status) { - connclose(conn, "SMTP done with bad status"); /* marked for closure */ - result = status; /* use the already set error code */ - } - else if(!data->set.connect_only && data->set.mail_rcpt && - (data->state.upload || IS_MIME_POST(data))) { + if(is_smtp_err) { + /* Remembering the last failure which we can report if all "RCPT TO" have + failed and we cannot proceed. */ + smtp->rcpt_last_error = smtpcode; - smtp_state(data, smtpc, SMTP_POSTDATA); + if(is_smtp_blocking_err) { + failf(data, "RCPT failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + } + } + else { + /* Some RCPT TO commands have succeeded. */ + smtp->rcpt_had_ok = TRUE; + } - /* Run the state-machine */ - result = smtp_block_statemach(data, smtpc, FALSE); + if(!is_smtp_blocking_err) { + smtp->rcpt = smtp->rcpt->next; + + if(smtp->rcpt) + /* Send the next RCPT TO command */ + result = smtp_perform_rcpt_to(data, smtpc, smtp); + else { + /* We were not able to issue a successful RCPT TO command while going + over recipients (potentially multiple). Sending back last error. */ + if(!smtp->rcpt_had_ok) { + failf(data, "RCPT failed: %d (last error)", smtp->rcpt_last_error); + result = CURLE_SEND_ERROR; + } + else { + /* Send the DATA command */ + result = Curl_pp_sendf(data, &smtpc->pp, "%s", "DATA"); + + if(!result) + smtp_state(data, smtpc, SMTP_DATA); + } + } } - /* Clear the transfer mode for the next request */ - smtp->transfer = PPTRANSFER_BODY; - CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d", - status, premature, result); return result; } -/*********************************************************************** - * - * smtp_perform() - * - * This is the actual DO function for SMTP. Transfer a mail, send a command - * or get some data according to the options previously setup. - */ -static CURLcode smtp_perform(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - bool *connected, - bool *dophase_done) +/* For DATA response */ +static CURLcode smtp_state_data_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, + smtpstate instate) { - /* This is SMTP and no proxy */ CURLcode result = CURLE_OK; + (void)instate; - CURL_TRC_SMTP(data, "smtp_perform(), start"); - - if(data->req.no_body) { - /* Requested no body means no transfer */ - smtp->transfer = PPTRANSFER_INFO; + if(smtpcode != 354) { + failf(data, "DATA failed: %d", smtpcode); + result = CURLE_SEND_ERROR; } + else { + /* Set the progress upload size */ + Curl_pgrsSetUploadSize(data, data->state.infilesize); - *dophase_done = FALSE; /* not done yet */ - - /* Store the first recipient (or NULL if not specified) */ - smtp->rcpt = data->set.mail_rcpt; - - /* Track of whether we have successfully sent at least one RCPT TO command */ - smtp->rcpt_had_ok = FALSE; + /* SMTP upload */ + Curl_xfer_setup_send(data, FIRSTSOCKET); - /* Track of the last error we have received by sending RCPT TO command */ - smtp->rcpt_last_error = 0; + /* End of DO phase */ + smtp_state(data, smtpc, SMTP_STOP); + } - /* Initial data character is the first character in line: it is implicitly - preceded by a virtual CRLF. */ - smtp->trailing_crlf = TRUE; - smtp->eob = 2; + return result; +} - /* Start the first command in the DO phase */ - if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) - /* MAIL transfer */ - result = smtp_perform_mail(data, smtpc, smtp); - else - /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ - result = smtp_perform_command(data, smtpc, smtp); +/* For POSTDATA responses, which are received after the entire DATA + part has been sent to the server */ +static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, + struct smtp_conn *smtpc, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; - if(result) - goto out; + (void)instate; - /* Run the state-machine */ - result = smtp_multi_statemach(data, dophase_done); + if(smtpcode != 250) + result = CURLE_WEIRD_SERVER_REPLY; - *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); + /* End of DONE phase */ + smtp_state(data, smtpc, SMTP_STOP); -out: - CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", - result, *connected, *dophase_done); return result; } -/*********************************************************************** - * - * smtp_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (smtp_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode smtp_do(struct Curl_easy *data, bool *done) +static CURLcode smtp_pp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { - struct smtp_conn *smtpc = - Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); - struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); CURLcode result = CURLE_OK; + int smtpcode; + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); + size_t nread = 0; - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - *done = FALSE; /* default to false */ if(!smtpc || !smtp) return CURLE_FAILED_INIT; - /* Parse the custom request */ - result = smtp_parse_custom_request(data, smtp); - if(result) - return result; + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ +upgrade_tls: + if(smtpc->state == SMTP_UPGRADETLS) { + result = smtp_perform_upgrade_tls(data, smtpc); + if(result || (smtpc->state == SMTP_UPGRADETLS)) + return result; + } - result = smtp_regular_transfer(data, smtpc, smtp, done); - CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); - return result; -} + /* Flush any data that needs to be sent */ + if(smtpc->pp.sendleft) + return Curl_pp_flushsend(data, &smtpc->pp); -/*********************************************************************** - * - * smtp_disconnect() - * - * Disconnect from an SMTP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode smtp_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); + do { + /* Read the response from the server */ + result = Curl_pp_readresp(data, FIRSTSOCKET, &smtpc->pp, + &smtpcode, &nread); + if(result) + return result; - (void)data; - if(!smtpc) - return CURLE_FAILED_INIT; + /* Store the latest response for later retrieval if necessary */ + if(smtpc->state != SMTP_QUIT && smtpcode != 1) + data->info.httpcode = smtpcode; - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. */ + if(!smtpcode) + break; - if(!dead_connection && conn->bits.protoconnstart && - !Curl_pp_needs_flush(data, &smtpc->pp)) { - if(!smtp_perform_quit(data, smtpc)) - (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ - } + /* We have now received a full SMTP server response */ + switch(smtpc->state) { + case SMTP_SERVERGREET: + result = smtp_state_servergreet_resp(data, smtpc, + smtpcode, smtpc->state); + break; - CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); - return CURLE_OK; -} + case SMTP_EHLO: + result = smtp_state_ehlo_resp(data, smtpc, smtpcode, smtpc->state); + break; -/* Call this when the DO phase has completed */ -static CURLcode smtp_dophase_done(struct Curl_easy *data, - struct SMTP *smtp, - bool connected) -{ - (void)connected; + case SMTP_HELO: + result = smtp_state_helo_resp(data, smtpc, smtpcode, smtpc->state); + break; - if(smtp->transfer != PPTRANSFER_BODY) - /* no data to transfer */ - Curl_xfer_setup_nop(data); + case SMTP_STARTTLS: + result = smtp_state_starttls_resp(data, smtpc, smtpcode, smtpc->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (smtpc->state == SMTP_UPGRADETLS)) + goto upgrade_tls; + break; - return CURLE_OK; -} + case SMTP_AUTH: + result = smtp_state_auth_resp(data, smtpc, smtpcode, smtpc->state); + break; -/* Called from multi.c while DOing */ -static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) -{ - struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); - CURLcode result; + case SMTP_COMMAND: + result = smtp_state_command_resp(data, smtpc, smtp, + smtpcode, smtpc->state); + break; - if(!smtp) - return CURLE_FAILED_INIT; - result = smtp_multi_statemach(data, dophase_done); - if(result) - DEBUGF(infof(data, "DO phase failed")); - else if(*dophase_done) { - result = smtp_dophase_done(data, smtp, FALSE /* not connected */); + case SMTP_MAIL: + result = smtp_state_mail_resp(data, smtpc, smtp, smtpcode, smtpc->state); + break; - DEBUGF(infof(data, "DO phase is complete")); - } + case SMTP_RCPT: + result = smtp_state_rcpt_resp(data, smtpc, smtp, smtpcode, smtpc->state); + break; - CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); - return result; -} - -/*********************************************************************** - * - * smtp_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - */ -static CURLcode smtp_regular_transfer(struct Curl_easy *data, - struct smtp_conn *smtpc, - struct SMTP *smtp, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - - /* Make sure size is unknown at this point */ - data->req.size = -1; - - /* Set the progress data */ - Curl_pgrsReset(data); + case SMTP_DATA: + result = smtp_state_data_resp(data, smtpc, smtpcode, smtpc->state); + break; - /* Carry out the perform */ - result = smtp_perform(data, smtpc, smtp, &connected, dophase_done); + case SMTP_POSTDATA: + result = smtp_state_postdata_resp(data, smtpc, smtpcode, smtpc->state); + break; - /* Perform post DO phase operations if necessary */ - if(!result && *dophase_done) - result = smtp_dophase_done(data, smtp, connected); + case SMTP_QUIT: + default: + /* internal error */ + smtp_state(data, smtpc, SMTP_STOP); + break; + } + } while(!result && smtpc->state != SMTP_STOP && + Curl_pp_moredata(&smtpc->pp)); - CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", - result, *dophase_done); return result; } -static void smtp_easy_dtor(void *key, size_t klen, void *entry) +/* Called repeatedly until done from multi.c */ +static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done) { - struct SMTP *smtp = entry; - (void)key; - (void)klen; - curlx_free(smtp); -} + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); -static void smtp_conn_dtor(void *key, size_t klen, void *entry) -{ - struct smtp_conn *smtpc = entry; - (void)key; - (void)klen; - Curl_pp_disconnect(&smtpc->pp); - Curl_safefree(smtpc->domain); - curlx_free(smtpc); + *done = FALSE; + if(!smtpc) + return CURLE_FAILED_INIT; + + result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); + *done = (smtpc->state == SMTP_STOP); + return result; } -static CURLcode smtp_setup_connection(struct Curl_easy *data, - struct connectdata *conn) +static CURLcode smtp_block_statemach(struct Curl_easy *data, + struct smtp_conn *smtpc, + bool disconnecting) { - struct smtp_conn *smtpc; - struct SMTP *smtp; CURLcode result = CURLE_OK; - smtpc = curlx_calloc(1, sizeof(*smtpc)); - if(!smtpc || - Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - smtp = curlx_calloc(1, sizeof(*smtp)); - if(!smtp || - Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor)) - result = CURLE_OUT_OF_MEMORY; + while(smtpc->state != SMTP_STOP && !result) + result = Curl_pp_statemach(data, &smtpc->pp, TRUE, disconnecting); -out: - CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); return result; } +/* For the SMTP "protocol connect" and "doing" phases only */ +static CURLcode smtp_pollset(struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + return smtpc ? Curl_pp_pollset(data, &smtpc->pp, ps) : CURLE_OK; +} + +/* SASL parameters for the smtp protocol */ +static const struct SASLproto saslsmtp = { + "smtp", /* The service name */ + smtp_perform_auth, /* Send authentication command */ + smtp_continue_auth, /* Send authentication continuation */ + smtp_cancel_auth, /* Cancel authentication */ + smtp_get_message, /* Get SASL response message */ + 512 - 8, /* Max line len - strlen("AUTH ") - 1 space - crlf */ + 334, /* Code received when continuation is expected */ + 235, /* Code to receive upon authentication success */ + SASL_AUTH_DEFAULT, /* Default mechanisms */ + SASL_FLAG_BASE64 /* Configuration flags */ +}; + /*********************************************************************** * - * smtp_parse_url_options() + * smtp_connect() * - * Parse the URL login options. + * This function should do everything that is to be considered a part of + * the connection phase. + * + * The variable pointed to by 'done' will be TRUE if the protocol-layer + * connect phase is done when this function returns, or FALSE if not. */ -static CURLcode smtp_parse_url_options(struct connectdata *conn, - struct smtp_conn *smtpc) +static CURLcode smtp_connect(struct Curl_easy *data, bool *done) { + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); CURLcode result = CURLE_OK; - const char *ptr = conn->options; - while(!result && ptr && *ptr) { - const char *key = ptr; - const char *value; + *done = FALSE; /* default to not done yet */ + if(!smtpc) + return CURLE_FAILED_INIT; - while(*ptr && *ptr != '=') - ptr++; + PINGPONG_SETUP(&smtpc->pp, smtp_pp_statemachine, smtp_endofresp); - value = ptr + 1; + /* Initialize the SASL storage */ + Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); - while(*ptr && *ptr != ';') - ptr++; + /* Initialise the pingpong layer */ + Curl_pp_init(&smtpc->pp, Curl_pgrs_now(data)); - if(curl_strnequal(key, "AUTH=", 5)) - result = Curl_sasl_parse_url_auth_option(&smtpc->sasl, - value, ptr - value); - else - result = CURLE_URL_MALFORMAT; + /* Parse the URL options */ + result = smtp_parse_url_options(data->conn, smtpc); + if(result) + return result; - if(*ptr == ';') - ptr++; - } + /* Parse the URL path */ + result = smtp_parse_url_path(data, smtpc); + if(result) + return result; + + /* Start off waiting for the server greeting response */ + smtp_state(data, smtpc, SMTP_SERVERGREET); + + result = smtp_multi_statemach(data, done); return result; } /*********************************************************************** * - * smtp_parse_url_path() + * smtp_done() * - * Parse the URL path into separate path components. + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. */ -static CURLcode smtp_parse_url_path(struct Curl_easy *data, - struct smtp_conn *smtpc) +static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, + bool premature) { - /* The SMTP struct is already initialised in smtp_connect() */ - const char *path = &data->state.up.path[1]; /* skip leading path */ - char localhost[HOSTNAME_MAX + 1]; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); - /* Calculate the path if necessary */ - if(!*path) { - if(!Curl_gethostname(localhost, sizeof(localhost))) - path = localhost; - else - path = "localhost"; + (void)premature; + + if(!smtpc) + return CURLE_FAILED_INIT; + if(!smtp) + return CURLE_OK; + + /* Cleanup our per-request based variables */ + Curl_safefree(smtp->custom); + + if(status) { + connclose(conn, "SMTP done with bad status"); /* marked for closure */ + result = status; /* use the already set error code */ } + else if(!data->set.connect_only && data->set.mail_rcpt && + (data->state.upload || IS_MIME_POST(data))) { - /* URL decode the path and use it as the domain in our EHLO */ - return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL); + smtp_state(data, smtpc, SMTP_POSTDATA); + + /* Run the state-machine */ + result = smtp_block_statemach(data, smtpc, FALSE); + } + + /* Clear the transfer mode for the next request */ + smtp->transfer = PPTRANSFER_BODY; + CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d", + status, premature, result); + return result; } /*********************************************************************** * - * smtp_parse_custom_request() + * smtp_perform() * - * Parse the custom request. + * This is the actual DO function for SMTP. Transfer a mail, send a command + * or get some data according to the options previously setup. */ -static CURLcode smtp_parse_custom_request(struct Curl_easy *data, - struct SMTP *smtp) +static CURLcode smtp_perform(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *connected, + bool *dophase_done) { + /* This is SMTP and no proxy */ CURLcode result = CURLE_OK; - const char *custom = data->set.str[STRING_CUSTOMREQUEST]; - /* URL decode the custom request */ - if(custom) - result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL); + CURL_TRC_SMTP(data, "smtp_perform(), start"); - return result; -} + if(data->req.no_body) { + /* Requested no body means no transfer */ + smtp->transfer = PPTRANSFER_INFO; + } -/*********************************************************************** - * - * smtp_parse_address() - * - * Parse the fully qualified mailbox address into a local address part and the - * hostname, converting the hostname to an IDN A-label, as per RFC-5890, if - * necessary. - * - * Parameters: - * - * conn [in] - The connection handle. - * fqma [in] - The fully qualified mailbox address (which may or - * may not contain UTF-8 characters). - * address [in/out] - A new allocated buffer which holds the local - * address part of the mailbox. This buffer must be - * free'ed by the caller. - * host [in/out] - The hostname structure that holds the original, - * and optionally encoded, hostname. - * Curl_free_idnconverted_hostname() must be called - * once the caller has finished with the structure. - * - * Returns CURLE_OK on success. - * - * Notes: - * - * Should a UTF-8 hostname require conversion to IDN ACE and we cannot honor - * that conversion then we shall return success. This allow the caller to send - * the data to the server as a U-label (as per RFC-6531 sect. 3.2). - * - * If an mailbox '@' separator cannot be located then the mailbox is considered - * to be either a local mailbox or an invalid mailbox (depending on what the - * calling function deems it to be) then the input will simply be returned in - * the address part with the hostname being NULL. - */ -static CURLcode smtp_parse_address(const char *fqma, char **address, - struct hostname *host, const char **suffix) -{ - CURLcode result = CURLE_OK; - size_t length; - char *addressend; + *dophase_done = FALSE; /* not done yet */ - /* Duplicate the fully qualified email address so we can manipulate it, - ensuring it does not contain the delimiters if specified */ - char *dup = curlx_strdup(fqma[0] == '<' ? fqma + 1 : fqma); - if(!dup) - return CURLE_OUT_OF_MEMORY; + /* Store the first recipient (or NULL if not specified) */ + smtp->rcpt = data->set.mail_rcpt; - if(fqma[0] != '<') { - length = strlen(dup); - if(length) { - if(dup[length - 1] == '>') - dup[length - 1] = '\0'; - } - } - else { - addressend = strrchr(dup, '>'); - if(addressend) { - *addressend = '\0'; - *suffix = addressend + 1; - } - } + /* Track of whether we have successfully sent at least one RCPT TO command */ + smtp->rcpt_had_ok = FALSE; - /* Extract the hostname from the address (if we can) */ - host->name = strpbrk(dup, "@"); - if(host->name) { - *host->name = '\0'; - host->name = host->name + 1; + /* Track of the last error we have received by sending RCPT TO command */ + smtp->rcpt_last_error = 0; - /* Attempt to convert the hostname to IDN ACE */ - (void)Curl_idnconvert_hostname(host); + /* Initial data character is the first character in line: it is implicitly + preceded by a virtual CRLF. */ + smtp->trailing_crlf = TRUE; + smtp->eob = 2; - /* If Curl_idnconvert_hostname() fails then we shall attempt to continue - and send the hostname using UTF-8 rather than as 7-bit ACE (which is - our preference) */ - } + /* Start the first command in the DO phase */ + if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) + /* MAIL transfer */ + result = smtp_perform_mail(data, smtpc, smtp); + else + /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ + result = smtp_perform_command(data, smtpc, smtp); - /* Extract the local address from the mailbox */ - *address = dup; + if(result) + goto out; + + /* Run the state-machine */ + result = smtp_multi_statemach(data, dophase_done); + + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); +out: + CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", + result, *connected, *dophase_done); return result; } -struct cr_eob_ctx { - struct Curl_creader super; - struct bufq buf; - size_t n_eob; /* how many EOB bytes we matched so far */ - size_t eob; /* Number of bytes of the EOB (End Of Body) that - have been received so far */ - BIT(read_eos); /* we read an EOS from the next reader */ - BIT(processed_eos); /* we read and processed an EOS */ - BIT(eos); /* we have returned an EOS */ -}; - -static CURLcode cr_eob_init(struct Curl_easy *data, - struct Curl_creader *reader) +/* Call this when the DO phase has completed */ +static CURLcode smtp_dophase_done(struct Curl_easy *data, + struct SMTP *smtp, + bool connected) { - struct cr_eob_ctx *ctx = reader->ctx; - (void)data; - /* The first char we read is the first on a line, as if we had - * read CRLF just before */ - ctx->n_eob = 2; - Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + (void)connected; + + if(smtp->transfer != PPTRANSFER_BODY) + /* no data to transfer */ + Curl_xfer_setup_nop(data); + return CURLE_OK; } -static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) +/*********************************************************************** + * + * smtp_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode smtp_regular_transfer(struct Curl_easy *data, + struct smtp_conn *smtpc, + struct SMTP *smtp, + bool *dophase_done) { - struct cr_eob_ctx *ctx = reader->ctx; - (void)data; - Curl_bufq_free(&ctx->buf); -} + CURLcode result = CURLE_OK; + bool connected = FALSE; -/* this is the 5-bytes End-Of-Body marker for SMTP */ -#define SMTP_EOB "\r\n.\r\n" -#define SMTP_EOB_FIND_LEN 3 + /* Make sure size is unknown at this point */ + data->req.size = -1; -/* client reader doing SMTP End-Of-Body escaping. */ -static CURLcode cr_eob_read(struct Curl_easy *data, - struct Curl_creader *reader, - char *buf, size_t blen, - size_t *pnread, bool *peos) + /* Set the progress data */ + Curl_pgrsReset(data); + + /* Carry out the perform */ + result = smtp_perform(data, smtpc, smtp, &connected, dophase_done); + + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) + result = smtp_dophase_done(data, smtp, connected); + + CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", + result, *dophase_done); + return result; +} + +/*********************************************************************** + * + * smtp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (smtp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode smtp_do(struct Curl_easy *data, bool *done) { - struct cr_eob_ctx *ctx = reader->ctx; + struct smtp_conn *smtpc = + Curl_conn_meta_get(data->conn, CURL_META_SMTP_CONN); + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); CURLcode result = CURLE_OK; - size_t nread, i, start, n; - bool eos; - if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { - /* Get more and convert it when needed */ - result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); - CURL_TRC_SMTP(data, "cr_eob_read, next_read(len=%zu) -> %d, %zu eos=%d", - blen, result, nread, eos); - if(result) - return result; + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + *done = FALSE; /* default to false */ + if(!smtpc || !smtp) + return CURLE_FAILED_INIT; - ctx->read_eos = eos; - if(nread) { - if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { - /* not in the middle of a match, no EOB start found, just pass */ - *pnread = nread; - *peos = FALSE; - return CURLE_OK; - } - /* scan for EOB (continuation) and convert */ - for(i = start = 0; i < nread; ++i) { - if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { - /* matched the EOB prefix and seeing additional char, add '.' */ - result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); - if(result) - return result; - result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); - if(result) - return result; - ctx->n_eob = 0; - start = i; - if(data->state.infilesize > 0) - data->state.infilesize++; - } + /* Parse the custom request */ + result = smtp_parse_custom_request(data, smtp); + if(result) + return result; - if(buf[i] != SMTP_EOB[ctx->n_eob]) - ctx->n_eob = 0; + result = smtp_regular_transfer(data, smtpc, smtp, done); + CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); + return result; +} - if(buf[i] == SMTP_EOB[ctx->n_eob]) { - /* matching another char of the EOB */ - ++ctx->n_eob; - } - } +/*********************************************************************** + * + * smtp_disconnect() + * + * Disconnect from an SMTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode smtp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) +{ + struct smtp_conn *smtpc = Curl_conn_meta_get(conn, CURL_META_SMTP_CONN); - /* add any remainder to buf */ - if(start < nread) { - result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); - if(result) - return result; - } - } - } + (void)data; + if(!smtpc) + return CURLE_FAILED_INIT; - *peos = FALSE; + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. */ - if(ctx->read_eos && !ctx->processed_eos) { - /* if we last matched a CRLF or if the data was empty, add ".\r\n" - * to end the body. If we sent something and it did not end with "\r\n", - * add "\r\n.\r\n" to end the body */ - const char *eob = SMTP_EOB; - CURL_TRC_SMTP(data, "auto-ending mail body with '\\r\\n.\\r\\n'"); - switch(ctx->n_eob) { - case 2: - /* seen a CRLF at the end, just add the remainder */ - eob = &SMTP_EOB[2]; - break; - case 3: - /* ended with '\r\n.', we should escape the last '.' */ - eob = "." SMTP_EOB; - break; - default: - break; - } - result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); - if(result) - return result; - ctx->processed_eos = TRUE; + if(!dead_connection && conn->bits.protoconnstart && + !Curl_pp_needs_flush(data, &smtpc->pp)) { + if(!smtp_perform_quit(data, smtpc)) + (void)smtp_block_statemach(data, smtpc, TRUE); /* ignore on QUIT */ } - if(!Curl_bufq_is_empty(&ctx->buf)) { - result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); - } - else - *pnread = 0; + CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); + return CURLE_OK; +} - if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { - /* no more data, read all, done. */ - CURL_TRC_SMTP(data, "mail body complete, returning EOS"); - ctx->eos = TRUE; +/* Called from multi.c while DOing */ +static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) +{ + struct SMTP *smtp = Curl_meta_get(data, CURL_META_SMTP_EASY); + CURLcode result; + + if(!smtp) + return CURLE_FAILED_INIT; + result = smtp_multi_statemach(data, dophase_done); + if(result) + DEBUGF(infof(data, "DO phase failed")); + else if(*dophase_done) { + result = smtp_dophase_done(data, smtp, FALSE /* not connected */); + + DEBUGF(infof(data, "DO phase is complete")); } - *peos = ctx->eos; - DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", - blen, result, *pnread, *peos)); + + CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); return result; } -static curl_off_t cr_eob_total_length(struct Curl_easy *data, - struct Curl_creader *reader) +static void smtp_easy_dtor(void *key, size_t klen, void *entry) { - /* this reader changes length depending on input */ - (void)data; - (void)reader; - return -1; + struct SMTP *smtp = entry; + (void)key; + (void)klen; + curlx_free(smtp); } -static const struct Curl_crtype cr_eob = { - "cr-smtp-eob", - cr_eob_init, - cr_eob_read, - cr_eob_close, - Curl_creader_def_needs_rewind, - cr_eob_total_length, - Curl_creader_def_resume_from, - Curl_creader_def_cntrl, - Curl_creader_def_is_paused, - Curl_creader_def_done, - sizeof(struct cr_eob_ctx) -}; +static void smtp_conn_dtor(void *key, size_t klen, void *entry) +{ + struct smtp_conn *smtpc = entry; + (void)key; + (void)klen; + Curl_pp_disconnect(&smtpc->pp); + Curl_safefree(smtpc->domain); + curlx_free(smtpc); +} -static CURLcode cr_eob_add(struct Curl_easy *data) +static CURLcode smtp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_creader *reader = NULL; - CURLcode result; + struct smtp_conn *smtpc; + struct SMTP *smtp; + CURLcode result = CURLE_OK; - result = Curl_creader_create(&reader, data, &cr_eob, CURL_CR_CONTENT_ENCODE); - if(!result) - result = Curl_creader_add(data, reader); + smtpc = curlx_calloc(1, sizeof(*smtpc)); + if(!smtpc || + Curl_conn_meta_set(conn, CURL_META_SMTP_CONN, smtpc, smtp_conn_dtor)) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } - if(result && reader) - Curl_creader_free(data, reader); + smtp = curlx_calloc(1, sizeof(*smtp)); + if(!smtp || + Curl_meta_set(data, CURL_META_SMTP_EASY, smtp, smtp_easy_dtor)) + result = CURLE_OUT_OF_MEMORY; + +out: + CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); return result; } +/* + * SMTP protocol handler. + */ +const struct Curl_handler Curl_handler_smtp = { + "smtp", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_pollset, /* proto_pollset */ + smtp_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_SMTP, /* defport */ + CURLPROTO_SMTP, /* protocol */ + CURLPROTO_SMTP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ + PROTOPT_URLOPTIONS | PROTOPT_SSL_REUSE | PROTOPT_CONN_REUSE +}; + +#ifdef USE_SSL +/* + * SMTPS protocol handler. + */ +const struct Curl_handler Curl_handler_smtps = { + "smtps", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_pollset, /* proto_pollset */ + smtp_pollset, /* doing_pollset */ + ZERO_NULL, /* domore_pollset */ + ZERO_NULL, /* perform_pollset */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + ZERO_NULL, /* follow */ + PORT_SMTPS, /* defport */ + CURLPROTO_SMTPS, /* protocol */ + CURLPROTO_SMTP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_NOURLQUERY | PROTOPT_URLOPTIONS | PROTOPT_CONN_REUSE +}; +#endif + #endif /* CURL_DISABLE_SMTP */