From 450c00f983fa54393019bb3c355de509a3523955 Mon Sep 17 00:00:00 2001 From: Dominik Tomecki Date: Wed, 30 Jul 2025 09:48:13 +0200 Subject: [PATCH] smtp: allow suffix behind a mail address for RFC 3461 Verified in test 3215 Closes #16643 --- .github/scripts/spellcheck.words | 1 + docs/libcurl/opts/CURLOPT_MAIL_FROM.md | 3 ++ docs/libcurl/opts/CURLOPT_MAIL_RCPT.md | 4 ++ lib/smtp.c | 50 +++++++++++++++-------- tests/data/Makefile.am | 3 +- tests/data/test3215 | 56 ++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 tests/data/test3215 diff --git a/.github/scripts/spellcheck.words b/.github/scripts/spellcheck.words index f8796eaa1f..90546bb65f 100644 --- a/.github/scripts/spellcheck.words +++ b/.github/scripts/spellcheck.words @@ -205,6 +205,7 @@ DoT doxygen drftpd dsa +DSN dtrace Dudka Dymond diff --git a/docs/libcurl/opts/CURLOPT_MAIL_FROM.md b/docs/libcurl/opts/CURLOPT_MAIL_FROM.md index cf32923903..aa5656e413 100644 --- a/docs/libcurl/opts/CURLOPT_MAIL_FROM.md +++ b/docs/libcurl/opts/CURLOPT_MAIL_FROM.md @@ -32,6 +32,9 @@ to specify the sender's email address when sending SMTP mail with libcurl. An originator email address should be specified with angled brackets (\<\>) around it, which if not specified are added automatically. +In order to specify DSN parameters (as per RFC 3461), the address has to be +written in angled brackets, followed by the parameters. + If this parameter is not specified then an empty address is sent to the SMTP server which might cause the email to be rejected. diff --git a/docs/libcurl/opts/CURLOPT_MAIL_RCPT.md b/docs/libcurl/opts/CURLOPT_MAIL_RCPT.md index 61c2155331..99ee600f58 100644 --- a/docs/libcurl/opts/CURLOPT_MAIL_RCPT.md +++ b/docs/libcurl/opts/CURLOPT_MAIL_RCPT.md @@ -40,6 +40,9 @@ pair of angled brackets (\<\>), however, should you not use an angled bracket as the first character libcurl assumes you provided a single email address and encloses that address within brackets for you. +In order to specify DSN parameters (as per RFC 3461), the address has to be +written in angled brackets, followed by the parameters. + When performing an address verification (**VRFY** command), each recipient should be specified as the username or username plus domain (as per Section 3.5 of RFC 5321). @@ -68,6 +71,7 @@ int main(void) struct curl_slist *list; list = curl_slist_append(NULL, "root@localhost"); list = curl_slist_append(list, "person@example.com"); + list = curl_slist_append(list, " NOTIFY=SUCCESS"); curl_easy_setopt(curl, CURLOPT_URL, "smtp://example.com/"); curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, list); res = curl_easy_perform(curl); diff --git a/lib/smtp.c b/lib/smtp.c index af8e69de5a..13b89b6ecc 100644 --- a/lib/smtp.c +++ b/lib/smtp.c @@ -169,7 +169,8 @@ static CURLcode smtp_parse_url_path(struct Curl_easy *data, 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); + 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, @@ -620,11 +621,12 @@ static CURLcode smtp_perform_command(struct Curl_easy *data, if((!smtp->custom) || (!smtp->custom[0])) { char *address = NULL; struct hostname host = { NULL, NULL, NULL, NULL }; + const char *suffix = ""; /* 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); + &address, &host, &suffix); if(result) return result; @@ -694,11 +696,12 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, 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); + &address, &host, &suffix); if(result) goto out; @@ -709,14 +712,14 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, (!Curl_is_ASCII_name(host.name))); if(host.name) { - from = aprintf("<%s@%s>", address, host.name); + from = aprintf("<%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 that and reply with a 501 error */ - from = aprintf("<%s>", address); + from = aprintf("<%s>%s", address, suffix); free(address); } @@ -734,11 +737,12 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, 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); + &address, &host, &suffix); if(result) goto out; @@ -750,14 +754,14 @@ static CURLcode smtp_perform_mail(struct Curl_easy *data, utf8 = TRUE; if(host.name) { - auth = aprintf("<%s@%s>", address, host.name); + auth = aprintf("<%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 = aprintf("<%s>", address); + auth = aprintf("<%s>%s", address, suffix); free(address); } else @@ -867,22 +871,24 @@ static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data, 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); + &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>", - address, 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>", address); + result = Curl_pp_sendf(data, &smtpc->pp, "RCPT TO:<%s>%s", + address, suffix); Curl_free_idnconverted_hostname(&host); free(address); @@ -1868,10 +1874,11 @@ static CURLcode smtp_parse_custom_request(struct Curl_easy *data, * the address part with the hostname being NULL. */ static CURLcode smtp_parse_address(const char *fqma, char **address, - struct hostname *host) + struct hostname *host, const char **suffix) { CURLcode result = CURLE_OK; size_t length; + char *addressend; /* Duplicate the fully qualified email address so we can manipulate it, ensuring it does not contain the delimiters if specified */ @@ -1879,10 +1886,19 @@ static CURLcode smtp_parse_address(const char *fqma, char **address, if(!dup) return CURLE_OUT_OF_MEMORY; - length = strlen(dup); - if(length) { - if(dup[length - 1] == '>') - dup[length - 1] = '\0'; + 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; + } } /* Extract the hostname from the address (if we can) */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 31f61216e6..a0a7879660 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -278,8 +278,7 @@ test3032 test3033 \ test3100 test3101 test3102 test3103 test3104 test3105 \ \ test3200 test3201 test3202 test3203 test3204 test3205 test3207 test3208 \ -test3209 test3210 test3211 test3212 test3213 test3214 \ -\ +test3209 test3210 test3211 test3212 test3213 test3214 test3215 \ test4000 test4001 EXTRA_DIST = $(TESTCASES) DISABLED diff --git a/tests/data/test3215 b/tests/data/test3215 new file mode 100644 index 0000000000..70c12e9d36 --- /dev/null +++ b/tests/data/test3215 @@ -0,0 +1,56 @@ + + + +SMTP DSN + + + +# +# Server-side + +# Special Replies, so the server does not have to understand DSN + +REPLY MAIL 250 Ok +REPLY RCPT 250 Ok + + + +# +# Client-side + + +smtp + + +SMTP DSN + + +From: different +To: another + +body + + +smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt " NOTIFY=SUCCESS,FAILURE" --mail-from " RET=HDRS" -T - + + + +# +# Verify data after the test has been "shot" + + +EHLO %TESTNUMBER +MAIL FROM: RET=HDRS +RCPT TO: NOTIFY=SUCCESS,FAILURE +DATA +QUIT + + +From: different +To: another + +body +. + + + -- 2.47.2