1,641 changes: 1,281 additions & 360 deletions doc/doc-docbook/spec.xfpt

Large diffs are not rendered by default.

344 changes: 339 additions & 5 deletions doc/doc-txt/ChangeLog

Large diffs are not rendered by default.

18 changes: 11 additions & 7 deletions doc/doc-txt/Exim4.upgrade
Original file line number Diff line number Diff line change
Expand Up @@ -468,11 +468,12 @@ Generic Router Options
. The way that require_files works has been changed. Each item in the list is
now separately expanded as the test proceeds. The use of leading ! and +
characters is unchanged. However, user and group checking is done differently.
Previously, seteuid() was used, but seteuid() is no longer used in Exim (see
"Security" below). Instead, Exim now scans along the components of the file
path and checks the access for the given uid and gid. It expects "x" access
on directories and "r" on the final file. This means that file access control
lists (on those operating systems that have them) are ignored.
Previously, seteuid() was used, but seteuid() is no longer used (see
"Security" below) for checking the files required by this option. Instead,
Exim now scans along the components of the file path and checks the access
for the given uid and gid. It expects "x" access on directories and "r" on
the final file. This means that file access control lists (on those
operating systems that have them) are ignored.


Other Consequences of the Director/Router Merge
Expand Down Expand Up @@ -1380,8 +1381,11 @@ Security
--------

Exim 3 could be run in a variety of ways as far as security was concerned. This
has all been simplified in Exim 4. The security-conscious might like to know
that it no longer makes any use of the seteuid() function.
has all been simplified in Exim 4. Exim dropped the use of seteuid() in
most places. But recent (2020-10/2021-04) vulnerabilities forced us to
re-introduce seteuid() for opening the database files (hint files) as secure as
possible. For future (>= 4.95) versions we work on a solution that
does not need the seteuid call.

. A UID and GID are required to be specified when Exim is compiled. They can be
now specified by name as well as by number, so the relevant options are now
Expand Down
62 changes: 60 additions & 2 deletions doc/doc-txt/NewStuff
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,64 @@ Before a formal release, there may be quite a lot of detail so that people can
test from the snapshots or the Git before the documentation is updated. Once
the documentation is updated, this file is reduced to a short list.

Version 4.95
------------

1. The fast-ramp two phase queue run support, previously experimental, is
now supported by default.

2. The native SRS support, previously experimental, is now supported. It is
not built unless specified in the Local/Makefile.

3. TLS resumption support, previously experimental, is now supported and
included in default builds.

4. Single-key LMDB lookups, previously experimental, are now supported.
The support is not built unless specified in the Local/Makefile.

5. Option "message_linelength_limit" on the smtp transport to enforce (by
default) the RFC 998 character limit.

6. An option to ignore the cache on a lookup.

7. Quota checking during reception (i.e. at SMTP time) for appendfile-
transport-managed quotas.

8. Sqlite lookups accept a "file=<path>" option to specify a per-operation
db file, replacing the previous prefix to the SQL string (which had
issues when the SQL used tainted values).

9. Lsearch lookups accept a "ret=full" option, to return both the portion
of the line matching the key, and the remainder.

10. A command-line option to have a daemon not create a notifier socket.

11. Faster TLS startup. When various configuration options contain no
expandable elements, the information can be preloaded and cached rather
than the previous behaviour of always loading at startup time for every
connection. This helps particularly for the CA bundle.

12. Proxy Protocol Timeout is configurable via "proxy_protocol_timeout"
main config option.

13. Option "smtp_accept_max_per_connection" is now expanded.

14. Log selector "queue_size_exclusive", enabled by default, to exclude the
time taken for reception from QT log elements.

15. Main option "smtp_backlog_monitor", to set a level above which listen
socket backlogs are logged.

16. Main option "hosts_require_helo", requiring HELO or EHLO before MAIL.

17. A main config option "allow_insecure_tainted_data" allows to turn

18. TLS ALPN handling. By default, refuse TLS connections that try to specify
a non-smtp (eg. http) use. Options for customising.

19. Support for MacOS (darwin) has been dropped.


Version 4.94
------------

Expand All @@ -30,7 +88,7 @@ Version 4.94
7. Named-list definitions can now be prefixed "hide" so that "-bP" commands do
not output the content. Previously this could only be done on options.

8. As an exerimental feature, the dovecot authenticatino driver supports inet
8. As an experimental feature, the dovecot authentication driver supports inet
sockets. Previously it was unix-domain sockets only.

9. The ACL control "queue_only" can also be spelled "queue", and now takes an
Expand All @@ -57,7 +115,7 @@ Version 4.94
16. An option on all single-key lookups, to return (on a hit) a de-tainted
version of the lookup key rather than the looked-up data.

17. $domain_data and $localpart_data are now set by all list-match successes.
17. $domain_data and $local_part_data are now set by all list-match successes.
Previously only list items that performed lookups did so.
Also, matching list items that are tail-match or RE-match now set the
numeric variables $0 (etc) in the same way os other RE matches.
Expand Down
18 changes: 14 additions & 4 deletions doc/doc-txt/OptionLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
LISTS OF EXIM OPTIONS
---------------------

This file contains complete lists of four kinds of Exim option:
This file contains an almost complete lists of four kinds of Exim option:

1. Those that can appear in the run time configuration file;
2. Those that can be used on the command line;
3. Those that can appear in the build time configuration (Local/Makefile);
4. Those that can appear in the build time configuration for the Exim monitor
(Local/eximon.conf).

This file was last updated for Exim release 4.72.
This file was last updated for Exim release 4.95.


1. RUN TIME OPTIONS
Expand Down Expand Up @@ -78,6 +78,7 @@ allow_fail boolean false redirect
allow_filter boolean false redirect 4.00
allow_freeze boolean false redirect 4.00
allow_fifo boolean false appendfile 3.13
allow_insecure_tainted_data boolean true main 4.95 support will drop ~4.97ŮŤ
allow_localhost boolean false smtp 1.73
allow_mx_to_ip boolean false main 3.14
allow_symlink boolean false appendfile
Expand Down Expand Up @@ -313,7 +314,10 @@ hosts_pipe_connect host_list unset smtp 4.93 if experimental
hosts_randomize boolean false manualroute 4.00
false smtp 3.14
hosts_require_auth host list unset smtp 4.00
hosts_require_alpn host list unset main 4.95
smtp 4.95
hosts_require_dane host list unset smtp 4.91 (4.85 experimental)
hosts_require_helo host list "*" main 4.95
hosts_require_ocsp host list unset smtp 4.82 if experimental_ocsp
hosts_require_tls host list unset smtp 3.20
hosts_treat_as_local domain list unset main 1.95
Expand Down Expand Up @@ -382,6 +386,7 @@ message_body_newlines boolean false main
message_body_visible integer 500 main
message_id_header_domain string* unset main 4.11
message_id_header_text string* unset main
message_linelength_limit integer 998 smtp 4.94
message_logs boolean true main 4.10
message_prefix string* + appendfile 4.00 replaces prefix
string* unset pipe 4.00 replaces prefix
Expand Down Expand Up @@ -443,6 +448,7 @@ qualify_recipient string + main
qualify_single boolean true dnslookup 4.00
query string* + iplookup 4.00
queue_domains domain list unset main 4.00
queue_fast_ramp boolean false main 4.95
queue_list_requires_admin boolean true main 1.95
queue_only boolean false main
queue_only_file string unset main 2.05
Expand All @@ -464,7 +470,7 @@ receive_timeout time 0s main
received_header_text string* + main
received_headers_max integer 30 main
recipient_unqualified_hosts host list unset main 4.00 replacing receiver_unqualified_hosts
recipients_max integer 0 main 1.60
recipients_max integer 50000 main 1.60 default changed in 4.95 (was 0)
recipients_max_reject boolean false main 1.70
redirect_router string unset routers 4.00
remote_max_parallel integer 1 main
Expand Down Expand Up @@ -536,6 +542,7 @@ smtp_accept_queue integer 0 main
smtp_accept_queue_per_connection integer 10 main 2.03
smtp_accept_reserve integer 0 main
smtp_active_hostname string* unset main 4.33
smtp_backlog_monitor integer 0 main 4.95
smtp_banner string* + main
smtp_check_spool_space boolean true main 2.10
smtp_connect_backlog integer 5 main
Expand Down Expand Up @@ -593,7 +600,8 @@ timeout_defer boolean false pipe
timeout_frozen_after time 0s main 3.20
timezone string + main 3.15
tls_advertise_hosts host list * main 3.20
tls_advertise_requiretls host list * main 4.92 if experimental_requiretls
tls_alpn string* unset main 4.95
smtp 4.95
tls_certificate string* unset main 3.20
unset smtp 3.20
tls_dh_max_bits integer 2236 main 4.80
Expand All @@ -606,6 +614,8 @@ tls_privatekey string* unset main
tls_remember_emstp boolean false main 4.21
tls_require_ciphers string* unset smtp 4.00 replaces tls_verify_ciphers
string* unset main 4.33
tls_resumption_hosts host list* unset main 4.95
host list* unset smtp 4.95
tls_sni string* unset main 4.80
tls_tempfail_tryclear boolean true smtp 4.05
tls_try_verify_hosts host list unset main 4.00
Expand Down
2 changes: 2 additions & 0 deletions doc/doc-txt/cve-2020-qualys
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
For the vulnerabilites reported by Qualys in October 2020 please see the
Exim Website: https://exim.org/static/doc/security/CVE-2020-qualys/
247 changes: 51 additions & 196 deletions doc/doc-txt/experimental-spec.txt
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,9 @@ These four steps are explained in more details below.

SRS (Sender Rewriting Scheme) Support (using libsrs_alt)
--------------------------------------------------------------
See also below, for an alternative native support implementation.
See also the main docs, for an alternative native support implementation.

Exim currently includes SRS support via Miles Wilton's
Exim can be built with SRS support using Miles Wilton's
libsrs_alt library. The current version of the supported
library is 0.5, there are reports of 1.0 working.

Expand All @@ -309,10 +309,14 @@ https://opsec.eu/src/srs/
Unpack the tarball, then refer to MTAs/README.EXIM
to proceed. You need to set

EXPERIMENTAL_SRS=yes
EXPERIMENTAL_SRS_ALT=yes

in your Local/Makefile.

The built-in support, included by SUPPORT_SRS,
shuold *not* be enabled if you wish to use the libsrs_alt
version.

The following main-section options become available:
srs_config string
srs_hashlength int
Expand Down Expand Up @@ -344,76 +348,6 @@ For configuration information see https://github.com/Exim/exim/wiki/SRS .



SRS (Sender Rewriting Scheme) Support (native)
--------------------------------------------------------------
This is less full-featured than the libsrs_alt version above.

The Exim build needs to be done with this in Local/Makefile:
EXPERIMENTAL_SRS_NATIVE=yes

The following are provided:
- an expansion item "srs_encode"
This takes three arguments:
- a site SRS secret
- the return_path
- the pre-forwarding domain

- an expansion condition "inbound_srs"
This takes two arguments: the local_part to check, and a site SRS secret.
If the secret is zero-length, only the pattern of the local_part is checked.
The $srs_recipient variable is set as a side-effect.

- an expansion variable $srs_recipient
This gets the original return_path encoded in the SRS'd local_part

- predefined macros _HAVE_SRS and _HAVE_NATIVE_SRS

Sample usage:

#macro
SRS_SECRET = <pick something unique for your site for this>

#routers

outbound:
driver = dnslookup
# if outbound, and forwarding has been done, use an alternate transport
domains = ! +my_domains
transport = ${if eq {$local_part@$domain} \
{$original_local_part@$original_domain} \
{remote_smtp} {remote_forwarded_smtp}}

inbound_srs:
driver = redirect
senders = :
domains = +my_domains
# detect inbound bounces which are SRS'd, and decode them
condition = ${if inbound_srs {$local_part} {SRS_SECRET}}
data = $srs_recipient

inbound_srs_failure:
driver = redirect
senders = :
domains = +my_domains
# detect inbound bounces which look SRS'd but are invalid
condition = ${if inbound_srs {$local_part} {}}
allow_fail
data = :fail: Invalid SRS recipient address

#... further routers here


# transport; should look like the non-forward outbound
# one, plus the max_rcpt and return_path options
remote_forwarded_smtp:
driver = smtp
# modify the envelope from, for mails that we forward
max_rcpt = 1
return_path = ${srs_encode {SRS_SECRET} {$return_path} {$original_domain}}




DCC Support
--------------------------------------------------------------
Distributed Checksum Clearinghouse; http://www.rhyolite.com/dcc/
Expand Down Expand Up @@ -532,52 +466,6 @@ Rationale:
Note that non-RFC-documented field names and data types are used.


