smtp: allow suffix behind a mail address for RFC 3461

Verified in test 3215

Closes #16643
This commit is contained in:
Dominik Tomecki 2025-07-30 09:48:13 +02:00 committed by Daniel Stenberg
parent 3ccffad28d
commit 450c00f983
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 98 additions and 19 deletions

View file

@ -205,6 +205,7 @@ DoT
doxygen
drftpd
dsa
DSN
dtrace
Dudka
Dymond

View file

@ -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.

View file

@ -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, "<other@example.com> NOTIFY=SUCCESS");
curl_easy_setopt(curl, CURLOPT_URL, "smtp://example.com/");
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, list);
res = curl_easy_perform(curl);

View file

@ -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) */

View file

@ -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

56
tests/data/test3215 Normal file
View file

@ -0,0 +1,56 @@
<testcase>
<info>
<keywords>
SMTP DSN
</keywords>
</info>
#
# Server-side
<reply>
# Special Replies, so the server does not have to understand DSN
<servercmd>
REPLY MAIL 250 Ok
REPLY RCPT 250 Ok
</servercmd>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP DSN
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/%TESTNUMBER --mail-rcpt "<recipient@example.com> NOTIFY=SUCCESS,FAILURE" --mail-from "<sender@example.com> RET=HDRS" -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
EHLO %TESTNUMBER
MAIL FROM:<sender@example.com> RET=HDRS
RCPT TO:<recipient@example.com> NOTIFY=SUCCESS,FAILURE
DATA
QUIT
</protocol>
<upload>
From: different
To: another
body
.
</upload>
</verify>
</testcase>