smtp: Allow RCPT TO command to fail for some recipients

Introduces CURLOPT_MAIL_RCPT_ALLLOWFAILS.

Verified with the new tests 3002-3007

Closes #4816
This commit is contained in:
Pavel Volgarev 2020-01-14 17:22:38 -05:00 committed by Daniel Stenberg
parent 23a17e039d
commit 4a4609bf3c
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
22 changed files with 483 additions and 26 deletions

View File

@ -35,9 +35,8 @@ problems may have been fixed or changed somewhat since this was written!
3. Email protocols
3.1 IMAP SEARCH ALL truncated response
3.2 No disconnect command
3.3 SMTP to multiple recipients
3.4 POP3 expects "CRLF.CRLF" eob for some single-line responses
3.5 AUTH PLAIN for SMTP is not working on all servers
3.3 POP3 expects "CRLF.CRLF" eob for some single-line responses
3.4 AUTH PLAIN for SMTP is not working on all servers
4. Command line
4.1 -J and -O with %-encoded file names
@ -276,21 +275,13 @@ problems may have been fixed or changed somewhat since this was written!
The disconnect commands (LOGOUT and QUIT) may not be sent by IMAP, POP3 and
SMTP if a failure occurs during the authentication phase of a connection.
3.3 SMTP to multiple recipients
When sending data to multiple recipients, curl will abort and return failure
if one of the recipients indicate failure (on the "RCPT TO"
command). Ordinary mail programs would proceed and still send to the ones
that can receive data. This is subject for change in the future.
https://curl.haxx.se/bug/view.cgi?id=1116
3.4 POP3 expects "CRLF.CRLF" eob for some single-line responses
3.3 POP3 expects "CRLF.CRLF" eob for some single-line responses
You have to tell libcurl not to expect a body, when dealing with one line
response commands. Please see the POP3 examples and test cases which show
this for the NOOP and DELE commands. https://curl.haxx.se/bug/?i=740
3.5 AUTH PLAIN for SMTP is not working on all servers
3.4 AUTH PLAIN for SMTP is not working on all servers
Specifying "--login-options AUTH=PLAIN" on the command line doesn't seem to
work correctly.

View File

@ -0,0 +1,15 @@
Long: mail-rcpt-allowfails
Help: Allow RCPT TO command to fail for some recipients
Protocols: SMTP
Added: 7.69.0
---
When sending data to multiple recipients, by default curl will abort SMTP
conversation if at least one of the recipients causes RCPT TO command to
return an error.
The default behavior can be changed by passing --mail-rcpt-allowfails
command-line option which will make curl ignore errors and proceed with the
remaining valid recipients.
In case when all recipients cause RCPT TO command to fail, curl will abort SMTP
conversation and return the error received from to the last RCPT TO command.

View File

@ -357,6 +357,8 @@ Address of the sender. See \fICURLOPT_MAIL_FROM(3)\fP
Address of the recipients. See \fICURLOPT_MAIL_RCPT(3)\fP
.IP CURLOPT_MAIL_AUTH
Authentication address. See \fICURLOPT_MAIL_AUTH(3)\fP
.IP CURLOPT_MAIL_RCPT_ALLLOWFAILS
Allow RCPT TO command to fail for some recipients. See \fICURLOPT_MAIL_RCPT_ALLLOWFAILS(3)\fP
.SH TFTP OPTIONS
.IP CURLOPT_TFTP_BLKSIZE
TFTP block size. See \fICURLOPT_TFTP_BLKSIZE(3)\fP

View File

