While a feature is experimental, there will be a build-time
option whose name starts "EXPERIMENTAL_" that must be set in
order to include the feature. This file contains information
- about experimenatal features, all of which are unstable and
- liable to incompatibile change.
+ about experimental features, all of which are unstable and
+ liable to incompatible change.
+
+
+ PRDR support
+ --------------------------------------------------------------
+
+ Per-Recipient Data Reponse is an SMTP extension proposed by Eric Hall
+ in a (now-expired) IETF draft from 2007. It's not hit mainstream
+ use, but has apparently been implemented in the META1 MTA.
+
+ There is mention at http://mail.aegee.org/intern/sendmail.html
+ of a patch to sendmail "to make it PRDR capable".
+
+ ref: http://www.eric-a-hall.com/specs/draft-hall-prdr-00.txt
+
+ If Exim is built with EXPERIMENTAL_PRDR there is a new config
+ boolean "prdr_enable" which controls whether PRDR is advertised
+ as part of an EHLO response, a new "acl_data_smtp_prdr" ACL
+ (called for each recipient, after data arrives but before the
+ data ACL), and a new smtp transport option "hosts_try_prdr".
+
+ PRDR may be used to support per-user content filtering. Without it
+ one must defer any recipient after the first that has a different
+ content-filter configuration. With PRDR, the RCPT-time check
+ for this can be disabled when the MAIL-time $smtp_command included
+ "PRDR". Any required difference in behaviour of the main DATA-time
+ ACL should however depend on the PRDR-time ACL having run, as Exim
+ will avoid doing so in some situations (eg. single-recipient mails).
+
OCSP Stapling support
--------------------------------------------------------------
- X509 PKI certificates expire and can be revoked; to handle this, the
+ X.509 PKI certificates expire and can be revoked; to handle this, the
clients need some way to determine if a particular certificate, from a
particular Certificate Authority (CA), is still valid. There are three
main ways to do so.
proof expires. The downside is that it requires server support.
If Exim is built with EXPERIMENTAL_OCSP and it was built with OpenSSL,
- then it gains one new option: "tls_ocsp_file".
+ then it gains a new global option: "tls_ocsp_file".
The file specified therein is expected to be in DER format, and contain
an OCSP proof. Exim will serve it as part of the TLS handshake. This
Exim will check for a valid next update timestamp in the OCSP proof;
if not present, or if the proof has expired, it will be ignored.
+ Also, given EXPERIMENTAL_OCSP and OpenSSL, the smtp transport gains
+ a "hosts_require_ocsp" option; a host-list for which an OCSP Stapling
+ is requested and required for the connection to proceed. The host(s)
+ should also be in "hosts_require_tls", and "tls_verify_certificates"
+ configured for the transport.
+
+ For the client to be able to verify the stapled OCSP the server must
+ also supply, in its stapled information, any intermediate
+ certificates for the chain leading to the OCSP proof from the signer
+ of the server certificate. There may be zero or one such. These
+ intermediate certificates should be added to the server OCSP stapling
+ file (named by tls_ocsp_file).
+
At this point in time, we're gathering feedback on use, to determine if
it's worth adding complexity to the Exim daemon to periodically re-fetch
- OCSP files and somehow handling multiple files. There is no client support
- for OCSP in Exim, this is feature expected to be used by mail clients.
+ OCSP files and somehow handling multiple files.
+
+ A helper script "ocsp_fetch.pl" for fetching a proof from a CA
+ OCSP server is supplied. The server URL may be included in the
+ server certificate, if the CA is helpful.
+
+ One fail mode seen was the OCSP Signer cert expiring before the end
+ of vailidity of the OCSP proof. The checking done by Exim/OpenSSL
+ noted this as invalid overall, but the re-fetch script did not.
You can now run SPF checks in incoming SMTP by using the "spf"
ACL condition in either the MAIL, RCPT or DATA ACLs. When
- using it in the RCPT ACL, you can make the checks dependend on
+ using it in the RCPT ACL, you can make the checks dependent on
the RCPT address (or domain), so you can check SPF records
only for certain target domains. This gives you the
possibility to opt-out certain customers that do not want
When the spf_guess condition has run, it sets up the same expansion
variables as when spf condition is run, described above.
- Additionally, since Best-guess is not standarized, you may redefine
+ Additionally, since Best-guess is not standardized, you may redefine
what "Best-guess" means to you by redefining spf_guess variable in
global config. For example, the following:
After that "$dcc_header" contains the X-DCC-Header.
- Returnvalues are:
+ Return values are:
fail for overall "R", "G" from dccifd
defer for overall "T" from dccifd
accept for overall "A", "S" from dccifd
If you want to pass even more headers in the middle of the
DATA stage you can set
$acl_m_dcc_add_header
- to tell the DCC routines add more information; eg, you might set
+ to tell the DCC routines to add more information; eg, you might set
this to some results from ClamAV. Be careful. Header syntax is
not checked and is added "as is".
+ In case you've troubles with sites sending the same queue items from several
+ hosts and fail to get through greylisting you can use
+ $acl_m_dcc_override_client_ip
+
+ Setting $acl_m_dcc_override_client_ip to an IP address overrides the default
+ of $sender_host_address. eg. use the following ACL in DATA stage:
+
+ warn set acl_m_dcc_override_client_ip = \
+ ${lookup{$sender_helo_name}nwildlsearch{/etc/mail/multipleip_sites}{$value}{}}
+ condition = ${if def:acl_m_dcc_override_client_ip}
+ log_message = dbg: acl_m_dcc_override_client_ip set to \
+ $acl_m_dcc_override_client_ip
+
+ Then set something like
+ # cat /etc/mail/multipleip_sites
+ mout-xforward.gmx.net 82.165.159.12
+ mout.gmx.net 212.227.15.16
+
+ Use a reasonable IP. eg. one the sending cluster acutally uses.
+
+ DMARC Support
+ --------------------------------------------------------------
+
+ DMARC combines feedback from SPF, DKIM, and header From: in order
+ to attempt to provide better indicators of the authenticity of an
+ email. This document does not explain the fundamentals, you
+ should read and understand how it works by visiting the website at
+ http://www.dmarc.org/.
+
+ DMARC support is added via the libopendmarc library. Visit:
+
+ http://sourceforge.net/projects/opendmarc/
+
+ to obtain a copy, or find it in your favorite rpm package
+ repository. If building from source, this description assumes
+ that headers will be in /usr/local/include, and that the libraries
+ are in /usr/local/lib.
+
+ 1. To compile Exim with DMARC support, you must first enable SPF.
+ Please read the above section on enabling the EXPERIMENTAL_SPF
+ feature. You must also have DKIM support, so you cannot set the
+ DISABLE_DKIM feature. Once both of those conditions have been met
+ you can enable DMARC in Local/Makefile:
+
+ EXPERIMENTAL_DMARC=yes
+ LDFLAGS += -lopendmarc
+ # CFLAGS += -I/usr/local/include
+ # LDFLAGS += -L/usr/local/lib
+
+ The first line sets the feature to include the correct code, and
+ the second line says to link the libopendmarc libraries into the
+ exim binary. The commented out lines should be uncommented if you
+ built opendmarc from source and installed in the default location.
+ Adjust the paths if you installed them elsewhere, but you do not
+ need to uncomment them if an rpm (or you) installed them in the
+ package controlled locations (/usr/include and /usr/lib).
+
+
+ 2. Use the following global settings to configure DMARC:
+
+ Required:
+ dmarc_tld_file Defines the location of a text file of valid
+ top level domains the opendmarc library uses
+ during domain parsing. Maintained by Mozilla,
+ the most current version can be downloaded
+ from a link at http://publicsuffix.org/list/.
+
+ Optional:
+ dmarc_history_file Defines the location of a file to log results
+ of dmarc verification on inbound emails. The
+ contents are importable by the opendmarc tools
+ which will manage the data, send out DMARC
+ reports, and expire the data. Make sure the
+ directory of this file is writable by the user
+ exim runs as.
+
+ dmarc_forensic_sender The email address to use when sending a
+ forensic report detailing alignment failures
+ if a sender domain's dmarc record specifies it
+ and you have configured Exim to send them.
+ Default: do-not-reply@$default_hostname
+
+
+ 3. By default, the DMARC processing will run for any remote,
+ non-authenticated user. It makes sense to only verify DMARC
+ status of messages coming from remote, untrusted sources. You can
+ use standard conditions such as hosts, senders, etc, to decide that
+ DMARC verification should *not* be performed for them and disable
+ DMARC with a control setting:
+
+ control = dmarc_verify_disable
+
+ A DMARC record can also specify a "forensic address", which gives
+ exim an email address to submit reports about failed alignment.
+ Exim does not do this by default because in certain conditions it
+ results in unintended information leakage (what lists a user might
+ be subscribed to, etc). You must configure exim to submit forensic
+ reports to the owner of the domain. If the DMARC record contains a
+ forensic address and you specify the control statement below, then
+ exim will send these forensic emails. It's also advised that you
+ configure a dmarc_forensic_sender because the default sender address
+ construction might be inadequate.
+
+ control = dmarc_forensic_enable
+
+ (AGAIN: You can choose not to send these forensic reports by simply
+ not putting the dmarc_forensic_enable control line at any point in
+ your exim config. If you don't tell it to send them, it will not
+ send them.)
+
+ There are no options to either control. Both must appear before
+ the DATA acl.
+
+
+ 4. You can now run DMARC checks in incoming SMTP by using the
+ "dmarc_status" ACL condition in the DATA ACL. You are required to
+ call the spf condition first in the ACLs, then the "dmarc_status"
+ condition. Putting this condition in the ACLs is required in order
+ for a DMARC check to actually occur. All of the variables are set
+ up before the DATA ACL, but there is no actual DMARC check that
+ occurs until a "dmarc_status" condition is encountered in the ACLs.
+
+ The dmarc_status condition takes a list of strings on its
+ right-hand side. These strings describe recommended action based
+ on the DMARC check. To understand what the policy recommendations
+ mean, refer to the DMARC website above. Valid strings are:
+
+ o accept The DMARC check passed and the library recommends
+ accepting the email.
+ o reject The DMARC check failed and the library recommends
+ rejecting the email.
+ o quarantine The DMARC check failed and the library recommends
+ keeping it for further inspection.
+ o norecord No policy section in the DMARC record for this
+ sender domain.
+ o nofrom Unable to determine the domain of the sender.
+ o none There is no DMARC record for this sender domain.
+ o error Library error or dns error.
+
+ You can prefix each string with an exclamation mark to invert its
+ meaning, for example "!accept" will match all results but
+ "accept". The string list is evaluated left-to-right in a
+ short-circuit fashion. When a string matches the outcome of the
+ DMARC check, the condition succeeds. If none of the listed
+ strings matches the outcome of the DMARC check, the condition
+ fails.
+
+ Of course, you can also use any other lookup method that Exim
+ supports, including LDAP, Postgres, MySQL, etc, as long as the
+ result is a list of colon-separated strings;
+
+ Several expansion variables are set before the DATA ACL is
+ processed, and you can use them in this ACL. The following
+ expansion variables are available:
+
+ o $dmarc_status
+ This is a one word status indicating what the DMARC library
+ thinks of the email.
+
+ o $dmarc_status_text
+ This is a slightly longer, human readable status.
+
+ o $dmarc_used_domain
+ This is the domain which DMARC used to look up the DMARC
+ policy record.
+
+ o $dmarc_ar_header
+ This is the entire Authentication-Results header which you can
+ add using an add_header modifier.
+
+
+ 5. How to enable DMARC advanced operation:
+ By default, Exim's DMARC configuration is intended to be
+ non-intrusive and conservative. To facilitate this, Exim will not
+ create any type of logging files without explicit configuration by
+ you, the admin. Nor will Exim send out any emails/reports about
+ DMARC issues without explicit configuration by you, the admin (other
+ than typical bounce messages that may come about due to ACL
+ processing or failure delivery issues).
+
+ In order to log statistics suitable to be imported by the opendmarc
+ tools, you need to:
+ a. Configure the global setting dmarc_history_file.
+ b. Configure cron jobs to call the appropriate opendmarc history
+ import scripts and truncating the dmarc_history_file.
+
+ In order to send forensic reports, you need to:
+ a. Configure the global setting dmarc_forensic_sender.
+ b. Configure, somewhere before the DATA ACL, the control option to
+ enable sending DMARC forensic reports.
+
+
+ 6. Example usage:
+ (RCPT ACL)
+ warn domains = +local_domains
+ hosts = +local_hosts
+ control = dmarc_verify_disable
+
+ warn !domains = +screwed_up_dmarc_records
+ control = dmarc_enable_forensic
+
+ (DATA ACL)
+ warn dmarc_status = accept : none : off
+ !authenticated = *
+ log_message = DMARC DEBUG: $dmarc_status $dmarc_used_domain
+ add_header = $dmarc_ar_header
+
+ warn dmarc_status = !accept
+ !authenticated = *
+ log_message = DMARC DEBUG: '$dmarc_status' for $dmarc_used_domain
+
+ warn dmarc_status = quarantine
+ !authenticated = *
+ set $acl_m_quarantine = 1
+ # Do something in a transport with this flag variable
+
+ deny dmarc_status = reject
+ !authenticated = *
+ message = Message from $domain_used_domain failed sender's DMARC policy, REJECT
+
+
+
+DBL (Database Logging)
+--------------------------------------------------------------
+
+This feature allows to write exim internal log information
+(not available otherwise) into a database.
+Initially implemented is logging of details about successfully
+completed remote deliveries, which are needed for reputation
+systems, and deferrals caused by a host error.
+
+In order to use DBL, you must set
+
+EXPERIMENTAL_DBL=yes
+
+in your Local/Makefile
+
+and define the database queries in the runtime config file, to
+be executed at end of delivery.
+
+Additionally, there are 8 more variables, available at end of
+delivery:
+
+dbl_delivery_ip IP of host, which has accepted delivery
+dbl_delivery_port Port of remote host which has accepted delivery
+dbl_delivery_fqdn FQDN of host, which has accepted delivery
+dbl_delivery_local_part local part of address being delivered
+dbl_delivery_domain domain part of address being delivered
+dbl_delivery_confirmation SMTP confirmation message
+
+In case of a deferral caused by a host-error:
+dbl_defer_errno Error number
+dbl_defer_errstr Error string possibly containing more details
+
+
+To log successful deliveries, set the following option in the main
+option part of runtime config.
+
+dbl_delivery_query
+
+An example might look like:
+
+dbl_delivery_query = \
+${lookup pgsql {SELECT * FROM record_Delivery( \
+ '${quote_pgsql:$sender_address_domain}',\
+ '${quote_pgsql:${lc:$sender_address_local_part}}', \
+ '${quote_pgsql:$dbl_delivery_domain}', \
+ '${quote_pgsql:${lc:$dbl_delivery_local_part}}', \
+ '${quote_pgsql:$dbl_delivery_ip}', \
+ '${quote_pgsql:${lc:$dbl_delivery_fqdn}}', \
+ '${quote_pgsql:$message_exim_id}')}}
+
+
+In order to log host deferrals, add the following option to an SMTP
+transport:
+
+dbl_host_defer_query
+
+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:
+
+dbl_host_defer_query = \
+${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:$dbl_delivery_local_part}}', \
+ delivdom = '${quote_mysql:$dbl_delivery_domain}', \
+ delivip = '${quote_mysql:$dbl_delivery_ip}', \
+ delivport = '${quote_mysql:$dbl_delivery_port}', \
+ delivfqdn = '${quote_mysql:$dbl_delivery_fqdn}', \
+ deliverrno = '${quote_mysql:$dbl_defer_errno}', \
+ deliverrstr = '${quote_mysql:$dbl_defer_errstr}' \
+ }}
--------------------------------------------------------------
End of file
# DISABLE_DKIM=yes
+ #------------------------------------------------------------------------------
+ # By default, Exim has support for checking the AD bit in a DNS response, to
+ # determine if DNSSEC validation was successful. If your system libraries
+ # do not support that bit, then set DISABLE_DNSSEC to "yes"
+
+ # DISABLE_DNSSEC=yes
+
+
#------------------------------------------------------------------------------
# Compiling Exim with experimental features. These are documented in
# experimental-spec.txt. "Experimental" means that the way these features are
# EXPERIMENTAL_OCSP=yes
+ # Uncomment the following line to add DMARC checking capability, implemented
+ # using libopendmarc libraries.
+ # EXPERIMENTAL_DMARC=yes
+ # CFLAGS += -I/usr/local/include
+ # LDFLAGS += -lopendmarc
+
+ # Uncomment the following line to add Per-Recipient-Data-Response support.
+ # EXPERIMENTAL_PRDR=yes
+
+# This feature allows to write log information about a completed
+# delivery into a database
+# EXPERIMENTAL_DBL=yes
+
+
###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
###############################################################################
#define DELIVER_IN_BUFFER_SIZE 8192
#define DELIVER_OUT_BUFFER_SIZE 8192
#define DISABLE_DKIM
+ #define DISABLE_DNSSEC
#define DISABLE_D_OPTION
#define ENABLE_DISABLE_FSYNC
#define EXIMDB_LOCK_TIMEOUT 60
#define EXIMDB_LOCKFILE_MODE 0640
#define EXIMDB_MODE 0640
- #define EXIM_CLIENT_DH_MIN_BITS
+ #define EXIM_CLIENT_DH_MIN_MIN_BITS 512
+ #define EXIM_CLIENT_DH_DEFAULT_MIN_BITS 1024
#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL
#define EXIM_SERVER_DH_BITS_PRE2_12
#define EXIM_PERL
/* EXPERIMENTAL features */
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_DCC
+ #define EXPERIMENTAL_DMARC
#define EXPERIMENTAL_OCSP
+ #define EXPERIMENTAL_PRDR
#define EXPERIMENTAL_SPF
#define EXPERIMENTAL_SRS
+#define EXPERIMENTAL_DBL
/* For developers */
#define WANT_DEEPER_PRINTF_CHECKS
#define ROOT_UID 0
#define ROOT_GID 0
- /* Sizes for integer arithmetic. Go for 64bit; can be overridden in OS/os.h-FOO */
- #ifndef int_eximarith_t
- #define int_eximarith_t int64_t
- #endif
- #ifndef PR_EXIM_ARITH
- #define PR_EXIM_ARITH "%" PRId64 /* C99 standard, printf %lld */
- #endif
- #ifndef SC_EXIM_ARITH
- #define SC_EXIM_ARITH "%" SCNi64 /* scanf incl. 0x prefix */
- #endif
- #ifndef SC_EXIM_DEC
- #define SC_EXIM_DEC "%" SCNd64 /* scanf decimal */
- #endif
+ /* Sizes for integer arithmetic.
+ Go for 64bit; can be overridden in OS/Makefile-FOO
+ If you make it a different number of bits, provide a definition
+ for EXIM_64B_MAX and _MIN in OS/oh.h-FOO */
+ #define int_eximarith_t int64_t
+ #define PR_EXIM_ARITH "%" PRId64 /* C99 standard, printf %lld */
+ #define SC_EXIM_ARITH "%" SCNi64 /* scanf incl. 0x prefix */
+ #define SC_EXIM_DEC "%" SCNd64 /* scanf decimal */
/* End of config.h.defaults */
+ /* If msg is NULL this is a delivery log and logchar is used. Otherwise
+ this is a nonstandard call; no two-characher delivery flag is written
+ but sender-host and sender are prefixed and "msg" is inserted in the log line.
+
+ Arguments:
+ flags passed to log_write()
+ */
+ void
+ delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
+ {
+ uschar *log_address;
+ int size = 256; /* Used for a temporary, */
+ int ptr = 0; /* expanding buffer, for */
+ uschar *s; /* building log lines; */
+ void *reset_point; /* released afterwards. */
+
+
+ /* Log the delivery on the main log. We use an extensible string to build up
+ the log line, and reset the store afterwards. Remote deliveries should always
+ 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. */
+
+ s = reset_point = store_get(size);
+
+ log_address = string_log_address(addr, (log_write_selector & L_all_parents) != 0, TRUE);
+ if (msg)
+ s = string_append(s, &size, &ptr, 3, host_and_ident(TRUE), US" ", log_address);
+ else
+ {
+ s[ptr++] = logchar;
+ s = string_append(s, &size, &ptr, 2, US"> ", log_address);
+ }
+
+ if ((log_extra_selector & LX_sender_on_delivery) != 0 || msg)
+ s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+
+ #ifdef EXPERIMENTAL_SRS
+ if(addr->p.srs_sender)
+ s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
+ #endif
++#ifdef EXPERIMENTAL_DBL
++ dbl_delivery_ip = NULL; /* presume no successful remote delivery */
++#endif
+
+ /* You might think that the return path must always be set for a successful
+ delivery; indeed, I did for some time, until this statement crashed. The case
+ when it is not set is for a delivery to /dev/null which is optimised by not
+ being run at all. */
+
+ if (used_return_path != NULL &&
+ (log_extra_selector & LX_return_path_on_delivery) != 0)
+ s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+
+ if (msg)
+ s = string_append(s, &size, &ptr, 2, US" ", msg);
+
+ /* For a delivery from a system filter, there may not be a router */
+ if (addr->router != NULL)
+ s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+
+ s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+
+ if ((log_extra_selector & LX_delivery_size) != 0)
+ s = string_append(s, &size, &ptr, 2, US" S=",
+ string_sprintf("%d", transport_count));
+
+ /* Local delivery */
+
+ if (addr->transport->info->local)
+ {
+ if (addr->host_list != NULL)
+ s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+ if (addr->shadow_message != NULL)
+ s = string_cat(s, &size, &ptr, addr->shadow_message,
+ Ustrlen(addr->shadow_message));
+ }
+
+ /* Remote delivery */
+
+ else
+ {
+ if (addr->host_used != NULL)
+ {
+ s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name,
+ US" [", addr->host_used->address, US"]");
+ if ((log_extra_selector & LX_outgoing_port) != 0)
+ s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d",
+ addr->host_used->port));
+ if (continue_sequence > 1)
+ s = string_cat(s, &size, &ptr, US"*", 1);
+ }
+
+ #ifdef SUPPORT_TLS
+ if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
+ s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher);
+ if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+ addr->cipher != NULL)
+ s = string_append(s, &size, &ptr, 2, US" CV=",
+ testflag(addr, af_cert_verified)? "yes":"no");
+ if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
+ s = string_append(s, &size, &ptr, 3, US" DN=\"",
+ string_printing(addr->peerdn), US"\"");
+ #endif
+
+ if (addr->authenticator)
+ {
+ s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+ if (addr->auth_id)
+ {
+ s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
+ if (log_extra_selector & LX_smtp_mailauth && addr->auth_sndr)
+ s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+ }
+ }
+
+ #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)
+ {
+ int i;
+ uschar *p = big_buffer;
+ uschar *ss = addr->message;
+ *p++ = '\"';
+ for (i = 0; i < 100 && ss[i] != 0; i++)
+ {
+ if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
+ *p++ = ss[i];
+ }
+ *p++ = '\"';
+ *p = 0;
+ s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+ }
+ }
+
+ /* Time on queue and actual time taken to deliver */
+
+ if ((log_extra_selector & LX_queue_time) != 0)
+ {
+ s = string_append(s, &size, &ptr, 2, US" QT=",
+ readconf_printtime(time(NULL) - received_time));
+ }
+
+ if ((log_extra_selector & LX_deliver_time) != 0)
+ {
+ s = string_append(s, &size, &ptr, 2, US" DT=",
+ readconf_printtime(addr->more_errno));
+ }
+
+ /* string_cat() always leaves room for the terminator. Release the
+ store we used to build the line after writing it. */
+
+ s[ptr] = 0;
+ log_write(0, flags, "%s", s);
++#ifdef EXPERIMENTAL_DBL
++DEBUG(D_deliver)
++ {
++ debug_printf(" DBL(Delivery): dbl_delivery_query=|%s| dbl_delivery_IP=%s\n", dbl_delivery_query, dbl_delivery_ip);
++ }
++if (dbl_delivery_ip != NULL && dbl_delivery_query != NULL)
++ expand_string(dbl_delivery_query);
++#endif
+ store_reset(reset_point);
+ return;
+ }
+
+
+
/*************************************************
* Actions at the end of handling an address *
*************************************************/
(void)close(addr->return_file);
}
- /* Create the address string for logging. Must not do this earlier, because
- an OK result may be changed to FAIL when a pipe returns text. */
-
- log_address = string_log_address(addr,
- (log_write_selector & L_all_parents) != 0, result == OK);
-
/* The sucess case happens only after delivery by a transport. */
if (result == OK)
child_done(addr, now);
}
- /* Log the delivery on the main log. We use an extensible string to build up
- the log line, and reset the store afterwards. Remote deliveries should always
- 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. */
-
- s = reset_point = store_get(size);
- s[ptr++] = logchar;
-
- s = string_append(s, &size, &ptr, 2, US"> ", log_address);
-
- if ((log_extra_selector & LX_sender_on_delivery) != 0)
- s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
-
- #ifdef EXPERIMENTAL_SRS
- if(addr->p.srs_sender)
- s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.srs_sender, US">");
- #endif
- #ifdef EXPERIMENTAL_DBL
- dbl_delivery_ip = NULL; /* presume no successful remote delivery */
- #endif
-
- /* You might think that the return path must always be set for a successful
- delivery; indeed, I did for some time, until this statement crashed. The case
- when it is not set is for a delivery to /dev/null which is optimised by not
- being run at all. */
-
- if (used_return_path != NULL &&
- (log_extra_selector & LX_return_path_on_delivery) != 0)
- s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
-
- /* For a delivery from a system filter, there may not be a router */
-
- if (addr->router != NULL)
- s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
-
- s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
-
- if ((log_extra_selector & LX_delivery_size) != 0)
- s = string_append(s, &size, &ptr, 2, US" S=",
- string_sprintf("%d", transport_count));
-
- /* Local delivery */
-
- if (addr->transport->info->local)
- {
- if (addr->host_list != NULL)
- s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
- if (addr->shadow_message != NULL)
- s = string_cat(s, &size, &ptr, addr->shadow_message,
- Ustrlen(addr->shadow_message));
- }
-
- /* Remote delivery */
-
- else
- {
- if (addr->host_used != NULL)
- {
- s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name,
- US" [", addr->host_used->address, US"]");
- if ((log_extra_selector & LX_outgoing_port) != 0)
- s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d",
- addr->host_used->port));
- if (continue_sequence > 1)
- s = string_cat(s, &size, &ptr, US"*", 1);
- #ifdef EXPERIMENTAL_DBL
- dbl_delivery_ip = string_copy(addr->host_used->address);
- dbl_delivery_port = addr->host_used->port;
- dbl_delivery_fqdn = string_copy(addr->host_used->name);
- dbl_delivery_local_part = string_copy(addr->local_part);
- dbl_delivery_domain = string_copy(addr->domain);
- dbl_delivery_confirmation = string_copy(addr->message);
- #endif
- }
- #ifdef SUPPORT_TLS
- if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher);
- if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
- addr->cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" CV=",
- testflag(addr, af_cert_verified)? "yes":"no");
- if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
- s = string_append(s, &size, &ptr, 3, US" DN=\"",
- string_printing(addr->peerdn), US"\"");
- #endif
-
- if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
- addr->message != NULL)
- {
- int i;
- uschar *p = big_buffer;
- uschar *ss = addr->message;
- *p++ = '\"';
- for (i = 0; i < 100 && ss[i] != 0; i++)
- {
- if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
- *p++ = ss[i];
- }
- *p++ = '\"';
- *p = 0;
- s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
- }
- }
-
- /* Time on queue and actual time taken to deliver */
-
- if ((log_extra_selector & LX_queue_time) != 0)
- {
- s = string_append(s, &size, &ptr, 2, US" QT=",
- readconf_printtime(time(NULL) - received_time));
- }
-
- if ((log_extra_selector & LX_deliver_time) != 0)
- {
- s = string_append(s, &size, &ptr, 2, US" DT=",
- readconf_printtime(addr->more_errno));
- }
-
- /* string_cat() always leaves room for the terminator. Release the
- store we used to build the line after writing it. */
-
- s[ptr] = 0;
- log_write(0, LOG_MAIN, "%s", s);
- #ifdef EXPERIMENTAL_DBL
- DEBUG(D_deliver)
- {
- debug_printf(" DBL(Delivery): dbl_delivery_query=|%s| dbl_delivery_IP=%s\n", dbl_delivery_query, dbl_delivery_ip);
- }
- if (dbl_delivery_ip != NULL && dbl_delivery_query != NULL)
- expand_string(dbl_delivery_query);
- #endif
- store_reset(reset_point);
+ delivery_log(LOG_MAIN, addr, logchar, NULL);
}
log. */
s = reset_point = store_get(size);
+
+ /* Create the address string for logging. Must not do this earlier, because
+ an OK result may be changed to FAIL when a pipe returns text. */
+
+ log_address = string_log_address(addr,
+ (log_write_selector & L_all_parents) != 0, result == OK);
+
s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
/* Either driver_name contains something and driver_kind contains
/* Build up the log line for the message and main logs */
s = reset_point = store_get(size);
+
+ /* Create the address string for logging. Must not do this earlier, because
+ an OK result may be changed to FAIL when a pipe returns text. */
+
+ log_address = string_log_address(addr,
+ (log_write_selector & L_all_parents) != 0, result == OK);
+
s = string_cat(s, &size, &ptr, log_address, Ustrlen(log_address));
if ((log_extra_selector & LX_sender_on_delivery) != 0)
set_process_info("delivering %s to %s using %s", message_id,
addr->local_part, addr->transport->name);
+ /* Setting this global in the subprocess means we need never clear it */
+ transport_name = addr->transport->name;
+
/* If a transport filter has been specified, set up its argument list.
Any errors will get put into the address, and FALSE yielded. */
int i;
int local_part_length = Ustrlen(addr2->local_part);
uschar *s;
+ int ret;
- (void)write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int));
- (void)write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count));
- (void)write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags));
- (void)write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int));
- (void)write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int));
- (void)write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int));
- (void)write(pfd[pipe_write], (void *)&(addr2->transport),
- sizeof(transport_instance *));
+ if( (ret = write(pfd[pipe_write], (void *)&(addr2->transport_return), sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], (void *)&transport_count, sizeof(transport_count))) != sizeof(transport_count)
+ || (ret = write(pfd[pipe_write], (void *)&(addr2->flags), sizeof(addr2->flags))) != sizeof(addr2->flags)
+ || (ret = write(pfd[pipe_write], (void *)&(addr2->basic_errno), sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], (void *)&(addr2->more_errno), sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], (void *)&(addr2->special_action), sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], (void *)&(addr2->transport),
+ sizeof(transport_instance *))) != sizeof(transport_instance *)
/* For a file delivery, pass back the local part, in case the original
was only part of the final delivery path. This gives more complete
logging. */
- if (testflag(addr2, af_file))
- {
- (void)write(pfd[pipe_write], (void *)&local_part_length, sizeof(int));
- (void)write(pfd[pipe_write], addr2->local_part, local_part_length);
- }
+ || (testflag(addr2, af_file)
+ && ( (ret = write(pfd[pipe_write], (void *)&local_part_length, sizeof(int))) != sizeof(int)
+ || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length
+ )
+ )
+ )
+ log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+ ret == -1 ? strerror(errno) : "short write");
/* Now any messages */
for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message)
{
int message_length = (s == NULL)? 0 : Ustrlen(s) + 1;
- (void)write(pfd[pipe_write], (void *)&message_length, sizeof(int));
- if (message_length > 0) (void)write(pfd[pipe_write], s, message_length);
+ if( (ret = write(pfd[pipe_write], (void *)&message_length, sizeof(int))) != sizeof(int)
+ || (message_length > 0 && (ret = write(pfd[pipe_write], s, message_length)) != message_length)
+ )
+ log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s\n",
+ ret == -1 ? strerror(errno) : "short write");
}
}
to do, which is for the ultimate address timeout. */
if (!ok)
- {
- retry_config *retry =
- retry_find_config(retry_key+2, addr2->domain,
- retry_record->basic_errno,
- retry_record->more_errno);
-
- DEBUG(D_deliver|D_retry)
- {
- debug_printf("retry time not reached for %s: "
- "checking ultimate address timeout\n", addr2->address);
- debug_printf(" now=%d first_failed=%d next_try=%d expired=%d\n",
- (int)now, (int)retry_record->first_failed,
- (int)retry_record->next_try, retry_record->expired);
- }
-
- if (retry != NULL && retry->rules != NULL)
- {
- retry_rule *last_rule;
- for (last_rule = retry->rules;
- last_rule->next != NULL;
- last_rule = last_rule->next);
- DEBUG(D_deliver|D_retry)
- debug_printf(" received_time=%d diff=%d timeout=%d\n",
- received_time, (int)now - received_time, last_rule->timeout);
- if (now - received_time > last_rule->timeout) ok = TRUE;
- }
- else
- {
- DEBUG(D_deliver|D_retry)
- debug_printf("no retry rule found: assume timed out\n");
- ok = TRUE; /* No rule => timed out */
- }
-
- DEBUG(D_deliver|D_retry)
- {
- if (ok) debug_printf("on queue longer than maximum retry for "
- "address - allowing delivery\n");
- }
- }
+ ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
+ retry_record, now);
}
}
else DEBUG(D_retry) debug_printf("no retry record exists\n");
break;
#endif
+ case 'C': /* client authenticator information */
+ switch (*ptr++)
+ {
+ case '1':
+ addr->authenticator = (*ptr)? string_copy(ptr) : NULL;
+ break;
+ case '2':
+ addr->auth_id = (*ptr)? string_copy(ptr) : NULL;
+ break;
+ case '3':
+ addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL;
+ break;
+ }
+ while (*ptr++);
+ break;
+
+ #ifdef EXPERIMENTAL_PRDR
+ case 'P':
+ addr->flags |= af_prdr_used; break;
+ #endif
+
case 'A':
if (addr == NULL)
{
+ static void
+ rmt_dlv_checked_write(int fd, void * buf, int size)
+ {
+ int ret = write(fd, buf, size);
+ if(ret != size)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed writing transport result to pipe: %s\n",
+ ret == -1 ? strerror(errno) : "short write");
+ }
+
/*************************************************
* Do remote deliveries *
*************************************************/
deliver_set_expansions(addr);
+ /* Ensure any transport-set auth info is fresh */
+ addr->authenticator = addr->auth_id = addr->auth_sndr = NULL;
+
/* Compute the return path, expanding a new one if required. The old one
must be set first, as it might be referred to in the expansion. */
int fd = pfd[pipe_write];
host_item *h;
- /* There are weird circumstances in which logging is disabled */
+ /* Setting this global in the subprocess means we need never clear it */
+ transport_name = tp->name;
+ /* There are weird circumstances in which logging is disabled */
disable_logging = tp->disable_logging;
/* Show pids on debug output if parallelism possible */
{
if (h->address == NULL || h->status < hstatus_unusable) continue;
sprintf(CS big_buffer, "H%c%c%s", h->status, h->why, h->address);
- (void)write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
+ rmt_dlv_checked_write(fd, big_buffer, Ustrlen(big_buffer+3) + 4);
}
/* The number of bytes written. This is the same for each address. Even
big_buffer[0] = 'S';
memcpy(big_buffer+1, &transport_count, sizeof(transport_count));
- (void)write(fd, big_buffer, sizeof(transport_count) + 1);
+ rmt_dlv_checked_write(fd, big_buffer, sizeof(transport_count) + 1);
- /* Information about what happened to each address. Three item types are
- used: an optional 'X' item first, for TLS information, followed by 'R'
- items for any retry settings, and finally an 'A' item for the remaining
- data. */
+ /* Information about what happened to each address. Four item types are
+ used: an optional 'X' item first, for TLS information, then an optional "C"
+ item for any client-auth info followed by 'R' items for any retry settings,
+ and finally an 'A' item for the remaining data. */
for(; addr != NULL; addr = addr->next)
{
/* The certificate verification status goes into the flags */
- if (tls_certificate_verified) setflag(addr, af_cert_verified);
+ if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
/* Use an X item only if there's something to send */
if (addr->cipher != NULL)
{
ptr = big_buffer;
- *ptr++ = 'X';
- sprintf(CS ptr, "%.128s", addr->cipher);
+ sprintf(CS ptr, "X%.128s", addr->cipher);
while(*ptr++);
if (addr->peerdn == NULL) *ptr++ = 0; else
{
sprintf(CS ptr, "%.512s", addr->peerdn);
while(*ptr++);
}
- (void)write(fd, big_buffer, ptr - big_buffer);
+ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
}
#endif
+ if (client_authenticator)
+ {
+ ptr = big_buffer;
+ sprintf(CS big_buffer, "C1%.64s", client_authenticator);
+ while(*ptr++);
+ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+ }
+ if (client_authenticated_id)
+ {
+ ptr = big_buffer;
+ sprintf(CS big_buffer, "C2%.64s", client_authenticated_id);
+ while(*ptr++);
+ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
+ }
+ if (client_authenticated_sender)
+ {
+ ptr = big_buffer;
+ sprintf(CS big_buffer, "C3%.64s", client_authenticated_sender);
+ while(*ptr++);
+ 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)
sprintf(CS ptr, "%.512s", r->message);
while(*ptr++);
}
- (void)write(fd, big_buffer, ptr - big_buffer);
+ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
}
/* The rest of the information goes in an 'A' item. */
memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port));
ptr += sizeof(addr->host_used->port);
}
- (void)write(fd, big_buffer, ptr - big_buffer);
+ rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
}
/* Add termination flag, close the pipe, and that's it. The character
big_buffer[0] = 'Z';
big_buffer[1] = (continue_transport == NULL)? '0' : '1';
- (void)write(fd, big_buffer, 2);
+ rmt_dlv_checked_write(fd, big_buffer, 2);
(void)close(fd);
exit(EXIT_SUCCESS);
}
int process_recipients = RECIP_ACCEPT;
open_db dbblock;
open_db *dbm_file;
+ extern int acl_where;
uschar *info = (queue_run_pid == (pid_t)0)?
string_sprintf("delivering %s", id) :
update_spool = FALSE;
remove_journal = TRUE;
+ /* Set a known context for any ACLs we call via expansions */
+ acl_where = ACL_WHERE_DELIVERY;
+
/* Reset the random number generator, so that if several delivery processes are
started from a queue runner that has already used random numbers (for sorting),
they don't all get the same sequence. */
will be far too many attempts for an address that gets a 4xx error. In
fact, after such an error, we should not get here because, the host should
not be remembered as one this message needs. However, there was a bug that
- used to cause this to happen, so it is best to be on the safe side. */
+ used to cause this to happen, so it is best to be on the safe side.
+
+ Even if we haven't reached the retry time in the hints, there is one more
+ check to do, which is for the ultimate address timeout. We only do this
+ check if there is an address retry record and there is not a domain retry
+ record; this implies that previous attempts to handle the address had the
+ retry_use_local_parts option turned on. We use this as an approximation
+ for the destination being like a local delivery, for example delivery over
+ LMTP to an IMAP message store. In this situation users are liable to bump
+ into their quota and thereby have intermittently successful deliveries,
+ which keep the retry record fresh, which can lead to us perpetually
+ deferring messages. */
else if (((queue_running && !deliver_force) || continue_hostname != NULL)
&&
||
(address_retry_record != NULL &&
now < address_retry_record->next_try))
- )
+ &&
+ (domain_retry_record != NULL ||
+ address_retry_record == NULL ||
+ !retry_ultimate_address_timeout(addr->address_retry_key,
+ addr->domain, address_retry_record, now)))
{
addr->message = US"retry time not reached";
addr->basic_errno = ERRNO_RRETRY;
that the mode is correct - the group setting doesn't always seem to get
set automatically. */
- (void)fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC);
- (void)fchown(journal_fd, exim_uid, exim_gid);
- (void)fchmod(journal_fd, SPOOL_MODE);
+ if( fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
+ || fchown(journal_fd, exim_uid, exim_gid)
+ || fchmod(journal_fd, SPOOL_MODE)
+ )
+ {
+ int ret = Uunlink(spoolname);
+ log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s",
+ spoolname, strerror(errno));
+ if(ret && errno != ENOENT)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s",
+ spoolname, strerror(errno));
+ return DELIVER_NOT_ATTEMPTED;
+ }
}
+
/* Now we can get down to the business of actually doing deliveries. Local
deliveries are done first, then remote ones. If ever the problems of how to
handle fallback transports are figured out, this section can be put into a loop
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. */
released. */
search_tidyup();
+ acl_where = ACL_WHERE_UNKNOWN;
return final_yield;
}
+ /*************************************************
+ * Enums for cmdline interface *
+ *************************************************/
+
+ enum commandline_info { CMDINFO_NONE=0,
+ CMDINFO_HELP, CMDINFO_SIEVE, CMDINFO_DSCP };
+
+
+
+
/*************************************************
* Compile regular expression and panic on fail *
*************************************************/
if (fd < 0) return;
- (void)write(fd, process_info, process_info_len);
+ {int dummy = write(fd, process_info, process_info_len); dummy = dummy; }
(void)close(fd);
}
if (smtp_input)
{
#ifdef SUPPORT_TLS
- tls_close(FALSE); /* Shut down the TLS library */
+ tls_close(FALSE, FALSE); /* Shut down the TLS library */
#endif
(void)close(fileno(smtp_in));
(void)close(fileno(smtp_out));
#ifdef EXPERIMENTAL_DCC
fprintf(f, " Experimental_DCC");
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ fprintf(f, " Experimental_DMARC");
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+ fprintf(f, " Experimental_OCSP");
+ #endif
+ #ifdef EXPERIMENTAL_PRDR
+ fprintf(f, " Experimental_PRDR");
+ #endif
+#ifdef EXPERIMENTAL_DBL
+ fprintf(f, " EXPERIMENTAL_DBL");
+#endif
fprintf(f, "\n");
fprintf(f, "Lookups (built-in):");
}
+ /*************************************************
+ * Show auxiliary information about Exim *
+ *************************************************/
+
+ static void
+ show_exim_information(enum commandline_info request, FILE *stream)
+ {
+ const uschar **pp;
+
+ switch(request)
+ {
+ case CMDINFO_NONE:
+ fprintf(stream, "Oops, something went wrong.\n");
+ return;
+ case CMDINFO_HELP:
+ fprintf(stream,
+ "The -bI: flag takes a string indicating which information to provide.\n"
+ "If the string is not recognised, you'll get this help (on stderr).\n"
+ "\n"
+ " exim -bI:help this information\n"
+ " exim -bI:dscp dscp value keywords known\n"
+ " exim -bI:sieve list of supported sieve extensions, one per line.\n"
+ );
+ return;
+ case CMDINFO_SIEVE:
+ for (pp = exim_sieve_extension_list; *pp; ++pp)
+ fprintf(stream, "%s\n", *pp);
+ return;
+ case CMDINFO_DSCP:
+ dscp_list_to_stream(stream);
+ return;
+ }
+ }
/*************************************************
BOOL count_queue = FALSE;
BOOL expansion_test = FALSE;
BOOL extract_recipients = FALSE;
+ BOOL flag_G = FALSE;
+ BOOL flag_n = FALSE;
BOOL forced_delivery = FALSE;
BOOL f_end_dot = FALSE;
BOOL deliver_give_up = FALSE;
BOOL version_printed = FALSE;
uschar *alias_arg = NULL;
uschar *called_as = US"";
+ uschar *cmdline_syslog_name = NULL;
uschar *start_queue_run_id = NULL;
uschar *stop_queue_run_id = NULL;
uschar *expansion_test_message = NULL;
uschar *malware_test_file = NULL;
uschar *real_sender_address;
uschar *originator_home = US"/";
+ size_t sz;
void *reset_point;
struct passwd *pw;
int passed_qr_pipe = -1;
gid_t group_list[NGROUPS_MAX];
+ /* For the -bI: flag */
+ enum commandline_info info_flag = CMDINFO_NONE;
+ BOOL info_stdout = FALSE;
+
/* Possible options for -R and -S */
static uschar *rsopts[] = { US"f", US"ff", US"r", US"rf", US"rff" };
switch(switchchar)
{
+
+ /* sendmail uses -Ac and -Am to control which .cf file is used;
+ we ignore them. */
+ case 'A':
+ if (*argrest == '\0') { badarg = TRUE; break; }
+ else
+ {
+ BOOL ignore = FALSE;
+ switch (*argrest)
+ {
+ case 'c':
+ case 'm':
+ if (*(argrest + 1) == '\0')
+ ignore = TRUE;
+ break;
+ }
+ if (!ignore) { badarg = TRUE; break; }
+ }
+ break;
+
/* -Btype is a sendmail option for 7bit/8bit setting. Exim is 8-bit clean
so has no need of it. */
else if (Ustrcmp(argrest, "i") == 0) bi_option = TRUE;
+ /* -bI: provide information, of the type to follow after a colon.
+ This is an Exim flag. */
+
+ else if (argrest[0] == 'I' && Ustrlen(argrest) >= 2 && argrest[1] == ':')
+ {
+ uschar *p = &argrest[2];
+ info_flag = CMDINFO_HELP;
+ if (Ustrlen(p))
+ {
+ if (strcmpic(p, CUS"sieve") == 0)
+ {
+ info_flag = CMDINFO_SIEVE;
+ info_stdout = TRUE;
+ }
+ else if (strcmpic(p, CUS"dscp") == 0)
+ {
+ info_flag = CMDINFO_DSCP;
+ info_stdout = TRUE;
+ }
+ else if (strcmpic(p, CUS"help") == 0)
+ {
+ info_stdout = TRUE;
+ }
+ }
+ }
+
/* -bm: Accept and deliver message - the default option. Reinstate
receiving_message, which got turned off for all -b options. */
}
break;
- /* This is some Sendmail thing which can be ignored */
+ /* -G: sendmail invocation to specify that it's a gateway submission and
+ sendmail may complain about problems instead of fixing them.
+ We make it equivalent to an ACL "control = suppress_local_fixups" and do
+ not at this time complain about problems. */
case 'G':
+ flag_G = TRUE;
break;
/* -h: Set the hop count for an incoming message. Exim does not currently
break;
+ /* -L: set the identifier used for syslog; equivalent to setting
+ syslog_processname in the config file, but needs to be an admin option. */
+
+ case 'L':
+ if (*argrest == '\0')
+ {
+ if(++i < argc) argrest = argv[i]; else
+ { badarg = TRUE; break; }
+ }
+ sz = Ustrlen(argrest);
+ if (sz > 32)
+ {
+ fprintf(stderr, "exim: the -L syslog name is too long: \"%s\"\n", argrest);
+ return EXIT_FAILURE;
+ }
+ if (sz < 1)
+ {
+ fprintf(stderr, "exim: the -L syslog name is too short\n");
+ return EXIT_FAILURE;
+ }
+ cmdline_syslog_name = argrest;
+ break;
+
case 'M':
receiving_message = FALSE;
break;
- /* -n: This means "don't alias" in sendmail, apparently. Just ignore
- it. */
+ /* -n: This means "don't alias" in sendmail, apparently.
+ For normal invocations, it has no effect.
+ It may affect some other options. */
case 'n':
+ flag_n = TRUE;
break;
/* -O: Just ignore it. In sendmail, apparently -O option=value means set
/* -tls-on-connect: don't wait for STARTTLS (for old clients) */
#ifdef SUPPORT_TLS
- else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_on_connect = TRUE;
+ else if (Ustrcmp(argrest, "ls-on-connect") == 0) tls_in.on_connect = TRUE;
#endif
else badarg = TRUE;
if (*argrest != 0) badarg = TRUE;
break;
+ /* -X: in sendmail: takes one parameter, logfile, and sends debugging
+ logs to that file. We swallow the parameter and otherwise ignore it. */
+
+ case 'X':
+ if (*argrest == '\0')
+ {
+ if (++i >= argc)
+ {
+ fprintf(stderr, "exim: string expected after -X\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ break;
+
/* All other initial characters are errors */
default:
readconf_main();
+ /* If an action on specific messages is requested, or if a daemon or queue
+ runner is being started, we need to know if Exim was called by an admin user.
+ This is the case if the real user is root or exim, or if the real group is
+ exim, or if one of the supplementary groups is exim or a group listed in
+ admin_groups. We don't fail all message actions immediately if not admin_user,
+ since some actions can be performed by non-admin users. Instead, set admin_user
+ for later interrogation. */
+
+ if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
+ admin_user = TRUE;
+ else
+ {
+ int i, j;
+ for (i = 0; i < group_count; i++)
+ {
+ if (group_list[i] == exim_gid) admin_user = TRUE;
+ else if (admin_groups != NULL)
+ {
+ for (j = 1; j <= (int)(admin_groups[0]); j++)
+ if (admin_groups[j] == group_list[i])
+ { admin_user = TRUE; break; }
+ }
+ if (admin_user) break;
+ }
+ }
+
+ /* Another group of privileged users are the trusted users. These are root,
+ exim, and any caller matching trusted_users or trusted_groups. Trusted callers
+ are permitted to specify sender_addresses with -f on the command line, and
+ other message parameters as well. */
+
+ if (real_uid == root_uid || real_uid == exim_uid)
+ trusted_caller = TRUE;
+ else
+ {
+ int i, j;
+
+ if (trusted_users != NULL)
+ {
+ for (i = 1; i <= (int)(trusted_users[0]); i++)
+ if (trusted_users[i] == real_uid)
+ { trusted_caller = TRUE; break; }
+ }
+
+ if (!trusted_caller && trusted_groups != NULL)
+ {
+ for (i = 1; i <= (int)(trusted_groups[0]); i++)
+ {
+ if (trusted_groups[i] == real_gid)
+ trusted_caller = TRUE;
+ else for (j = 0; j < group_count; j++)
+ {
+ if (trusted_groups[i] == group_list[j])
+ { trusted_caller = TRUE; break; }
+ }
+ if (trusted_caller) break;
+ }
+ }
+ }
+
/* Handle the decoding of logging options. */
decode_bits(&log_write_selector, &log_extra_selector, 0, 0,
}
}
+ /* See if an admin user overrode our logging. */
+
+ if (cmdline_syslog_name != NULL)
+ {
+ if (admin_user)
+ {
+ syslog_processname = cmdline_syslog_name;
+ log_file_path = string_copy(CUS"syslog");
+ }
+ else
+ {
+ /* not a panic, non-privileged users should not be able to spam paniclog */
+ fprintf(stderr,
+ "exim: you lack sufficient privilege to specify syslog process name\n");
+ return EXIT_FAILURE;
+ }
+ }
+
/* Paranoia check of maximum lengths of certain strings. There is a check
on the length of the log file path in log.c, which will come into effect
if there are any calls to write the log earlier than this. However, if we
{
int i;
uschar *p = big_buffer;
- Ustrcpy(p, "cwd=");
- (void)getcwd(CS p+4, big_buffer_size - 4);
+ char * dummy;
+ Ustrcpy(p, "cwd= (failed)");
+ dummy = /* quieten compiler */ getcwd(CS p+4, big_buffer_size - 4);
while (*p) p++;
(void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
while (*p) p++;
if (Uchdir(spool_directory) != 0)
{
+ int dummy;
(void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
- (void)Uchdir(spool_directory);
+ dummy = /* quieten compiler */ Uchdir(spool_directory);
}
/* Handle calls with the -bi option. This is a sendmail option to rebuild *the*
}
}
- /* If an action on specific messages is requested, or if a daemon or queue
- runner is being started, we need to know if Exim was called by an admin user.
- This is the case if the real user is root or exim, or if the real group is
- exim, or if one of the supplementary groups is exim or a group listed in
- admin_groups. We don't fail all message actions immediately if not admin_user,
- since some actions can be performed by non-admin users. Instead, set admin_user
- for later interrogation. */
-
- if (real_uid == root_uid || real_uid == exim_uid || real_gid == exim_gid)
- admin_user = TRUE;
- else
- {
- int i, j;
- for (i = 0; i < group_count; i++)
- {
- if (group_list[i] == exim_gid) admin_user = TRUE;
- else if (admin_groups != NULL)
- {
- for (j = 1; j <= (int)(admin_groups[0]); j++)
- if (admin_groups[j] == group_list[i])
- { admin_user = TRUE; break; }
- }
- if (admin_user) break;
- }
- }
-
- /* Another group of privileged users are the trusted users. These are root,
- exim, and any caller matching trusted_users or trusted_groups. Trusted callers
- are permitted to specify sender_addresses with -f on the command line, and
- other message parameters as well. */
-
- if (real_uid == root_uid || real_uid == exim_uid)
- trusted_caller = TRUE;
- else
- {
- int i, j;
-
- if (trusted_users != NULL)
- {
- for (i = 1; i <= (int)(trusted_users[0]); i++)
- if (trusted_users[i] == real_uid)
- { trusted_caller = TRUE; break; }
- }
-
- if (!trusted_caller && trusted_groups != NULL)
- {
- for (i = 1; i <= (int)(trusted_groups[0]); i++)
- {
- if (trusted_groups[i] == real_gid)
- trusted_caller = TRUE;
- else for (j = 0; j < group_count; j++)
- {
- if (trusted_groups[i] == group_list[j])
- { trusted_caller = TRUE; break; }
- }
- if (trusted_caller) break;
- }
- }
- }
+ /* We moved the admin/trusted check to be immediately after reading the
+ configuration file. We leave these prints here to ensure that syslog setup,
+ logfile setup, and so on has already happened. */
if (trusted_caller) DEBUG(D_any) debug_printf("trusted user\n");
if (admin_user) DEBUG(D_any) debug_printf("admin user\n");
interface_port = check_port(interface_address);
}
+ /* If the caller is trusted, then they can use -G to suppress_local_fixups. */
+ if (flag_G)
+ {
+ if (trusted_caller)
+ {
+ suppress_local_fixups = suppress_local_fixups_default = TRUE;
+ DEBUG(D_acl) debug_printf("suppress_local_fixups forced on by -G\n");
+ }
+ else
+ {
+ fprintf(stderr, "exim: permission denied (-G requires a trusted user)\n");
+ return EXIT_FAILURE;
+ }
+ }
+
/* If an SMTP message is being received check to see if the standard input is a
TCP/IP socket. If it is, we assume that Exim was called from inetd if the
caller is root or the Exim user, or if the port is a privileged one. Otherwise,
interface_address = host_ntoa(-1, &interface_sock, NULL,
&interface_port);
- if (host_is_tls_on_connect_port(interface_port)) tls_on_connect = TRUE;
+ if (host_is_tls_on_connect_port(interface_port)) tls_in.on_connect = TRUE;
if (real_uid == root_uid || real_uid == exim_uid || interface_port < 1024)
{
}
/* Handle a request to list one or more configuration options */
+ /* If -n was set, we suppress some information */
if (list_options)
{
set_process_info("listing variables");
- if (recipients_arg >= argc) readconf_print(US"all", NULL);
+ if (recipients_arg >= argc) readconf_print(US"all", NULL, flag_n);
else for (i = recipients_arg; i < argc; i++)
{
if (i < argc - 1 &&
Ustrcmp(argv[i], "authenticator") == 0 ||
Ustrcmp(argv[i], "macro") == 0))
{
- readconf_print(argv[i+1], argv[i]);
+ readconf_print(argv[i+1], argv[i], flag_n);
i++;
}
- else readconf_print(argv[i], NULL);
+ else readconf_print(argv[i], NULL, flag_n);
}
exim_exit(EXIT_SUCCESS);
}
/* Arrange for message reception if recipients or SMTP were specified;
otherwise complain unless a version print (-bV) happened or this is a filter
- verification test. In the former case, show the configuration file name. */
+ verification test or info dump.
+ In the former case, show the configuration file name. */
if (recipients_arg >= argc && !extract_recipients && !smtp_input)
{
return EXIT_SUCCESS;
}
+ if (info_flag != CMDINFO_NONE)
+ {
+ show_exim_information(info_flag, info_stdout ? stdout : stderr);
+ return info_stdout ? EXIT_SUCCESS : EXIT_FAILURE;
+ }
+
if (filter_test == FTEST_NONE)
exim_usage(called_as);
}
if (ftest_prefix != NULL) printf("Prefix = %s\n", ftest_prefix);
if (ftest_suffix != NULL) printf("Suffix = %s\n", ftest_suffix);
- (void)chdir("/"); /* Get away from wherever the user is running this from */
+ if (chdir("/")) /* Get away from wherever the user is running this from */
+ {
+ DEBUG(D_receive) debug_printf("chdir(\"/\") failed\n");
+ exim_exit(EXIT_FAILURE);
+ }
/* Now we run either a system filter test, or a user filter test, or both.
In the latter case, headers added by the system filter will persist and be
alphabetical order. */
static uschar *item_table[] = {
+ US"acl",
US"dlfunc",
US"extract",
US"filter",
US"tr" };
enum {
+ EITEM_ACL,
EITEM_DLFUNC,
EITEM_EXTRACT,
EITEM_FILTER,
US"h",
US"hash",
US"hex2b64",
+ US"hexquote",
US"l",
US"lc",
US"length",
+ US"listcount",
+ US"listnamed",
US"mask",
US"md5",
US"nh",
EOP_H,
EOP_HASH,
EOP_HEX2B64,
+ EOP_HEXQUOTE,
EOP_L,
EOP_LC,
EOP_LENGTH,
+ EOP_LISTCOUNT,
+ EOP_LISTNAMED,
EOP_MASK,
EOP_MD5,
EOP_NH,
US"==", /* Backward compatibility */
US">",
US">=",
+ US"acl",
US"and",
US"bool",
US"bool_lax",
ECOND_NUM_EE,
ECOND_NUM_G,
ECOND_NUM_GE,
+ ECOND_ACL,
ECOND_AND,
ECOND_BOOL,
ECOND_BOOL_LAX,
vtype_ino, /* value is address of ino_t (not always an int) */
vtype_uid, /* value is address of uid_t (not always an int) */
vtype_gid, /* value is address of gid_t (not always an int) */
+ vtype_bool, /* value is address of bool */
vtype_stringptr, /* value is address of pointer to string */
vtype_msgbody, /* as stringptr, but read when first required */
vtype_msgbody_end, /* ditto, the end of the message */
vtype_msgheaders_raw, /* the message's headers, unprocessed */
vtype_localpart, /* extract local part from string */
vtype_domain, /* extract domain from string */
- vtype_recipients, /* extract recipients from recipients list */
- /* (available only in system filters, ACLs, and */
- /* local_scan()) */
+ vtype_string_func, /* value is string returned by given function */
vtype_todbsdin, /* value not used; generate BSD inbox tod */
vtype_tode, /* value not used; generate tod in epoch format */
vtype_todel, /* value not used; generate tod in epoch/usec format */
#endif
};
+ static uschar * fn_recipients(void);
+
/* This table must be kept in alphabetical order. */
static var_entry var_table[] = {
/* WARNING: Do not invent variables whose names start acl_c or acl_m because
they will be confused with user-creatable ACL variables. */
+ { "acl_arg1", vtype_stringptr, &acl_arg[0] },
+ { "acl_arg2", vtype_stringptr, &acl_arg[1] },
+ { "acl_arg3", vtype_stringptr, &acl_arg[2] },
+ { "acl_arg4", vtype_stringptr, &acl_arg[3] },
+ { "acl_arg5", vtype_stringptr, &acl_arg[4] },
+ { "acl_arg6", vtype_stringptr, &acl_arg[5] },
+ { "acl_arg7", vtype_stringptr, &acl_arg[6] },
+ { "acl_arg8", vtype_stringptr, &acl_arg[7] },
+ { "acl_arg9", vtype_stringptr, &acl_arg[8] },
+ { "acl_narg", vtype_int, &acl_narg },
{ "acl_verify_message", vtype_stringptr, &acl_verify_message },
{ "address_data", vtype_stringptr, &deliver_address_data },
{ "address_file", vtype_stringptr, &address_file },
{ "compile_date", vtype_stringptr, &version_date },
{ "compile_number", vtype_stringptr, &version_cnumber },
{ "csa_status", vtype_stringptr, &csa_status },
+#ifdef EXPERIMENTAL_DBL
+ { "dbl_defer_errno", vtype_int, &dbl_defer_errno },
+ { "dbl_defer_errstr", vtype_stringptr, &dbl_defer_errstr },
+ { "dbl_delivery_confirmation", vtype_stringptr, &dbl_delivery_confirmation },
+ { "dbl_delivery_domain", vtype_stringptr, &dbl_delivery_domain },
+ { "dbl_delivery_fqdn", vtype_stringptr, &dbl_delivery_fqdn },
+ { "dbl_delivery_ip", vtype_stringptr, &dbl_delivery_ip },
+ { "dbl_delivery_local_part",vtype_stringptr,&dbl_delivery_local_part },
+ { "dbl_delivery_port", vtype_int, &dbl_delivery_port },
+#endif
#ifdef EXPERIMENTAL_DCC
{ "dcc_header", vtype_stringptr, &dcc_header },
{ "dcc_result", vtype_stringptr, &dcc_result },
{ "dkim_signers", vtype_stringptr, &dkim_signers },
{ "dkim_verify_reason", vtype_dkim, (void *)DKIM_VERIFY_REASON },
{ "dkim_verify_status", vtype_dkim, (void *)DKIM_VERIFY_STATUS},
+ #endif
+ #ifdef EXPERIMENTAL_DMARC
+ { "dmarc_ar_header", vtype_stringptr, &dmarc_ar_header },
+ { "dmarc_status", vtype_stringptr, &dmarc_status },
+ { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
+ { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
#endif
{ "dnslist_domain", vtype_stringptr, &dnslist_domain },
{ "dnslist_matched", vtype_stringptr, &dnslist_matched },
#ifdef WITH_OLD_DEMIME
{ "found_extension", vtype_stringptr, &found_extension },
#endif
+ { "headers_added", vtype_string_func, &fn_hdrs_added },
{ "home", vtype_stringptr, &deliver_home },
{ "host", vtype_stringptr, &deliver_host },
{ "host_address", vtype_stringptr, &deliver_host_address },
{ "received_time", vtype_int, &received_time },
{ "recipient_data", vtype_stringptr, &recipient_data },
{ "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
- { "recipients", vtype_recipients, NULL },
+ { "recipients", vtype_string_func, &fn_recipients },
{ "recipients_count", vtype_int, &recipients_count },
#ifdef WITH_CONTENT_SCAN
{ "regex_match_string", vtype_stringptr, ®ex_match_string },
{ "reply_address", vtype_reply, NULL },
{ "return_path", vtype_stringptr, &return_path },
{ "return_size_limit", vtype_int, &bounce_return_size_limit },
+ { "router_name", vtype_stringptr, &router_name },
{ "runrc", vtype_int, &runrc },
{ "self_hostname", vtype_stringptr, &self_hostname },
{ "sender_address", vtype_stringptr, &sender_address },
{ "sender_helo_name", vtype_stringptr, &sender_helo_name },
{ "sender_host_address", vtype_stringptr, &sender_host_address },
{ "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
+ { "sender_host_dnssec", vtype_bool, &sender_host_dnssec },
{ "sender_host_name", vtype_host_lookup, NULL },
{ "sender_host_port", vtype_int, &sender_host_port },
{ "sender_ident", vtype_stringptr, &sender_ident },
{ "srs_status", vtype_stringptr, &srs_status },
#endif
{ "thisaddress", vtype_stringptr, &filter_thisaddress },
- { "tls_bits", vtype_int, &tls_bits },
- { "tls_certificate_verified", vtype_int, &tls_certificate_verified },
- { "tls_cipher", vtype_stringptr, &tls_cipher },
- { "tls_peerdn", vtype_stringptr, &tls_peerdn },
- #ifdef SUPPORT_TLS
- { "tls_sni", vtype_stringptr, &tls_sni },
+
+ /* The non-(in,out) variables are now deprecated */
+ { "tls_bits", vtype_int, &tls_in.bits },
+ { "tls_certificate_verified", vtype_int, &tls_in.certificate_verified },
+ { "tls_cipher", vtype_stringptr, &tls_in.cipher },
+
+ { "tls_in_bits", vtype_int, &tls_in.bits },
+ { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
+ { "tls_in_cipher", vtype_stringptr, &tls_in.cipher },
+ { "tls_in_peerdn", vtype_stringptr, &tls_in.peerdn },
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_in_sni", vtype_stringptr, &tls_in.sni },
#endif
+ { "tls_out_bits", vtype_int, &tls_out.bits },
+ { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
+ { "tls_out_cipher", vtype_stringptr, &tls_out.cipher },
+ { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn },
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_out_sni", vtype_stringptr, &tls_out.sni },
+ #endif
+
+ { "tls_peerdn", vtype_stringptr, &tls_in.peerdn }, /* mind the alphabetical order! */
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_sni", vtype_stringptr, &tls_in.sni }, /* mind the alphabetical order! */
+ #endif
+
{ "tod_bsdinbox", vtype_todbsdin, NULL },
{ "tod_epoch", vtype_tode, NULL },
{ "tod_epoch_l", vtype_todel, NULL },
{ "tod_logfile", vtype_todlf, NULL },
{ "tod_zone", vtype_todzone, NULL },
{ "tod_zulu", vtype_todzulu, NULL },
+ { "transport_name", vtype_stringptr, &transport_name },
{ "value", vtype_stringptr, &lookup_value },
{ "version_number", vtype_stringptr, &version_string },
{ "warn_message_delay", vtype_stringptr, &warnmsg_delay },
/* This function is called to expand a string, and test the result for a "true"
or "false" value. Failure of the expansion yields FALSE; logged unless it was a
- forced fail or lookup defer. All store used by the function can be released on
- exit.
+ forced fail or lookup defer.
+
+ We used to release all store used, but this is not not safe due
+ to ${dlfunc } and ${acl }. In any case expand_string_internal()
+ is reasonably careful to release what it can.
The actual false-value tests should be replicated for ECOND_BOOL_LAX.
expand_check_condition(uschar *condition, uschar *m1, uschar *m2)
{
int rc;
- void *reset_point = store_get(0);
uschar *ss = expand_string(condition);
if (ss == NULL)
{
}
rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 &&
strcmpic(ss, US"false") != 0;
- store_reset(reset_point);
return rc;
}
+ /*************************************************
+ * Return list of recipients *
+ *************************************************/
+ /* A recipients list is available only during system message filtering,
+ during ACL processing after DATA, and while expanding pipe commands
+ generated from a system filter, but not elsewhere. */
+
+ static uschar *
+ fn_recipients(void)
+ {
+ if (!enable_dollar_recipients) return NULL; else
+ {
+ int size = 128;
+ int ptr = 0;
+ int i;
+ uschar * s = store_get(size);
+ for (i = 0; i < recipients_count; i++)
+ {
+ if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
+ s = string_cat(s, &size, &ptr, recipients_list[i].address,
+ Ustrlen(recipients_list[i].address));
+ }
+ s[ptr] = 0; /* string_cat() leaves room */
+ return s;
+ }
+ }
+
+
/*************************************************
* Find value of a variable *
*************************************************/
sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */
return var_buffer;
+ case vtype_bool:
+ sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */
+ return var_buffer;
+
case vtype_stringptr: /* Pointer to string */
s = *((uschar **)(var_table[middle].value));
return (s == NULL)? US"" : s;
}
return (s == NULL)? US"" : s;
- /* A recipients list is available only during system message filtering,
- during ACL processing after DATA, and while expanding pipe commands
- generated from a system filter, but not elsewhere. */
-
- case vtype_recipients:
- if (!enable_dollar_recipients) return NULL; else
+ case vtype_string_func:
{
- int size = 128;
- int ptr = 0;
- int i;
- s = store_get(size);
- for (i = 0; i < recipients_count; i++)
- {
- if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
- s = string_cat(s, &size, &ptr, recipients_list[i].address,
- Ustrlen(recipients_list[i].address));
- }
- s[ptr] = 0; /* string_cat() leaves room */
+ uschar * (*fn)() = var_table[middle].value;
+ return fn();
}
- return s;
case vtype_pspace:
{
+ void
+ modify_variable(uschar *name, void * value)
+ {
+ int first = 0;
+ int last = var_table_size;
+
+ while (last > first)
+ {
+ int middle = (first + last)/2;
+ int c = Ustrcmp(name, var_table[middle].name);
+
+ if (c > 0) { first = middle + 1; continue; }
+ if (c < 0) { last = middle; continue; }
+
+ /* Found an existing variable; change the item it refers to */
+ var_table[middle].value = value;
+ return;
+ }
+ return; /* Unknown variable name, fail silently */
+ }
+
+
+
+
+
/*************************************************
* Read and expand substrings *
*************************************************/
+ /*
+ Load args from sub array to globals, and call acl_check().
+ Sub array will be corrupted on return.
+
+ Returns: OK access is granted by an ACCEPT verb
+ DISCARD access is granted by a DISCARD verb
+ FAIL access is denied
+ FAIL_DROP access is denied; drop the connection
+ DEFER can't tell at the moment
+ ERROR disaster
+ */
+ static int
+ eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+ {
+ int i;
+ uschar *tmp;
+ int sav_narg = acl_narg;
+ int ret;
+ extern int acl_where;
+
+ if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+ for (i = 0; i < nsub && sub[i+1]; i++)
+ {
+ tmp = acl_arg[i];
+ acl_arg[i] = sub[i+1]; /* place callers args in the globals */
+ sub[i+1] = tmp; /* stash the old args using our caller's storage */
+ }
+ acl_narg = i;
+ while (i < nsub)
+ {
+ sub[i+1] = acl_arg[i];
+ acl_arg[i++] = NULL;
+ }
+
+ DEBUG(D_expand)
+ debug_printf("expanding: acl: %s arg: %s%s\n",
+ sub[0],
+ acl_narg>0 ? sub[1] : US"<none>",
+ acl_narg>1 ? " +more" : "");
+
+ ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
+
+ for (i = 0; i < nsub; i++)
+ acl_arg[i] = sub[i+1]; /* restore old args */
+ acl_narg = sav_narg;
+
+ return ret;
+ }
+
+
+
+
/*************************************************
* Read and evaluate a condition *
*************************************************/
int_eximarith_t num[2];
struct stat statbuf;
uschar name[256];
- uschar *sub[4];
+ uschar *sub[10];
const pcre *re;
const uschar *rerror;
Ustrncmp(name, "bheader_", 8) == 0)
{
s = read_header_name(name, 256, s);
+ /* {-for-text-editors */
if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
if (yield != NULL) *yield =
(find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
case ECOND_PWCHECK:
while (isspace(*s)) s++;
- if (*s != '{') goto COND_FAILED_CURLY_START;
+ if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
if (sub[0] == NULL) return NULL;
+ /* {-for-text-editors */
if (*s++ != '}') goto COND_FAILED_CURLY_END;
if (yield == NULL) return s; /* No need to run the test if skipping */
return s;
+ /* call ACL (in a conditional context). Accept true, deny false.
+ Defer is a forced-fail. Anything set by message= goes to $value.
+ Up to ten parameters are used; we use the braces round the name+args
+ like the saslauthd condition does, to permit a variable number of args.
+ See also the expansion-item version EITEM_ACL and the traditional
+ acl modifier ACLC_ACL.
+ */
+
+ case ECOND_ACL:
+ /* ${if acl {{name}{arg1}{arg2}...} {yes}{no}} */
+ {
+ uschar *user_msg;
+ BOOL cond = FALSE;
+ int size = 0;
+ int ptr = 0;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/
+
+ switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+ &s, yield == NULL, TRUE, US"acl"))
+ {
+ case 1: expand_string_message = US"too few arguments or bracketing "
+ "error for acl";
+ case 2:
+ case 3: return NULL;
+ }
+
+ if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ cond = TRUE;
+ case FAIL:
+ lookup_value = NULL;
+ if (user_msg)
+ {
+ lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+ lookup_value[ptr] = '\0';
+ }
+ *yield = cond == testfor;
+ break;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ return NULL;
+ }
+ return s;
+ }
+
+
/* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
${if saslauthd {{username}{password}{service}{realm}} {yes}[no}}
However, the last two are optional. That is why the whole set is enclosed
- in their own set or braces. */
+ in their own set of braces. */
case ECOND_SASLAUTHD:
#ifndef CYRUS_SASLAUTHD_SOCKET
goto COND_FAILED_NOT_COMPILED;
#else
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
{
case 1: expand_string_message = US"too few arguments or bracketing "
{
case ECOND_NUM_E:
case ECOND_NUM_EE:
- *yield = (num[0] == num[1]) == testfor;
+ tempcond = (num[0] == num[1]);
break;
case ECOND_NUM_G:
- *yield = (num[0] > num[1]) == testfor;
+ tempcond = (num[0] > num[1]);
break;
case ECOND_NUM_GE:
- *yield = (num[0] >= num[1]) == testfor;
+ tempcond = (num[0] >= num[1]);
break;
case ECOND_NUM_L:
- *yield = (num[0] < num[1]) == testfor;
+ tempcond = (num[0] < num[1]);
break;
case ECOND_NUM_LE:
- *yield = (num[0] <= num[1]) == testfor;
+ tempcond = (num[0] <= num[1]);
break;
case ECOND_STR_LT:
- *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
break;
case ECOND_STR_LTI:
- *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) < 0);
break;
case ECOND_STR_LE:
- *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
break;
case ECOND_STR_LEI:
- *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) <= 0);
break;
case ECOND_STR_EQ:
- *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
break;
case ECOND_STR_EQI:
- *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) == 0);
break;
case ECOND_STR_GT:
- *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
break;
case ECOND_STR_GTI:
- *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) > 0);
break;
case ECOND_STR_GE:
- *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
break;
case ECOND_STR_GEI:
- *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) >= 0);
break;
case ECOND_MATCH: /* Regular expression match */
"\"%s\": %s at offset %d", sub[1], rerror, roffset);
return NULL;
}
- *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor;
+ tempcond = regex_match_and_setup(re, sub[0], 0, -1);
break;
case ECOND_MATCH_ADDRESS: /* Match in an address list */
switch(rc)
{
case OK:
- *yield = testfor;
+ tempcond = TRUE;
break;
case FAIL:
- *yield = !testfor;
+ tempcond = FALSE;
break;
case DEFER:
/* Various "encrypted" comparisons. If the second string starts with
"{" then an encryption type is given. Default to crypt() or crypt16()
(build-time choice). */
+ /* }-for-text-editors */
case ECOND_CRYPTEQ:
#ifndef SUPPORT_CRYPTEQ
uschar *coded = auth_b64encode((uschar *)digest, 16);
DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+5);
- *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor;
+ tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
}
else if (sublen == 32)
{
coded[32] = 0;
DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+5);
- *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor;
+ tempcond = (strcmpic(coded, sub[1]+5) == 0);
}
else
{
DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
"fail\n crypted=%s\n", sub[1]+5);
- *yield = !testfor;
+ tempcond = FALSE;
}
}
uschar *coded = auth_b64encode((uschar *)digest, 20);
DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+6);
- *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor;
+ tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
}
else if (sublen == 40)
{
coded[40] = 0;
DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+6);
- *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor;
+ tempcond = (strcmpic(coded, sub[1]+6) == 0);
}
else
{
DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
"fail\n crypted=%s\n", sub[1]+6);
- *yield = !testfor;
+ tempcond = FALSE;
}
}
sub[1] += 9;
which = 2;
}
- else if (sub[1][0] == '{')
+ else if (sub[1][0] == '{') /* }-for-text-editors */
{
expand_string_message = string_sprintf("unknown encryption mechanism "
"in \"%s\"", sub[1]);
salt), force failure. Otherwise we get false positives: with an empty
string the yield of crypt() is an empty string! */
- *yield = (Ustrlen(sub[1]) < 2)? !testfor :
- (Ustrcmp(coded, sub[1]) == 0) == testfor;
+ tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
+ (Ustrcmp(coded, sub[1]) == 0);
}
break;
#endif /* SUPPORT_CRYPTEQ */
case ECOND_INLISTI:
{
int sep = 0;
- BOOL found = FALSE;
uschar *save_iterate_item = iterate_item;
int (*compare)(const uschar *, const uschar *);
+ tempcond = FALSE;
if (cond_type == ECOND_INLISTI)
compare = strcmpic;
else
while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
if (compare(sub[0], iterate_item) == 0)
{
- found = TRUE;
+ tempcond = TRUE;
break;
}
iterate_item = save_iterate_item;
- *yield = found;
}
} /* Switch for comparison conditions */
+ *yield = tempcond == testfor;
return s; /* End of comparison conditions */
combined_cond = (cond_type == ECOND_AND);
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
for (;;)
{
while (isspace(*s)) s++;
+ /* {-for-text-editors */
if (*s == '}') break;
- if (*s != '{')
+ if (*s != '{') /* }-for-text-editors */
{
expand_string_message = string_sprintf("each subcondition "
"inside an \"%s{...}\" condition must be in its own {}", name);
}
while (isspace(*s)) s++;
+ /* {-for-text-editors */
if (*s++ != '}')
{
+ /* {-for-text-editors */
expand_string_message = string_sprintf("missing } at end of condition "
"inside \"%s\" group", name);
return NULL;
uschar *save_iterate_item = iterate_item;
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
if (sub[0] == NULL) return NULL;
+ /* {-for-text-editors */
if (*s++ != '}') goto COND_FAILED_CURLY_END;
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
sub[1] = s;
}
while (isspace(*s)) s++;
+ /* {-for-text-editors */
if (*s++ != '}')
{
+ /* {-for-text-editors */
expand_string_message = string_sprintf("missing } at end of condition "
"inside \"%s\"", name);
return NULL;
size_t len;
BOOL boolvalue = FALSE;
while (isspace(*s)) s++;
- if (*s != '{') goto COND_FAILED_CURLY_START;
+ if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
{
* can just let the other invalid results occur otherwise, as they have
* until now. For this one case, we can coerce.
*/
- if (y == -1 && x == LLONG_MIN && op != '*')
+ if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
{
DEBUG(D_expand)
debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
- LLONG_MIN, op, LLONG_MAX);
- x = LLONG_MAX;
+ EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+ x = EXIM_ARITH_MAX;
continue;
}
if (op == '*')
There's a problem if a ${dlfunc item has side-effects that cause allocation,
since resetting the store at the end of the expansion will free store that was
allocated by the plugin code as well as the slop after the expanded string. So
- we skip any resets if ${dlfunc has been used. This is an unfortunate
- consequence of string expansion becoming too powerful.
+ we skip any resets if ${dlfunc has been used. The same applies for ${acl. This
+ is an unfortunate consequence of string expansion becoming too powerful.
Arguments:
string the string to be expanded
switch(item_type)
{
+ /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9.
+ If the ACL returns accept or reject we return content set by "message ="
+ There is currently no limit on recursion; this would have us call
+ acl_check_internal() directly and get a current level from somewhere.
+ See also the acl expansion condition ECOND_ACL and the traditional
+ acl modifier ACLC_ACL.
+ Assume that the function has side-effects on the store that must be preserved.
+ */
+
+ case EITEM_ACL:
+ /* ${acl {name} {arg1}{arg2}...} */
+ {
+ uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
+ uschar *user_msg;
+
+ switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+ if (skipping) continue;
+
+ resetok = FALSE;
+ switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ case FAIL:
+ if (user_msg)
+ yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+ continue;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ goto EXPAND_FAILED;
+ }
+ }
+
/* Handle conditionals - preserve the values of the numerical expansion
variables in case they get changed by a regular expression match in the
condition. If not, they retain their external settings. At the end
continue;
}
+ /* Convert octets outside 0x21..0x7E to \xXX form */
+
+ case EOP_HEXQUOTE:
+ {
+ uschar *t = sub - 1;
+ while (*(++t) != 0)
+ {
+ if (*t < 0x21 || 0x7E < *t)
+ yield = string_cat(yield, &size, &ptr,
+ string_sprintf("\\x%02x", *t), 4);
+ else
+ yield = string_cat(yield, &size, &ptr, t, 1);
+ }
+ continue;
+ }
+
+ /* count the number of list elements */
+
+ case EOP_LISTCOUNT:
+ {
+ int cnt = 0;
+ int sep = 0;
+ uschar * cp;
+ uschar buffer[256];
+
+ while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+ cp = string_sprintf("%d", cnt);
+ yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+ continue;
+ }
+
+ /* expand a named list given the name */
+ /* handles nested named lists; requotes as colon-sep list */
+
+ case EOP_LISTNAMED:
+ {
+ tree_node *t = NULL;
+ uschar * list;
+ int sep = 0;
+ uschar * item;
+ uschar * suffix = US"";
+ BOOL needsep = FALSE;
+ uschar buffer[256];
+
+ if (*sub == '+') sub++;
+ if (arg == NULL) /* no-argument version */
+ {
+ if (!(t = tree_search(addresslist_anchor, sub)) &&
+ !(t = tree_search(domainlist_anchor, sub)) &&
+ !(t = tree_search(hostlist_anchor, sub)))
+ t = tree_search(localpartlist_anchor, sub);
+ }
+ else switch(*arg) /* specific list-type version */
+ {
+ case 'a': t = tree_search(addresslist_anchor, sub); suffix = US"_a"; break;
+ case 'd': t = tree_search(domainlist_anchor, sub); suffix = US"_d"; break;
+ case 'h': t = tree_search(hostlist_anchor, sub); suffix = US"_h"; break;
+ case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
+ default:
+ expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+ goto EXPAND_FAILED;
+ }
+
+ if(!t)
+ {
+ expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
+ sub, !arg?""
+ : *arg=='a'?"address "
+ : *arg=='d'?"domain "
+ : *arg=='h'?"host "
+ : *arg=='l'?"localpart "
+ : 0);
+ goto EXPAND_FAILED;
+ }
+
+ list = ((namedlist_block *)(t->data.ptr))->string;
+
+ while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+ {
+ uschar * buf = US" : ";
+ if (needsep)
+ yield = string_cat(yield, &size, &ptr, buf, 3);
+ else
+ needsep = TRUE;
+
+ if (*item == '+') /* list item is itself a named list */
+ {
+ uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item);
+ item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE);
+ }
+ else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */
+ {
+ char * cp;
+ char tok[3];
+ tok[0] = sep; tok[1] = ':'; tok[2] = 0;
+ while ((cp= strpbrk((const char *)item, tok)))
+ {
+ yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+ if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
+ {
+ yield = string_cat(yield, &size, &ptr, US"::", 2);
+ item = (uschar *)cp;
+ }
+ else /* sep in item; should already be doubled; emit once */
+ {
+ yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+ if (*cp == sep) cp++;
+ item = (uschar *)cp;
+ }
+ }
+ }
+ yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+ }
+ continue;
+ }
+
/* mask applies a mask to an IP address; for example the result of
${mask:131.111.10.206/28} is 131.111.10.192/28. */
}
else
{
- if (tolower(*endptr) == 'k')
+ switch (tolower(*endptr))
{
- if (value > LLONG_MAX/1024 || value < LLONG_MIN/1024) errno = ERANGE;
+ default:
+ break;
+ case 'k':
+ if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
else value *= 1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'm')
- {
- if (value > LLONG_MAX/(1024*1024) || value < LLONG_MIN/(1024*1024))
- errno = ERANGE;
- else value *= 1024*1024;
- endptr++;
+ endptr++;
+ break;
+ case 'm':
+ if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE;
+ else value *= 1024*1024;
+ endptr++;
+ break;
+ case 'g':
+ if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
+ else value *= 1024*1024*1024;
+ endptr++;
+ break;
}
if (errno == ERANGE)
msg = US"absolute value of integer \"%s\" is too large (overflow)";
optionlist optionlist_auths[] = {
{ "client_condition", opt_stringptr | opt_public,
(void *)(offsetof(auth_instance, client_condition)) },
+ { "client_set_id", opt_stringptr | opt_public,
+ (void *)(offsetof(auth_instance, set_client_id)) },
{ "driver", opt_stringptr | opt_public,
(void *)(offsetof(auth_instance, driver_name)) },
{ "public_name", opt_stringptr | opt_public,
cluttered in several places (e.g. during logging) if we can always refer to
them. Also, the tls_ variables are now always visible. */
- BOOL tls_active = -1;
- int tls_bits = 0;
- BOOL tls_certificate_verified = FALSE;
- uschar *tls_cipher = NULL;
- BOOL tls_on_connect = FALSE;
- uschar *tls_on_connect_ports = NULL;
- uschar *tls_peerdn = NULL;
+ tls_support tls_in = {
+ -1, /* tls_active */
+ 0, /* tls_bits */
+ FALSE,/* tls_certificate_verified */
+ NULL, /* tls_cipher */
+ FALSE,/* tls_on_connect */
+ NULL, /* tls_on_connect_ports */
+ NULL, /* tls_peerdn */
+ NULL /* tls_sni */
+ };
+ tls_support tls_out = {
+ -1, /* tls_active */
+ 0, /* tls_bits */
+ FALSE,/* tls_certificate_verified */
+ NULL, /* tls_cipher */
+ FALSE,/* tls_on_connect */
+ NULL, /* tls_on_connect_ports */
+ NULL, /* tls_peerdn */
+ NULL /* tls_sni */
+ };
+
#ifdef SUPPORT_TLS
BOOL gnutls_compat_mode = FALSE;
+ BOOL gnutls_enable_pkcs11 = FALSE;
uschar *gnutls_require_mac = NULL;
uschar *gnutls_require_kx = NULL;
uschar *gnutls_require_proto = NULL;
uschar *tls_privatekey = NULL;
BOOL tls_remember_esmtp = FALSE;
uschar *tls_require_ciphers = NULL;
- uschar *tls_sni = NULL;
uschar *tls_try_verify_hosts = NULL;
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
header_line *acl_added_headers = NULL;
tree_node *acl_anchor = NULL;
+ uschar *acl_arg[9] = {NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL};
+ int acl_narg = 0;
uschar *acl_not_smtp = NULL;
#ifdef WITH_CONTENT_SCAN
uschar *acl_not_smtp_mime = NULL;
#endif
uschar *acl_not_smtp_start = NULL;
-
+ 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
US"MIME",
US"DKIM",
US"DATA",
+ #ifdef EXPERIMENTAL_PRDR
+ US"PRDR",
+ #endif
US"non-SMTP",
US"AUTH",
US"connection",
US"NOTQUIT",
US"QUIT",
US"STARTTLS",
- US"VRFY"
+ US"VRFY",
+ US"delivery",
+ US"unknown"
};
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 */
US"0", /* NOTQUIT; not relevant */
US"0", /* QUIT; not relevant */
US"550", /* STARTTLS */
- US"252" /* VRFY */
+ US"252", /* VRFY */
+ US"0", /* delivery; not relevant */
+ US"0" /* unknown; not relevant */
};
BOOL active_local_from_check = FALSE;
BOOL active_local_sender_retain = FALSE;
+ int body_8bitmime = 0;
BOOL accept_8bitmime = TRUE; /* deliberately not RFC compliant */
address_item *addr_duplicate = NULL;
NULL, /* cipher */
NULL, /* peerdn */
#endif
+ NULL, /* authenticator */
+ NULL, /* auth_id */
+ NULL, /* auth_sndr */
(uid_t)(-1), /* uid */
(gid_t)(-1), /* gid */
0, /* flags */
NULL, /* client_condition */
NULL, /* public_name */
NULL, /* set_id */
+ NULL, /* set_client_id */
NULL, /* server_mail_auth_condition */
NULL, /* server_debug_string */
NULL, /* server_condition */
BOOL check_rfc2047_length = TRUE;
int check_spool_inodes = 0;
int check_spool_space = 0;
+ uschar *client_authenticator = NULL;
+ uschar *client_authenticated_id = NULL;
+ uschar *client_authenticated_sender = NULL;
int clmacro_count = 0;
uschar *clmacros[MAX_CLMACROS];
BOOL config_changed = FALSE;
uschar *continue_transport = NULL;
uschar *csa_status = NULL;
+ BOOL cutthrough_delivery = FALSE;
+ int cutthrough_fd = -1;
BOOL daemon_listen = FALSE;
uschar *daemon_smtp_port = US"smtp";
int daemon_startup_retries = 9;
int daemon_startup_sleep = 30;
+#ifdef EXPERIMENTAL_DBL
+int dbl_defer_errno = 0;
+uschar *dbl_defer_errstr = NULL;
+uschar *dbl_delivery_query = NULL;
+uschar *dbl_delivery_ip = NULL;
+int dbl_delivery_port = 0;
+uschar *dbl_delivery_fqdn = NULL;
+uschar *dbl_delivery_local_part= NULL;
+uschar *dbl_delivery_domain = NULL;
+uschar *dbl_delivery_confirmation = NULL;
+#endif
+
#ifdef EXPERIMENTAL_DCC
BOOL dcc_direct_add_header = FALSE;
uschar *dcc_header = NULL;
BOOL dkim_collect_input = FALSE;
BOOL dkim_disable_verify = FALSE;
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ BOOL dmarc_has_been_checked = FALSE;
+ uschar *dmarc_ar_header = NULL;
+ uschar *dmarc_forensic_sender = NULL;
+ uschar *dmarc_history_file = NULL;
+ uschar *dmarc_status = NULL;
+ uschar *dmarc_status_text = NULL;
+ uschar *dmarc_tld_file = NULL;
+ uschar *dmarc_used_domain = NULL;
+ BOOL dmarc_disable_verify = FALSE;
+ BOOL dmarc_enable_forensic = FALSE;
+ #endif
uschar *dns_again_means_nonexist = NULL;
int dns_csa_search_limit = 5;
uschar *dns_ipv4_lookup = NULL;
int dns_retrans = 0;
int dns_retry = 0;
+ int dns_dnssec_ok = -1; /* <0 = not coerced */
int dns_use_edns0 = -1; /* <0 = not coerced */
uschar *dnslist_domain = NULL;
uschar *dnslist_matched = NULL;
/* Note that this list must be in alphabetical order. */
bit_table log_options[] = {
+ { US"8bitmime", LX_8bitmime },
{ US"acl_warn_skipped", LX_acl_warn_skipped },
{ US"address_rewrite", L_address_rewrite },
{ US"all", L_all },
{ US"smtp_confirmation", LX_smtp_confirmation },
{ US"smtp_connection", L_smtp_connection },
{ US"smtp_incomplete_transaction", L_smtp_incomplete_transaction },
+ { US"smtp_mailauth", LX_smtp_mailauth },
{ US"smtp_no_mail", LX_smtp_no_mail },
{ US"smtp_protocol_error", L_smtp_protocol_error },
{ US"smtp_syntax_error", L_smtp_syntax_error },
NULL /* redirect_router */
};
+ uschar *router_name = NULL;
+
ip_address_item *running_interfaces = NULL;
BOOL running_in_test_harness = FALSE;
uschar *sender_host_address = NULL;
uschar *sender_host_authenticated = NULL;
unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32];
+ BOOL sender_host_dnssec = FALSE;
uschar *sender_host_name = NULL;
int sender_host_port = 0;
BOOL sender_host_notsocket = FALSE;
BOOL submission_mode = FALSE;
uschar *submission_name = NULL;
BOOL suppress_local_fixups = FALSE;
+ BOOL suppress_local_fixups_default = FALSE;
BOOL synchronous_delivery = FALSE;
BOOL syslog_duplication = TRUE;
int syslog_facility = LOG_MAIL;
};
int transport_count;
+ uschar *transport_name = NULL;
int transport_newlines;
uschar **transport_filter_argv = NULL;
int transport_filter_timeout;
cluttered in several places (e.g. during logging) if we can always refer to
them. Also, the tls_ variables are now always visible. */
- extern int tls_active; /* fd/socket when in a TLS session */
- extern int tls_bits; /* bits used in TLS session */
- extern BOOL tls_certificate_verified; /* Client certificate verified */
- extern uschar *tls_cipher; /* Cipher used */
- extern BOOL tls_on_connect; /* For older MTAs that don't STARTTLS */
- extern uschar *tls_on_connect_ports; /* Ports always tls-on-connect */
- extern uschar *tls_peerdn; /* DN from peer */
+ typedef struct {
+ int active; /* fd/socket when in a TLS session */
+ int bits; /* bits used in TLS session */
+ BOOL certificate_verified; /* Client certificate verified */
+ uschar *cipher; /* Cipher used */
+ BOOL on_connect; /* For older MTAs that don't STARTTLS */
+ uschar *on_connect_ports; /* Ports always tls-on-connect */
+ uschar *peerdn; /* DN from peer */
+ uschar *sni; /* Server Name Indication */
+ } tls_support;
+ extern tls_support tls_in;
+ extern tls_support tls_out;
#ifdef SUPPORT_TLS
extern BOOL gnutls_compat_mode; /* Less security, more compatibility */
+ extern BOOL gnutls_enable_pkcs11; /* Let GnuTLS autoload PKCS11 modules */
extern uschar *gnutls_require_mac; /* So some can be avoided */
extern uschar *gnutls_require_kx; /* So some can be avoided */
extern uschar *gnutls_require_proto; /* So some can be avoided */
extern uschar *tls_privatekey; /* Private key file */
extern BOOL tls_remember_esmtp; /* For YAEB */
extern uschar *tls_require_ciphers; /* So some can be avoided */
- extern uschar *tls_sni; /* Server Name Indication */
extern uschar *tls_try_verify_hosts; /* Optional client verification */
extern uschar *tls_verify_certificates;/* Path for certificates to check */
extern uschar *tls_verify_hosts; /* Mandatory client verification */
/* General global variables */
extern BOOL accept_8bitmime; /* Allow *BITMIME incoming */
+ extern int body_8bitmime; /* sender declared BODY= ; 7=7BIT, 8=8BITMIME */
extern header_line *acl_added_headers; /* Headers added by an ACL */
extern tree_node *acl_anchor; /* Tree of named ACLs */
+ extern uschar *acl_arg[9]; /* Argument to ACL call */
+ extern int acl_narg; /* Number of arguments to ACL call */
extern uschar *acl_not_smtp; /* ACL run for non-SMTP messages */
#ifdef WITH_CONTENT_SCAN
extern uschar *acl_not_smtp_mime; /* For MIME parts of ditto */
#endif
extern uschar *acl_not_smtp_start; /* ACL run at the beginning of a non-SMTP session */
+ 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
extern BOOL check_rfc2047_length; /* Check RFC 2047 encoded string length */
extern int check_spool_inodes; /* Minimum for message acceptance */
extern int check_spool_space; /* Minimum for message acceptance */
+ extern uschar *client_authenticator; /* Authenticator name used for smtp delivery */
+ extern uschar *client_authenticated_id; /* "login" name used for SMTP AUTH */
+ extern uschar *client_authenticated_sender; /* AUTH option to SMTP MAIL FROM (not yet used) */
extern int clmacro_count; /* Number of command line macros */
extern uschar *clmacros[]; /* Copy of them, for re-exec */
extern int connection_max_messages;/* Max down one SMTP connection */
extern uschar *continue_transport; /* Transport for continued delivery */
extern uschar *csa_status; /* Client SMTP Authorization result */
+ extern BOOL cutthrough_delivery; /* Deliver in foreground */
+ extern int cutthrough_fd; /* Connection for ditto */
extern BOOL daemon_listen; /* True if listening required */
extern uschar *daemon_smtp_port; /* Can be a list of ports */
extern uschar *dccifd_options; /* options for the dccifd daemon */
#endif
+#ifdef EXPERIMENTAL_DBL
+extern int dbl_defer_errno; /* error number set when a remote delivery is deferred with a host error */
+extern uschar *dbl_defer_errstr; /* error string set when a remote delivery is deferred with a host error */
+extern uschar *dbl_delivery_query; /* query string to log delivery info in DB */
+extern uschar *dbl_delivery_ip; /* IP of host, which has accepted delivery */
+extern int dbl_delivery_port; /* port of host, which has accepted delivery */
+extern uschar *dbl_delivery_fqdn; /* FQDN of host, which has accepted delivery */
+extern uschar *dbl_delivery_local_part;/* local part of address being delivered */
+extern uschar *dbl_delivery_domain; /* domain part of address being delivered */
+extern uschar *dbl_delivery_confirmation; /* SMTP confirmation message */
+#endif
+
extern BOOL debug_daemon; /* Debug the daemon process only */
extern int debug_fd; /* The fd for debug_file */
extern FILE *debug_file; /* Where to write debugging info */
extern BOOL dkim_collect_input; /* Runtime flag that tracks wether SMTP input is fed to DKIM validation */
extern BOOL dkim_disable_verify; /* Set via ACL control statement. When set, DKIM verification is disabled for the current message */
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ extern BOOL dmarc_has_been_checked; /* Global variable to check if test has been called yet */
+ extern uschar *dmarc_ar_header; /* Expansion variable, suggested header for dmarc auth results */
+ extern uschar *dmarc_forensic_sender; /* Set sender address for forensic reports */
+ extern uschar *dmarc_history_file; /* Expansion variable, file to store dmarc results */
+ extern uschar *dmarc_status; /* Expansion variable, one word value */
+ extern uschar *dmarc_status_text; /* Expansion variable, human readable value */
+ extern uschar *dmarc_tld_file; /* Mozilla TLDs text file */
+ extern uschar *dmarc_used_domain; /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */
+ extern BOOL dmarc_disable_verify; /* Set via ACL control statement. When set, DMARC verification is disabled for the current message */
+ extern BOOL dmarc_enable_forensic; /* Set via ACL control statement. When set, DMARC forensic reports are enabled for the current message */
+ #endif
extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
extern int dns_csa_search_limit; /* How deep to search for CSA SRV records */
extern uschar *dns_ipv4_lookup; /* For these domains, don't look for AAAA (or A6) */
extern int dns_retrans; /* Retransmission time setting */
extern int dns_retry; /* Number of retries */
+ extern int dns_dnssec_ok; /* When constructing DNS query, set DO flag */
extern int dns_use_edns0; /* Coerce EDNS0 support on/off in resolver. */
extern uschar *dnslist_domain; /* DNS (black) list domain */
extern uschar *dnslist_matched; /* DNS (black) list matched key */
extern gid_t exim_gid; /* To be used with exim_uid */
extern BOOL exim_gid_set; /* TRUE if exim_gid set */
extern uschar *exim_path; /* Path to exec exim */
+ extern const uschar *exim_sieve_extension_list[]; /* list of sieve extensions */
extern uid_t exim_uid; /* Non-root uid for exim */
extern BOOL exim_uid_set; /* TRUE if exim_uid set */
extern int expand_forbid; /* RDO flags for forbidding things */
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 */
extern router_info routers_available[];/* Vector of available routers */
extern router_instance *routers; /* Chain of instantiated routers */
extern router_instance router_defaults;/* Default values */
+ extern uschar *router_name; /* Name of router last started */
extern BOOL running_in_test_harness; /*TRUE when running_status is patched */
extern ip_address_item *running_interfaces; /* Host's running interfaces */
extern uschar *running_status; /* Flag string for testing */
extern uschar *sender_helo_name; /* Host name from HELO/EHLO */
extern uschar **sender_host_aliases; /* Points to list of alias names */
extern unsigned int sender_host_cache[(MAX_NAMED_LIST * 2)/32]; /* Cache bits for incoming host */
+ extern BOOL sender_host_dnssec; /* true if sender_host_name verified in DNSSEC */
extern BOOL sender_host_notsocket; /* Set for -bs and -bS */
extern BOOL sender_host_unknown; /* TRUE for -bs and -bS except inetd */
extern uschar *sender_ident; /* Sender identity via RFC 1413 */
extern BOOL submission_mode; /* Can be forced from ACL */
extern uschar *submission_name; /* User name set from ACL */
extern BOOL suppress_local_fixups; /* Can be forced from ACL */
+ extern BOOL suppress_local_fixups_default; /* former is reset to this; override with -G */
extern BOOL synchronous_delivery; /* TRUE if -odi is set */
extern BOOL syslog_duplication; /* FALSE => no duplicate logging */
extern int syslog_facility; /* As defined by Syslog.h */
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 */
+ 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 */
extern uschar **transport_filter_argv; /* For on-the-fly filtering */
{ "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
{ "daemon_smtp_ports", opt_stringptr, &daemon_smtp_port },
{ "daemon_startup_retries", opt_int, &daemon_startup_retries },
{ "daemon_startup_sleep", opt_time, &daemon_startup_sleep },
+#ifdef EXPERIMENTAL_DBL
+ { "dbl_delivery_query", opt_stringptr, &dbl_delivery_query },
+#endif
#ifdef EXPERIMENTAL_DCC
{ "dcc_direct_add_header", opt_bool, &dcc_direct_add_header },
{ "dccifd_address", opt_stringptr, &dccifd_address },
{ "disable_ipv6", opt_bool, &disable_ipv6 },
#ifndef DISABLE_DKIM
{ "dkim_verify_signers", opt_stringptr, &dkim_verify_signers },
+ #endif
+ #ifdef EXPERIMENTAL_DMARC
+ { "dmarc_forensic_sender", opt_stringptr, &dmarc_forensic_sender },
+ { "dmarc_history_file", opt_stringptr, &dmarc_history_file },
+ { "dmarc_tld_file", opt_stringptr, &dmarc_tld_file },
#endif
{ "dns_again_means_nonexist", opt_stringptr, &dns_again_means_nonexist },
{ "dns_check_names_pattern", opt_stringptr, &check_dns_names_pattern },
{ "dns_csa_search_limit", opt_int, &dns_csa_search_limit },
{ "dns_csa_use_reverse", opt_bool, &dns_csa_use_reverse },
+ { "dns_dnssec_ok", opt_int, &dns_dnssec_ok },
{ "dns_ipv4_lookup", opt_stringptr, &dns_ipv4_lookup },
{ "dns_retrans", opt_time, &dns_retrans },
{ "dns_retry", opt_int, &dns_retry },
{ "gecos_pattern", opt_stringptr, &gecos_pattern },
#ifdef SUPPORT_TLS
{ "gnutls_compat_mode", opt_bool, &gnutls_compat_mode },
+ { "gnutls_enable_pkcs11", opt_bool, &gnutls_enable_pkcs11 },
/* These three gnutls_require_* options stopped working in Exim 4.80 */
{ "gnutls_require_kx", opt_stringptr, &gnutls_require_kx },
{ "gnutls_require_mac", opt_stringptr, &gnutls_require_mac },
#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 },
{ "tls_crl", opt_stringptr, &tls_crl },
{ "tls_dh_max_bits", opt_int, &tls_dh_max_bits },
{ "tls_dhparam", opt_stringptr, &tls_dhparam },
- #if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
+ # if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS)
{ "tls_ocsp_file", opt_stringptr, &tls_ocsp_file },
- #endif
- { "tls_on_connect_ports", opt_stringptr, &tls_on_connect_ports },
+ # endif
+ { "tls_on_connect_ports", opt_stringptr, &tls_in.on_connect_ports },
{ "tls_privatekey", opt_stringptr, &tls_privatekey },
{ "tls_remember_esmtp", opt_bool, &tls_remember_esmtp },
{ "tls_require_ciphers", opt_stringptr, &tls_require_ciphers },
gid_t gid;
BOOL boolvalue = TRUE;
BOOL freesptr = TRUE;
- BOOL extra_condition = FALSE;
optionlist *ol, *ol2;
struct passwd *pw;
void *reset_point;
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, CS unknown_txt, name);
}
- if ((ol->type & opt_set) != 0)
- {
- uschar *mname = name;
- if (Ustrncmp(mname, "no_", 3) == 0) mname += 3;
- if (Ustrcmp(mname, "condition") == 0)
- extra_condition = TRUE;
- else
- log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
- "\"%s\" option set for the second time", mname);
- }
+ if ((ol->type & opt_set) && !(ol->type & (opt_rep_con | opt_rep_str)))
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+ "\"%s\" option set for the second time", name);
ol->type |= opt_set | issecure;
type = ol->type & opt_mask;
str_target = (uschar **)(ol->value);
else
str_target = (uschar **)((uschar *)data_block + (long int)(ol->value));
- if (extra_condition)
+ if (ol->type & opt_rep_con)
{
/* We already have a condition, we're conducting a crude hack to let
multiple condition rules be chained together, despite storing them in
strtemp = string_sprintf("${if and{{bool_lax{%s}}{bool_lax{%s}}}}",
saved_condition, sptr);
*str_target = string_copy_malloc(strtemp);
- /* TODO(pdp): there is a memory leak here when we set 3 or more
- conditions; I still don't understand the store mechanism enough
- to know what's the safe way to free content from an earlier store.
+ /* TODO(pdp): there is a memory leak here and just below
+ when we set 3 or more conditions; I still don't
+ understand the store mechanism enough to know
+ what's the safe way to free content from an earlier store.
AFAICT, stores stack, so freeing an early stored item also stores
all data alloc'd after it. If we knew conditions were adjacent,
we could survive that, but we don't. So I *think* we need to take
Because we only do this once, near process start-up, I'm prepared to
let this slide for the time being, even though it rankles. */
}
+ else if (*str_target && (ol->type & opt_rep_str))
+ {
+ uschar sep = Ustrncmp(name, "headers_add", 11)==0 ? '\n' : ':';
+ saved_condition = *str_target;
+ strtemp = saved_condition + Ustrlen(saved_condition)-1;
+ if (*strtemp == sep) *strtemp = 0; /* eliminate trailing list-sep */
+ strtemp = string_sprintf("%s%c%s", saved_condition, sep, sptr);
+ *str_target = string_copy_malloc(strtemp);
+ }
else
{
*str_target = sptr;
resides.
oltop points to the option list in which ol exists
last one more than the offset of the last entry in optop
+ no_labels do not show "foo = " at the start.
Returns: nothing
*/
static void
print_ol(optionlist *ol, uschar *name, void *options_block,
- optionlist *oltop, int last)
+ optionlist *oltop, int last, BOOL no_labels)
{
struct passwd *pw;
struct group *gr;
if (!admin_user && (ol->type & opt_secure) != 0)
{
- printf("%s = <value not displayable>\n", name);
+ const char * const hidden = "<value not displayable>";
+ if (no_labels)
+ printf("%s\n", hidden);
+ else
+ printf("%s = %s\n", name, hidden);
return;
}
case opt_stringptr:
case opt_rewrite: /* Show the text value */
s = *((uschar **)value);
- printf("%s = %s\n", name, (s == NULL)? US"" : string_printing2(s, FALSE));
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", (s == NULL)? US"" : string_printing2(s, FALSE));
break;
case opt_int:
- printf("%s = %d\n", name, *((int *)value));
+ if (!no_labels) printf("%s = ", name);
+ printf("%d\n", *((int *)value));
break;
case opt_mkint:
c = 'M';
x >>= 10;
}
- printf("%s = %d%c\n", name, x, c);
+ if (!no_labels) printf("%s = ", name);
+ printf("%d%c\n", x, c);
+ }
+ else
+ {
+ if (!no_labels) printf("%s = ", name);
+ printf("%d\n", x);
}
- else printf("%s = %d\n", name, x);
}
break;
case opt_Kint:
{
int x = *((int *)value);
- if (x == 0) printf("%s = 0\n", name);
- else if ((x & 1023) == 0) printf("%s = %dM\n", name, x >> 10);
- else printf("%s = %dK\n", name, x);
+ if (!no_labels) printf("%s = ", name);
+ if (x == 0) printf("0\n");
+ else if ((x & 1023) == 0) printf("%dM\n", x >> 10);
+ else printf("%dK\n", x);
}
break;
case opt_octint:
- printf("%s = %#o\n", name, *((int *)value));
+ if (!no_labels) printf("%s = ", name);
+ printf("%#o\n", *((int *)value));
break;
/* Can be negative only when "unset", in which case integer */
int d = 100;
if (x < 0) printf("%s =\n", name); else
{
- printf("%s = %d.", name, x/1000);
+ if (!no_labels) printf("%s = ", name);
+ printf("%d.", x/1000);
do
{
printf("%d", f/d);
if (options_block != NULL)
value2 = (void *)((uschar *)options_block + (long int)value2);
s = *((uschar **)value2);
- printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", (s == NULL)? US"" : string_printing(s));
break;
}
}
/* Else fall through */
case opt_uid:
+ if (!no_labels) printf("%s = ", name);
if (! *get_set_flag(name, oltop, last, options_block))
- printf("%s =\n", name);
+ printf("\n");
else
{
pw = getpwuid(*((uid_t *)value));
if (pw == NULL)
- printf("%s = %ld\n", name, (long int)(*((uid_t *)value)));
- else printf("%s = %s\n", name, pw->pw_name);
+ printf("%ld\n", (long int)(*((uid_t *)value)));
+ else printf("%s\n", pw->pw_name);
}
break;
if (options_block != NULL)
value2 = (void *)((uschar *)options_block + (long int)value2);
s = *((uschar **)value2);
- printf("%s = %s\n", name, (s == NULL)? US"" : string_printing(s));
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", (s == NULL)? US"" : string_printing(s));
break;
}
}
/* Else fall through */
case opt_gid:
+ if (!no_labels) printf("%s = ", name);
if (! *get_set_flag(name, oltop, last, options_block))
- printf("%s =\n", name);
+ printf("\n");
else
{
gr = getgrgid(*((int *)value));
if (gr == NULL)
- printf("%s = %ld\n", name, (long int)(*((int *)value)));
- else printf("%s = %s\n", name, gr->gr_name);
+ printf("%ld\n", (long int)(*((int *)value)));
+ else printf("%s\n", gr->gr_name);
}
break;
case opt_uidlist:
uidlist = *((uid_t **)value);
- printf("%s =", name);
+ if (!no_labels) printf("%s =", name);
if (uidlist != NULL)
{
int i;
uschar sep = ' ';
+ if (no_labels) sep = '\0';
for (i = 1; i <= (int)(uidlist[0]); i++)
{
uschar *name = NULL;
pw = getpwuid(uidlist[i]);
if (pw != NULL) name = US pw->pw_name;
- if (name != NULL) printf("%c%s", sep, name);
- else printf("%c%ld", sep, (long int)(uidlist[i]));
+ if (sep != '\0') printf("%c", sep);
+ if (name != NULL) printf("%s", name);
+ else printf("%ld", (long int)(uidlist[i]));
sep = ':';
}
}
case opt_gidlist:
gidlist = *((gid_t **)value);
- printf("%s =", name);
+ if (!no_labels) printf("%s =", name);
if (gidlist != NULL)
{
int i;
uschar sep = ' ';
+ if (no_labels) sep = '\0';
for (i = 1; i <= (int)(gidlist[0]); i++)
{
uschar *name = NULL;
gr = getgrgid(gidlist[i]);
if (gr != NULL) name = US gr->gr_name;
- if (name != NULL) printf("%c%s", sep, name);
- else printf("%c%ld", sep, (long int)(gidlist[i]));
+ if (sep != '\0') printf("%c", sep);
+ if (name != NULL) printf("%s", name);
+ else printf("%ld", (long int)(gidlist[i]));
sep = ':';
}
}
break;
case opt_time:
- printf("%s = %s\n", name, readconf_printtime(*((int *)value)));
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", readconf_printtime(*((int *)value)));
break;
case opt_timelist:
{
int i;
int *list = (int *)value;
- printf("%s = ", name);
+ if (!no_labels) printf("%s = ", name);
for (i = 0; i < list[1]; i++)
printf("%s%s", (i == 0)? "" : ":", readconf_printtime(list[i+2]));
printf("\n");
s = *((uschar **)value2);
if (s != NULL)
{
- printf("%s = %s\n", name, string_printing(s));
+ if (!no_labels) printf("%s = ", name);
+ printf("%s\n", string_printing(s));
break;
}
/* s == NULL => string not set; fall through */
Arguments:
name option name if type == NULL; else driver name
type NULL or driver type name, as described above
+ no_labels avoid the "foo = " at the start of an item
Returns: nothing
*/
void
- readconf_print(uschar *name, uschar *type)
+ readconf_print(uschar *name, uschar *type, BOOL no_labels)
{
BOOL names_only = FALSE;
optionlist *ol;
if (t != NULL)
{
found = TRUE;
- printf("%slist %s = %s\n", types[i], name+1,
- ((namedlist_block *)(t->data.ptr))->string);
+ if (no_labels)
+ printf("%s\n", ((namedlist_block *)(t->data.ptr))->string);
+ else
+ printf("%slist %s = %s\n", types[i], name+1,
+ ((namedlist_block *)(t->data.ptr))->string);
}
}
ol < optionlist_config + optionlist_config_size; ol++)
{
if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, NULL, optionlist_config, optionlist_config_size);
+ print_ol(ol, US ol->name, NULL,
+ optionlist_config, optionlist_config_size,
+ no_labels);
}
return;
}
ol < local_scan_options + local_scan_options_count; ol++)
{
print_ol(ol, US ol->name, NULL, local_scan_options,
- local_scan_options_count);
+ local_scan_options_count, no_labels);
}
#endif
return;
else
{
print_ol(find_option(name, optionlist_config, optionlist_config_size),
- name, NULL, optionlist_config, optionlist_config_size);
+ name, NULL, optionlist_config, optionlist_config_size, no_labels);
return;
}
}
for (ol = ol2; ol < ol2 + size; ol++)
{
if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, d, ol2, size);
+ print_ol(ol, US ol->name, d, ol2, size, no_labels);
}
for (ol = d->info->options;
ol < d->info->options + *(d->info->options_count); ol++)
{
if ((ol->type & opt_hidden) == 0)
- print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count));
+ print_ol(ol, US ol->name, d, d->info->options, *(d->info->options_count), no_labels);
}
if (name != NULL) return;
}
pp = p;
while (mac_isgraph(*p)) p++;
if (p - pp <= 0) log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
- "missing error type");
+ "missing error type in retry rule");
/* Test error names for things we understand. */
(void *)offsetof(transport_instance, connection_max_messages) },
{ "data_timeout", opt_time,
(void *)offsetof(smtp_transport_options_block, data_timeout) },
+#ifdef EXPERIMENTAL_DBL
+ { "dbl_host_defer_query", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dbl_host_defer_query) },
+#endif
{ "delay_after_cutoff", opt_bool,
(void *)offsetof(smtp_transport_options_block, delay_after_cutoff) },
#ifndef DISABLE_DKIM
(void *)offsetof(smtp_transport_options_block, dns_qualify_single) },
{ "dns_search_parents", opt_bool,
(void *)offsetof(smtp_transport_options_block, dns_search_parents) },
+ { "dscp", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, dscp) },
{ "fallback_hosts", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, fallback_hosts) },
{ "final_timeout", opt_time,
{ "hosts_require_auth", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_auth) },
#ifdef SUPPORT_TLS
+ # if defined EXPERIMENTAL_OCSP
+ { "hosts_require_ocsp", opt_stringptr,
+ (void *)offsetof(smtp_transport_options_block, hosts_require_ocsp) },
+ # endif
{ "hosts_require_tls", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, hosts_require_tls) },
#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) },
+ #endif
{ "interface", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, interface) },
{ "keepalive", opt_bool,
(void *)offsetof(smtp_transport_options_block, tls_certificate) },
{ "tls_crl", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_crl) },
+ { "tls_dh_min_bits", opt_int,
+ (void *)offsetof(smtp_transport_options_block, tls_dh_min_bits) },
{ "tls_privatekey", opt_stringptr,
(void *)offsetof(smtp_transport_options_block, tls_privatekey) },
{ "tls_require_ciphers", opt_stringptr,
NULL, /* interface */
NULL, /* port */
US"smtp", /* protocol */
+ NULL, /* DSCP */
NULL, /* serialize_hosts */
NULL, /* hosts_try_auth */
NULL, /* hosts_require_auth */
+ #ifdef EXPERIMENTAL_PRDR
+ NULL, /* hosts_try_prdr */
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+ NULL, /* hosts_require_ocsp */
+ #endif
NULL, /* hosts_require_tls */
NULL, /* hosts_avoid_tls */
+ US"*", /* hosts_verify_avoid_tls */
NULL, /* hosts_avoid_pipelining */
NULL, /* hosts_avoid_esmtp */
NULL, /* hosts_nopass_tls */
NULL, /* gnutls_require_kx */
NULL, /* gnutls_require_mac */
NULL, /* gnutls_require_proto */
+ NULL, /* tls_sni */
NULL, /* tls_verify_certificates */
- TRUE, /* tls_tempfail_tryclear */
- NULL /* tls_sni */
+ EXIM_CLIENT_DH_DEFAULT_MIN_BITS,
+ /* tls_dh_min_bits */
+ TRUE /* tls_tempfail_tryclear */
#endif
#ifndef DISABLE_DKIM
,NULL, /* dkim_canon */
NULL, /* dkim_sign_headers */
NULL /* dkim_strict */
#endif
+#ifdef EXPERIMENTAL_DBL
+ ,NULL /* dbl_host_defer_query */
+#endif
};
+#ifdef EXPERIMENTAL_DBL
+/*************************************************
+* Write error message to database log *
+*************************************************/
+
+/* This writes to the database log
+
+Arguments:
+ dbl_host_defer_query dbl_host_defer_query from the transport options block
+ addr the address item containing error information
+ host the current host
+
+Returns: nothing
+*/
+
+static void
+dbl_write_defer_log(uschar *dbl_host_defer_query, address_item *addr, host_item *host)
+{
+if (dbl_host_defer_query == NULL)
+ return;
+
+dbl_delivery_ip = string_copy(host->address);
+dbl_delivery_port = (host->port == PORT_NONE)? 25 : host->port;
+dbl_delivery_fqdn = string_copy(host->name);
+dbl_delivery_local_part = string_copy(addr->local_part);
+dbl_delivery_domain = string_copy(addr->domain);
+dbl_defer_errno = addr->basic_errno;
+
+dbl_defer_errstr = NULL;
+if (addr->message != NULL)
+ if (addr->basic_errno > 0)
+ dbl_defer_errstr = string_sprintf("%s: %s", addr->message, strerror(addr->basic_errno));
+ else
+ dbl_defer_errstr = string_copy(addr->message);
+else if (addr->basic_errno > 0)
+ dbl_defer_errstr = string_copy(strerror(addr->basic_errno));
+
+DEBUG(D_transport) {
+ debug_printf(" DBL(host defer): dbl_host_defer_query=|%s| dbl_delivery_IP=%s\n", dbl_host_defer_query, dbl_delivery_ip);
+}
+expand_string(dbl_host_defer_query);
+}
+#endif
+
+
+
/*************************************************
* Synchronize SMTP responses *
*************************************************/
+ /* Do the client side of smtp-level authentication */
+ /*
+ Arguments:
+ buffer EHLO response from server (gets overwritten)
+ addrlist chain of potential addresses to deliver
+ host host to deliver to
+ ob transport options
+ ibp, obp comms channel control blocks
+
+ Returns:
+ OK Success, or failed (but not required): global "smtp_authenticated" set
+ DEFER Failed authentication (and was required)
+ ERROR Internal problem
+
+ FAIL_SEND Failed communications - transmit
+ FAIL - response
+ */
+
+ int
+ smtp_auth(uschar *buffer, unsigned bufsize, address_item *addrlist, host_item *host,
+ smtp_transport_options_block *ob, BOOL is_esmtp,
+ smtp_inblock *ibp, smtp_outblock *obp)
+ {
+ int require_auth;
+ uschar *fail_reason = US"server did not advertise AUTH support";
+
+ smtp_authenticated = FALSE;
+ client_authenticator = client_authenticated_id = client_authenticated_sender = NULL;
+ require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
+ host->name, host->address, NULL);
+
+ if (is_esmtp && !regex_AUTH) regex_AUTH =
+ regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
+ FALSE, TRUE);
+
+ if (is_esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+ {
+ uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
+ expand_nmax = -1; /* reset */
+
+ /* Must not do this check until after we have saved the result of the
+ regex match above. */
+
+ if (require_auth == OK ||
+ verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
+ host->address, NULL) == OK)
+ {
+ auth_instance *au;
+ fail_reason = US"no common mechanisms were found";
+
+ DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
+
+ /* Scan the configured authenticators looking for one which is configured
+ for use as a client, which is not suppressed by client_condition, and
+ whose name matches an authentication mechanism supported by the server.
+ If one is found, attempt to authenticate by calling its client function.
+ */
+
+ for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
+ {
+ uschar *p = names;
+ if (!au->client ||
+ (au->client_condition != NULL &&
+ !expand_check_condition(au->client_condition, au->name,
+ US"client authenticator")))
+ {
+ DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
+ au->name,
+ (au->client)? "client_condition is false" :
+ "not configured as a client");
+ continue;
+ }
+
+ /* Loop to scan supported server mechanisms */
+
+ while (*p != 0)
+ {
+ int rc;
+ int len = Ustrlen(au->public_name);
+ while (isspace(*p)) p++;
+
+ if (strncmpic(au->public_name, p, len) != 0 ||
+ (p[len] != 0 && !isspace(p[len])))
+ {
+ while (*p != 0 && !isspace(*p)) p++;
+ continue;
+ }
+
+ /* Found data for a listed mechanism. Call its client entry. Set
+ a flag in the outblock so that data is overwritten after sending so
+ that reflections don't show it. */
+
+ fail_reason = US"authentication attempt(s) failed";
+ obp->authenticating = TRUE;
+ rc = (au->info->clientcode)(au, ibp, obp,
+ ob->command_timeout, buffer, bufsize);
+ obp->authenticating = FALSE;
+ DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
+ au->name, rc);
+
+ /* A temporary authentication failure must hold up delivery to
+ this host. After a permanent authentication failure, we carry on
+ to try other authentication methods. If all fail hard, try to
+ deliver the message unauthenticated unless require_auth was set. */
+
+ switch(rc)
+ {
+ case OK:
+ smtp_authenticated = TRUE; /* stops the outer loop */
+ client_authenticator = au->name;
+ if (au->set_client_id != NULL)
+ client_authenticated_id = expand_string(au->set_client_id);
+ break;
+
+ /* Failure after writing a command */
+
+ case FAIL_SEND:
+ return FAIL_SEND;
+
+ /* Failure after reading a response */
+
+ case FAIL:
+ if (errno != 0 || buffer[0] != '5') return FAIL;
+ log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
+ au->name, host->name, host->address, buffer);
+ break;
+
+ /* Failure by some other means. In effect, the authenticator
+ decided it wasn't prepared to handle this case. Typically this
+ is the result of "fail" in an expansion string. Do we need to
+ log anything here? Feb 2006: a message is now put in the buffer
+ if logging is required. */
+
+ case CANCELLED:
+ if (*buffer != 0)
+ log_write(0, LOG_MAIN, "%s authenticator cancelled "
+ "authentication H=%s [%s] %s", au->name, host->name,
+ host->address, buffer);
+ break;
+
+ /* Internal problem, message in buffer. */
+
+ case ERROR:
+ set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
+ return ERROR;
+ }
+
+ break; /* If not authenticated, try next authenticator */
+ } /* Loop for scanning supported server mechanisms */
+ } /* Loop for further authenticators */
+ }
+ }
+
+ /* If we haven't authenticated, but are required to, give up. */
+
+ if (require_auth == OK && !smtp_authenticated)
+ {
+ set_errno(addrlist, ERRNO_AUTHFAIL,
+ string_sprintf("authentication required but %s", fail_reason), DEFER,
+ FALSE);
+ return DEFER;
+ }
+
+ return OK;
+ }
+
+
+ /* Construct AUTH appendix string for MAIL TO */
+ /*
+ Arguments
+ buffer to build string
+ addrlist chain of potential addresses to deliver
+ ob transport options
+
+ Globals smtp_authenticated
+ client_authenticated_sender
+ Return True on error, otherwise buffer has (possibly empty) terminated string
+ */
+
+ BOOL
+ smtp_mail_auth_str(uschar *buffer, unsigned bufsize, address_item *addrlist,
+ smtp_transport_options_block *ob)
+ {
+ uschar *local_authenticated_sender = authenticated_sender;
+
+ #ifdef notdef
+ debug_printf("smtp_mail_auth_str: as<%s> os<%s> SA<%s>\n", authenticated_sender, ob->authenticated_sender, smtp_authenticated?"Y":"N");
+ #endif
+
+ if (ob->authenticated_sender != NULL)
+ {
+ uschar *new = expand_string(ob->authenticated_sender);
+ if (new == NULL)
+ {
+ if (!expand_string_forcedfail)
+ {
+ uschar *message = string_sprintf("failed to expand "
+ "authenticated_sender: %s", expand_string_message);
+ set_errno(addrlist, 0, message, DEFER, FALSE);
+ return TRUE;
+ }
+ }
+ else if (new[0] != 0) local_authenticated_sender = new;
+ }
+
+ /* Add the authenticated sender address if present */
+
+ if ((smtp_authenticated || ob->authenticated_sender_force) &&
+ local_authenticated_sender != NULL)
+ {
+ string_format(buffer, bufsize, " AUTH=%s",
+ auth_xtextencode(local_authenticated_sender,
+ Ustrlen(local_authenticated_sender)));
+ client_authenticated_sender = string_copy(local_authenticated_sender);
+ }
+ else
+ *buffer= 0;
+
+ return FALSE;
+ }
+
+
+
/*************************************************
* Deliver address list to given host *
*************************************************/
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;
uschar *igquotstr = US"";
- uschar *local_authenticated_sender = authenticated_sender;
uschar *helo_data = NULL;
uschar *message = NULL;
uschar new_message_id[MESSAGE_ID_LENGTH + 1];
/* Reset the parameters of a TLS session. */
- tls_bits = 0;
- tls_cipher = NULL;
- tls_peerdn = NULL;
+ tls_in.bits = 0;
+ tls_in.cipher = NULL; /* for back-compatible behaviour */
+ tls_in.peerdn = NULL;
#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
- tls_sni = NULL;
+ tls_in.sni = NULL;
#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.
- Other expansion failures are serious. An empty result is ignored, but there is
- otherwise no check - this feature is expected to be used with LMTP and other
- cases where non-standard addresses (e.g. without domains) might be required. */
-
- if (ob->authenticated_sender != NULL)
- {
- uschar *new = expand_string(ob->authenticated_sender);
- if (new == NULL)
- {
- if (!expand_string_forcedfail)
- {
- uschar *message = string_sprintf("failed to expand "
- "authenticated_sender: %s", expand_string_message);
- set_errno(addrlist, 0, message, DEFER, FALSE);
- return ERROR;
- }
- }
- else if (new[0] != 0) local_authenticated_sender = new;
- }
+ tls_out.bits = 0;
+ tls_out.cipher = NULL; /* the one we may use for this transport */
+ tls_out.peerdn = NULL;
+ #if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ tls_out.sni = NULL;
+ #endif
#ifndef SUPPORT_TLS
if (smtps)
{
inblock.sock = outblock.sock =
smtp_connect(host, host_af, port, interface, ob->connect_timeout,
- ob->keepalive); /* This puts port into host->port */
+ ob->keepalive, ob->dscp); /* This puts port into host->port */
if (inblock.sock < 0)
{
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
int rc = tls_client_start(inblock.sock,
host,
addrlist,
- NULL, /* No DH param */
ob->tls_certificate,
ob->tls_privatekey,
ob->tls_sni,
ob->tls_verify_certificates,
ob->tls_crl,
ob->tls_require_ciphers,
+ #ifdef EXPERIMENTAL_OCSP
+ ob->hosts_require_ocsp,
+ #endif
+ ob->tls_dh_min_bits,
ob->command_timeout);
/* TLS negotiation failed; give an error. From outside, this function may
{
if (addr->transport_return == PENDING_DEFER)
{
- addr->cipher = tls_cipher;
- addr->peerdn = tls_peerdn;
+ addr->cipher = tls_out.cipher;
+ addr->peerdn = tls_out.peerdn;
}
}
}
expand it here. $sending_ip_address and $sending_port are set up right at the
start of the Exim process (in exim.c). */
- if (tls_active >= 0)
+ if (tls_out.active >= 0)
{
char *greeting_cmd;
if (helo_data == NULL)
if (continue_hostname == NULL
#ifdef SUPPORT_TLS
- || tls_active >= 0
+ || tls_out.active >= 0
#endif
)
{
- int require_auth;
- uschar *fail_reason = US"server did not advertise AUTH support";
-
/* Set for IGNOREQUOTA if the response to LHLO specifies support and the
lmtp_ignore_quota option was set. */
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
authenticator's client driver is running. */
- smtp_authenticated = FALSE;
- require_auth = verify_check_this_host(&(ob->hosts_require_auth), NULL,
- host->name, host->address, NULL);
-
- if (esmtp && regex_match_and_setup(regex_AUTH, buffer, 0, -1))
+ switch (yield = smtp_auth(buffer, sizeof(buffer), addrlist, host,
+ ob, esmtp, &inblock, &outblock))
{
- uschar *names = string_copyn(expand_nstring[1], expand_nlength[1]);
- expand_nmax = -1; /* reset */
-
- /* Must not do this check until after we have saved the result of the
- regex match above. */
-
- if (require_auth == OK ||
- verify_check_this_host(&(ob->hosts_try_auth), NULL, host->name,
- host->address, NULL) == OK)
- {
- auth_instance *au;
- fail_reason = US"no common mechanisms were found";
-
- DEBUG(D_transport) debug_printf("scanning authentication mechanisms\n");
-
- /* Scan the configured authenticators looking for one which is configured
- for use as a client, which is not suppressed by client_condition, and
- whose name matches an authentication mechanism supported by the server.
- If one is found, attempt to authenticate by calling its client function.
- */
-
- for (au = auths; !smtp_authenticated && au != NULL; au = au->next)
- {
- uschar *p = names;
- if (!au->client ||
- (au->client_condition != NULL &&
- !expand_check_condition(au->client_condition, au->name,
- US"client authenticator")))
- {
- DEBUG(D_transport) debug_printf("skipping %s authenticator: %s\n",
- au->name,
- (au->client)? "client_condition is false" :
- "not configured as a client");
- continue;
- }
-
- /* Loop to scan supported server mechanisms */
-
- while (*p != 0)
- {
- int rc;
- int len = Ustrlen(au->public_name);
- while (isspace(*p)) p++;
-
- if (strncmpic(au->public_name, p, len) != 0 ||
- (p[len] != 0 && !isspace(p[len])))
- {
- while (*p != 0 && !isspace(*p)) p++;
- continue;
- }
-
- /* Found data for a listed mechanism. Call its client entry. Set
- a flag in the outblock so that data is overwritten after sending so
- that reflections don't show it. */
-
- fail_reason = US"authentication attempt(s) failed";
- outblock.authenticating = TRUE;
- rc = (au->info->clientcode)(au, &inblock, &outblock,
- ob->command_timeout, buffer, sizeof(buffer));
- outblock.authenticating = FALSE;
- DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n",
- au->name, rc);
-
- /* A temporary authentication failure must hold up delivery to
- this host. After a permanent authentication failure, we carry on
- to try other authentication methods. If all fail hard, try to
- deliver the message unauthenticated unless require_auth was set. */
-
- switch(rc)
- {
- case OK:
- smtp_authenticated = TRUE; /* stops the outer loop */
- break;
-
- /* Failure after writing a command */
-
- case FAIL_SEND:
- goto SEND_FAILED;
-
- /* Failure after reading a response */
-
- case FAIL:
- if (errno != 0 || buffer[0] != '5') goto RESPONSE_FAILED;
- log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s",
- au->name, host->name, host->address, buffer);
- break;
-
- /* Failure by some other means. In effect, the authenticator
- decided it wasn't prepared to handle this case. Typically this
- is the result of "fail" in an expansion string. Do we need to
- log anything here? Feb 2006: a message is now put in the buffer
- if logging is required. */
-
- case CANCELLED:
- if (*buffer != 0)
- log_write(0, LOG_MAIN, "%s authenticator cancelled "
- "authentication H=%s [%s] %s", au->name, host->name,
- host->address, buffer);
- break;
-
- /* Internal problem, message in buffer. */
-
- case ERROR:
- yield = ERROR;
- set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
- goto SEND_QUIT;
- }
-
- break; /* If not authenticated, try next authenticator */
- } /* Loop for scanning supported server mechanisms */
- } /* Loop for further authenticators */
- }
- }
-
- /* If we haven't authenticated, but are required to, give up. */
-
- if (require_auth == OK && !smtp_authenticated)
- {
- yield = DEFER;
- set_errno(addrlist, ERRNO_AUTHFAIL,
- string_sprintf("authentication required but %s", fail_reason), DEFER,
- FALSE);
- goto SEND_QUIT;
+ default: goto SEND_QUIT;
+ case OK: break;
+ case FAIL_SEND: goto SEND_FAILED;
+ case FAIL: goto RESPONSE_FAILED;
}
}
while (*p) p++;
}
- /* Add the authenticated sender address if present */
-
- if ((smtp_authenticated || ob->authenticated_sender_force) &&
- local_authenticated_sender != NULL)
+ #ifdef EXPERIMENTAL_PRDR
+ prdr_active = FALSE;
+ if (prdr_offered)
{
- string_format(p, sizeof(buffer) - (p-buffer), " AUTH=%s",
- auth_xtextencode(local_authenticated_sender,
- Ustrlen(local_authenticated_sender)));
+ 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.
+ Other expansion failures are serious. An empty result is ignored, but there is
+ otherwise no check - this feature is expected to be used with LMTP and other
+ cases where non-standard addresses (e.g. without domains) might be required. */
+
+ if (smtp_mail_auth_str(p, sizeof(buffer) - (p-buffer), addrlist, ob))
+ return ERROR;
/* From here until we send the DATA command, we can make use of PIPELINING
if the server host supports it. The code has to be able to check the responses
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)
{
/* Set up confirmation if needed - applies only to SMTP */
- if ((log_extra_selector & LX_smtp_confirmation) != 0 && !lmtp)
+ if (
+ #ifndef EXPERIMENTAL_DBL
+ (log_extra_selector & LX_smtp_confirmation) != 0 &&
+ #endif
+ !lmtp
+ )
{
uschar *s = string_printing(buffer);
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)
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')
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;
}
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)
BOOL more;
if (first_addr != NULL || continue_more ||
(
- (tls_active < 0 ||
+ (tls_out.active < 0 ||
verify_check_this_host(&(ob->hosts_nopass_tls), NULL, host->name,
host->address, NULL) != OK)
&&
don't get a good response, we don't attempt to pass the socket on. */
#ifdef SUPPORT_TLS
- if (tls_active >= 0)
+ if (tls_out.active >= 0)
{
- tls_close(TRUE);
+ tls_close(FALSE, TRUE);
if (smtps)
ok = FALSE;
else
END_OFF:
#ifdef SUPPORT_TLS
- tls_close(TRUE);
+ tls_close(FALSE, TRUE);
#endif
/* Close the socket, and return the appropriate value, first setting
first_addr->basic_errno != ERRNO_TLSFAILURE)
write_logs(first_addr, host);
+ #ifdef EXPERIMENTAL_DBL
+ if (rc == DEFER)
+ dbl_write_defer_log(ob->dbl_host_defer_query, 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_DBL
+ if (rc == DEFER)
+ dbl_write_defer_log(ob->dbl_host_defer_query, first_addr, host);
+ #endif
}
#endif
}
/* Update the database which keeps information about which messages are waiting
for which hosts to become available. For some message-specific errors, the
update_waiting flag is turned off because we don't want follow-on deliveries in
- those cases. */
+ those cases. If this transport instance is explicitly limited to one message
+ per connection then follow-on deliveries are not possible and there's no need
+ to create/update the per-transport wait-<transport_name> database. */
- if (update_waiting) transport_update_waiting(hostlist, tblock->name);
+ if (update_waiting && tblock->connection_max_messages != 1)
+ transport_update_waiting(hostlist, tblock->name);
END_TRANSPORT:
uschar *interface;
uschar *port;
uschar *protocol;
+ uschar *dscp;
uschar *serialize_hosts;
uschar *hosts_try_auth;
uschar *hosts_require_auth;
+ #ifdef EXPERIMENTAL_PRDR
+ uschar *hosts_try_prdr;
+ #endif
+ #ifdef EXPERIMENTAL_OCSP
+ uschar *hosts_require_ocsp;
+ #endif
uschar *hosts_require_tls;
uschar *hosts_avoid_tls;
+ uschar *hosts_verify_avoid_tls;
uschar *hosts_avoid_pipelining;
uschar *hosts_avoid_esmtp;
uschar *hosts_nopass_tls;
uschar *gnutls_require_kx;
uschar *gnutls_require_mac;
uschar *gnutls_require_proto;
+ uschar *tls_sni;
uschar *tls_verify_certificates;
+ int tls_dh_min_bits;
BOOL tls_tempfail_tryclear;
- uschar *tls_sni;
#endif
#ifndef DISABLE_DKIM
uschar *dkim_domain;
uschar *dkim_sign_headers;
uschar *dkim_strict;
#endif
+ #ifdef EXPERIMENTAL_DBL
+ uschar *dbl_host_defer_query;
+ #endif
} smtp_transport_options_block;
/* Data for reading the private options. */
extern void smtp_transport_init(transport_instance *);
extern void smtp_transport_closedown(transport_instance *);
+
+
+ extern int smtp_auth(uschar *, unsigned, address_item *, host_item *,
+ smtp_transport_options_block *, BOOL,
+ smtp_inblock *, smtp_outblock *);
+ extern BOOL smtp_mail_auth_str(uschar *, unsigned,
+ address_item *, smtp_transport_options_block *);
+
/* End of transports/smtp.h */