summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/src/EDITME4
-rw-r--r--src/src/acl.c72
-rw-r--r--src/src/config.h.defaults1
-rw-r--r--src/src/deliver.c19
-rw-r--r--src/src/exim.c3
-rw-r--r--src/src/globals.c15
-rw-r--r--src/src/globals.h8
-rw-r--r--src/src/macros.h3
-rw-r--r--src/src/readconf.c6
-rw-r--r--src/src/receive.c110
-rw-r--r--src/src/smtp_in.c94
-rw-r--r--src/src/structs.h4
-rw-r--r--src/src/transports/smtp.c176
-rw-r--r--src/src/transports/smtp.h3
14 files changed, 462 insertions, 56 deletions
diff --git a/src/src/EDITME b/src/src/EDITME
index 95a0c02f4..7de915ae9 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -460,6 +460,10 @@ EXIM_MONITOR=eximon.bin
# EXPERIMENTAL_OCSP=yes
+# Uncomment the following line to add Per-Recipient-Data-Response support.
+
+# EXPERIMENTAL_PRDR=yes
+
###############################################################################
diff --git a/src/src/acl.c b/src/src/acl.c
index 5af408c5c..f61d2dfdf 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -368,6 +368,9 @@ static unsigned int cond_forbids[] = {
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* add_header */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_DKIM)|
(1<<ACL_WHERE_NOTSMTP_START)),
@@ -380,6 +383,9 @@ static unsigned int cond_forbids[] = {
(1<<ACL_WHERE_AUTH)| /* bmi_optin */
(1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
(1<<ACL_WHERE_MAILAUTH)|
(1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
@@ -398,7 +404,11 @@ static unsigned int cond_forbids[] = {
#ifdef EXPERIMENTAL_DCC
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* dcc */
+ ~((1<<ACL_WHERE_DATA)| /* dcc */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifdef WITH_CONTENT_SCAN
@@ -410,7 +420,11 @@ static unsigned int cond_forbids[] = {
#ifdef WITH_OLD_DEMIME
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* demime */
+ ~((1<<ACL_WHERE_DATA)| /* demime */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifndef DISABLE_DKIM
@@ -425,7 +439,11 @@ static unsigned int cond_forbids[] = {
(1<<ACL_WHERE_NOTSMTP_START),
(unsigned int)
- ~(1<<ACL_WHERE_RCPT), /* domains */
+ ~((1<<ACL_WHERE_RCPT) /* domains */
+ #ifdef EXPERIMENTAL_PRDR
+ |(1<<ACL_WHERE_PRDR)
+ #endif
+ ),
(1<<ACL_WHERE_NOTSMTP)| /* encrypted */
(1<<ACL_WHERE_CONNECT)|
@@ -438,7 +456,11 @@ static unsigned int cond_forbids[] = {
(1<<ACL_WHERE_NOTSMTP_START),
(unsigned int)
- ~(1<<ACL_WHERE_RCPT), /* local_parts */
+ ~((1<<ACL_WHERE_RCPT) /* local_parts */
+ #ifdef EXPERIMENTAL_PRDR
+ |(1<<ACL_WHERE_PRDR)
+ #endif
+ ),
0, /* log_message */
@@ -448,7 +470,11 @@ static unsigned int cond_forbids[] = {
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* malware */
+ ~((1<<ACL_WHERE_DATA)| /* malware */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
0, /* message */
@@ -465,13 +491,20 @@ static unsigned int cond_forbids[] = {
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* regex */
+ ~((1<<ACL_WHERE_DATA)| /* regex */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_MIME)),
#endif
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* remove_header */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_NOTSMTP_START)),
@@ -491,7 +524,11 @@ static unsigned int cond_forbids[] = {
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* spam */
+ ~((1<<ACL_WHERE_DATA)| /* spam */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifdef EXPERIMENTAL_SPF
@@ -535,6 +572,9 @@ static unsigned int control_forbids[] = {
#ifndef DISABLE_DKIM
(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* dkim_disable_verify */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
(1<<ACL_WHERE_NOTSMTP_START),
#endif
@@ -562,11 +602,13 @@ static unsigned int control_forbids[] = {
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* freeze */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* queue_only */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
(unsigned int)
@@ -582,17 +624,24 @@ static unsigned int control_forbids[] = {
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* no_mbox_unspool */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_MIME)),
#endif
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakedefer */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
(1<<ACL_WHERE_MIME)),
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakereject */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
(1<<ACL_WHERE_MIME)),
(1<<ACL_WHERE_NOTSMTP)| /* no_multiline */
@@ -4067,7 +4116,11 @@ sender_verified_failed = NULL;
ratelimiters_cmd = NULL;
log_reject_target = LOG_MAIN|LOG_REJECT;
-if (where == ACL_WHERE_RCPT)
+#ifdef EXPERIMENTAL_PRDR
+if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR )
+#else
+if (where == ACL_WHERE_RCPT )
+#endif
{
adb = address_defaults;
addr = &adb;
@@ -4107,6 +4160,9 @@ If conn-failure, no action (and keep the spooled copy).
switch (where)
{
case ACL_WHERE_RCPT:
+#ifdef EXPERIMENTAL_PRDR
+case ACL_WHERE_PRDR:
+#endif
if( rcpt_count > 1 )
cancel_cutthrough_connection("more than one recipient");
else if (rc == OK && cutthrough_delivery && cutthrough_fd < 0)
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 361051858..b6772c275 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -166,6 +166,7 @@ it's a default value. */
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_OCSP
+#define EXPERIMENTAL_PRDR
#define EXPERIMENTAL_SPF
#define EXPERIMENTAL_SRS
diff --git a/src/src/deliver.c b/src/src/deliver.c
index e2605ab2c..23e63d553 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -785,6 +785,11 @@ else
}
}
+ #ifdef EXPERIMENTAL_PRDR
+ if (addr->flags & af_prdr_used)
+ s = string_append(s, &size, &ptr, 1, US" PRDR");
+ #endif
+
if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
addr->message != NULL)
{
@@ -2913,6 +2918,11 @@ while (!done)
while (*ptr++);
break;
+#ifdef EXPERIMENTAL_PRDR
+ case 'P':
+ addr->flags |= af_prdr_used; break;
+#endif
+
case 'A':
if (addr == NULL)
{
@@ -4017,6 +4027,10 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
}
+ #ifdef EXPERIMENTAL_PRDR
+ if (addr->flags & af_prdr_used) rmt_dlv_checked_write(fd, "P", 1);
+ #endif
+
/* Retry information: for most success cases this will be null. */
for (r = addr->retries; r != NULL; r = r->next)
@@ -6101,6 +6115,11 @@ if (addr_remote != NULL)
regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
#endif
+ #ifdef EXPERIMENTAL_PRDR
+ if (regex_PRDR == NULL) regex_PRDR =
+ regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
+ #endif
+
/* Now sort the addresses if required, and do the deliveries. The yield of
do_remote_deliveries is FALSE when mua_wrapper is set and all addresses
cannot be delivered in one transaction. */
diff --git a/src/src/exim.c b/src/src/exim.c
index 91a3f7a4c..e66a9664d 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -819,6 +819,9 @@ fprintf(f, "Support for:");
#ifdef EXPERIMENTAL_OCSP
fprintf(f, " Experimental_OCSP");
#endif
+#ifdef EXPERIMENTAL_PRDR
+ fprintf(f, " Experimental_PRDR");
+#endif
fprintf(f, "\n");
fprintf(f, "Lookups (built-in):");
diff --git a/src/src/globals.c b/src/src/globals.c
index 43cf73d5b..5db858bfc 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -145,6 +145,12 @@ uschar *tls_verify_certificates= NULL;
uschar *tls_verify_hosts = NULL;
#endif
+#ifdef EXPERIMENTAL_PRDR
+/* Per Recipient Data Response variables */
+BOOL prdr_enable = FALSE;
+BOOL prdr_requested = FALSE;
+const pcre *regex_PRDR = NULL;
+#endif
/* Input-reading functions for messages, so we can use special ones for
incoming TCP/IP. The defaults use stdin. We never need these for any
@@ -202,6 +208,9 @@ uschar *acl_removed_headers = NULL;
uschar *acl_smtp_auth = NULL;
uschar *acl_smtp_connect = NULL;
uschar *acl_smtp_data = NULL;
+#ifdef EXPERIMENTAL_PRDR
+uschar *acl_smtp_data_prdr = NULL;
+#endif
#ifndef DISABLE_DKIM
uschar *acl_smtp_dkim = NULL;
#endif
@@ -235,6 +244,9 @@ uschar *acl_wherenames[] = { US"RCPT",
US"MIME",
US"DKIM",
US"DATA",
+#ifdef EXPERIMENTAL_PRDR
+ US"PRDR",
+#endif
US"non-SMTP",
US"AUTH",
US"connection",
@@ -257,6 +269,9 @@ uschar *acl_wherecodes[] = { US"550", /* RCPT */
US"550", /* MIME */
US"550", /* DKIM */
US"550", /* DATA */
+#ifdef EXPERIMENTAL_PRDR
+ US"550", /* RCPT PRDR */
+#endif
US"0", /* not SMTP; not relevant */
US"503", /* AUTH */
US"550", /* connect */
diff --git a/src/src/globals.h b/src/src/globals.h
index 06cbf312b..8d83be710 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -147,6 +147,10 @@ extern uschar *acl_removed_headers; /* Headers deleted by an ACL */
extern uschar *acl_smtp_auth; /* ACL run for AUTH */
extern uschar *acl_smtp_connect; /* ACL run on SMTP connection */
extern uschar *acl_smtp_data; /* ACL run after DATA received */
+#ifdef EXPERIMENTAL_PRDR
+extern uschar *acl_smtp_data_prdr; /* ACL run after DATA received if in PRDR mode*/
+const extern pcre *regex_PRDR; /* For recognizing PRDR settings */
+#endif
#ifndef DISABLE_DKIM
extern uschar *acl_smtp_dkim; /* ACL run for DKIM signatures / domains */
#endif
@@ -560,6 +564,10 @@ extern uschar *percent_hack_domains; /* Local domains for which '% operates */
extern uschar *pid_file_path; /* For writing daemon pids */
extern uschar *pipelining_advertise_hosts; /* As it says */
extern BOOL pipelining_enable; /* As it says */
+#ifdef EXPERIMENTAL_PRDR
+extern BOOL prdr_enable; /* As it says */
+extern BOOL prdr_requested; /* Connecting mail server wants PRDR */
+#endif
extern BOOL preserve_message_logs; /* Save msglog files */
extern uschar *primary_hostname; /* Primary name of this computer */
extern BOOL print_topbitchars; /* Topbit chars are printing chars */
diff --git a/src/src/macros.h b/src/src/macros.h
index f19d6fdbf..b878b415c 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -813,6 +813,9 @@ enum { ACL_WHERE_RCPT, /* Some controls are for RCPT only */
ACL_WHERE_MIME, /* ) implemented by <= WHERE_NOTSMTP */
ACL_WHERE_DKIM, /* ) */
ACL_WHERE_DATA, /* ) */
+#ifdef EXPERIMENTAL_PRDR
+ ACL_WHERE_PRDR, /* ) */
+#endif
ACL_WHERE_NOTSMTP, /* ) */
ACL_WHERE_AUTH, /* These remaining ones are not currently */
diff --git a/src/src/readconf.c b/src/src/readconf.c
index dbec45de3..bba532594 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -140,6 +140,9 @@ static optionlist optionlist_config[] = {
{ "acl_smtp_auth", opt_stringptr, &acl_smtp_auth },
{ "acl_smtp_connect", opt_stringptr, &acl_smtp_connect },
{ "acl_smtp_data", opt_stringptr, &acl_smtp_data },
+#ifdef EXPERIMENTAL_PRDR
+ { "acl_smtp_data_prdr", opt_stringptr, &acl_smtp_data_prdr },
+#endif
#ifndef DISABLE_DKIM
{ "acl_smtp_dkim", opt_stringptr, &acl_smtp_dkim },
#endif
@@ -316,6 +319,9 @@ static optionlist optionlist_config[] = {
#endif
{ "pid_file_path", opt_stringptr, &pid_file_path },
{ "pipelining_advertise_hosts", opt_stringptr, &pipelining_advertise_hosts },
+#ifdef EXPERIMENTAL_PRDR
+ { "prdr_enable", opt_bool, &prdr_enable },
+#endif
{ "preserve_message_logs", opt_bool, &preserve_message_logs },
{ "primary_hostname", opt_stringptr, &primary_hostname },
{ "print_topbitchars", opt_bool, &print_topbitchars },
diff --git a/src/src/receive.c b/src/src/receive.c
index efd0766e7..e0c1c7393 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -482,6 +482,34 @@ recipients_list[recipients_count++].errors_to = NULL;
/*************************************************
+* Send user response message *
+*************************************************/
+
+/* This function is passed a default response code and a user message. It calls
+smtp_message_code() to check and possibly modify the response code, and then
+calls smtp_respond() to transmit the response. I put this into a function
+just to avoid a lot of repetition.
+
+Arguments:
+ code the response code
+ user_msg the user message
+
+Returns: nothing
+*/
+
+static void
+smtp_user_msg(uschar *code, uschar *user_msg)
+{
+int len = 3;
+smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_respond(code, len, TRUE, user_msg);
+}
+
+
+
+
+
+/*************************************************
* Remove a recipient from the list *
*************************************************/
@@ -3199,6 +3227,77 @@ else
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
+#ifdef EXPERIMENTAL_PRDR
+ if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr != NULL )
+ {
+ unsigned int c;
+ int all_pass = OK;
+ int all_fail = FAIL;
+
+ smtp_printf("353 PRDR content analysis beginning\r\n");
+ /* Loop through recipients, responses must be in same order received */
+ for (c = 0; recipients_count > c; c++)
+ {
+ uschar * addr= recipients_list[c].address;
+ uschar * msg= US"PRDR R=<%s> %s";
+ uschar * code;
+ DEBUG(D_receive)
+ debug_printf("PRDR processing recipient %s (%d of %d)\n",
+ addr, c+1, recipients_count);
+ rc = acl_check(ACL_WHERE_PRDR, addr,
+ acl_smtp_data_prdr, &user_msg, &log_msg);
+
+ /* If any recipient rejected content, indicate it in final message */
+ all_pass |= rc;
+ /* If all recipients rejected, indicate in final message */
+ all_fail &= rc;
+
+ switch (rc)
+ {
+ case OK: case DISCARD: code = US"250"; break;
+ case DEFER: code = US"450"; break;
+ default: code = US"550"; break;
+ }
+ if (user_msg != NULL)
+ smtp_user_msg(code, user_msg);
+ else
+ {
+ switch (rc)
+ {
+ case OK: case DISCARD:
+ msg = string_sprintf(CS msg, addr, "acceptance"); break;
+ case DEFER:
+ msg = string_sprintf(CS msg, addr, "temporary refusal"); break;
+ default:
+ msg = string_sprintf(CS msg, addr, "refusal"); break;
+ }
+ smtp_user_msg(code, msg);
+ }
+ if (log_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg);
+ else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg);
+ else log_write(0, LOG_MAIN, CS msg);
+
+ if (rc != OK) { receive_remove_recipient(addr); c--; }
+ }
+ /* Set up final message, used if data acl gives OK */
+ smtp_reply = string_sprintf("%s id=%s message %s",
+ all_fail == FAIL ? US"550" : US"250",
+ message_id,
+ all_fail == FAIL
+ ? US"rejected for all recipients"
+ : all_pass == OK
+ ? US"accepted"
+ : US"accepted for some recipients");
+ if (recipients_count == 0)
+ {
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP;
+ }
+ }
+ else
+ prdr_requested = FALSE;
+#endif /* EXPERIMENTAL_PRDR */
+
/* Check the recipients count again, as the MIME ACL might have changed
them. */
@@ -3615,6 +3714,11 @@ if (sender_host_authenticated != NULL)
}
}
+#ifdef EXPERIMENTAL_PRDR
+if (prdr_requested)
+ s = string_append(s, &size, &sptr, 1, US" PRDR");
+#endif
+
sprintf(CS big_buffer, "%d", msg_size);
s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
@@ -3831,7 +3935,11 @@ if(cutthrough_fd >= 0)
}
}
-if(smtp_reply == NULL)
+if(smtp_reply == NULL
+#ifdef EXPERIMENTAL_PRDR
+ || prdr_requested
+#endif
+ )
{
log_write(0, LOG_MAIN |
(((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) |
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 243b8f7d1..cb1a86991 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -210,7 +210,10 @@ static uschar *protocols[] = {
/* Sanity check and validate optional args to MAIL FROM: envelope */
enum {
ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
- ENV_MAIL_OPT_PRDR, ENV_MAIL_OPT_NULL
+#ifdef EXPERIMENTAL_PRDR
+ ENV_MAIL_OPT_PRDR,
+#endif
+ ENV_MAIL_OPT_NULL
};
typedef struct {
uschar * name; /* option requested during MAIL cmd */
@@ -222,7 +225,10 @@ static env_mail_type_t env_mail_type_list[] = {
{ US"SIZE", ENV_MAIL_OPT_SIZE, TRUE },
{ US"BODY", ENV_MAIL_OPT_BODY, TRUE },
{ US"AUTH", ENV_MAIL_OPT_AUTH, TRUE },
- { US"NULL", ENV_MAIL_OPT_NULL, FALSE } /* Placeholder for ending */
+#ifdef EXPERIMENTAL_PRDR
+ { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE },
+#endif
+ { US"NULL", ENV_MAIL_OPT_NULL, FALSE }
};
/* When reading SMTP from a remote host, we have to use our own versions of the
@@ -998,19 +1004,23 @@ uschar *n;
uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
while (isspace(*v)) v--;
v[1] = 0;
-
while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
-if (*v != '=') return FALSE;
n = v;
-while(isalpha(n[-1])) n--;
-
-/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
-if (!isspace(n[-1])) return FALSE;
-
-n[-1] = 0;
-*name = n;
+if (*v == '=')
+{
+ while(isalpha(n[-1])) n--;
+ /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
+ if (!isspace(n[-1])) return FALSE;
+ n[-1] = 0;
+}
+else
+{
+ n++;
+ if (v == smtp_cmd_data) return FALSE;
+}
*v++ = 0;
+*name = n;
*value = v;
return TRUE;
}
@@ -2201,6 +2211,9 @@ uschar *what =
#endif
(where == ACL_WHERE_PREDATA)? US"DATA" :
(where == ACL_WHERE_DATA)? US"after DATA" :
+#ifdef EXPERIMENTAL_PRDR
+ (where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
+#endif
(smtp_cmd_data == NULL)?
string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data);
@@ -3119,6 +3132,7 @@ while (done <= 0)
pipelining_advertised = TRUE;
}
+
/* If any server authentication mechanisms are configured, advertise
them if the current host is in auth_advertise_hosts. The problem with
advertising always is that some clients then require users to
@@ -3177,6 +3191,14 @@ while (done <= 0)
}
#endif
+ #ifdef EXPERIMENTAL_PRDR
+ /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
+ if (prdr_enable) {
+ s = string_cat(s, &size, &ptr, smtp_code, 3);
+ s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
+ }
+ #endif
+
/* Finish off the multiline reply with one that is always available. */
s = string_cat(s, &size, &ptr, smtp_code, 3);
@@ -3293,17 +3315,12 @@ while (done <= 0)
}
if (mail_args->need_value && strcmpic(value, US"") == 0)
break;
- /* This doesn't seem right to use
- if ((char *)mail_args >= (char *)env_mail_type_list + sizeof(env_mail_type_list))
- goto BAD_MAIL_ARGS;
- */
switch(mail_args->value)
{
/* Handle SIZE= by reading the value. We don't do the check till later,
in order to be able to log the sender address on failure. */
case ENV_MAIL_OPT_SIZE:
- /* if (strcmpic(name, US"SIZE") == 0 && */
if (((size = Ustrtoul(value, &end, 10)), *end == 0))
{
if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
@@ -3355,8 +3372,8 @@ while (done <= 0)
if (auth_xtextdecode(value, &authenticated_sender) < 0)
{
/* Put back terminator overrides for error message */
- name[-1] = ' ';
value[-1] = '=';
+ name[-1] = ' ';
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"invalid data for AUTH");
goto COMMAND_LOOP;
@@ -3399,22 +3416,30 @@ while (done <= 0)
overrides for error message */
default:
- name[-1] = ' ';
value[-1] = '=';
+ name[-1] = ' ';
(void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
log_msg);
goto COMMAND_LOOP;
}
}
break;
-
+
+#ifdef EXPERIMENTAL_PRDR
+ case ENV_MAIL_OPT_PRDR:
+ if ( prdr_enable )
+ prdr_requested = TRUE;
+ break;
+#endif
+
/* Unknown option. Stick back the terminator characters and break
- the loop. An error for a malformed address will occur. */
+ the loop. Do the name-terminator second as extract_option sets
+ value==name when it found no equal-sign.
+ An error for a malformed address will occur. */
default:
-
- /* BAD_MAIL_ARGS: */
- name[-1] = ' ';
value[-1] = '=';
+ name[-1] = ' ';
+ arg_error = TRUE;
break;
}
/* Break out of for loop if switch() had bad argument or
@@ -3536,8 +3561,21 @@ while (done <= 0)
if (rc == OK || rc == DISCARD)
{
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
- else smtp_user_msg(US"250", user_msg);
+ if (user_msg == NULL)
+ smtp_printf("%s%s%s", US"250 OK",
+ #ifdef EXPERIMENTAL_PRDR
+ prdr_requested == TRUE ? US", PRDR Requested" :
+ #endif
+ US"",
+ US"\r\n");
+ else
+ {
+ #ifdef EXPERIMENTAL_PRDR
+ if ( prdr_requested == TRUE )
+ user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
+ #endif
+ smtp_user_msg(US"250",user_msg);
+ }
smtp_delay_rcpt = smtp_rlr_base;
recipients_discarded = (rc == DISCARD);
was_rej_mail = FALSE;
@@ -3801,9 +3839,11 @@ while (done <= 0)
if (rc == OK)
{
+ uschar * code;
+ code = US"354";
if (user_msg == NULL)
- smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n");
- else smtp_user_msg(US"354", user_msg);
+ smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code);
+ else smtp_user_msg(code, user_msg);
done = 3;
message_ended = END_NOTENDED; /* Indicate in middle of data */
}
diff --git a/src/src/structs.h b/src/src/structs.h
index 5fc01e9e5..d11e91adb 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -483,6 +483,10 @@ typedef struct address_item_propagated {
#define af_pass_message 0x02000000 /* pass message in bounces */
#define af_bad_reply 0x04000000 /* filter could not generate autoreply */
+#ifdef EXPERIMENTAL_PRDR
+# define af_prdr_used 0x08000000 /* delivery used SMTP PRDR */
+#endif
+
/* These flags must be propagated when a child is created */
#define af_propagate (af_ignore_error)
diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c
index 6c3507609..ee260a129 100644
--- a/src/src/transports/smtp.c
+++ b/src/src/transports/smtp.c
@@ -106,6 +106,10 @@ optionlist smtp_transport_options[] = {
#endif
{ "hosts_try_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_try_auth) },
+#ifdef EXPERIMENTAL_PRDR
+ { "hosts_try_prdr", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_try_prdr) },
+#endif
#ifdef SUPPORT_TLS
{ "hosts_verify_avoid_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_verify_avoid_tls) },
@@ -172,6 +176,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
NULL, /* serialize_hosts */
NULL, /* hosts_try_auth */
NULL, /* hosts_require_auth */
+#ifdef EXPERIMENTAL_PRDR
+ NULL, /* hosts_try_prdr */
+#endif
NULL, /* hosts_require_tls */
NULL, /* hosts_avoid_tls */
US"*", /* hosts_verify_avoid_tls */
@@ -871,6 +878,10 @@ BOOL completed_address = FALSE;
BOOL esmtp = TRUE;
BOOL pending_MAIL;
BOOL pass_message = FALSE;
+#ifdef EXPERIMENTAL_PRDR
+BOOL prdr_offered = FALSE;
+BOOL prdr_active;
+#endif
smtp_inblock inblock;
smtp_outblock outblock;
int max_rcpt = tblock->max_addresses;
@@ -1066,6 +1077,17 @@ goto SEND_QUIT;
pcre_exec(regex_STARTTLS, NULL, CS buffer, Ustrlen(buffer), 0,
PCRE_EOPT, NULL, 0) >= 0;
#endif
+
+ #ifdef EXPERIMENTAL_PRDR
+ prdr_offered = esmtp &&
+ (pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(buffer), 0,
+ PCRE_EOPT, NULL, 0) >= 0) &&
+ (verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+ host->address, NULL) == OK);
+
+ if (prdr_offered)
+ {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+ #endif
}
/* For continuing deliveries down the same channel, the socket is the standard
@@ -1266,6 +1288,17 @@ if (continue_hostname == NULL
DEBUG(D_transport) debug_printf("%susing PIPELINING\n",
smtp_use_pipelining? "" : "not ");
+#ifdef EXPERIMENTAL_PRDR
+ prdr_offered = esmtp &&
+ pcre_exec(regex_PRDR, NULL, CS buffer, Ustrlen(CS buffer), 0,
+ PCRE_EOPT, NULL, 0) >= 0 &&
+ verify_check_this_host(&(ob->hosts_try_prdr), NULL, host->name,
+ host->address, NULL) == OK;
+
+ if (prdr_offered)
+ {DEBUG(D_transport) debug_printf("PRDR usable\n");}
+#endif
+
/* Note if the response to EHLO specifies support for the AUTH extension.
If it has, check that this host is one we want to authenticate to, and do
the business. The host name and address must be available when the
@@ -1469,6 +1502,26 @@ if (smtp_use_size)
while (*p) p++;
}
+#ifdef EXPERIMENTAL_PRDR
+prdr_active = FALSE;
+if (prdr_offered)
+ {
+ for (addr = first_addr; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ {
+ for (addr = addr->next; addr; addr = addr->next)
+ if (addr->transport_return == PENDING_DEFER)
+ { /* at least two recipients to send */
+ prdr_active = TRUE;
+ sprintf(CS p, " PRDR"); p += 5;
+ goto prdr_is_active;
+ }
+ break;
+ }
+ }
+prdr_is_active:
+#endif
+
/* If an authenticated_sender override has been specified for this transport
instance, expand it. If the expansion is forced to fail, and there was already
an authenticated_sender for this message, the original value will be used.
@@ -1709,8 +1762,31 @@ if (!ok) ok = TRUE; else
smtp_command = US"end of data";
- /* For SMTP, we now read a single response that applies to the whole message.
- If it is OK, then all the addresses have been delivered. */
+#ifdef EXPERIMENTAL_PRDR
+ /* For PRDR we optionally get a partial-responses warning
+ * followed by the individual responses, before going on with
+ * the overall response. If we don't get the warning then deal
+ * with per non-PRDR. */
+ if(prdr_active)
+ {
+ ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '3',
+ ob->final_timeout);
+ if (!ok && errno == 0)
+ switch(buffer[0])
+ {
+ case '2': prdr_active = FALSE;
+ ok = TRUE;
+ break;
+ case '4': errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ break;
+ }
+ }
+ else
+#endif
+
+ /* For non-PRDR SMTP, we now read a single response that applies to the
+ whole message. If it is OK, then all the addresses have been delivered. */
if (!lmtp)
{
@@ -1764,7 +1840,7 @@ if (!ok) ok = TRUE; else
conf = (s == buffer)? (uschar *)string_copy(s) : s;
}
- /* Process all transported addresses - for LMTP, read a status for
+ /* Process all transported addresses - for LMTP or PRDR, read a status for
each one. */
for (addr = addrlist; addr != first_addr; addr = addr->next)
@@ -1776,13 +1852,22 @@ if (!ok) ok = TRUE; else
address. For temporary errors, add a retry item for the address so that
it doesn't get tried again too soon. */
+#ifdef EXPERIMENTAL_PRDR
+ if (lmtp || prdr_active)
+#else
if (lmtp)
+#endif
{
if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
ob->final_timeout))
{
if (errno != 0 || buffer[0] == 0) goto RESPONSE_FAILED;
- addr->message = string_sprintf("LMTP error after %s: %s",
+ addr->message = string_sprintf(
+#ifdef EXPERIMENTAL_PRDR
+ "%s error after %s: %s", prdr_active ? "PRDR":"LMTP",
+#else
+ "LMTP error after %s: %s",
+#endif
big_buffer, string_printing(buffer));
setflag(addr, af_pass_message); /* Allow message to go to user */
if (buffer[0] == '5')
@@ -1792,7 +1877,10 @@ if (!ok) ok = TRUE; else
errno = ERRNO_DATA4XX;
addr->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
addr->transport_return = DEFER;
- retry_add_item(addr, addr->address_retry_key, 0);
+#ifdef EXPERIMENTAL_PRDR
+ if (!prdr_active)
+#endif
+ retry_add_item(addr, addr->address_retry_key, 0);
}
continue;
}
@@ -1812,25 +1900,73 @@ if (!ok) ok = TRUE; else
addr->host_used = thost;
addr->special_action = flag;
addr->message = conf;
+#ifdef EXPERIMENTAL_PRDR
+ if (prdr_active) addr->flags |= af_prdr_used;
+#endif
flag = '-';
- /* Update the journal. For homonymic addresses, use the base address plus
- the transport name. See lots of comments in deliver.c about the reasons
- for the complications when homonyms are involved. Just carry on after
- write error, as it may prove possible to update the spool file later. */
-
- if (testflag(addr, af_homonym))
- sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
- else
- sprintf(CS buffer, "%.500s\n", addr->unique);
-
- DEBUG(D_deliver) debug_printf("journalling %s", buffer);
- len = Ustrlen(CS buffer);
- if (write(journal_fd, buffer, len) != len)
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
- "%s: %s", buffer, strerror(errno));
+#ifdef EXPERIMENTAL_PRDR
+ if (!prdr_active)
+#endif
+ {
+ /* Update the journal. For homonymic addresses, use the base address plus
+ the transport name. See lots of comments in deliver.c about the reasons
+ for the complications when homonyms are involved. Just carry on after
+ write error, as it may prove possible to update the spool file later. */
+
+ if (testflag(addr, af_homonym))
+ sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ else
+ sprintf(CS buffer, "%.500s\n", addr->unique);
+
+ DEBUG(D_deliver) debug_printf("journalling %s", buffer);
+ len = Ustrlen(CS buffer);
+ if (write(journal_fd, buffer, len) != len)
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+ "%s: %s", buffer, strerror(errno));
+ }
}
+#ifdef EXPERIMENTAL_PRDR
+ if (prdr_active)
+ {
+ /* PRDR - get the final, overall response. For any non-success
+ upgrade all the address statuses. */
+ ok = smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
+ ob->final_timeout);
+ if (!ok)
+ {
+ if(errno == 0 && buffer[0] == '4')
+ {
+ errno = ERRNO_DATA4XX;
+ addrlist->more_errno |= ((buffer[1] - '0')*10 + buffer[2] - '0') << 8;
+ }
+ for (addr = addrlist; addr != first_addr; addr = addr->next)
+ if (buffer[0] == '5' || addr->transport_return == OK)
+ addr->transport_return = PENDING_OK; /* allow set_errno action */
+ goto RESPONSE_FAILED;
+ }
+
+ /* Update the journal, or setup retry. */
+ for (addr = addrlist; addr != first_addr; addr = addr->next)
+ if (addr->transport_return == OK)
+ {
+ if (testflag(addr, af_homonym))
+ sprintf(CS buffer, "%.500s/%s\n", addr->unique + 3, tblock->name);
+ else
+ sprintf(CS buffer, "%.500s\n", addr->unique);
+
+ DEBUG(D_deliver) debug_printf("journalling(PRDR) %s", buffer);
+ len = Ustrlen(CS buffer);
+ if (write(journal_fd, buffer, len) != len)
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to write journal for "
+ "%s: %s", buffer, strerror(errno));
+ }
+ else if (addr->transport_return == DEFER)
+ retry_add_item(addr, addr->address_retry_key, -2);
+ }
+#endif
+
/* Ensure the journal file is pushed out to disk. */
if (EXIMfsync(journal_fd) < 0)
diff --git a/src/src/transports/smtp.h b/src/src/transports/smtp.h
index 79f1b8c50..ef53292bc 100644
--- a/src/src/transports/smtp.h
+++ b/src/src/transports/smtp.h
@@ -21,6 +21,9 @@ typedef struct {
uschar *serialize_hosts;
uschar *hosts_try_auth;
uschar *hosts_require_auth;
+#ifdef EXPERIMENTAL_PRDR
+ uschar *hosts_try_prdr;
+#endif
uschar *hosts_require_tls;
uschar *hosts_avoid_tls;
uschar *hosts_verify_avoid_tls;