LMDB Lookup support
-------------------
LMDB is an ultra-fast, ultra-compact, crash-proof key-value embedded data store.
It is modeled loosely on the BerkeleyDB API. You should read about the feature
set as well as operation modes at https://symas.com/products/lightning-memory-mapped-database/

LMDB single key lookup support is provided by linking to the LMDB C library.
The current implementation does not support writing to the LMDB database.

Visit https://github.com/LMDB/lmdb to download the library or find it in your
operating systems 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. In order to build exim with LMDB lookup support add or uncomment

EXPERIMENTAL_LMDB=yes

to your Local/Makefile. (Re-)build/install exim. exim -d should show
Experimental_LMDB in the line "Support for:".

EXPERIMENTAL_LMDB=yes
LDFLAGS += -llmdb
# 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 LMDB libraries into the
exim binary. The commented out lines should be uncommented if you
built LMDB 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. Create your LMDB files, you can use the mdb_load utility which is
part of the LMDB distribution our your favourite language bindings.

3. Add the single key lookups to your exim.conf file, example lookups
are below.

${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}{$value}}
${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}{$value}fail}
${lookup{$sender_address_domain}lmdb{/var/lib/baruwa/data/db/relaydomains.mdb}}


Queuefile transport
-------------------
Queuefile is a pseudo transport which does not perform final delivery.
Expand Down Expand Up @@ -750,67 +638,8 @@ used via the transport in question.




TLS Session Resumption
----------------------
TLS Session Resumption for TLS 1.2 and TLS 1.3 connections can be used (defined
in RFC 5077 for 1.2). The support for this can be included by building with
EXPERIMENTAL_TLS_RESUME defined. This requires GnuTLS 3.6.3 or OpenSSL 1.1.1
(or later).

Session resumption (this is the "stateless" variant) involves the server sending
a "session ticket" to the client on one connection, which can be stored by the
client and used for a later session. The ticket contains sufficient state for
the server to reconstruct the TLS session, avoiding some expensive crypto
calculation and one full packet roundtrip time.

Operational cost/benefit:
The extra data being transmitted costs a minor amount, and the client has
extra costs in storing and retrieving the data.

In the Exim/Gnutls implementation the extra cost on an initial connection
which is TLS1.2 over a loopback path is about 6ms on 2017-laptop class hardware.
The saved cost on a subsequent connection is about 4ms; three or more
connections become a net win. On longer network paths, two or more
connections will have an average lower startup time thanks to the one
saved packet roundtrip. TLS1.3 will save the crypto cpu costs but not any
packet roundtrips.

Since a new hints DB is used, the hints DB maintenance should be updated
to additionally handle "tls".

Security aspects:
The session ticket is encrypted, but is obviously an additional security
vulnarability surface. An attacker able to decrypt it would have access
all connections using the resumed session.
The session ticket encryption key is not committed to storage by the server
and is rotated regularly (OpenSSL: 1hr, and one previous key is used for
overlap; GnuTLS 6hr but does not specify any overlap).
Tickets have limited lifetime (2hr, and new ones issued after 1hr under
OpenSSL. GnuTLS 2hr, appears to not do overlap).

There is a question-mark over the security of the Diffie-Helman parameters
used for session negotiation. TBD. q-value; cf bug 1895

Observability:
New log_selector "tls_resumption", appends an asterisk to the tls_cipher "X="
element.

Variables $tls_{in,out}_resumption have bits 0-4 indicating respectively
support built, client requested ticket, client offered session,
server issued ticket, resume used. A suitable decode list is provided
in the builtin macro _RESUME_DECODE for ${listextract {}{}}.

Issues:
In a resumed session:
$tls_{in,out}_cipher will have values different to the original (under GnuTLS)
$tls_{in,out}_ocsp will be "not requested" or "no response", and
hosts_require_ocsp will fail



Dovecot authenticator via inet socket
------------------------------------
--------------------------------------------------------------
If Dovecot is configured similar to :-

