From 68fb25fa3fcff62dda71d2c67894eedf93291e05 Mon Sep 17 00:00:00 2001 From: Steve Holme Date: Fri, 7 Feb 2020 15:53:25 +0000 Subject: smtp: Support UTF-8 based host names in the MAIL command Non-ASCII host names will be ACE encoded if IDN is supported. --- lib/smtp.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 6 deletions(-) (limited to 'lib/smtp.c') diff --git a/lib/smtp.c b/lib/smtp.c index 7bcff0ce7..18c45afbe 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -27,6 +27,8 @@ * RFC4752 The Kerberos V5 ("GSSAPI") SASL Mechanism * RFC4954 SMTP Authentication * RFC5321 SMTP protocol + * RFC5890 Internationalized Domain Names for Applications (IDNA) + * RFC6531 SMTP Extension for Internationalized Email * RFC6749 OAuth 2.0 Authorization Framework * RFC8314 Use of TLS for Email Submission and Access * Draft SMTP URL Interface @@ -101,6 +103,8 @@ static CURLcode smtp_setup_connection(struct connectdata *conn); static CURLcode smtp_parse_url_options(struct connectdata *conn); static CURLcode smtp_parse_url_path(struct connectdata *conn); static CURLcode smtp_parse_custom_request(struct connectdata *conn); +static CURLcode smtp_parse_address(struct connectdata *conn, const char *fqma, + char **address, struct hostname *host); static CURLcode smtp_perform_auth(struct connectdata *conn, const char *mech, const char *initresp); static CURLcode smtp_continue_auth(struct connectdata *conn, const char *resp); @@ -516,18 +520,58 @@ static CURLcode smtp_perform_mail(struct connectdata *conn) if(!data->set.str[STRING_MAIL_FROM]) /* Null reverse-path, RFC-5321, sect. 3.6.3 */ from = strdup("<>"); - else if(data->set.str[STRING_MAIL_FROM][0] == '<') - from = aprintf("%s", data->set.str[STRING_MAIL_FROM]); - else - from = aprintf("<%s>", data->set.str[STRING_MAIL_FROM]); + else { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the FROM mailbox into the local address and host name parts, + converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(conn, data->set.str[STRING_MAIL_FROM], + &address, &host); + if(result) + return result; + + if(host.name) { + from = aprintf("<%s@%s>", address, host.name); + + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we'll simply let the server worry + about that and reply with a 501 error */ + from = aprintf("<%s>", address); + + free(address); + } if(!from) return CURLE_OUT_OF_MEMORY; /* Calculate the optional AUTH parameter */ if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.sasl.authused) { - if(data->set.str[STRING_MAIL_AUTH][0] != '\0') - auth = aprintf("%s", data->set.str[STRING_MAIL_AUTH]); + if(data->set.str[STRING_MAIL_AUTH][0] != '\0') { + char *address = NULL; + struct hostname host = { NULL, NULL, NULL, NULL }; + + /* Parse the AUTH mailbox into the local address and host name parts, + converting the host name to an IDN A-label if necessary */ + result = smtp_parse_address(conn, data->set.str[STRING_MAIL_AUTH], + &address, &host); + if(result) + return result; + + if(host.name) { + from = aprintf("<%s@%s>", address, host.name); + + Curl_free_idnconverted_hostname(&host); + } + else + /* An invalid mailbox was provided but we'll simply let the server + worry about it */ + auth = aprintf("<%s>", address); + + free(address); + } else /* Empty AUTH, RFC-2554, sect. 5 */ auth = strdup("<>"); @@ -561,6 +605,7 @@ static CURLcode smtp_perform_mail(struct connectdata *conn) if(result) { free(from); free(auth); + return result; } @@ -1566,6 +1611,76 @@ static CURLcode smtp_parse_custom_request(struct connectdata *conn) return result; } +/*********************************************************************** + * + * smtp_parse_address() + * + * Parse the fully qualified mailbox address into a local address part and the + * host name, converting the host name 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 host name structure that holds the original, + * and optionally encoded, host name. + * Curl_free_idnconverted_hostname() must be called + * once the caller has finished with the structure. + * + * Returns CURLE_OK on success. + * + * Notes: + * + * If an mailbox '@' seperator 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 host name being NULL. + */ +static CURLcode smtp_parse_address(struct connectdata *conn, const char *fqma, + char **address, struct hostname *host) +{ + CURLcode result = CURLE_OK; + size_t length; + + /* Duplicate the fully qualified email address so we can manipulate it, + ensuring it doesn't contain the delimiters if specified */ + char *dup = strdup(fqma[0] == '<' ? fqma + 1 : fqma); + if(!dup) + return CURLE_OUT_OF_MEMORY; + + length = strlen(dup); + if(dup[length - 1] == '>') + dup[length - 1] = '\0'; + + /* Extract the host name from the addresss (if we can) */ + host->name = strpbrk(dup, "@"); + if(host->name) { + *host->name = '\0'; + host->name = host->name + 1; + + /* Convert the host name to IDN ACE */ + result = Curl_idnconvert_hostname(conn, host); + if(result) { + free(dup); + host->name = NULL; + + return result; + } + } + else + host->name = NULL; + + /* Extract the local address from the mailbox */ + *address = dup; + + return result; +} + CURLcode Curl_smtp_escape_eob(struct connectdata *conn, const ssize_t nread) { /* When sending a SMTP payload we must detect CRLF. sequences making sure -- cgit v1.2.3