Security release, plus one further release the same day.
4.99.5:
JH/01 Restrict named-queue names. Previously, files could be corrupted by
poor choices of name. (GCVE-25-2026-07-45-1)
JH/02 Do not expand a local_part gotten from a .forward file, under
force_command in a pipe transport. (GCVE-25-2026-07-45-3)
4.100: fixes a use-after-free in duplicate processing combined with
continued transport; fixes crashes after deferred local deliveries
from an uninitialised pointer, and excessive DNS lookups on temporary
errors for remote deliveries, both from a mistaken 4.99 optimisation;
fixes a memory leak in DNS lookups present since 4.95; fixes
Proxy-protocol connections evaluating host-based ACL options
(host_reject_connection, helo_verify_hosts, message_size_limit, and
others) against the proxy's own address/name instead of the real
client behind it. Also: dbm lookups no longer return tainted data by
mistake, RFC2047-encoding header size counting, DMARC for empty
envelope senders, and a number of smaller build/portability fixes.
Refreshed patches/030-openssl-deprecated.patch,
patches/100-localscan_dlopen.patch and patches/200-fix-build.patch
for context moved by the version bump. patches/100 also needed six
log_write() call sites fixed: 4.100 dropped log_write()'s leading
selector argument, so the patch's dlopen-failure diagnostics were
passing LOG_MAIN|LOG_REJECT into the format-string slot. All four
patches, including 050, now carry git am headers.
Extended patches/050-fix-crosscompile.patch: 4.100 fixed the dkim.o
partial-link rule in src/miscmods/Makefile (raw `ld -r` -> `$(LD) -r`)
but left the sibling dmarc.o/dmarc_native.o and sieve_filter.o rules
on the unqualified `ld`, which cannot parse a foreign architecture's
relocations when cross-compiling. Applied the same fix to both; these
are the only two `ld -r` sites left in the pristine 4.100 tree.
Build-verified locally for the gnutls and ldap variants on
x86_64/musl.
Signed-off-by: Daniel Golle <redacted>
include $(TOPDIR)/rules.mk
PKG_NAME:=exim
-PKG_VERSION:=4.99.4
+PKG_VERSION:=4.100
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=https://downloads.exim.org/exim4/
-PKG_HASH:=87ff38815700dfb1ee4eb7e8dba7916df7a755905354d2d0faa1ae1790c4fd9d
+PKG_HASH:=5bd0a3e353dbfcd5c8174388b824316a61ee2455d9052ea2f0877dee939d33b3
PKG_MAINTAINER:=Daniel Golle <daniel@makrotopia.org>
PKG_LICENSE:=GPL-2.0-or-later
+From de9c527e9a2e5a3437b884c6f583e9dcbe15f3f8 Mon Sep 17 00:00:00 2001
+From: Rosen Penev <rosenp@gmail.com>
+Date: Sat, 20 Mar 2021 14:56:09 -0700
+Subject: [PATCH] exim: fix compilation without deprecated OpenSSL APIs
+
+Signed-off-by: Rosen Penev <rosenp@gmail.com>
+---
--- a/src/dane-openssl.c
+++ b/src/dane-openssl.c
@@ -1122,8 +1122,8 @@ if (dane->selectors[DANESSL_USAGE_DANE_T
#include <openssl/ec.h>
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
-@@ -909,8 +912,9 @@ pkey = EVP_RSA_gen(2048);
+@@ -934,8 +937,9 @@ pkey = EVP_RSA_gen(2048);
X509_set_version(x509, 2); /* N+1 - version 3 */
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_set_pubkey(x509, pkey);
name = X509_get_subject_name(x509);
-@@ -5031,8 +5035,8 @@ return string_fmt_append(g,
+@@ -5056,8 +5060,8 @@ return string_fmt_append(g,
" Runtime: %s\n"
" : %s\n",
OPENSSL_VERSION_TEXT,
/* third line is 38 characters for the %s and the line is 73 chars long;
the OpenSSL output includes a "built on: " prefix already. */
}
-@@ -5074,8 +5078,6 @@ if (pidnow != pidlast)
+@@ -5099,8 +5103,6 @@ if (pidnow != pidlast)
is unique for each thread", this doesn't apparently apply across processes,
so our own warning from vaguely_random_number_fallback() applies here too.
Fix per PostgreSQL. */
--- a/src/tlscert-openssl.c
+++ b/src/tlscert-openssl.c
-@@ -219,13 +219,13 @@ return mod ? tls_field_from_dn(cp, mod)
+@@ -221,13 +221,13 @@ return cp ? string_copy_taint(cp, GET_TA
uschar *
tls_cert_not_before(void * cert, const uschar * mod)
{
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 21 Aug 2026 14:40:13 +0100
+Subject: [PATCH] fix-crosscompile: use $(LD) instead of raw ld for the
+ remaining partial-link rules
+
+src/miscmods/Makefile builds dkim.o, dmarc.o/dmarc_native.o and
+sieve_filter.o by compiling several .c files separately and then
+partial-linking (`ld -r`) the resulting .o files into one. Upstream
+4.100 switched the dkim.o rule from a raw `ld -r` to `$(LD) -r`, but
+left the dmarc and sieve_filter rules using the unqualified system
+`ld`, which cannot parse the target architecture's relocations when
+cross-compiling (fails with e.g. "Relocations in generic ELF (EM:
+183)" / "file in wrong format" on non-x86_64 targets - x86_64 happens
+to build "successfully" only because the build host's own `ld`
+coincidentally still understands x86_64 relocations).
+
+Apply the same $(LD) fix consistently to both remaining rules. These
+are the only two: grepping the full pristine 4.100 source tree for
+`ld -r` turns up nothing outside src/miscmods/Makefile.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
--- a/src/miscmods/Makefile
+++ b/src/miscmods/Makefile
-@@ -62,7 +62,7 @@ dkim.o:
- $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) pdkim.c
- $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $(TLS_INCLUDE) signing.c
- $(FE)mv dkim.o dkim_tmp.o
-- $(FE)ld -r -o dkim.o $(LDFLAGS_PARTIAL) \
-+ $(FE)$(CC) -r -o dkim.o $(LDFLAGS_PARTIAL) \
- dkim_tmp.o dkim_transport.o pdkim.o signing.o
+@@ -96,7 +96,7 @@ dmarc.o dmarc_native.o:
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) dmarc_common.c
+ $(FE)mv $@ dmarc_tmp.o
+- $(FE)ld -r -o $@ $(LDFLAGS_PARTIAL) dmarc_tmp.o dmarc_common.o
++ $(FE)$(LD) -r -o $@ $(LDFLAGS_PARTIAL) dmarc_tmp.o dmarc_common.o
- # Similarly, we want a single .so for the dynamic-load module
+ # dmarc_native is special in the same way as spf_perl
+ dmarc.so dmarc_native.so:
+@@ -112,7 +112,7 @@ sieve_filter.o:
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) sieve_filter_body.c
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) sieve_filter_input.c
+ $(FE)mv sieve_filter.o sieve_filter_tmp.o
+- $(FE)ld -r -o sieve_filter.o $(LDFLAGS_PARTIAL) \
++ $(FE)$(LD) -r -o sieve_filter.o $(LDFLAGS_PARTIAL) \
+ sieve_filter_tmp.o sieve_filter_body.o sieve_filter_input.o
+
+ sieve_filter.so:
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Fri, 21 Aug 2026 14:40:13 +0100
+Subject: [PATCH] localscan: allow dlopen-based local_scan() libraries
+
+From:/Date: above record who carries this patch in the feed and when
+this header was last written, not original authorship. See Author:
+below for that.
+
Description: Allow one to use and switch between different local_scan functions
without recompiling exim.
http://marc.merlins.org/linux/exim/files/sa-exim-current/ Original patch from
Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/
Forwarded: https://bugs.exim.org/show_bug.cgi?id=2671
Last-Update: 2021-07-28
-
+---
--- a/src/EDITME
+++ b/src/EDITME
-@@ -998,6 +998,21 @@ HEADERS_CHARSET="ISO-8859-1"
+@@ -989,6 +989,21 @@ HEADERS_CHARSET="ISO-8859-1"
#------------------------------------------------------------------------------
#define CONFIGURE_FILE
--- a/src/globals.c
+++ b/src/globals.c
-@@ -120,6 +120,10 @@ int dsn_ret = 0;
+@@ -83,6 +83,10 @@ int dsn_ret = 0;
const pcre2_code *regex_DSN = NULL;
- uschar *dsn_advertise_hosts = NULL;
+ const uschar *dsn_advertise_hosts = NULL;
+#ifdef DLOPEN_LOCAL_SCAN
+uschar *local_scan_path = NULL;
BOOL gnutls_allow_auto_pkcs11 = FALSE;
--- a/src/globals.h
+++ b/src/globals.h
-@@ -164,6 +164,9 @@ extern int dsn_ret; /
+@@ -128,6 +128,9 @@ extern int dsn_ret; /
extern const pcre2_code *regex_DSN; /* For recognizing DSN settings */
- extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */
+ extern const uschar *dsn_advertise_hosts; /* host for which TLS is advertised */
+#ifdef DLOPEN_LOCAL_SCAN
+extern uschar *local_scan_path; /* Path to local_scan() library */
+local_scan_lib = dlopen(local_scan_path, RTLD_NOW);
+if (!local_scan_lib)
+ {
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library open failed - "
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() library open failed - "
+ "message temporarily rejected");
+ return FALSE;
+ }
+if (!local_scan_version_fn)
+ {
+ dlclose(local_scan_lib);
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
+ "local_scan_version_major() function - message temporarily rejected");
+ return FALSE;
+ }
+if (!local_scan_version_fn)
+ {
+ dlclose(local_scan_lib);
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
+ "local_scan_version_minor() function - message temporarily rejected");
+ return FALSE;
+ }
+ {
+ dlclose(local_scan_lib);
+ local_scan_lib = NULL;
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major"
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major"
+ "version number, you need to recompile your module for this version"
+ "of exim (The module was compiled for version %d.%d and this exim provides"
+ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
+ {
+ dlclose(local_scan_lib);
+ local_scan_lib = NULL;
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor"
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor"
+ "version number, you need to recompile your module for this version"
+ "of exim (The module was compiled for version %d.%d and this exim provides"
+ "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
+if (!local_scan_fn)
+ {
+ dlclose(local_scan_lib);
-+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
++ log_write(LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
+ "local_scan() function - message temporarily rejected");
+ return FALSE;
+ }
/* End of local_scan.c */
--- a/src/local_scan.h
+++ b/src/local_scan.h
-@@ -30,6 +30,7 @@ settings, and the store functions. */
-
+@@ -31,6 +31,7 @@ settings, and the store functions. */
#include <stdarg.h>
+ #include <stdint.h>
#include <sys/types.h>
+#pragma GCC visibility push(default)
#include "config.h"
#include "mytypes.h"
#include "store.h"
-@@ -179,6 +180,9 @@ extern header_line *header_list; /
+@@ -175,6 +176,9 @@ extern header_line *header_list; /
extern BOOL host_checking; /* Set when checking a host */
extern uschar *interface_address; /* Interface for incoming call */
extern int interface_port; /* Port number for incoming call */
extern uschar *message_id; /* Internal id of message being handled */
extern uschar *received_protocol; /* Name of incoming protocol */
extern int recipients_count; /* Number of recipients */
-@@ -249,5 +253,7 @@ extern pid_t child_open_exim2_functio
+@@ -259,5 +263,7 @@ extern pid_t child_open_exim2_functio
extern pid_t child_open_function(uschar **, uschar **, int, int *, int *, BOOL, const uschar *);
#endif
/* End of local_scan.h */
--- a/src/readconf.c
+++ b/src/readconf.c
-@@ -218,6 +218,9 @@ static optionlist optionlist_config[] =
+@@ -211,6 +211,9 @@ static optionlist optionlist_config[] =
{ "local_from_prefix", opt_stringptr, {&local_from_prefix} },
{ "local_from_suffix", opt_stringptr, {&local_from_suffix} },
{ "local_interfaces", opt_stringptr, {&local_interfaces} },
#endif
--- a/src/string.c
+++ b/src/string.c
-@@ -459,6 +459,7 @@ return ss;
+@@ -472,6 +472,7 @@ return ss;
#if (defined(HAVE_LOCAL_SCAN) || defined(EXPAND_DLFUNC)) \
&& !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
/*************************************************
* Copy and save string *
*************************************************/
-@@ -504,6 +505,7 @@ string_copyn_function(const uschar * s,
+@@ -517,6 +518,7 @@ string_copyn_function(const uschar * s,
{
return string_copyn(s, n);
}
+From f2763b95afc57b88dc9d494b3fbf3841ba38a314 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 7 Sep 2022 18:24:22 +0100
+Subject: [PATCH] fix-build: avoid spurious extra vararg and a missing stdlib.h
+ include
+
+exim_nullstd() calls string_open_failed("/dev/null", NULL) - a
+variadic, format-string-checked helper - with a literal path as the
+format string and an unused NULL vararg, since the literal contains
+no conversion specifier. Use a proper "%s" format with the path as
+its argument instead, via a named devnullpath constant shared with
+the preceding open() call.
+
+local_scan.h is missing an explicit #include <stdlib.h>, relying on
+it being pulled in transitively by another header.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ src/exim.c | 6 ++++--
+ src/local_scan.h | 1 +
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
--- a/src/exim.c
+++ b/src/exim.c
-@@ -660,13 +660,15 @@ exim_nullstd(void)
+@@ -668,13 +668,15 @@ exim_nullstd(void)
{
int devnull = -1;
struct stat statbuf;
{
- if (devnull < 0) devnull = open("/dev/null", O_RDWR);
+ if (devnull < 0) devnull = open(devnullpath, O_RDWR);
- if (devnull < 0) log_write_die(0, LOG_MAIN, "%s",
+ if (devnull < 0) log_write_die(LOG_MAIN, "%s",
- string_open_failed("/dev/null", NULL));
+ string_open_failed("%s", devnullpath));
if (devnull != i) (void)dup2(devnull, i);
}
--- a/src/local_scan.h
+++ b/src/local_scan.h
-@@ -29,6 +29,7 @@ store.c
- settings, and the store functions. */
+@@ -30,6 +30,7 @@ settings, and the store functions. */
#include <stdarg.h>
+ #include <stdint.h>
+#include <stdlib.h>
#include <sys/types.h>
#pragma GCC visibility push(default)