+Transport post-delivery actions
+--------------------------------------------------------------
+
+An arbitrary per-transport string can be expanded on successful delivery,
+and (for SMTP transports) a second string on deferrals caused by a host error.
+This feature may be used, for example, to write exim internal log information
+(not available otherwise) into a database.
+
+In order to use the feature, you must set
+
+EXPERIMENTAL_TPDA=yes
+
+in your Local/Makefile
+
+and define the expandable strings in the runtime config file, to
+be executed at end of delivery.
+
+Additionally, there are 6 more variables, available at end of
+delivery:
+
+tpda_delivery_ip IP of host, which has accepted delivery
+tpda_delivery_port Port of remote host which has accepted delivery
+tpda_delivery_fqdn FQDN of host, which has accepted delivery
+tpda_delivery_local_part local part of address being delivered
+tpda_delivery_domain domain part of address being delivered
+tpda_delivery_confirmation SMTP confirmation message
+
+In case of a deferral caused by a host-error:
+tpda_defer_errno Error number
+tpda_defer_errstr Error string possibly containing more details
+
+The $router_name and $transport_name variables are also usable.
+
+
+To take action after successful deliveries, set the following option
+on any transport of interest.
+
+tpda_delivery_action
+
+An example might look like:
+
+tpda_delivery_action = \
+${lookup pgsql {SELECT * FROM record_Delivery( \
+ '${quote_pgsql:$sender_address_domain}',\
+ '${quote_pgsql:${lc:$sender_address_local_part}}', \
+ '${quote_pgsql:$tpda_delivery_domain}', \
+ '${quote_pgsql:${lc:$tpda_delivery_local_part}}', \
+ '${quote_pgsql:$tpda_delivery_ip}', \
+ '${quote_pgsql:${lc:$tpda_delivery_fqdn}}', \
+ '${quote_pgsql:$message_exim_id}')}}
+
+The string is expanded after the delivery completes and any
+side-effects will happen. The result is then discarded.
+Note that for complex operations an ACL expansion can be used.
+
+
+In order to log host deferrals, add the following option to an SMTP
+transport:
+
+tpda_host_defer_action
+
+This is a private option of the SMTP transport. It is intended to
+log failures of remote hosts. It is executed only when exim has
+attempted to deliver a message to a remote host and failed due to
+an error which doesn't seem to be related to the individual
+message, sender, or recipient address.
+See section 45.2 of the exim documentation for more details on how
+this is determined.
+
+Example:
+
+tpda_host_defer_action = \
+${lookup mysql {insert into delivlog set \
+ msgid = '${quote_mysql:$message_exim_id}', \
+ senderlp = '${quote_mysql:${lc:$sender_address_local_part}}', \
+ senderdom = '${quote_mysql:$sender_address_domain}', \
+ delivlp = '${quote_mysql:${lc:$tpda_delivery_local_part}}', \
+ delivdom = '${quote_mysql:$tpda_delivery_domain}', \
+ delivip = '${quote_mysql:$tpda_delivery_ip}', \
+ delivport = '${quote_mysql:$tpda_delivery_port}', \
+ delivfqdn = '${quote_mysql:$tpda_delivery_fqdn}', \
+ deliverrno = '${quote_mysql:$tpda_defer_errno}', \
+ deliverrstr = '${quote_mysql:$tpda_defer_errstr}' \
+ }}
--------------------------------------------------------------
End of file
# Uncomment the following line to add Per-Recipient-Data-Response support.
# EXPERIMENTAL_PRDR=yes
+# Uncomment the following line to support Transport post-delivery actions,
+# eg. for logging to a database.
+# EXPERIMENTAL_TPDA=yes
+
###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
#define EXPERIMENTAL_PRDR
#define EXPERIMENTAL_SPF
#define EXPERIMENTAL_SRS
+#define EXPERIMENTAL_TPDA
/* For developers */
#define WANT_DEEPER_PRINTF_CHECKS
have a pointer to the host item that succeeded; local deliveries can have a
pointer to a single host item in their host list, for use by the transport. */
+#ifdef EXPERIMENTAL_TPDA
+ tpda_delivery_ip = NULL; /* presume no successful remote delivery */
+ tpda_delivery_port = 0;
+ tpda_delivery_fqdn = NULL;
+ tpda_delivery_local_part = NULL;
+ tpda_delivery_domain = NULL;
+ tpda_delivery_confirmation = NULL;
+#endif
+
s = reset_point = store_get(size);
log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
if (addr->transport->info->local)
{
if (addr->host_list != NULL)
+ {
s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+ #ifdef EXPERIMENTAL_TPDA
+ tpda_delivery_fqdn = addr->host_list->name;
+ #endif
+ }
if (addr->shadow_message != NULL)
s = string_cat(s, &size, &ptr, addr->shadow_message,
Ustrlen(addr->shadow_message));
addr->host_used->port));
if (continue_sequence > 1)
s = string_cat(s, &size, &ptr, US"*", 1);
+
+ #ifdef EXPERIMENTAL_TPDA
+ tpda_delivery_ip = addr->host_used->address;
+ tpda_delivery_port = addr->host_used->port;
+ tpda_delivery_fqdn = addr->host_used->name;
+ tpda_delivery_local_part = addr->local_part;
+ tpda_delivery_domain = addr->domain;
+ tpda_delivery_confirmation = addr->message;
+ #endif
}
#ifdef SUPPORT_TLS
s[ptr] = 0;
log_write(0, flags, "%s", s);
+
+#ifdef EXPERIMENTAL_TPDA
+if (addr->transport->tpda_delivery_action)
+ {
+ DEBUG(D_deliver)
+ debug_printf(" TPDA(Delivery): tpda_deliver_action=|%s| tpda_delivery_IP=%s\n",
+ addr->transport->tpda_delivery_action, tpda_delivery_ip);
+
+ router_name = addr->router->name;
+ transport_name = addr->transport->name;
+ if (!expand_string(addr->transport->tpda_delivery_action) && *expand_string_message)
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_deliver_action in %s: %s\n",
+ transport_name, expand_string_message);
+ router_name = NULL;
+ transport_name = NULL;
+ }
+#endif
store_reset(reset_point);
return;
}
#ifdef EXPERIMENTAL_PRDR
fprintf(f, " Experimental_PRDR");
#endif
+#ifdef EXPERIMENTAL_TPDA
+ fprintf(f, " Experimental_TPDA");
+#endif
fprintf(f, "\n");
fprintf(f, "Lookups (built-in):");
{ "tod_logfile", vtype_todlf, NULL },
{ "tod_zone", vtype_todzone, NULL },
{ "tod_zulu", vtype_todzulu, NULL },
+#ifdef EXPERIMENTAL_TPDA
+ { "tpda_defer_errno", vtype_int, &tpda_defer_errno },
+ { "tpda_defer_errstr", vtype_stringptr, &tpda_defer_errstr },
+ { "tpda_delivery_confirmation", vtype_stringptr, &tpda_delivery_confirmation },
+ { "tpda_delivery_domain", vtype_stringptr, &tpda_delivery_domain },
+ { "tpda_delivery_fqdn", vtype_stringptr, &tpda_delivery_fqdn },
+ { "tpda_delivery_ip", vtype_stringptr, &tpda_delivery_ip },
+ { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part },
+ { "tpda_delivery_port", vtype_int, &tpda_delivery_port },
+#endif
{ "transport_name", vtype_stringptr, &transport_name },
{ "value", vtype_stringptr, &lookup_value },
{ "version_number", vtype_stringptr, &version_string },
int timeout_frozen_after = 0;
BOOL timestamps_utc = FALSE;
+#ifdef EXPERIMENTAL_TPDA
+int tpda_defer_errno = 0;
+uschar *tpda_defer_errstr = NULL;
+uschar *tpda_delivery_ip = NULL;
+int tpda_delivery_port = 0;
+uschar *tpda_delivery_fqdn = NULL;
+uschar *tpda_delivery_local_part= NULL;
+uschar *tpda_delivery_domain = NULL;
+uschar *tpda_delivery_confirmation = NULL;
+#endif
+
transport_instance *transports = NULL;
transport_instance transport_defaults = {
extern int thismessage_size_limit; /* Limit for this message */
extern int timeout_frozen_after; /* Max time to keep frozen messages */
extern BOOL timestamps_utc; /* Use UTC for all times */
+
+#ifdef EXPERIMENTAL_TPDA
+extern int tpda_defer_errno; /* error number set when a remote delivery is deferred with a host error */
+extern uschar *tpda_defer_errstr; /* error string set when a remote delivery is deferred with a host error */
+extern uschar *tpda_delivery_ip; /* IP of host, which has accepted delivery */
+extern int tpda_delivery_port; /* port of host, which has accepted delivery */
+extern uschar *tpda_delivery_fqdn; /* FQDN of host, which has accepted delivery */
+extern uschar *tpda_delivery_local_part;/* local part of address being delivered */
+extern uschar *tpda_delivery_domain; /* domain part of address being delivered */
+extern uschar *tpda_delivery_confirmation; /* SMTP confirmation message */
+#endif
+
extern uschar *transport_name; /* Name of transport last started */
extern int transport_count; /* Count of bytes transported */
extern int transport_newlines; /* Accurate count of number of newline chars transported */
BOOL log_fail_output;
BOOL log_defer_output;
BOOL retry_use_local_part; /* Defaults true for local, false for remote */
+#ifdef EXPERIMENTAL_TPDA
+ uschar *tpda_delivery_action; /* String to expand on success */
+#endif
} transport_instance;
(void *)offsetof(transport_instance, shadow_condition) },
{ "shadow_transport", opt_stringptr|opt_public,
(void *)offsetof(transport_instance, shadow) },
+#ifdef EXPERIMENTAL_TPDA
+ { "tpda_delivery_action",opt_stringptr | opt_public,
+ (void *)offsetof(transport_instance, tpda_delivery_action) },
+#endif
{ "transport_filter", opt_stringptr|opt_public,
(void *)offsetof(transport_instance, filter_command) },
{ "transport_filter_timeout", opt_time|opt_public,
{ "tls_verify_certificates", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_verify_certificates) }
#endif
+#ifdef EXPERIMENTAL_TPDA
+ ,{ "tpda_host_defer_action", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, tpda_host_defer_action) },
+#endif
};
/* Size of the options list. An extern variable has to be used so that its
NULL, /* dkim_sign_headers */
NULL /* dkim_strict */
#endif
+#ifdef EXPERIMENTAL_TPDA
+ ,NULL /* tpda_host_defer_action */
+#endif
};
+#ifdef EXPERIMENTAL_TPDA
+/*************************************************
+* Post-defer action *
+*************************************************/
+
+/* This expands an arbitrary per-transport string.
+ It might, for example, be used to write to the database log.
+
+Arguments:
+ ob transport options block
+ addr the address item containing error information
+ host the current host
+
+Returns: nothing
+*/
+
+static void
+tpda_deferred(smtp_transport_options_block *ob, address_item *addr, host_item *host)
+{
+uschar *action = ob->tpda_host_defer_action;
+if (!action)
+ return;
+
+tpda_delivery_ip = string_copy(host->address);
+tpda_delivery_port = (host->port == PORT_NONE)? 25 : host->port;
+tpda_delivery_fqdn = string_copy(host->name);
+tpda_delivery_local_part = string_copy(addr->local_part);
+tpda_delivery_domain = string_copy(addr->domain);
+tpda_defer_errno = addr->basic_errno;
+
+tpda_defer_errstr = addr->message
+ ? addr->basic_errno > 0
+ ? string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno))
+ : string_copy(addr->message)
+ : addr->basic_errno > 0
+ ? string_copy(strerror(addr->basic_errno))
+ : NULL;
+
+DEBUG(D_transport)
+ debug_printf(" TPDA(host defer): tpda_host_defer_action=|%s| tpda_delivery_IP=%s\n",
+ action, tpda_delivery_ip);
+
+router_name = addr->router->name;
+transport_name = addr->transport->name;
+if (!expand_string(action) && *expand_string_message)
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_defer_action in %s: %s\n",
+ transport_name, expand_string_message);
+router_name = transport_name = NULL;
+}
+#endif
+
+
+
/*************************************************
* Synchronize SMTP responses *
*************************************************/
FALSE);
return DEFER;
}
-
+
return OK;
}
/* Set up confirmation if needed - applies only to SMTP */
- if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
+ if (
+ #ifndef EXPERIMENTAL_TPDA
+ (log_extra_selector & LX_smtp_confirmation) != 0 &&
+ #endif
+ !lmtp
+ )
{
uschar *s = string_printing(buffer);
conf = (s == buffer)? (uschar *)string_copy(s) : s;
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)
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)
first_addr->basic_errno != ERRNO_TLSFAILURE)
write_logs(first_addr, host);
+ #ifdef EXPERIMENTAL_TPDA
+ if (rc == DEFER)
+ tpda_deferred(ob, first_addr, host);
+ #endif
+
/* If STARTTLS was accepted, but there was a failure in setting up the
TLS session (usually a certificate screwup), and the host is not in
hosts_require_tls, and tls_tempfail_tryclear is true, try again, with
expanded_hosts != NULL, &message_defer, TRUE);
if (rc == DEFER && first_addr->basic_errno != ERRNO_AUTHFAIL)
write_logs(first_addr, host);
+ #ifdef EXPERIMENTAL_TPDA
+ if (rc == DEFER)
+ tpda_deferred(ob, first_addr, host);
+ #endif
}
#endif
}
uschar *dkim_sign_headers;
uschar *dkim_strict;
#endif
+ #ifdef EXPERIMENTAL_TPDA
+ uschar *tpda_host_defer_action;
+ #endif
} smtp_transport_options_block;
/* Data for reading the private options. */
--- /dev/null
+# Exim test configuration 5700
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_rcpt = accept
+acl_smtp_data = accept
+
+
+# ----- ACL -----
+
+begin acl
+
+logger:
+ accept condition = ${if eq {$acl_arg2}{domain1}}
+ logwrite = \
+ $acl_arg1 \
+ ip <$tpda_delivery_ip> \
+ port <$tpda_delivery_port> \
+ fqdn <$tpda_delivery_fqdn> \
+ local_part <$tpda_delivery_local_part> \
+ domain <$tpda_delivery_domain> \
+ confirmation <$tpda_delivery_confirmation> \
+ errno <$tpda_defer_errno> \
+ errstr <$tpda_defer_errstr> \
+ router <$router_name> \
+ transport <$transport_name>
+
+ accept logwrite = $this_expansion_will_fail
+
+# ----- Routers -----
+
+begin routers
+
+dump_bounces:
+ driver = redirect
+ senders = :
+ data = :blackhole:
+
+others:
+ driver = manualroute
+ route_list = * 127.0.0.1 byname
+ self = send
+ transport = smtp
+ no_more
+
+
+# ----- Transports -----
+
+begin transports
+
+smtp:
+ driver = smtp
+ port = PORT_S
+ command_timeout = 1s
+ final_timeout = 1s
+ tpda_delivery_action = ${acl {logger} {delivery} {$domain} }
+ tpda_host_defer_action = ${acl {logger} {deferral} {$domain} }
+
+# End
--- /dev/null
+1999-03-02 09:44:33 10HmaY-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp -qqf
+1999-03-02 09:44:33 10HmaY-0005vi-00 => userx@domain1 R=others T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmaY-0005vi-00 delivery ip <127.0.0.1> port <1224> fqdn <127.0.0.1> local_part <userx> domain <domain1> confirmation <250 OK> errno <0> errstr <> router <others> transport <smtp>
+1999-03-02 09:44:33 10HmaY-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp -qqf
+1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 Start queue run: pid=pppp -qqf
+1999-03-02 09:44:33 10HmaX-0005vi-00 => userx@domain2 R=others T=smtp H=127.0.0.1 [127.0.0.1] C="250 OK"
+1999-03-02 09:44:33 10HmaX-0005vi-00 failed to expand tpda_deliver_action in smtp: error from acl "logger"
+
+1999-03-02 09:44:33 10HmaX-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp -qqf
+1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss
+1999-03-02 09:44:33 10HmaZ-0005vi-00 SMTP timeout while connected to 127.0.0.1 [127.0.0.1] after EHLO the.local.host.name: Connection timed out
+1999-03-02 09:44:33 10HmaZ-0005vi-00 deferral ip <127.0.0.1> port <1224> fqdn <127.0.0.1> local_part <userx> domain <domain1> confirmation <> errno <110> errstr <SMTP timeout while connected to 127.0.0.1 [127.0.0.1] after EHLO the.local.host.name: Connection timed out> router <others> transport <smtp>
+1999-03-02 09:44:33 10HmaZ-0005vi-00 == userx@domain1 R=others T=smtp defer (dd): Connection timed out: SMTP timeout while connected to 127.0.0.1 [127.0.0.1] after EHLO the.local.host.name
+1999-03-02 09:44:33 10HmaZ-0005vi-00 ** userx@domain1: retry timeout exceeded
+1999-03-02 09:44:33 10HmbA-0005vi-00 <= <> R=10HmaZ-0005vi-00 U=EXIMUSER P=local S=sss
+1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: <CALLER@the.local.host.name> R=dump_bounces
+1999-03-02 09:44:33 10HmbA-0005vi-00 Completed
+1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed
--- /dev/null
+1999-03-02 09:44:33 10HmaX-0005vi-00 failed to expand tpda_deliver_action in smtp: error from acl "logger"
+
--- /dev/null
+# Arbtirary expansion after transport
+# (EXPERIMENTAL_TPDA)
+#
+need_ipv4
+#
+exim -odq userx@domain1
+A deliverable message
+****
+server PORT_S
+220 ESMTP
+EHLO
+250-OK
+250 HELP
+MAIL
+250 OK
+RCPT
+250 OK
+DATA
+354 More...
+.
+250 OK
+QUIT
+220 OK
+****
+exim -qqf
+****
+#
+#
+#
+#
+exim -odq userx@domain2
+A deliverable message which will hit a coding error in the config
+****
+server PORT_S
+220 ESMTP
+EHLO
+250-OK
+250 HELP
+MAIL
+250 OK
+RCPT
+250 OK
+DATA
+354 More...
+.
+250 OK
+QUIT
+220 OK
+****
+exim -qqf
+****
+#
+#
+#
+server PORT_S
+220 ESMTP
+EHLO
+*sleep 4
+****
+exim -odi userx@domain1
+A message which will hit a timeout at the destination server
+.
+****
+#
--- /dev/null
+support Experimental_TPDA
--- /dev/null
+1999-03-02 09:44:33 10HmaX-0005vi-00 failed to expand tpda_deliver_action in smtp: error from acl "logger"
+
--- /dev/null
+
+******** SERVER ********
+Listening on port 1224 ...
+Connection request from [127.0.0.1]
+220 ESMTP
+EHLO the.local.host.name
+250-OK
+250 HELP
+MAIL FROM:<CALLER@the.local.host.name>
+250 OK
+RCPT TO:<userx@domain1>
+250 OK
+DATA
+354 More...
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+ (envelope-from <CALLER@the.local.host.name>)
+ id 10HmaY-0005vi-00
+ for userx@domain1; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaY-0005vi-00@the.local.host.name>
+From: CALLER_NAME <CALLER@the.local.host.name>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+A deliverable message
+.
+250 OK
+QUIT
+220 OK
+End of script
+Listening on port 1224 ...
+Connection request from [127.0.0.1]
+220 ESMTP
+EHLO the.local.host.name
+250-OK
+250 HELP
+MAIL FROM:<CALLER@the.local.host.name>
+250 OK
+RCPT TO:<userx@domain2>
+250 OK
+DATA
+354 More...
+Received: from CALLER by the.local.host.name with local (Exim x.yz)
+ (envelope-from <CALLER@the.local.host.name>)
+ id 10HmaX-0005vi-00
+ for userx@domain2; Tue, 2 Mar 1999 09:44:33 +0000
+Message-Id: <E10HmaX-0005vi-00@the.local.host.name>
+From: CALLER_NAME <CALLER@the.local.host.name>
+Date: Tue, 2 Mar 1999 09:44:33 +0000
+
+A deliverable message which will hit a coding error in the config
+.
+250 OK
+QUIT
+220 OK
+End of script
+Listening on port 1224 ...
+Connection request from [127.0.0.1]
+220 ESMTP
+EHLO the.local.host.name
+*sleep 4
+End of script