service auth {
Expand All @@ -837,28 +666,54 @@ and a whitespace-separated port number must be given.



Twophase queue run fast ramp
----------------------------
To include this feature, add to Local/Makefile:
EXPERIMENTAL_QUEUE_RAMP=yes

If the (added for this feature) main-section option "queue_fast_ramp" (boolean)
is set, and a two-phase ("-qq") queue run finds, during the first phase, a
suitably large number of message routed for a given host - then (subject to
the usual queue-runner resource limits) delivery for that host is initiated
immediately, overlapping with the remainder of the first phase.
Logging protocol unusual states
---------------------------------------------------------------
An extra log_selector, "protocol_detail" has been added in the default build.
The name may change in future, hence the Experimenal status.

Currrently the only effect is to enable logging, under TLS,
of a TCP RST received directly after a QUIT (in server mode).

Outlook is consistently doing this; not waiting for the SMTP response
to its QUIT, not properly closing the TLS session and not properly closing
the TCP connection. Previously this resulted is an error from SSL_write
being logged.



Limits ESMTP extension
---------------------------------------------------------------
Per https://datatracker.ietf.org/doc/html/draft-freed-smtp-limits-01

If compiled with EXPERIMENTAL_ESMTP_LIMITS=yes :-

As a server, Exim will advertise, in the EHLO response, the limit for RCPT
commands set by the recipients_max main-section config option (if it is set),
and the limit for MAIL commands set by the smtp_accept_max_per_connection
option.

Note that as of writing, smtp_accept_max_per_connection is expanded but
recipients_max is not.

A new main-section option "limits_advertise_hosts" controls whether
the limits are advertised; the default for the option is "*".

As a client, Exim will:

This is incompatible with queue_run_in_order.
- note an advertised MAILMAX; the lower of the value given and the
value from the transport connection_max_messages option is used.

The result should be a faster startup of deliveries when a large queue is
present and reasonable numbers of messages are routed to common hosts; this
could be a smarthost case, or delivery onto the Internet where a large proportion
of recipients hapen to be on a Gorilla-sized provider.
- note an advertised RCPTMAX; the lower of the
value given and the value from the transport max_rcpt option is used.
Parallisation of transactions is not done if due to a RCPTMAX, unlike
max_rcpt.

As usual, the presence of a configuration option is associated with a
predefined macro, making it possible to write portable configurations.
For this one, the macro is _OPT_MAIN_QUEUE_FAST_RAMP.
- note an advertised RCPTDOMAINMAX, and behave as if the transport
multi_domains option was set to false. The value advertised is ignored.

Values advertised are only noted for TLS connections and ones for which
the server does not advertise TLS support.


--------------------------------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion release-process/scripts/mk_exim_release
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ package Context {
my $workspace = $context->{workspace};

if (not defined $workspace) {
$workspace = $context->{workspace} = File::Temp->newdir(File::Spec->tmpdir . '/exim-packaging-XXXX');
$workspace = $context->{workspace} = File::Temp->newdir(
TEMPLATE => File::Spec->tmpdir . '/exim-packaging-XXXX',
CLEANUP => $context->{cleanup});
}
else {
# ensure the working directory is not in place
Expand Down
12 changes: 8 additions & 4 deletions src/OS/Makefile-Base
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ Makefile: ../OS/Makefile-Base ../OS/Makefile-Default \
# Build (link) the os.h file

os.h: $(SCRIPTS)/Configure-os.h \
$(O)/os.h-Darwin \
$(O)/os.h-FreeBSD \
$(O)/os.h-GNU \
$(O)/os.h-Linux \
Expand Down Expand Up @@ -113,7 +112,7 @@ MACRO_HSRC = macro_predef.h os.h globals.h config.h macros.h \

OBJ_MACRO = macro_predef.o \
macro-globals.o macro-readconf.o macro-route.o macro-transport.o macro-drtables.o \
macro-tls.o \
macro-acl.o macro-tls.o \
macro-appendfile.o macro-autoreply.o macro-lmtp.o macro-pipe.o macro-queuefile.o \
macro-smtp.o macro-accept.o macro-dnslookup.o macro-ipliteral.o macro-iplookup.o \
macro-manualroute.o macro-queryprogram.o macro-redirect.o \
Expand Down Expand Up @@ -141,6 +140,9 @@ macro-transport.o: transport.c
macro-drtables.o : drtables.c
@echo "$(CC) -DMACRO_PREDEF drtables.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ drtables.c
macro-acl.o: acl.c
@echo "$(CC) -DMACRO_PREDEF acl.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ acl.c
macro-tls.o: tls.c tls-gnu.c tls-openssl.c
@echo "$(CC) -DMACRO_PREDEF tls.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ tls.c
Expand Down Expand Up @@ -484,9 +486,9 @@ OBJ_LOOKUPS = lookups/lf_quote.o lookups/lf_check_file.o lookups/lf_sqlperform.o

OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
filtertest.o globals.o dkim.o dkim_transport.o hash.o \
filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \
header.o host.o ip.o log.o lss.o match.o md5.o moan.o \
os.o parse.o queue.o \
os.o parse.o priv.o queue.o \
rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o \
route.o search.o sieve.o smtp_in.o smtp_out.o spool_in.o spool_out.o \
std-crypto.o store.o string.o tls.o tod.o transport.o tree.o verify.o \
Expand Down Expand Up @@ -774,6 +776,7 @@ debug.o: $(HDRS) debug.c
deliver.o: $(HDRS) transports/smtp.h deliver.c
directory.o: $(HDRS) directory.c
dns.o: $(HDRS) dns.c
dnsbl.o: $(HDRS) dnsbl.c
enq.o: $(HDRS) enq.c
exim.o: $(HDRS) exim.c
expand.o: $(HDRS) expand.c
Expand All @@ -792,6 +795,7 @@ md5.o: $(HDRS) md5.c
moan.o: $(HDRS) moan.c
os.o: $(HDRS) $(OS_C_INCLUDES) os.c
parse.o: $(HDRS) parse.c
priv.o: $(HDRS) priv.c
queue.o: $(HDRS) queue.c
rda.o: $(HDRS) rda.c
readconf.o: $(HDRS) readconf.c
Expand Down
5 changes: 5 additions & 0 deletions src/OS/os.h-FreeBSD
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
#define HAVE_SETCLASSRESOURCES
#define HAVE_MMAP
#define HAVE_SYS_MOUNT_H
#define HAVE_GETIFADDRS
#define SIOCGIFCONF_GIVES_ADDR
#define HAVE_SRANDOMDEV
#define HAVE_ARC4RANDOM
#define EXIM_HAVE_OPENAT
#define EXIM_HAVE_FUTIMENS

/* Applications should not call arc4random_stir() explicitly after
* FreeBSD r227520 (approximately 1000002).
Expand Down Expand Up @@ -68,4 +71,6 @@ extern ssize_t os_sendfile(int, int, off_t *, size_t);

/*******************/

#define EXIM_HAVE_KEVENT

/* End */
6 changes: 6 additions & 0 deletions src/OS/os.h-GNU
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* Exim: OS-specific C header file for GNU/Hurd */
/* Copyright (c) The Exim Maintainers 2020 */

#include <features.h>

#define CRYPT_H
#define GLIBC_IP_OPTIONS
#define HAVE_BSD_GETLOADAVG
Expand All @@ -25,4 +27,8 @@ typedef struct flock flock_t;
as well as any supplementary groups*/
#define OS_SETGROUPS_ZERO_DROPS_ALL

#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOURCE
# define EXIM_HAVE_OPENAT
#endif

/* End */
11 changes: 10 additions & 1 deletion src/OS/os.h-Linux
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ with the issue. */
#define HAVE_MMAP
#define HAVE_BSD_GETLOADAVG
#define HAVE_SYS_STATVFS_H
#define HAVE_GETIFADDRS
#define NO_IP_VAR_H
#define SIG_IGN_WORKS

Expand Down Expand Up @@ -71,8 +72,9 @@ then change the 0 to 1 in the next block. */
# define LLONG_MAX LONG_LONG_MAX
#endif

#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOUCE
#if _POSIX_C_SOURCE >= 200809L || _ATFILE_SOURCE
# define EXIM_HAVE_OPENAT
# define EXIM_HAVE_FUTIMENS
#endif

/* TCP Fast Open support */
Expand All @@ -90,5 +92,12 @@ then change the 0 to 1 in the next block. */
/* "Abstract" Unix-socket names */
#define EXIM_HAVE_ABSTRACT_UNIX_SOCKETS

/* inotify(7) etc syscalls */
#define EXIM_HAVE_INOTIFY

/* Needed for uClibc */
#ifndef NS_MAXMSG
# define NS_MAXMSG 65535
#endif

/* End */
6 changes: 5 additions & 1 deletion src/OS/os.h-OpenBSD
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#define HAVE_BSD_GETLOADAVG
#define HAVE_MMAP
#define HAVE_SYS_MOUNT_H
#define SIOCGIFCONF_GIVES_ADDR
#define HAVE_GETIFADDRS
#define EXIM_HAVE_OPENAT
#define EXIM_HAVE_FUTIMENS
#define HAVE_ARC4RANDOM
/* In May 2014, OpenBSD 5.5 was released which cleaned up the arc4random_* API
which removed the arc4random_stir() function. Set NOT_HAVE_ARC4RANDOM_STIR
Expand Down Expand Up @@ -57,4 +59,6 @@ typedef struct __res_state *res_state;
Space-constrained devices could use much smaller; a few k. */
#define NS_MAXMSG 65535

#define EXIM_HAVE_KEVENT

/* End */
6 changes: 6 additions & 0 deletions src/OS/os.h-SunOS5
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#define HAVE_GETIPNODEBYNAME 1
#define HAVE_GETIPNODEBYADDR 1
#define EXIM_HAVE_OPENAT
#define EXIM_HAVE_FUTIMENS

#define HAVE_KSTAT
#define LOAD_AVG_KSTAT "system_misc"
Expand Down Expand Up @@ -36,6 +38,10 @@ it seems. */
# define MISSING_UNSETENV_3
#endif

#if _POSIX_C_SOURCE < 200809L
# define MISSING_POSIX_MEMALIGN
#endif


/* SunOS5 doesn't accept getcwd(NULL, 0) to auto-allocate
a buffer */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ HAVE_SA_LEN=YES

# Removed -DBIND_8_COMPAT for 4.61
# CFLAGS=-O -no-cpp-precomp -DBIND_8_COMPAT
#
# 2020/05/12 disable TLS resume support; it results in
# "1 select() failure: No such file or directory"
# being logged by the daeomn (sending the testsuite red...)

CFLAGS=-O -no-cpp-precomp
CFLAGS=-O -no-cpp-precomp -DDISABLE_TLS_RESUME
LIBRESOLV=-lresolv

USE_DB = yes
Expand Down
6 changes: 6 additions & 0 deletions src/OS/os.h-Darwin → src/OS/unsupported/os.h-Darwin
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define HAVE_SYS_MOUNT_H
#define PAM_H_IN_PAM
#define SIOCGIFCONF_GIVES_ADDR
#define EXIM_HAVE_OPENAT


#define F_FREESP O_TRUNC
Expand Down Expand Up @@ -55,4 +56,9 @@ in "man 2 getgroups". */
rather than a modified sendto() */
#define EXIM_TFO_CONNECTX

/* MacOS, at least on the buildfarm animal, does not seem to push out
the SMTP response to QUIT with our usual handling which is trying to get
the client to FIN first so that the server does not get the TIME_WAIT */
#define SERVERSIDE_CLOSE_NOWAIT

/* End */
2 changes: 2 additions & 0 deletions src/OS/unsupported/os.h-NetBSD
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ typedef struct flock flock_t;
/* default is non-const */
#define ICONV_ARG2_TYPE const char **

#define EXIM_HAVE_KEVENT

/* End */
22 changes: 20 additions & 2 deletions src/README.UPDATING
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,32 @@ The rest of this document contains information about changes in 4.xx releases
that might affect a running system.


Exim version 4.95
-----------------

Various length limits have been applied to Exim's parsing of its command-line.
These are all set to be at least as long as any valid input, so we do not believe
that any real use-cases have been affected by this.

The names of various drivers (authenticators, routers, transports, ...) have
always been limited to 64 characters, but before this release the names were
silently truncated, inviting problems. Now the length limit should be enforced.
If this affects you, then please rename to use shorter names.

The default maximum number of recipients of a single email has changed from
"unlimited" (ie: as much as CPU and memory will allow, until something breaks
badly) to 50,000. You can raise or lower this as you see fit, but we strongly
caution against using zero/unlimited.


Exim version 4.94
-----------------

Some Transports now refuse to use tainted data in constructing their delivery
location; this WILL BREAK configurations which are not updated accordingly.
In particular: any Transport use of $local_user which has been relying upon
In particular: any Transport use of $local_part which has been relying upon
check_local_user far away in the Router to make it safe, should be updated to
replace $local_user with $local_part_data.
replace $local_part with $local_part_data.

Attempting to remove, in router or transport, a header name that ends with
an asterisk (which is a standards-legal name) will now result in all headers
Expand Down
2 changes: 2 additions & 0 deletions src/exim_monitor/em_globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ uschar *queue_name = US"";
int received_count = 0;
uschar *received_protocol = NULL;
struct timeval received_time = { 0, 0 };
struct timeval received_time_complete = { 0, 0 };
int recipients_count = 0;
recipient_item *recipients_list = NULL;
int recipients_list_max = 0;
Expand All @@ -205,6 +206,7 @@ uschar *sender_address = NULL;
uschar *sender_fullhost = NULL;
uschar *sender_helo_name = NULL;
uschar *sender_host_address = NULL;
uschar *sender_host_auth_pubname = NULL;
uschar *sender_host_authenticated = NULL;
uschar *sender_host_name = NULL;
int sender_host_port = 0;
Expand Down
28 changes: 4 additions & 24 deletions src/exim_monitor/em_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ va_start(ap, format);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
va_end(ap);
selector = selector; /* Keep picky compilers happy */
flags = flags;
}


Expand Down Expand Up @@ -240,19 +238,13 @@ if (action_queue_update) tick_queue_accumulator = 999999;

void updateAction(Widget w, XtPointer client_data, XtPointer call_data)
{
w = w; /* Keep picky compilers happy */
client_data = client_data;
call_data = call_data;
scan_spool_input(TRUE);
queue_display();
tick_queue_accumulator = 0;
}

void hideAction(Widget w, XtPointer client_data, XtPointer call_data)
{
w = w; /* Keep picky compilers happy */
client_data = client_data;
call_data = call_data;
actioned_message[0] = 0;
dialog_ref_widget = w;
dialog_action = da_hide;
Expand All @@ -263,11 +255,7 @@ void unhideAction(Widget w, XtPointer client_data, XtPointer call_data)
{
skip_item *sk = queue_skip;

w = w; /* Keep picky compilers happy */
client_data = client_data;
call_data = call_data;

while (sk != NULL)
while (sk)
{
skip_item *next = sk->next;
store_free(sk);
Expand All @@ -285,9 +273,6 @@ tick_queue_accumulator = 0;

void quitAction(Widget w, XtPointer client_data, XtPointer call_data)
{
w = w; /* Keep picky compilers happy */
client_data = client_data;
call_data = call_data;
exit(0);
}

Expand Down Expand Up @@ -318,10 +303,6 @@ Dimension width, height;
XWindowAttributes a;
Window w = XtWindow(toplevel_widget);

button = button; /* Keep picky compilers happy */
client_data = client_data;
call_data = call_data;

/* Get the position and size of the top level widget. */

sizepos_args[0].value = (XtArgVal)(&width);
Expand Down Expand Up @@ -473,9 +454,6 @@ tick_queue_accumulator += tick_interval;
tick_stripchart_accumulator += tick_interval;
read_log();

pt = pt; /* Keep picky compilers happy */
i = i;

/* If we have passed the queue update time, we must do a full
scan of the queue, checking for new arrivals, etc. This will
as a by-product set the count of items for use by the stripchart
Expand Down Expand Up @@ -592,7 +570,8 @@ return ret;
* Initialize *
*************************************************/

int main(int argc, char **argv)
int
main(int argc, char **argv)
{
int i;
struct stat statdata;
Expand All @@ -613,6 +592,7 @@ message_subdir[1] = 0;
constructing file names and things. This call will initialize
the store_get() function. */

store_init();
big_buffer = store_get(big_buffer_size, FALSE);

/* Set up the version string and date and output them */
Expand Down
161 changes: 48 additions & 113 deletions src/exim_monitor/em_menu.c

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/exim_monitor/em_version.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@

#define EM_VERSION_C

/* Needed by macros.h */
/* Some systems have PATH_MAX and some have MAX_PATH_LEN. */

#ifndef PATH_MAX
# ifdef MAX_PATH_LEN
# define PATH_MAX MAX_PATH_LEN
# else
# define PATH_MAX 1024
# endif
#endif

#include "mytypes.h"
#include "store.h"
#include "macros.h"
Expand Down
4 changes: 2 additions & 2 deletions src/scripts/MakeLinks
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ for f in blob.h dbfunctions.h dbstuff.h exim.h functions.h globals.h \
macros.h mytypes.h osfunctions.h store.h structs.h lookupapi.h sha_ver.h \
\
acl.c buildconfig.c base64.c child.c crypt16.c daemon.c dbfn.c debug.c \
deliver.c directory.c dns.c drtables.c dummies.c enq.c exim.c \
deliver.c directory.c dns.c dnsbl.c drtables.c dummies.c enq.c exim.c \
exim_dbmbuild.c exim_dbutil.c exim_lock.c expand.c filter.c filtertest.c \
globals.c hash.c header.c host.c ip.c log.c lss.c match.c md5.c moan.c \
parse.c perl.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \
parse.c perl.c priv.c queue.c rda.c readconf.c receive.c retry.c rewrite.c \
rfc2047.c route.c search.c setenv.c environment.c \
sieve.c smtp_in.c smtp_out.c spool_in.c spool_out.c std-crypto.c store.c \
string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \
Expand Down
13 changes: 10 additions & 3 deletions src/scripts/reversion
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ fi
# Read version information that was generated by a previous run of
# this script, or during the release process.

if [ -f ./version.sh ]; then
# Override, used for automated testing w/o access to the
# .git directory (w.g. inside a git worktree)
if [ -n "$EXIM_RELEASE_VERSION" ]; then
:
elif [ -f ./version.sh ]; then
. ./version.sh
elif [ -f ../src/version.sh ]; then
. ../src/version.sh
Expand All @@ -51,9 +55,12 @@ elif [ -d ../../.git ] || [ -f ../../.git ] || [ "$1" = release ]; then
EXIM_VARIANT_VERSION="$3"
rm -f version.h
fi
else
fi

if [ -z "$EXIM_RELEASE_VERSION" ]; then
echo "Cannot determine the release number" >&2
exit
echo "You may want to override it with EXIM_RELEASE_VERSION" >&2
exit 1
fi

# If you are maintaining a patched version of Exim, you can either
Expand Down
56 changes: 31 additions & 25 deletions src/src/EDITME
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ SPOOL_DIRECTORY=/var/spool/exim
# the libraries and headers are installed, as the pkg-config .pc
# specification should include all -L/-I information necessary.
# Enabling the USE_*_PC options should be sufficient. If not using
# pkg-config, then you have to specify the libraries, and you mmight
# pkg-config, then you have to specify the libraries, and you might
# need to specify the locations too.

# Uncomment the following lines if you want
Expand All @@ -207,7 +207,7 @@ SPOOL_DIRECTORY=/var/spool/exim
# Unless you do this, you must define one of USE_OPENSSL or USE_GNUTLS
# below.

# If you are buliding with TLS, the library configuration must be done:
# If you are building with TLS, the library configuration must be done:

# Uncomment this if you are using OpenSSL
# USE_OPENSSL=yes
Expand Down Expand Up @@ -276,6 +276,9 @@ SPOOL_DIRECTORY=/var/spool/exim
# specified in INCLUDE.


# Uncomment the following line to remove support for TLS Resumption
# DISABLE_TLS_RESUME=yes


###############################################################################
# THESE ARE THINGS YOU PROBABLY WANT TO SPECIFY #
Expand Down Expand Up @@ -411,6 +414,8 @@ LOOKUP_DNSDB=yes
# LOOKUP_IBASE=yes
# LOOKUP_JSON=yes
# LOOKUP_LDAP=yes
# LOOKUP_LMDB=yes

# LOOKUP_MYSQL=yes
# LOOKUP_MYSQL_PC=mariadb
# LOOKUP_NIS=yes
Expand Down Expand Up @@ -487,7 +492,8 @@ SUPPORT_DANE=yes
# You do not need to use this for any lookup information added via pkg-config.

# LOOKUP_INCLUDE=-I /usr/local/ldap/include -I /usr/local/mysql/include -I /usr/local/pgsql/include
# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3
# LOOKUP_INCLUDE +=-I /usr/local/include
# LOOKUP_LIBS=-L/usr/local/lib -lldap -llber -lmysqlclient -lpq -lgds -lsqlite3 -llmdb


#------------------------------------------------------------------------------
Expand Down Expand Up @@ -560,13 +566,20 @@ DISABLE_MAL_MKS=yes
# DISABLE_DNSSEC=yes

# To disable support for Events set DISABLE_EVENT to "yes"

# DISABLE_EVENT=yes


# Uncomment this line to include support for early pipelining, per
# Uncomment this line to remove support for early pipelining, per
# https://datatracker.ietf.org/doc/draft-harris-early-pipe/
# SUPPORT_PIPE_CONNECT=yes
# DISABLE_PIPE_CONNECT=yes


# Uncomment the following to remove the fast-ramp two-phase-queue-run support
# DISABLE_QUEUE_RAMP=yes

# Uncomment the following lines to add SRS (Sender Rewriting Scheme) support
# using only native facilities. See EXPERIMENTAL_SRS_ALT for an alternative.
# SUPPORT_SRS=yes


#------------------------------------------------------------------------------
Expand All @@ -580,21 +593,20 @@ DISABLE_MAL_MKS=yes

# EXPERIMENTAL_DCC=yes

# Uncomment the following lines to add SRS (Sender rewriting scheme) support.
# Uncomment the following lines to add SRS (Sender rewriting scheme) support
# using the implementation in linbsrs_alt.
# You need to have libsrs_alt installed on your system (srs.mirtol.com).
# Depending on where it is installed you may have to edit the CFLAGS and
# LDFLAGS lines.

# EXPERIMENTAL_SRS=yes
# EXPERIMENTAL_SRS_ALT=yes
# CFLAGS += -I/usr/local/include
# LDFLAGS += -lsrs_alt

# Uncomment the following lines to add SRS (Sender rewriting scheme) support
# using only native facilities.
# EXPERIMENTAL_SRS_NATIVE=yes

# Uncomment the following line to add DMARC checking capability, implemented
# using libopendmarc libraries. You must have SPF and DKIM support enabled also.
# Library version libopendmarc-1.4.1-1.fc33.x86_64 (on Fedora 33) is known broken;
# 1.3.2-3 works. I seems that the OpenDMARC project broke their API.
# SUPPORT_DMARC=yes
# CFLAGS += -I/usr/local/include
# LDFLAGS += -lopendmarc
Expand All @@ -618,22 +630,9 @@ DISABLE_MAL_MKS=yes
# Uncomment the following to include extra information in fail DSN message (bounces)
# EXPERIMENTAL_DSN_INFO=yes

# Uncomment the following to add LMDB lookup support
# You need to have LMDB installed on your system (https://github.com/LMDB/lmdb)
# Depending on where it is installed you may have to edit the CFLAGS and LDFLAGS lines.
# EXPERIMENTAL_LMDB=yes
# CFLAGS += -I/usr/local/include
# LDFLAGS += -llmdb

# Uncomment the following line to add queuefile transport support
# EXPERIMENTAL_QUEUEFILE=yes

# Uncomment the following line to include support for TLS Resumption
# EXPERIMENTAL_TLS_RESUME=yes

# Uncomment the following to include the fast-ramp two-phase-queue-run support
# EXPERIMENTAL_QUEUE_RAMP=yes

###############################################################################
# THESE ARE THINGS YOU MIGHT WANT TO SPECIFY #
###############################################################################
Expand Down Expand Up @@ -749,6 +748,13 @@ FIXED_NEVER_USERS=root

# WHITELIST_D_MACROS=TLS:SPOOL

# The next setting enables a main config option
# "allow_insecure_tainted_data" to turn taint failures into warnings.
# Though this option is new, it is deprecated already now, and will be
# ignored in future releases of Exim. It is meant as mitigation for
# upgrading old (possibly insecure) configurations to more secure ones.
ALLOW_INSECURE_TAINTED_DATA=yes

#------------------------------------------------------------------------------
# Exim has support for the AUTH (authentication) extension of the SMTP
# protocol, as defined by RFC 2554. If you don't know what SMTP authentication
Expand Down
349 changes: 245 additions & 104 deletions src/src/acl.c

Large diffs are not rendered by default.

41 changes: 32 additions & 9 deletions src/src/arc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,23 @@ return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE);



/* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id
or a selector are ALPHA/DIGGIT/'-'/'.'
Check, to help catch misconfigurations such as a missing selector
element in the arc_sign list.
*/

static BOOL
arc_valid_id(const uschar * s)
{
for (uschar c; c = *s++; )
if (!isalnum(c) && c != '-' && c != '.') return FALSE;
return TRUE;
}



/* ARC signing. Called from the smtp transport, if the arc_sign option is set.
The dkim_exim_sign() function has already been called, so will have hashed the
message body for us so long as we requested a hash previously.
Expand Down Expand Up @@ -1590,15 +1607,16 @@ expire = now = 0;

/* Parse the signing specification */

identity = string_nextinlist(&signspec, &sep, NULL, 0);
selector = string_nextinlist(&signspec, &sep, NULL, 0);
if ( !*identity || !*selector
|| !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
{
log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)",
!*identity ? "identity" : !*selector ? "selector" : "private-key");
return sigheaders ? sigheaders : string_get(0);
}
if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity)
{ s = US"identity"; goto bad_arg_ret; }
if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector)
{ s = US"selector"; goto bad_arg_ret; }
if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey)
{ s = US"privkey"; goto bad_arg_ret; }
if (!arc_valid_id(identity))
{ s = US"identity"; goto bad_arg_ret; }
if (!arc_valid_id(selector))
{ s = US"selector"; goto bad_arg_ret; }
if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey)))
return sigheaders ? sigheaders : string_get(0);