@ -0,0 +1,71 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
.\" *
.\" * This software is licensed as described in the file COPYING, which
.\" * you should have received as part of this distribution. The terms
.\" * are also available at https://curl.haxx.se/docs/copyright.html.
.\" *
.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
.\" * copies of the Software, and permit persons to whom the Software is
.\" * furnished to do so, under the terms of the COPYING file.
.\" *
.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
.\" * KIND, either express or implied.
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_MAIL_RCPT_ALLLOWFAILS 3 "16 Jan 2020" "libcurl 7.69.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_MAIL_RCPT_ALLLOWFAILS \- allow RCPT TO command to fail for some recipients
.SH SYNOPSIS
.nf
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_MAIL_RCPT_ALLLOWFAILS,
long allow);
.SH DESCRIPTION
If \fIallow\fP is set to 1L, allow RCPT TO command to fail for some recipients.
When sending data to multiple recipients, by default curl will abort SMTP
conversation if at least one of the recipients causes RCPT TO command to
return an error.
The default behavior can be changed by setting \fIignore\fP to 1L which will
make curl ignore errors and proceed with the remaining valid recipients.
In case when all recipients cause RCPT TO command to fail, curl will abort SMTP
conversation and return the error received from to the last RCPT TO command.
.SH DEFAULT
0
.SH PROTOCOLS
SMTP
.SH EXAMPLE
.nf
CURL *curl = curl_easy_init();
if(curl) {
struct curl_slist *list;
/* Adding one valid and one invalid email address */
list = curl_slist_append(NULL, "person@example.com");
list = curl_slist_append(list, "invalidemailaddress");
curl_easy_setopt(curl, CURLOPT_URL, "smtp://example.com/");
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT_ALLLOWFAILS, 1L);
ret = curl_easy_perform(curl);
curl_slist_free_all(list);
curl_easy_cleanup(curl);
}
.fi
.SH AVAILABILITY
Added in 7.69.0.
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH "SEE ALSO"
.BR CURLOPT_MAIL_FROM "(3), " CURLOPT_MAIL_RCPT "(3), "

View File

@ -188,6 +188,7 @@ man_MANS = \
CURLOPT_MAIL_AUTH.3 \
CURLOPT_MAIL_FROM.3 \
CURLOPT_MAIL_RCPT.3 \
CURLOPT_MAIL_RCPT_ALLLOWFAILS.3 \
CURLOPT_MAXAGE_CONN.3 \
CURLOPT_MAXCONNECTS.3 \
CURLOPT_MAXFILESIZE.3 \

View File

@ -471,6 +471,7 @@ CURLOPT_LOW_SPEED_TIME 7.1
CURLOPT_MAIL_AUTH 7.25.0
CURLOPT_MAIL_FROM 7.20.0
CURLOPT_MAIL_RCPT 7.20.0
CURLOPT_MAIL_RCPT_ALLLOWFAILS 7.69.0
CURLOPT_MAXAGE_CONN 7.65.0
CURLOPT_MAXCONNECTS 7.7
CURLOPT_MAXFILESIZE 7.10.8

View File

