lib: reorder protocol functions to avoid forward declarations (email)
No binary (build failed)
—
(log yok)
| @@ -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 */ | |
| @@ -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 */ | |
| @@ -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 <mech> ...<crlf> */ |
| - | /* 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 <mech> ...<crlf> */ |
| + | /* 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 */ | |