Expand Down Expand Up @@ -1718,6 +1736,11 @@ if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr);
(void) string_from_gstring(g);
gstring_release_unused(g);
return g;


bad_arg_ret:
log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s);
return sigheaders ? sigheaders : string_get(0);
}


Expand Down
2 changes: 0 additions & 2 deletions src/src/auths/auth-spa.c
Original file line number Diff line number Diff line change
Expand Up @@ -1395,8 +1395,6 @@ int i;
int p = (int)getpid();
int random_seed = (int)time(NULL) ^ ((p << 16) | p);

request = request; /* Added by PH to stop compilers whinging */

/* Ensure challenge data is cleared, in case it isn't all used. This
patch added by PH on suggestion of Russell King */

Expand Down
5 changes: 2 additions & 3 deletions src/src/auths/call_pam.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ for (int i = 0; i < num_msg; i++)
{
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF:
arg = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
if (!arg)
if (!(arg = string_nextinlist(&pam_args, &sep, NULL, 0)))
{
arg = US"";
pam_arg_ended = TRUE;
Expand Down Expand Up @@ -155,7 +154,7 @@ pam_arg_ended = FALSE;
fail. PAM doesn't support authentication with an empty user (it prompts for it,
causing a potential mis-interpretation). */

user = string_nextinlist(&pam_args, &sep, big_buffer, big_buffer_size);
user = string_nextinlist(&pam_args, &sep, NULL, 0);
if (user == NULL || user[0] == 0) return FAIL;

/* Start off PAM interaction */
Expand Down
3 changes: 1 addition & 2 deletions src/src/auths/call_radius.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ int sep = 0;
#endif


user = string_nextinlist(&radius_args, &sep, big_buffer, big_buffer_size);
if (!user) user = US"";
if (!(user = string_nextinlist(&radius_args, &sep, NULL, 0))) user = US"";

DEBUG(D_auth) debug_printf("Running RADIUS authentication for user \"%s\" "
"and \"%s\"\n", user, radius_args);
Expand Down
10 changes: 8 additions & 2 deletions src/src/auths/get_data.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,20 @@ if (!ss)
len = Ustrlen(ss);

/* The character ^ is used as an escape for a binary zero character, which is
needed for the PLAIN mechanism. It must be doubled if really needed. */
needed for the PLAIN mechanism. It must be doubled if really needed.
The parsing ambiguity of ^^^ is taken as ^^ -> ^ ; ^ -> NUL - and there is
no way to get a leading ^ after a NUL. We would need to intro new syntax to
support that (probably preferring to take a more-standard exim list as a source
and concat the elements with intervening NULs. Either a magic marker on the
source string for client_send, or a new option). */

for (int i = 0; i < len; i++)
if (ss[i] == '^')
if (ss[i+1] != '^')
ss[i] = 0;
else
if (--len > ++i) memmove(ss + i, ss + i + 1, len - i);
if (--len > i+1) memmove(ss + i + 1, ss + i + 2, len - i);

/* The first string is attached to the AUTH command; others are sent
unembellished. */
Expand Down
152 changes: 95 additions & 57 deletions src/src/auths/gsasl_exim.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ sense in all contexts. For some, we can do checks at init time.
*/

#include "../exim.h"
#define CHANNELBIND_HACK

#ifndef AUTH_GSASL
/* dummy function to satisfy compilers when we link in an "empty" file. */
Expand All @@ -40,12 +39,22 @@ static void dummy(int x) { dummy2(x-1); }
#include "gsasl_exim.h"


#if GSASL_VERSION_MINOR >= 9
#if GSASL_VERSION_MINOR >= 10
# define EXIM_GSASL_HAVE_SCRAM_SHA_256
# define EXIM_GSASL_SCRAM_S_KEY

#elif GSASL_VERSION_MINOR == 9
# define EXIM_GSASL_HAVE_SCRAM_SHA_256

# if GSASL_VERSION_PATCH >= 1
# define EXIM_GSASL_SCRAM_S_KEY
# endif
# if GSASL_VERSION_PATCH < 2
# define CHANNELBIND_HACK
# endif

#else
# define CHANNELBIND_HACK
#endif


Expand Down Expand Up @@ -299,44 +308,41 @@ gsasl_prop_code_to_name(Gsasl_property prop)
{
switch (prop)
{
case GSASL_AUTHID: return US"AUTHID";
case GSASL_AUTHZID: return US"AUTHZID";
case GSASL_PASSWORD: return US"PASSWORD";
case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN";
case GSASL_SERVICE: return US"SERVICE";
case GSASL_HOSTNAME: return US"HOSTNAME";
case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME";
case GSASL_PASSCODE: return US"PASSCODE";
case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN";
case GSASL_PIN: return US"PIN";
case GSASL_REALM: return US"REALM";
case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD";
case GSASL_QOPS: return US"QOPS";
case GSASL_QOP: return US"QOP";
case GSASL_SCRAM_ITER: return US"SCRAM_ITER";
case GSASL_SCRAM_SALT: return US"SCRAM_SALT";
case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD";
case GSASL_AUTHID: return US"AUTHID";
case GSASL_AUTHZID: return US"AUTHZID";
case GSASL_PASSWORD: return US"PASSWORD";
case GSASL_ANONYMOUS_TOKEN: return US"ANONYMOUS_TOKEN";
case GSASL_SERVICE: return US"SERVICE";
case GSASL_HOSTNAME: return US"HOSTNAME";
case GSASL_GSSAPI_DISPLAY_NAME: return US"GSSAPI_DISPLAY_NAME";
case GSASL_PASSCODE: return US"PASSCODE";
case GSASL_SUGGESTED_PIN: return US"SUGGESTED_PIN";
case GSASL_PIN: return US"PIN";
case GSASL_REALM: return US"REALM";
case GSASL_DIGEST_MD5_HASHED_PASSWORD: return US"DIGEST_MD5_HASHED_PASSWORD";
case GSASL_QOPS: return US"QOPS";
case GSASL_QOP: return US"QOP";
case GSASL_SCRAM_ITER: return US"SCRAM_ITER";
case GSASL_SCRAM_SALT: return US"SCRAM_SALT";
case GSASL_SCRAM_SALTED_PASSWORD: return US"SCRAM_SALTED_PASSWORD";
#ifdef EXIM_GSASL_SCRAM_S_KEY
case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
case GSASL_SCRAM_STOREDKEY: return US"SCRAM_STOREDKEY";
case GSASL_SCRAM_SERVERKEY: return US"SCRAM_SERVERKEY";
#endif
case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE";
case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER";
case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL";
case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL";
case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA";
case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER";
case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
#ifdef EXIM_GSASL_SCRAM_S_KEY
case GSASL_SCRAM_CLIENTKEY: return US"SCRAM_CLIENTKEY";
#endif
case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE";
case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL";
case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS";
case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI";
case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID";
case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20";
case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20";
case GSASL_CB_TLS_UNIQUE: return US"CB_TLS_UNIQUE";
case GSASL_SAML20_IDP_IDENTIFIER: return US"SAML20_IDP_IDENTIFIER";
case GSASL_SAML20_REDIRECT_URL: return US"SAML20_REDIRECT_URL";
case GSASL_OPENID20_REDIRECT_URL: return US"OPENID20_REDIRECT_URL";
case GSASL_OPENID20_OUTCOME_DATA: return US"OPENID20_OUTCOME_DATA";
case GSASL_SAML20_AUTHENTICATE_IN_BROWSER: return US"SAML20_AUTHENTICATE_IN_BROWSER";
case GSASL_OPENID20_AUTHENTICATE_IN_BROWSER: return US"OPENID20_AUTHENTICATE_IN_BROWSER";
case GSASL_VALIDATE_SIMPLE: return US"VALIDATE_SIMPLE";
case GSASL_VALIDATE_EXTERNAL: return US"VALIDATE_EXTERNAL";
case GSASL_VALIDATE_ANONYMOUS: return US"VALIDATE_ANONYMOUS";
case GSASL_VALIDATE_GSSAPI: return US"VALIDATE_GSSAPI";
case GSASL_VALIDATE_SECURID: return US"VALIDATE_SECURID";
case GSASL_VALIDATE_SAML20: return US"VALIDATE_SAML20";
case GSASL_VALIDATE_OPENID20: return US"VALIDATE_OPENID20";
}
return CUS string_sprintf("(unknown prop: %d)", (int)prop);
}
Expand Down Expand Up @@ -365,7 +371,7 @@ HDEBUG(D_auth)
#ifndef DISABLE_TLS
if (tls_in.channelbinding && ob->server_channelbinding)
{
# ifdef EXPERIMENTAL_TLS_RESUME
# ifndef DISABLE_TLS_RESUME
if (!tls_in.ext_master_secret && tls_in.resumption == RESUME_USED)
{ /* per RFC 7677 section 4 */
HDEBUG(D_auth) debug_printf(
Expand All @@ -374,9 +380,9 @@ if (tls_in.channelbinding && ob->server_channelbinding)
}
# endif
# ifdef CHANNELBIND_HACK
/* This is a gross hack to get around the library a) requiring that
c-b was already set, at the _start() call, and b) caching a b64'd
version of the binding then which it never updates. */
/* This is a gross hack to get around the library before 1.9.2
a) requiring that c-b was already set, at the _start() call, and
b) caching a b64'd version of the binding then which it never updates. */

gsasl_callback_hook_set(gsasl_ctx, tls_in.channelbinding);
# endif
Expand Down Expand Up @@ -429,6 +435,12 @@ if (tls_in.channelbinding)
would then result in mechanism name changes on a library update, we
have little choice but to default it off and let the admin choose to
enable it. *sigh*
Earlier library versions need this set early, during the _start() call,
so we had to misuse gsasl_callback_hook_set/get() as a data transfer
mech for the callback done at that time to get the bind-data. More recently
the callback is done (if needed) during the first gsasl_stop(). We know
the bind-data here so can set it (and should not get a callback).
*/
if (ob->server_channelbinding)
{
Expand Down Expand Up @@ -570,14 +582,19 @@ return GSASL_AUTHENTICATION_ERROR;
}


/* Set the "next" $auth[n] and increment expand_nmax */

static void
set_exim_authvar_from_prop(Gsasl_session * sctx, Gsasl_property prop)
{
uschar * propval = US gsasl_property_fast(sctx, prop);
int i = expand_nmax, j = i + 1;
propval = propval ? string_copy(propval) : US"";
auth_vars[i] = expand_nstring[j] = propval;
HDEBUG(D_auth) debug_printf("auth[%d] <= %s'%s'\n",
j, gsasl_prop_code_to_name(prop), propval);
expand_nstring[j] = propval;
expand_nlength[j] = Ustrlen(propval);
if (i < AUTH_VARS) auth_vars[i] = propval;
expand_nmax = j;
}

Expand Down Expand Up @@ -740,7 +757,7 @@ switch (prop)
for memory wiping, so expanding strings will leave stuff laying around.
But no need to compound the problem, so get rid of the one we can. */

memset(tmps, '\0', strlen(tmps));
if (US tmps != s) memset(tmps, '\0', strlen(tmps));
cbrc = GSASL_OK;
break;

Expand Down Expand Up @@ -814,18 +831,19 @@ HDEBUG(D_auth)
#ifndef DISABLE_TLS
if (tls_out.channelbinding && ob->client_channelbinding)
{
# ifdef EXPERIMENTAL_TLS_RESUME
# ifndef DISABLE_TLS_RESUME
if (!tls_out.ext_master_secret && tls_out.resumption == RESUME_USED)
{ /* per RFC 7677 section 4 */
{ /* Per RFC 7677 section 4. See also RFC 7627, "Triple Handshake"
vulnerability, and https://www.mitls.org/pages/attacks/3SHAKE */
string_format(buffer, buffsize, "%s",
"channel binding not usable on resumed TLS without extended-master-secret");
return FAIL;
}
# endif
# ifdef CHANNELBIND_HACK
/* This is a gross hack to get around the library a) requiring that
c-b was already set, at the _start() call, and b) caching a b64'd
version of the binding then which it never updates. */
/* This is a gross hack to get around the library before 1.9.2
a) requiring that c-b was already set, at the _start() call, and
b) caching a b64'd version of the binding then which it never updates. */

gsasl_callback_hook_set(gsasl_ctx, tls_out.channelbinding);
# endif
Expand All @@ -846,10 +864,7 @@ gsasl_session_hook_set(sctx, &cb_state);

/* Set properties */

if ( !set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, ob->client_spassword,
0, buffer, buffsize)
&&
!set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
if ( !set_client_prop(sctx, GSASL_PASSWORD, ob->client_password,
0, buffer, buffsize)
|| !set_client_prop(sctx, GSASL_AUTHID, ob->client_username,
0, buffer, buffsize)
Expand Down Expand Up @@ -927,10 +942,13 @@ for(s = NULL; ;)
}

done:
HDEBUG(D_auth)
if (yield == OK)
{
const uschar * s = CUS gsasl_property_fast(sctx, GSASL_SCRAM_SALTED_PASSWORD);
if (s) debug_printf(" - SaltedPassword: '%s'\n", s);
expand_nmax = 0;
set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD);
}

gsasl_finish(sctx);
Expand All @@ -944,11 +962,31 @@ HDEBUG(D_auth) debug_printf("GNU SASL callback %s for %s/%s as client\n",
gsasl_prop_code_to_name(prop), ablock->name, ablock->public_name);
switch (prop)
{
case GSASL_CB_TLS_UNIQUE:
case GSASL_CB_TLS_UNIQUE: /*XXX should never get called for this */
HDEBUG(D_auth)
debug_printf(" filling in\n");
gsasl_property_set(sctx, GSASL_CB_TLS_UNIQUE, CCS tls_out.channelbinding);
break;
case GSASL_SCRAM_SALTED_PASSWORD:
{
uschar * client_spassword =
((auth_gsasl_options_block *) ablock->options_block)->client_spassword;
uschar dummy[4];
HDEBUG(D_auth) if (!client_spassword)
debug_printf(" client_spassword option unset\n");
if (client_spassword)
{
expand_nmax = 0;
set_exim_authvar_from_prop(sctx, GSASL_AUTHID);
set_exim_authvar_from_prop(sctx, GSASL_SCRAM_ITER);
set_exim_authvar_from_prop(sctx, GSASL_SCRAM_SALT);
set_client_prop(sctx, GSASL_SCRAM_SALTED_PASSWORD, client_spassword,
0, dummy, sizeof(dummy));
for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
expand_nmax = 0;
}
break;
}
default:
HDEBUG(D_auth)
debug_printf(" not providing one\n");
Expand Down
2 changes: 1 addition & 1 deletion src/src/auths/plaintext.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ already been provided as part of the AUTH command. For the rest, send them
out as prompts, and get a data item back. If the data item is "*", abandon the
authentication attempt. Otherwise, split it into items as above. */

while ( (s = string_nextinlist(&prompts, &sep, big_buffer, big_buffer_size))
while ( (s = string_nextinlist(&prompts, &sep, NULL, 0))
&& expand_nmax < EXPAND_MAXN)
if (number++ > expand_nmax)
if ((rc = auth_prompt(CUS s)) != OK)
Expand Down
6 changes: 0 additions & 6 deletions src/src/auths/pwcheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ int pwcheck_verify_password(const char *userid,
const char *passwd,
const char **reply)
{
userid = userid; /* Keep picky compilers happy */
passwd = passwd;
*reply = "pwcheck support is not included in this Exim binary";
return PWCHECK_FAIL;
}
Expand Down Expand Up @@ -163,10 +161,6 @@ int saslauthd_verify_password(const uschar *userid,
const uschar *realm,
const uschar **reply)
{
userid = userid; /* Keep picky compilers happy */
passwd = passwd;
service = service;
realm = realm;
*reply = US"saslauthd support is not included in this Exim binary";
return PWCHECK_FAIL;
}
Expand Down
4 changes: 3 additions & 1 deletion src/src/bmi_spam.c
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,11 @@ int bmi_check_rule(uschar *base64_verdict, uschar *option_list) {
}

/* loop through numbers */
/* option_list doesn't seem to be expanded so cannot be tainted. If it ever is we
will trap here */
rule_ptr = option_list;
while ((rule_num = string_nextinlist(&rule_ptr, &sep,
rule_buffer, 32)) != NULL) {
rule_buffer, sizeof(rule_buffer)))) {
int rule_int = -1;

/* try to translate to int */
Expand Down
4 changes: 3 additions & 1 deletion src/src/child.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ we want, duplicate the fd, then close the old one.
Returns: nothing
*/

static void
void
force_fd(int oldfd, int newfd)
{
if (oldfd == newfd) return;
Expand Down Expand Up @@ -269,6 +269,8 @@ if (pid == 0)
}
}

testharness_pause_ms(100); /* let child work even longer, for exec */

/* Parent process. Save fork() errno and close the reading end of the stdin
pipe. */

Expand Down
15 changes: 9 additions & 6 deletions src/src/config.h.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Do not put spaces between # and the 'define'.
#define ALT_CONFIG_PREFIX
#define TRUSTED_CONFIG_LIST

#define ALLOW_INSECURE_TAINTED_DATA

#define APPENDFILE_MODE 0600
#define APPENDFILE_DIRECTORY_MODE 0700
#define APPENDFILE_LOCKFILE_MODE 0600
Expand All @@ -31,7 +33,7 @@ Do not put spaces between # and the 'define'.
#define AUTH_SPA
#define AUTH_TLS

#define AUTH_VARS 3
#define AUTH_VARS 4

#define BIN_DIRECTORY

Expand All @@ -52,7 +54,9 @@ Do not put spaces between # and the 'define'.
#define DISABLE_OCSP
#define DISABLE_PIPE_CONNECT
#define DISABLE_PRDR
#define DISABLE_QUEUE_RAMP
#define DISABLE_TLS
#define DISABLE_TLS_RESUME
#define DISABLE_D_OPTION

#define ENABLE_DISABLE_FSYNC
Expand Down Expand Up @@ -97,6 +101,7 @@ Do not put spaces between # and the 'define'.
#define LOOKUP_IBASE
#define LOOKUP_JSON
#define LOOKUP_LDAP
#define LOOKUP_LMDB
#define LOOKUP_LSEARCH
#define LOOKUP_MYSQL
#define LOOKUP_NIS
Expand Down Expand Up @@ -156,6 +161,7 @@ Do not put spaces between # and the 'define'.
#define SUPPORT_PROXY
#define SUPPORT_SOCKS
#define SUPPORT_SPF
#define SUPPORT_SRS
#define SUPPORT_TRANSLATE_IP_ADDRESS

#define SYSLOG_LOG_PID
Expand Down Expand Up @@ -201,12 +207,9 @@ Do not put spaces between # and the 'define'.
#define EXPERIMENTAL_BRIGHTMAIL
#define EXPERIMENTAL_DCC
#define EXPERIMENTAL_DSN_INFO
#define EXPERIMENTAL_LMDB
#define EXPERIMENTAL_QUEUE_RAMP
#define EXPERIMENTAL_ESMTP_LIMITS
#define EXPERIMENTAL_QUEUEFILE
#define EXPERIMENTAL_SRS
#define EXPERIMENTAL_SRS_NATIVE
#define EXPERIMENTAL_TLS_RESUME
#define EXPERIMENTAL_SRS_ALT


/* For developers */
Expand Down
67 changes: 43 additions & 24 deletions src/src/configure.default
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,15 @@ acl_smtp_data = acl_check_data
# spamd_address = 127.0.0.1 783


# If Exim is compiled with support for TLS, you may want to enable the
# following options so that Exim allows clients to make encrypted
# connections. In the authenticators section below, there are template
# configurations for plaintext username/password authentication. This kind
# of authentication is only safe when used within a TLS connection, so the
# authenticators will only work if the following TLS settings are turned on
# as well.
# If Exim is compiled with support for TLS, you may want to change the
# following option so that Exim disallows certain clients from makeing encrypted
# connections. The default is to allow all.
# In the authenticators section below, there are template configurations for
# plaintext username/password authentication. This kind of authentication is
# only safe when used within a TLS connection, so the authenticators will only
# work if TLS is allowed here.

# Allow any client to use TLS.
# This is equivalent to the default.

# tls_advertise_hosts = *

Expand All @@ -169,7 +169,16 @@ acl_smtp_data = acl_check_data
# tls_privatekey = /etc/ssl/exim.pem

# For OpenSSL, prefer EC- over RSA-authenticated ciphers
# tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
.ifdef _HAVE_OPENSSL
tls_require_ciphers = ECDSA:RSA:!COMPLEMENTOFDEFAULT
.endif

# Don't offer resumption to (most) MUAs, who we don't want to reuse
# tickets. Once the TLS extension for vended ticket numbers comes
# though, re-examine since resumption on a single-use ticket is still a benefit.
.ifdef _HAVE_TLS_RESUME
tls_resumption_hosts = ${if inlist {$received_port}{587:465} {:}{*}}
.endif

# In order to support roaming users who wish to send email from anywhere,
# you may want to make Exim listen on other ports as well as port 25, in
Expand Down Expand Up @@ -449,6 +458,20 @@ acl_check_rcpt:

require verify = sender

# Reject all RCPT commands after too many bad recipients
# This is partly a defense against spam abuse and partly attacker abuse.
# Real senders should manage, by the time they get to 10 RCPT directives,
# to have had at least half of them be real addresses.
#
# This is a lightweight check and can protect you against repeated
# invocations of more heavy-weight checks which would come after it.

deny condition = ${if and {\
{>{$rcpt_count}{10}}\
{<{$recipients_count}{${eval:$rcpt_count/2}}} }}
message = Rejected for too many bad recipients
logwrite = REJECT [$sender_host_address]: bad recipient count high [${eval:$rcpt_count-$recipients_count}]

# Accept if the message comes from one of the hosts for which we are an
# outgoing relay. It is assumed that such hosts are most likely to be MUAs,
# so we set control=submission to make Exim treat the message as a
Expand Down Expand Up @@ -481,11 +504,6 @@ acl_check_rcpt:
control = submission
control = dkim_disable_verify

# Insist that a HELO/EHLO was accepted.

require message = nice hosts say HELO first
condition = ${if def:sender_helo_name}

# Insist that any other recipient address that we accept is either in one of
# our local domains, or is in a domain for which we explicitly allow
# relaying. Any other domain is rejected as being unacceptable for relaying.
Expand Down Expand Up @@ -531,11 +549,11 @@ acl_check_rcpt:
# to the first recipient must be deferred unless the sender talks PRDR.
#
# defer !condition = $prdr_requested
# condition = ${if > {0}{$receipients_count}}
# condition = ${if > {0}{$recipients_count}}
# condition = ${if !eq {$acl_m_content_filter} \
# {${lookup PER_RCPT_CONTENT_FILTER}}}
# warn !condition = $prdr_requested
# condition = ${if > {0}{$receipients_count}}
# condition = ${if > {0}{$recipients_count}}
# set acl_m_content_filter = ${lookup PER_RCPT_CONTENT_FILTER}
#############################################################################

Expand Down Expand Up @@ -801,13 +819,12 @@ begin transports


# This transport is used for delivering messages over SMTP connections.
# Refuse to send any message with over-long lines, which could have
# been received other than via SMTP. The use of message_size_limit to
# enforce this is a red herring.

remote_smtp:
driver = smtp
message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
.ifdef _HAVE_TLS_RESUME
tls_resumption_hosts = *
.endif


# This transport is used for delivering messages to a smarthost, if the
Expand All @@ -819,17 +836,16 @@ remote_smtp:

smarthost_smtp:
driver = smtp
message_size_limit = ${if > {$max_received_linelength}{998} {1}{0}}
multi_domain
#
.ifdef _HAVE_TLS
# Comment out any of these which you have to, then file a Support
# request with your smarthost provider to get things fixed:
hosts_require_tls = *
tls_verify_hosts = *
# As long as tls_verify_hosts is enabled, this won't matter, but if you
# have to comment it out then this will at least log whether you succeed
# or not:
# As long as tls_verify_hosts is enabled, this this will have no effect,
# but if you have to comment it out then this will at least log whether
# you succeed or not:
tls_try_verify_hosts = *
#
# The SNI name should match the name which we'll expect to verify;
Expand All @@ -845,6 +861,9 @@ smarthost_smtp:
.ifdef _HAVE_GNUTLS
tls_require_ciphers = SECURE192:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1
.endif
.ifdef _HAVE_TLS_RESUME
tls_resumption_hosts = *
.endif
.endif


Expand Down
507 changes: 344 additions & 163 deletions src/src/daemon.c

Large diffs are not rendered by default.

98 changes: 43 additions & 55 deletions src/src/dbfn.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

#include "exim.h"

/* We have buffers holding path names for database files.
PATH_MAX could be used here, but would be wasting memory, as we deal
with database files like $spooldirectory/db/<name> */
#define PATHLEN 256


/* Functions for accessing Exim's hints database, which consists of a number of
different DBM files. This module does not contain code for reading DBM files
Expand Down Expand Up @@ -58,8 +63,6 @@ log_write(0, LOG_MAIN, "Berkeley DB error: %s", msg);
#endif




/*************************************************
* Open and lock a database file *
*************************************************/
Expand Down Expand Up @@ -91,9 +94,8 @@ dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof, BOOL panic)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
BOOL created = FALSE;
flock_t lock_data;
uschar dirname[256], filename[256];
uschar dirname[PATHLEN], filename[PATHLEN];

DEBUG(D_hints_lookup) acl_level++;

Expand All @@ -113,17 +115,18 @@ exists, there is no error. */
snprintf(CS dirname, sizeof(dirname), "%s/db", spool_directory);
snprintf(CS filename, sizeof(filename), "%s/%s.lockfile", dirname, name);

priv_drop_temp(exim_uid, exim_gid);
if ((dbblock->lockfd = Uopen(filename, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
{
created = TRUE;
(void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, panic);
dbblock->lockfd = Uopen(filename, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
}
priv_restore();

if (dbblock->lockfd < 0)
{
log_write(0, LOG_MAIN, "%s",
string_open_failed(errno, "database lock file %s", filename));
string_open_failed("database lock file %s", filename));
errno = 0; /* Indicates locking failure */
DEBUG(D_hints_lookup) acl_level--;
return NULL;
Expand Down Expand Up @@ -167,73 +170,31 @@ it easy to pin this down, there are now debug statements on either side of the
open call. */

snprintf(CS filename, sizeof(filename), "%s/%s", dirname, name);
EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));

priv_drop_temp(exim_uid, exim_gid);
EXIM_DBOPEN(filename, dirname, flags, EXIMDB_MODE, &(dbblock->dbptr));
if (!dbblock->dbptr && errno == ENOENT && flags == O_RDWR)
{
DEBUG(D_hints_lookup)
debug_printf_indent("%s appears not to exist: trying to create\n", filename);
created = TRUE;
EXIM_DBOPEN(filename, dirname, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
}

save_errno = errno;

/* If we are running as root and this is the first access to the database, its
files will be owned by root. We want them to be owned by exim. We detect this
situation by noting above when we had to create the lock file or the database
itself. Because the different dbm libraries use different extensions for their
files, I don't know of any easier way of arranging this than scanning the
directory for files with the appropriate base name. At least this deals with
the lock file at the same time. Also, the directory will typically have only
half a dozen files, so the scan will be quick.
This code is placed here, before the test for successful opening, because there
was a case when a file was created, but the DBM library still returned NULL
because of some problem. It also sorts out the lock file if that was created
but creation of the database file failed. */

if (created && geteuid() == root_uid)
{
DIR * dd;
uschar *lastname = Ustrrchr(filename, '/') + 1;
int namelen = Ustrlen(name);

*lastname = 0;

if ((dd = exim_opendir(filename)))
for (struct dirent *ent; ent = readdir(dd); )
if (Ustrncmp(ent->d_name, name, namelen) == 0)
{
struct stat statbuf;
/* Filenames from readdir() are trusted,
so use a taint-nonchecking copy */
strcpy(CS lastname, CCS ent->d_name);
if (Ustat(filename, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
{
DEBUG(D_hints_lookup)
debug_printf_indent("ensuring %s is owned by exim\n", filename);
if (exim_chown(filename, exim_uid, exim_gid))
DEBUG(D_hints_lookup)
debug_printf_indent("failed setting %s to owned by exim\n", filename);
}
}

closedir(dd);
}
priv_restore();

/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
log the event - also for debugging - but debug only if the file just doesn't
exist. */

if (!dbblock->dbptr)
{
errno = save_errno;
if (lof && save_errno != ENOENT)
log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
log_write(0, LOG_MAIN, "%s", string_open_failed("DB file %s",
filename));
else
DEBUG(D_hints_lookup)
debug_printf_indent("%s\n", CS string_open_failed(save_errno, "DB file %s",
debug_printf_indent("%s\n", CS string_open_failed("DB file %s",
filename));
(void)close(dbblock->lockfd);
errno = save_errno;
Expand Down Expand Up @@ -332,6 +293,34 @@ return yield;
}


/* Read a record. If the length is not as expected then delete it, write
an error log line, delete the record and return NULL.
Use this for fixed-size records (so not retry or wait records).
Arguments:
dbblock a pointer to an open database block
key the key of the record to be read
length the expected record length
Returns: a pointer to the retrieved record, or
NULL if the record is not found/bad
*/

void *
dbfn_read_enforce_length(open_db * dbblock, const uschar * key, size_t length)
{
int rlen;
void * yield = dbfn_read_with_length(dbblock, key, &rlen);

if (yield)
{
if (rlen == length) return yield;
log_write(0, LOG_MAIN|LOG_PANIC, "Bad db record size for '%s'", key);
dbfn_delete(dbblock, key);
}
return NULL;
}


/*************************************************
* Write to database file *
Expand Down Expand Up @@ -423,7 +412,6 @@ dbfn_scan(open_db *dbblock, BOOL start, EXIM_CURSOR **cursor)
{
EXIM_DATUM key_datum, value_datum;
uschar *yield;
value_datum = value_datum; /* dummy; not all db libraries use this */

DEBUG(D_hints_lookup) debug_printf_indent("dbfn_scan\n");

Expand Down
3 changes: 2 additions & 1 deletion src/src/dbfunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Exim - an Internet mail transport agent *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 2015 */
/* Copyright (c) University of Cambridge 1995 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */


Expand All @@ -12,6 +12,7 @@ void dbfn_close(open_db *);
int dbfn_delete(open_db *, const uschar *);
open_db *dbfn_open(uschar *, int, open_db *, BOOL, BOOL);
void *dbfn_read_with_length(open_db *, const uschar *, int *);
void *dbfn_read_enforce_length(open_db *, const uschar *, size_t);
uschar *dbfn_scan(open_db *, BOOL, EXIM_CURSOR **);
int dbfn_write(open_db *, const uschar *, void *, int);

Expand Down
33 changes: 18 additions & 15 deletions src/src/dbstuff.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ free() must not die when passed NULL */
key.dptr != NULL)

/* EXIM_DBDELETE_CURSOR - terminate scanning operation. */
#define EXIM_DBDELETE_CURSOR(cursor) free(cursor)
#define EXIM_DBDELETE_CURSOR(cursor) store_free(cursor)

/* EXIM_DBCLOSE */
#define EXIM_DBCLOSE__(db) tdb_close(db)
Expand Down Expand Up @@ -436,9 +436,8 @@ before been able to pass successfully. */
#define EXIM_DBSCAN(db, key, data, first, cursor) \
((db)->seq(db, &key, &data, (first? R_FIRST : R_NEXT)) == 0)

/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). Make it
refer to cursor, to keep picky compilers happy. */
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). */
#define EXIM_DBDELETE_CURSOR(cursor) { }

/* EXIM_DBCLOSE */
#define EXIM_DBCLOSE__(db) (db)->close(db)
Expand Down Expand Up @@ -524,9 +523,8 @@ typedef struct {
(((db)->lkey.dptr != NULL)? (free((db)->lkey.dptr),1) : 1),\
db->lkey = key, key.dptr != NULL)

/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). Make it
refer to cursor, to keep picky compilers happy. */
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). */
#define EXIM_DBDELETE_CURSOR(cursor) { }

/* EXIM_DBCLOSE */
#define EXIM_DBCLOSE__(db) \
Expand Down Expand Up @@ -602,9 +600,8 @@ interface */
#define EXIM_DBSCAN(db, key, data, first, cursor) \
(key = (first? dbm_firstkey(db) : dbm_nextkey(db)), key.dptr != NULL)

/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). Make it
refer to cursor, to keep picky compilers happy. */
#define EXIM_DBDELETE_CURSOR(cursor) { cursor = cursor; }
/* EXIM_DBDELETE_CURSOR - terminate scanning operation (null). */
#define EXIM_DBDELETE_CURSOR(cursor) { }

/* EXIM_DBCLOSE */
#define EXIM_DBCLOSE__(db) dbm_close(db)
Expand Down Expand Up @@ -643,11 +640,9 @@ after reading data. */
: (flags) == O_RDWR ? "O_RDWR" \
: (flags) == (O_RDWR|O_CREAT) ? "O_RDWR|O_CREAT" \
: "??"); \
if (is_tainted(name) || is_tainted(dirname)) \
{ \
log_write(0, LOG_MAIN|LOG_PANIC, "Tainted name for DB file not permitted"); \
if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB file not permitted", name) \
|| is_tainted2(dirname, LOG_MAIN|LOG_PANIC, "Tainted name '%s' for DB directory not permitted", dirname)) \
*dbpp = NULL; \
} \
else \
{ EXIM_DBOPEN__(name, dirname, flags, mode, dbpp); } \
DEBUG(D_hints_lookup) debug_printf_indent("returned from EXIM_DBOPEN: %p\n", *dbpp); \
Expand Down Expand Up @@ -795,13 +790,21 @@ typedef struct {

#ifndef DISABLE_PIPE_CONNECT
/* This structure records the EHLO responses, cleartext and crypted,
for an IP, as bitmasks (cf. OPTION_TLS) */
for an IP, as bitmasks (cf. OPTION_TLS). For LIMITS, also values
advertised for MAILMAX, RCPTMAX and RCPTDOMAINMAX; zero meaning no
value advertised. */

typedef struct {
unsigned short cleartext_features;
unsigned short crypted_features;
unsigned short cleartext_auths;
unsigned short crypted_auths;

# ifdef EXPERIMENTAL_ESMTP_LIMITS
unsigned int limit_mail;
unsigned int limit_rcpt;
unsigned int limit_rcptdom;
# endif
} ehlo_resp_precis;

typedef struct {
Expand Down
780 changes: 403 additions & 377 deletions src/src/dcc.c

Large diffs are not rendered by default.

74 changes: 44 additions & 30 deletions src/src/debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,16 @@ const uschar * rc_names[] = { /* Mostly for debug output */
[UNEXPECTED] = US"UNEXPECTED",
[CANCELLED] = US"CANCELLED",
[FAIL_SEND] = US"FAIL_SEND",
[FAIL_DROP] = US"FAIL_DROP"
[FAIL_DROP] = US"FAIL_DROP",
[DANE] = US"DANE",
};

const uschar * dns_rc_names[] = {
[DNS_SUCCEED] = US"DNS_SUCCEED",
[DNS_NOMATCH] = US"DNS_NOMATCH",
[DNS_NODATA] = US"DNS_NODATA",
[DNS_AGAIN] = US"DNS_AGAIN",
[DNS_FAIL] = US"DNS_FAIL",
};


Expand All @@ -43,8 +52,8 @@ hold the line-drawing characters that need to be printed on every line as it
moves down the page. This function is used only in debugging circumstances. The
output is done via debug_printf(). */

#define tree_printlinesize 132 /* line size for printing */
static uschar tree_printline[tree_printlinesize];
#define TREE_PRINTLINESIZE 132 /* line size for printing */
static uschar tree_printline[TREE_PRINTLINESIZE];

/* Internal recursive subroutine.
Expand All @@ -57,12 +66,12 @@ Returns: nothing
*/

static void
tree_printsub(tree_node *p, int pos, int barswitch)
tree_printsub(tree_node * p, int pos, int barswitch)
{
if (p->right) tree_printsub(p->right, pos+2, 1);
for (int i = 0; i <= pos-1; i++) debug_printf("%c", tree_printline[i]);
debug_printf("-->%s [%d]\n", p->name, p->balance);
tree_printline[pos] = barswitch? '|' : ' ';
for (int i = 0; i <= pos-1; i++) debug_printf_indent(" %c", tree_printline[i]);
debug_printf_indent(" -->%s [%d]\n", p->name, p->balance);
tree_printline[pos] = barswitch ? '|' : ' ';
if (p->left)
{
tree_printline[pos+2] = '|';
Expand All @@ -73,11 +82,12 @@ if (p->left)
/* The external function, with just a tree node argument. */

void
debug_print_tree(tree_node *p)
debug_print_tree(const char * title, tree_node * p)
{
for (int i = 0; i < tree_printlinesize; i++) tree_printline[i] = ' ';
if (!p) debug_printf("Empty Tree\n"); else tree_printsub(p, 0, 0);
debug_printf("---- End of tree ----\n");
debug_printf_indent("%s:\n", title);
for (int i = 0; i < TREE_PRINTLINESIZE; i++) tree_printline[i] = ' ';
if (!p) debug_printf_indent(" Empty Tree\n"); else tree_printsub(p, 0, 0);
debug_printf_indent("---- End of tree ----\n");
}


Expand Down Expand Up @@ -328,48 +338,52 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
gstring * g = NULL;
int val;
socklen_t vlen = sizeof(val);
struct sockaddr a;
struct sockaddr_storage a;
socklen_t alen = sizeof(a);
struct sockaddr_in * sinp = (struct sockaddr_in *)&a;
struct sockaddr_in6 * sin6p = (struct sockaddr_in6 *)&a;
struct sockaddr_un * sa_unp ; (struct sockaddr_un *)&a;
struct sockaddr_un * sunp = (struct sockaddr_un *)&a;

if (getsockname(fd, &a, &alen) == 0)
switch (sinp->sin_family)
if (getsockname(fd, (struct sockaddr*)&a, &alen) == 0)
switch (a.ss_family)
{
case AF_INET:
g = string_cat(g, US" domain AF_INET");
g = string_cat(g, US"domain AF_INET");
g = string_fmt_append(g, " lcl [%s]:%u",
inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port));
if (getpeername(fd, &a, &alen) == 0)
alen = sizeof(*sinp);
if (getpeername(fd, (struct sockaddr *)sinp, &alen) == 0)
g = string_fmt_append(g, " rmt [%s]:%u",
inet_ntoa(sinp->sin_addr), ntohs(sinp->sin_port));
break;
case AF_INET6:
{
uschar buf[46];
g = string_cat(g, US" domain AF_INET6");
g = string_cat(g, US"domain AF_INET6");
g = string_fmt_append(g, " lcl [%s]:%u",
inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)),
ntohs(sin6p->sin6_port));
if (getpeername(fd, &a, &alen) == 0)
alen = sizeof(*sin6p);
if (getpeername(fd, (struct sockaddr *)sin6p, &alen) == 0)
g = string_fmt_append(g, " rmt [%s]:%u",
inet_ntop(AF_INET6, &sin6p->sin6_addr, CS buf, sizeof(buf)),
ntohs(sin6p->sin6_port));
break;
}
case AF_UNIX:
g = string_cat(g, US" domain AF_UNIX");
g = string_fmt_append(g, " lcl %s%s",
sa_unp->sun_path[0] ? US"" : US"@",
sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1);
if (getpeername(fd, &a, &alen) == 0)
g = string_fmt_append(g, " rmt %s%s",
sa_unp->sun_path[0] ? US"" : US"@",
sa_unp->sun_path[0] ? sa_unp->sun_path : sa_unp->sun_path+1);
break;
g = string_cat(g, US"domain AF_UNIX");
if (alen > sizeof(sa_family_t)) /* not unix(7) "unnamed socket" */
g = string_fmt_append(g, " lcl %s%s",
sunp->sun_path[0] ? US"" : US"@",
sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1);
alen = sizeof(*sunp);
if (getpeername(fd, (struct sockaddr *)sunp, &alen) == 0)
g = string_fmt_append(g, " rmt %s%s",
sunp->sun_path[0] ? US"" : US"@",
sunp->sun_path[0] ? sunp->sun_path : sunp->sun_path+1);
break;
default:
g = string_fmt_append(g, " domain %u", sinp->sin_family);
g = string_fmt_append(g, "domain %u", sinp->sin_family);
break;
}
if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &val, &vlen) == 0)
Expand All @@ -384,7 +398,7 @@ if (fstat(fd, &s) == 0 && (s.st_mode & S_IFMT) == S_IFSOCK)
{
struct protoent * p = getprotobynumber(val);
g = p
? string_fmt_append(g, " proto %s\n", p->p_name)
? string_fmt_append(g, " proto %s", p->p_name)
: string_fmt_append(g, " proto %d", val);
}
#endif
Expand Down
209 changes: 104 additions & 105 deletions src/src/deliver.c

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/src/directory.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ uschar c = 1;
struct stat statbuf;
uschar * path;

if (is_tainted2(name, LOG_MAIN|LOG_PANIC, "Tainted path '%s' for new directory", name))
{ p = US"create"; path = US name; errno = EACCES; goto bad; }

if (parent)
{
path = string_sprintf("%s%s%s", parent, US"/", name);
Expand Down Expand Up @@ -85,7 +88,7 @@ return TRUE;

bad:
if (panic) log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"Failed to %s directory \"%s\": %s\n", p, path, strerror(errno));
"Failed to %s directory \"%s\": %s\n", p, path, exim_errstr(errno));
return FALSE;
}

