]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
smtp: allow suffix behind a mail address for RFC 3461
authorDominik Tomecki <my-name-is-already-taken@users.noreply.github.com>
Wed, 30 Jul 2025 07:48:13 +0000 (09:48 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 30 Jul 2025 07:52:34 +0000 (09:52 +0200)
Verified in test 3215

Closes #16643

.github/scripts/spellcheck.words
docs/libcurl/opts/CURLOPT_MAIL_FROM.md
docs/libcurl/opts/CURLOPT_MAIL_RCPT.md
lib/smtp.c
tests/data/Makefile.am
tests/data/test3215 [new file with mode: 0644]

index f8796eaa1ff1d44d413da2d8354f3d42ee977a98..90546bb65f5217e8d2933a40070d4897143762b3 100644 (file)
@@ -205,6 +205,7 @@ DoT
 doxygen
 drftpd
 dsa
+DSN
 dtrace
 Dudka
 Dymond
index cf329239032bc9930442f74f9a3c514c42e096e4..aa5656e413e334132d13fa201ad0f4bd1d57de68 100644 (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.
 
index 61c2155331d47b6c2cfefaafd3724460eef884e5..99ee600f589ebd4b35231c1f3dfc7f9f68eaa5a8 100644 (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);
index af8e69de5afbd2cf54db8fd2ed609a898f8b80c3..13b89b6ecce1fd881d7803054637f5441fed3fac 100644 (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) */
index 31f61216e6b26d61b5b56b61613d1e58a0abfa2c..a0a78796607c2e539d358f16259dc6afe2bdedcf 100644 (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
diff --git a/tests/data/test3215 b/tests/data/test3215
new file mode 100644 (file)
index 0000000..70c12e9
--- /dev/null
@@ -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\r
+To: another\r
+\r
+body\r
+</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\r
+MAIL FROM:<sender@example.com> RET=HDRS\r
+RCPT TO:<recipient@example.com> NOTIFY=SUCCESS,FAILURE\r
+DATA\r
+QUIT\r
+</protocol>
+<upload>
+From: different\r
+To: another\r
+\r
+body\r
+.\r
+</upload>
+</verify>
+</testcase>