@ -1937,6 +1937,9 @@ typedef enum {
/* SASL authorisation identity */
CURLOPT(CURLOPT_SASL_AUTHZID, CURLOPTTYPE_STRINGPOINT, 289),
/* allow RCPT TO command to fail for some recipients */
CURLOPT(CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOPTTYPE_LONG, 290),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -2391,6 +2391,10 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
/* Set the list of mail recipients */
data->set.mail_rcpt = va_arg(param, struct curl_slist *);
break;
case CURLOPT_MAIL_RCPT_ALLLOWFAILS:
/* allow RCPT TO command to fail for some recipients */
data->set.mail_rcpt_allowfails = (0 != va_arg(param, long)) ? TRUE : FALSE;
break;
#endif
case CURLOPT_SASL_AUTHZID:

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -915,25 +915,53 @@ static CURLcode smtp_state_rcpt_resp(struct connectdata *conn, int smtpcode,
CURLcode result = CURLE_OK;
struct Curl_easy *data = conn->data;
struct SMTP *smtp = data->req.protop;
bool is_smtp_err = FALSE;
bool is_smtp_blocking_err = FALSE;
(void)instate; /* no use for this yet */
if(smtpcode/100 != 2) {
failf(data, "RCPT failed: %d", smtpcode);
result = CURLE_SEND_ERROR;
is_smtp_err = (smtpcode/100 != 2) ? TRUE : FALSE;
/* If there's multiple RCPT TO to be issued, it's possible to ignore errors
and proceed with only the valid addresses. */
is_smtp_blocking_err =
(is_smtp_err && !data->set.mail_rcpt_allowfails) ? TRUE : FALSE;
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;
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;
}
if(!is_smtp_blocking_err) {
smtp->rcpt = smtp->rcpt->next;
if(smtp->rcpt)
/* Send the next RCPT TO command */
result = smtp_perform_rcpt_to(conn);
else {
/* Send the DATA command */
result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", "DATA");
/* We weren't 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(&conn->proto.smtpc.pp, "%s", "DATA");
if(!result)
state(conn, SMTP_DATA);
if(!result)
state(conn, SMTP_DATA);
}
}
}
@ -1287,6 +1315,12 @@ static CURLcode smtp_perform(struct connectdata *conn, bool *connected,
/* Store the first recipient (or NULL if not specified) */
smtp->rcpt = data->set.mail_rcpt;
/* Track of whether we've successfully sent at least one RCPT TO command */
smtp->rcpt_had_ok = FALSE;
/* Track of the last error we've received by sending RCPT TO command */
smtp->rcpt_last_error = 0;
/* Initial data character is the first character in line: it is implicitly
preceded by a virtual CRLF. */
smtp->trailing_crlf = TRUE;

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2009 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 2009 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -55,6 +55,9 @@ struct SMTP {
curl_pp_transfer transfer;
char *custom; /* Custom Request */
struct curl_slist *rcpt; /* Recipient list */
bool rcpt_had_ok; /* Whether any of RCPT TO commands (depends on
total number of recipients) succeeded so far */
int rcpt_last_error; /* The last error received for RCPT TO command */
size_t eob; /* Number of bytes of the EOB (End Of Body) that
have been received so far */
bool trailing_crlf; /* Specifies if the tailing CRLF is present */

View File

@ -1791,6 +1791,8 @@ struct UserDefined {
BIT(doh); /* DNS-over-HTTPS enabled */
BIT(doh_get); /* use GET for DoH requests, instead of POST */
BIT(http09_allowed); /* allow HTTP/0.9 responses */
BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some
recipients */
};
struct Names {

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -108,6 +108,7 @@ struct OperationConfig {
char *mail_from;
struct curl_slist *mail_rcpt;
char *mail_auth;
bool mail_rcpt_allowfails; /* --mail-rcpt-allowfails */
char *sasl_authzid; /* Authorisation identity (identity to use) */
bool sasl_ir; /* Enable/disable SASL initial response */
bool proxytunnel;

View File

@ -273,6 +273,7 @@ static const struct LongShort aliases[]= {
{"f", "fail", ARG_BOOL},
{"fa", "fail-early", ARG_BOOL},
{"fb", "styled-output", ARG_BOOL},
{"fc", "mail-rcpt-allowfails", ARG_BOOL},
{"F", "form", ARG_STRING},
{"Fs", "form-string", ARG_STRING},
{"g", "globoff", ARG_BOOL},
@ -1722,6 +1723,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
case 'b': /* --styled-output */
global->styled_output = toggle;
break;
case 'c': /* --mail-rcpt-allowfails */
config->mail_rcpt_allowfails = toggle;
break;
default: /* --fail (hard on errors) */
config->failonerror = toggle;
}

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -239,6 +239,8 @@ static const struct helptxt helptext[] = {
"Mail from this address"},
{" --mail-rcpt <address>",
"Mail to this address"},
{" --mail-rcpt-allowfails",
"Allow RCPT TO command to fail for some recipients"},
{"-M, --manual",
"Display the full manual"},
{" --max-filesize <bytes>",

View File

@ -1835,6 +1835,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
if(config->mail_rcpt)
my_setopt_slist(curl, CURLOPT_MAIL_RCPT, config->mail_rcpt);
/* curl 7.69.x */
my_setopt(curl, CURLOPT_MAIL_RCPT_ALLLOWFAILS,
config->mail_rcpt_allowfails ? 1L : 0L);
/* curl 7.20.x */
if(config->ftp_pret)
my_setopt(curl, CURLOPT_FTP_USE_PRET, 1L);

View File

@ -208,4 +208,5 @@ test2078 \
test2080 \
test2100 \
\
test3000 test3001
test3000 test3001 \
test3002 test3003 test3004 test3005 test3006 test3007

55
tests/data/test3002 Normal file
View File

@ -0,0 +1,55 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with multiple and invalid (first) --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3002 --mail-rcpt-allowfails --mail-rcpt invalid.one --mail-rcpt recipient.two@example.com --mail-rcpt recipient.three@example.com --mail-rcpt recipient.four@example.com --mail-rcpt recipient.five@example.com --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
EHLO 3002
MAIL FROM:<sender@example.com>
RCPT TO:<invalid.one>
RCPT TO:<recipient.two@example.com>
RCPT TO:<recipient.three@example.com>
RCPT TO:<recipient.four@example.com>
RCPT TO:<recipient.five@example.com>
DATA
QUIT
</protocol>
<upload>
From: different
To: another
body
.
</upload>
</verify>
</testcase>

55
tests/data/test3003 Normal file
View File

@ -0,0 +1,55 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with multiple and invalid (last) --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3003 --mail-rcpt-allowfails --mail-rcpt recipient.one@example.com --mail-rcpt recipient.two@example.com --mail-rcpt recipient.three@example.com --mail-rcpt recipient.four@example.com --mail-rcpt invalid.five --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
EHLO 3003
MAIL FROM:<sender@example.com>
RCPT TO:<recipient.one@example.com>
RCPT TO:<recipient.two@example.com>
RCPT TO:<recipient.three@example.com>
RCPT TO:<recipient.four@example.com>
RCPT TO:<invalid.five>
DATA
QUIT
</protocol>
<upload>
From: different
To: another
body
.
</upload>
</verify>
</testcase>

55
tests/data/test3004 Normal file
View File

@ -0,0 +1,55 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with multiple and invalid (middle) --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3004 --mail-rcpt-allowfails --mail-rcpt recipient.one@example.com --mail-rcpt recipient.two@example.com --mail-rcpt invalid.three --mail-rcpt recipient.four@example.com --mail-rcpt recipient.five@example.com --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
EHLO 3004
MAIL FROM:<sender@example.com>
RCPT TO:<recipient.one@example.com>
RCPT TO:<recipient.two@example.com>
RCPT TO:<invalid.three>
RCPT TO:<recipient.four@example.com>
RCPT TO:<recipient.five@example.com>
DATA
QUIT
</protocol>
<upload>
From: different
To: another
body
.
</upload>
</verify>
</testcase>

55
tests/data/test3005 Normal file
View File

@ -0,0 +1,55 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with multiple and invalid (all but one) --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3005 --mail-rcpt-allowfails --mail-rcpt invalid.one --mail-rcpt recipient.two@example.com --mail-rcpt invalid.three --mail-rcpt invalid.four --mail-rcpt invalid.five --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol>
EHLO 3005
MAIL FROM:<sender@example.com>
RCPT TO:<invalid.one>
RCPT TO:<recipient.two@example.com>
RCPT TO:<invalid.three>
RCPT TO:<invalid.four>
RCPT TO:<invalid.five>
DATA
QUIT
</protocol>
<upload>
From: different
To: another
body
.
</upload>
</verify>
</testcase>

51
tests/data/test3006 Normal file
View File

@ -0,0 +1,51 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with multiple invalid (all) --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3006 --mail-rcpt-allowfails --mail-rcpt invalid.one --mail-rcpt invalid.two --mail-rcpt invalid.three --mail-rcpt invalid.four --mail-rcpt invalid.five --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
# 55 - CURLE_SEND_ERROR
<errorcode>
55
</errorcode>
<protocol>
EHLO 3006
MAIL FROM:<sender@example.com>
RCPT TO:<invalid.one>
RCPT TO:<invalid.two>
RCPT TO:<invalid.three>
RCPT TO:<invalid.four>
RCPT TO:<invalid.five>
QUIT
</protocol>
</verify>
</testcase>

47
tests/data/test3007 Normal file
View File

@ -0,0 +1,47 @@
<testcase>
<info>
<keywords>
SMTP
</keywords>
</info>
#
# Server-side
<reply>
</reply>
#
# Client-side
<client>
<server>
smtp
</server>
<name>
SMTP with invalid --mail-rcpt and --mail-rcpt-allowfails
</name>
<stdin>
From: different
To: another
body
</stdin>
<command>
smtp://%HOSTIP:%SMTPPORT/3007 --mail-rcpt-allowfails --mail-rcpt invalid.one --mail-from sender@example.com -T -
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
# 55 - CURLE_SEND_ERROR
<errorcode>
55
</errorcode>
<protocol>
EHLO 3007
MAIL FROM:<sender@example.com>
RCPT TO:<invalid.one>
QUIT
</protocol>
</verify>
</testcase>