Expand Down
40 changes: 22 additions & 18 deletions src/src/dkim.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,20 @@ dkim_exim_query_dns_txt(const uschar * name)
dns_answer * dnsa = store_get_dns_answer();
dns_scan dnss;
rmark reset_point = store_mark();
gstring * g = NULL;
gstring * g = string_get_tainted(256, TRUE);

lookup_dnssec_authenticated = NULL;
if (dns_lookup(dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
return NULL; /*XXX better error detail? logging? */
goto bad;

/* Search for TXT record */

for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
if (rr->type == T_TXT)
{
int rr_offset = 0;

/* Copy record content to the answer buffer */

while (rr_offset < rr->size)
{ /* Copy record content to the answer buffer */
for (int rr_offset = 0; rr_offset < rr->size; )
{
uschar len = rr->data[rr_offset++];

Expand All @@ -78,18 +74,20 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
rr_offset += len;
}

/* check if this looks like a DKIM record */
/* Check if this looks like a DKIM record */
if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
{
store_free_dns_answer(dnsa);
gstring_release_unused(g);
return string_from_gstring(g);
}

if (g) g->ptr = 0; /* overwrite previous record */
g->ptr = 0; /* overwrite previous record */
}

bad:
store_reset(reset_point);
store_free_dns_answer(dnsa);
return NULL; /*XXX better error detail? logging? */
}

Expand All @@ -109,12 +107,15 @@ dkim_exim_verify_init(BOOL dot_stuffing)
{
dkim_exim_init();

/* There is a store-reset between header & body reception
so cannot use the main pool. Any allocs done by Exim
memory-handling must use the perm pool. */
/* There is a store-reset between header & body reception for the main pool
(actually, after every header line) so cannot use that as we need the data we
store per-header, during header processing, at the end of body reception
for evaluating the signature. Any allocs done for dkim verify
memory-handling must use a different pool. We use a separate one that we
can reset per message. */

dkim_verify_oldpool = store_pool;
store_pool = POOL_PERM;
store_pool = POOL_MESSAGE;

/* Free previous context if there is one */

Expand All @@ -127,19 +128,22 @@ dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
dkim_collect_error = NULL;

/* Start feed up with any cached data */
receive_get_cache();
/* Start feed up with any cached data, but limited to message data */
receive_get_cache(chunking_state == CHUNKING_LAST
? chunking_data_left : GETC_BUFFER_UNLIMITED);

store_pool = dkim_verify_oldpool;
}


/* Submit a chunk of data for verification input.
Only use the data when the feed is activated. */
void
dkim_exim_verify_feed(uschar * data, int len)
{
int rc;

store_pool = POOL_PERM;
store_pool = POOL_MESSAGE;
if ( dkim_collect_input
&& (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
{
Expand Down Expand Up @@ -305,7 +309,7 @@ int rc;
gstring * g = NULL;
const uschar * errstr = NULL;

store_pool = POOL_PERM;
store_pool = POOL_MESSAGE;

/* Delete eventual previous signature chain */

Expand Down
207 changes: 112 additions & 95 deletions src/src/dmarc.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,34 @@ static dmarc_exim_p dmarc_policy_description[] = {
{ US"reject", DMARC_RECORD_P_REJECT },
{ NULL, 0 }
};


void
dmarc_version_report(FILE *f)
{
const char *implementation, *version;

fprintf(f, "Library version: dmarc: Compile: %d.%d.%d.%d\n",
(OPENDMARC_LIB_VERSION & 0xff000000) >> 24, (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16,
(OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, OPENDMARC_LIB_VERSION & 0x000000ff);
}


/* Accept an error_block struct, initialize if empty, parse to the
* end, and append the two strings passed to it. Used for adding
* variable amounts of value:pair data to the forensic emails. */
end, and append the two strings passed to it. Used for adding
variable amounts of value:pair data to the forensic emails. */

static error_block *
add_to_eblock(error_block *eblock, uschar *t1, uschar *t2)
{
error_block *eb = store_malloc(sizeof(error_block));
if (eblock == NULL)
if (!eblock)
eblock = eb;
else
{
/* Find the end of the eblock struct and point it at eb */
error_block *tmp = eblock;
while(tmp->next != NULL)
while(tmp->next)
tmp = tmp->next;
tmp->next = eb;
}
Expand All @@ -76,8 +89,8 @@ return eblock;
}

/* dmarc_init sets up a context that can be re-used for several
messages on the same SMTP connection (that come from the
same host with the same HELO string) */
messages on the same SMTP connection (that come from the
same host with the same HELO string) */

int
dmarc_init()
Expand Down Expand Up @@ -218,11 +231,103 @@ if (rc == DNS_SUCCEED)
for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
if (rr->type == T_TXT && rr->size > 3)
return string_copyn(US rr->data, rr->size);
{
store_free_dns_answer(dnsa);
return string_copyn_taint(US rr->data, rr->size, TRUE);
}
store_free_dns_answer(dnsa);
return NULL;
}


static int
dmarc_write_history_file()
{
int history_file_fd;
ssize_t written_len;
int tmp_ans;
u_char **rua; /* aggregate report addressees */
uschar *history_buffer = NULL;

if (!dmarc_history_file)
{
DEBUG(D_receive) debug_printf("DMARC history file not set\n");
return DMARC_HIST_DISABLED;
}
history_file_fd = log_open_as_exim(dmarc_history_file);

if (history_file_fd < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
dmarc_history_file);
return DMARC_HIST_FILE_ERR;
}

/* Generate the contents of the history file */
history_buffer = string_sprintf(
"job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
message_id, primary_hostname, time(NULL), sender_host_address,
header_from_sender, expand_string(US"$sender_address_domain"));

if (spf_response)
history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
/* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */

history_buffer = string_sprintf(
"%s%spdomain %s\npolicy %d\n",
history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);

if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
else
history_buffer = string_sprintf("%srua -\n", history_buffer);

opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);

history_buffer = string_sprintf(
"%salign_dkim %d\nalign_spf %d\naction %d\n",
history_buffer, da, sa, action);

/* Write the contents to the history file */
DEBUG(D_receive)
debug_printf("DMARC logging history data for opendmarc reporting%s\n",
(host_checking || f.running_in_test_harness) ? " (not really)" : "");
if (host_checking || f.running_in_test_harness)
{
DEBUG(D_receive)
debug_printf("DMARC history data for debugging:\n%s", history_buffer);
}
else
{
written_len = write_to_fd_buf(history_file_fd,
history_buffer,
Ustrlen(history_buffer));
if (written_len == 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
dmarc_history_file);
return DMARC_HIST_WRITE_ERR;
}
(void)close(history_file_fd);
}
return DMARC_HIST_OK;
}


/* dmarc_process adds the envelope sender address to the existing
context (if any), retrieves the result, sets up expansion
strings and evaluates the condition outcome. */
Expand Down Expand Up @@ -514,94 +619,6 @@ if (!f.dmarc_disable_verify)
return OK;
}

static int
dmarc_write_history_file()
{
int history_file_fd;
ssize_t written_len;
int tmp_ans;
u_char **rua; /* aggregate report addressees */
uschar *history_buffer = NULL;

if (!dmarc_history_file)
{
DEBUG(D_receive) debug_printf("DMARC history file not set\n");
return DMARC_HIST_DISABLED;
}
history_file_fd = log_create(dmarc_history_file);

if (history_file_fd < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failure to create DMARC history file: %s",
dmarc_history_file);
return DMARC_HIST_FILE_ERR;
}

/* Generate the contents of the history file */
history_buffer = string_sprintf(
"job %s\nreporter %s\nreceived %ld\nipaddr %s\nfrom %s\nmfrom %s\n",
message_id, primary_hostname, time(NULL), sender_host_address,
header_from_sender, expand_string(US"$sender_address_domain"));

if (spf_response)
history_buffer = string_sprintf("%sspf %d\n", history_buffer, dmarc_spf_ares_result);
/* history_buffer = string_sprintf("%sspf -1\n", history_buffer); */

history_buffer = string_sprintf(
"%s%spdomain %s\npolicy %d\n",
history_buffer, dkim_history_buffer, dmarc_used_domain, dmarc_policy);

if ((rua = opendmarc_policy_fetch_rua(dmarc_pctx, NULL, 0, 1)))
for (tmp_ans = 0; rua[tmp_ans]; tmp_ans++)
history_buffer = string_sprintf("%srua %s\n", history_buffer, rua[tmp_ans]);
else
history_buffer = string_sprintf("%srua -\n", history_buffer);

opendmarc_policy_fetch_pct(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%spct %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_adkim(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%sadkim %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_aspf(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%saspf %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_p(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%sp %d\n", history_buffer, tmp_ans);

opendmarc_policy_fetch_sp(dmarc_pctx, &tmp_ans);
history_buffer = string_sprintf("%ssp %d\n", history_buffer, tmp_ans);

history_buffer = string_sprintf(
"%salign_dkim %d\nalign_spf %d\naction %d\n",
history_buffer, da, sa, action);

/* Write the contents to the history file */
DEBUG(D_receive)
debug_printf("DMARC logging history data for opendmarc reporting%s\n",
(host_checking || f.running_in_test_harness) ? " (not really)" : "");
if (host_checking || f.running_in_test_harness)
{
DEBUG(D_receive)
debug_printf("DMARC history data for debugging:\n%s", history_buffer);
}
else
{
written_len = write_to_fd_buf(history_file_fd,
history_buffer,
Ustrlen(history_buffer));
if (written_len == 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failure to write to DMARC history file: %s",
dmarc_history_file);
return DMARC_HIST_WRITE_ERR;
}
(void)close(history_file_fd);
}
return DMARC_HIST_OK;
}


uschar *
dmarc_exim_expand_query(int what)
{
Expand Down
4 changes: 1 addition & 3 deletions src/src/dmarc.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
# endif /* SUPPORT_SPF */

/* prototypes */
void dmarc_version_report(FILE *);
int dmarc_init();
int dmarc_store_data(header_line *);
int dmarc_process();
uschar *dmarc_exim_expand_query(int);
uschar *dmarc_exim_expand_defaults(int);
uschar *dmarc_auth_results_header(header_line *,uschar *);
static int dmarc_write_history_file();

#define DMARC_AR_HEADER US"Authentication-Results:"
#define DMARC_VERIFY_STATUS 1

#define DMARC_HIST_OK 1
Expand Down
99 changes: 45 additions & 54 deletions src/src/dns.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ char * trace = NULL;
#ifdef rr_trace
# define TRACE DEBUG(D_dns)
#else
trace = trace;
# define TRACE if (FALSE)
#endif

Expand Down Expand Up @@ -516,7 +515,7 @@ if ( !h->aa
|| !(trusted = expand_string(dns_trust_aa))
|| !*trusted
|| !(auth_name = dns_extract_auth_name(dnsa))
|| OK != match_isinlist(auth_name, &trusted, 0, NULL, NULL,
|| OK != match_isinlist(auth_name, &trusted, 0, &domainlist_anchor, NULL,
MCL_DOMAIN, TRUE, NULL)
)
return FALSE;
Expand Down Expand Up @@ -672,13 +671,10 @@ e = previous->data.ptr;
val = e->data.val;
rc = e->expiry && e->expiry <= time(NULL) ? -1 : val;

DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: %scached value %s%s\n",
DEBUG(D_dns) debug_printf("DNS lookup of %.255s (%s): %scached value %s%s\n",
name, dns_text_type(type),
rc == -1 ? "" : "using ",
val == DNS_NOMATCH ? "DNS_NOMATCH" :
val == DNS_NODATA ? "DNS_NODATA" :
val == DNS_AGAIN ? "DNS_AGAIN" :
val == DNS_FAIL ? "DNS_FAIL" : "??",
dns_rc_names[val],
rc == -1 ? " past valid time" : "");

return rc;
Expand All @@ -695,9 +691,11 @@ packet length has been lost inside libresolv, so we have to guess a
replacement value. (The only way to fix this properly would be to
re-implement res_search() and res_query() so that they don't muddle their
success and packet length return values.) For added safety we only reset
the packet length if the packet header looks plausible. */
the packet length if the packet header looks plausible.
static void
Return TRUE iff it seemed ok */

static BOOL
fake_dnsa_len_for_fail(dns_answer * dnsa, int type)
{
const HEADER * h = (const HEADER *)dnsa->answer;
Expand All @@ -714,7 +712,11 @@ if ( h->qr == 1 /* a response */
DEBUG(D_dns) debug_printf("faking res_search(%s) response length as %d\n",
dns_text_type(type), (int)sizeof(dnsa->answer));
dnsa->answerlen = sizeof(dnsa->answer);
return TRUE;
}
DEBUG(D_dns) debug_printf("DNS: couldn't fake dnsa len\n");
/* Maybe we should just do a second lookup for an SOA? */
return FALSE;
}


Expand All @@ -728,37 +730,37 @@ dns_expire_from_soa(dns_answer * dnsa, int type)
{
dns_scan dnss;

fake_dnsa_len_for_fail(dnsa, type);
if (fake_dnsa_len_for_fail(dnsa, type))
for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
) if (rr->type == T_SOA)
{
const uschar * p = rr->data;
uschar discard_buf[256];
int len;
unsigned long ttl;

/* Skip the mname & rname strings */

if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
break;
p += len;
if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
break;
p += len;

/* Skip the SOA serial, refresh, retry & expire. Grab the TTL */

if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ)
break;
p += 4 * INT32SZ;
GETLONG(ttl, p);

return time(NULL) + ttl;
}

for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
) if (rr->type == T_SOA)
{
const uschar * p = rr->data;
uschar discard_buf[256];
int len;
unsigned long ttl;

/* Skip the mname & rname strings */

if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
break;
p += len;
if ((len = dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen,
p, (DN_EXPAND_ARG4_TYPE)discard_buf, 256)) < 0)
break;
p += len;

/* Skip the SOA serial, refresh, retry & expire. Grab the TTL */

if (p > dnsa->answer + dnsa->answerlen - 5 * INT32SZ)
break;
p += 4 * INT32SZ;
GETLONG(ttl, p);

return time(NULL) + ttl;
}
DEBUG(D_dns) debug_printf("DNS: no SOA record found for neg-TTL\n");
return 0;
}
Expand Down Expand Up @@ -908,8 +910,8 @@ if (dnsa->answerlen < 0) switch (h_errno)
#ifndef STAND_ALONE
save_domain = deliver_domain;
deliver_domain = string_copy(name); /* set $domain */
rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL,
MCL_DOMAIN, TRUE, NULL);
rc = match_isinlist(name, CUSS &dns_again_means_nonexist, 0,
&domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL);
deliver_domain = save_domain;
if (rc != OK)
{
Expand Down Expand Up @@ -1203,18 +1205,7 @@ switch (type)
If the TLD and the 2LD exist but the explicit CSA record lookup failed, then
the AUTHORITY SOA will be the 2LD's or a subdomain thereof. */

if (rc == DNS_NOMATCH)
{
fake_dnsa_len_for_fail(dnsa, T_CSA);

for (rr = dns_next_rr(dnsa, &dnss, RESET_AUTHORITY);
rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
)
if (rr->type != T_SOA) continue;
else if (strcmpic(rr->name, US"") == 0 ||
strcmpic(rr->name, tld) == 0) return DNS_NOMATCH;
else break;
}
if (rc == DNS_NOMATCH) return DNS_NOMATCH;

for (i = 0; i < limit; i++)
{
Expand Down Expand Up @@ -1249,7 +1240,7 @@ switch (type)

/* Extract the numerical SRV fields (p is incremented) */
GETSHORT(priority, p);
GETSHORT(weight, p); weight = weight; /* compiler quietening */
GETSHORT(weight, p);
GETSHORT(port, p);

/* Check the CSA version number */
Expand Down
Loading