Gitweb:
https://git.exim.org/exim.git/commitdiff/cccb2c06bbb96fa98edf6867f0eba4059aa757a0
Commit: cccb2c06bbb96fa98edf6867f0eba4059aa757a0
Parent: a0ecb20496a00e26cf7345a75cc1137eb3ac0709
Author: Jeremy Harris <jgh146exb@???>
AuthorDate: Wed Aug 28 00:16:43 2024 +0100
Committer: Jeremy Harris <jgh146exb@???>
CommitDate: Wed Aug 28 11:17:07 2024 +0100
spf dynamic module
---
doc/doc-docbook/spec.xfpt | 6 ++
doc/doc-txt/NewStuff | 6 +-
src/OS/Makefile-Base | 21 +++--
src/scripts/Configure-Makefile | 1 +
src/scripts/MakeLinks | 11 +++
src/scripts/lookups-Makefile | 4 +-
src/src/EDITME | 9 +-
src/src/acl.c | 90 +++++++++++++++++---
src/src/daemon.c | 4 +-
src/src/dmarc.c | 37 ++++++--
src/src/dmarc.h | 1 +
src/src/drtables.c | 153 ++++++++++++++++++++++++++++-----
src/src/exim.c | 13 ++-
src/src/exim.h | 2 +-
src/src/expand.c | 107 ++++++++++--------------
src/src/functions.h | 5 +-
src/src/globals.c | 14 ----
src/src/globals.h | 10 ---
src/src/lookups/Makefile | 2 -
src/src/lookups/spf.c | 92 +++++---------------
src/src/macros.h | 2 +-
src/src/miscmods/Makefile | 31 +++++++
src/src/{ => miscmods}/spf.c | 186 ++++++++++++++++++++++++++++++++++++++---
src/src/{ => miscmods}/spf.h | 7 --
src/src/readconf.c | 20 ++++-
src/src/receive.c | 2 +-
src/src/smtp_in.c | 28 ++++++-
src/src/structs.h | 69 +++++++++++++++
test/runtest | 15 ++--
test/stderr/2610 | 2 -
30 files changed, 697 insertions(+), 253 deletions(-)
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 600eee4af..428cbc079 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -42314,6 +42314,12 @@ This includes retransmissions done by traditional forwarders.
SPF verification support is built into Exim if SUPPORT_SPF=yes is set in
&_Local/Makefile_&. The support uses the &_libspf2_& library
&url(
https://www.libspf2.org/).
+.new
+.cindex "dynamic modules"
+The support can be built as a dynamic-load module if desired;
+see the comments in that Makefile.
+.wen
+
There is no Exim involvement in the transmission of messages;
publishing certain DNS records is all that is required.
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 9a34f8ac2..640bd58cd 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -14,9 +14,9 @@ Version 4.98
3. Events smtp:fail:protocol and smtp:fail:syntax
- 4. JSON and LDAP lookup support, all the router and authenticator drivers,
- and all the transport drivers except smtp, can now be built as loadable
- modules
+ 4. JSON and LDAP lookup support, SPF support, all the router and authenticator
+ drivers, and all the transport drivers except smtp, can now be built as
+ loadable modules
Version 4.98
------------
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base
index 2e8728ae2..caae1e536 100644
--- a/src/OS/Makefile-Base
+++ b/src/OS/Makefile-Base
@@ -245,7 +245,7 @@ macro.c: macro_predef
.PHONY: all config utils \
buildauths buildlookups buildpdkim buildrouters \
- buildtransports dynmodules checklocalmake clean
+ buildtransports buildmisc dynmodules checklocalmake clean
utils: $(EXIM_MONITOR) exicyclog exinext exiwhat \
@@ -501,7 +501,6 @@ OBJ_EXPERIMENTAL = arc.o \
dcc.o \
dmarc.o \
imap_utf7.o \
- spf.o \
utf8.o \
xclient.o
@@ -529,12 +528,12 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
$(OBJ_EXPERIMENTAL)
exim: buildlookups buildauths pdkim/pdkim.a \
- buildrouters buildtransports \
+ buildrouters buildtransports buildmisc \
$(OBJ_EXIM) version.o
@echo "$(LNCC) -o exim"
$(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \
routers/routers.a transports/transports.a lookups/lookups.a \
- auths/auths.a pdkim/pdkim.a \
+ auths/auths.a pdkim/pdkim.a miscmods/miscmods.a \
$(LIBRESOLV) $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) \
$(EXTRALIBS_EXIM) $(DBMLIB) $(LOOKUP_LIBS) $(AUTH_LIBS) \
$(PERL_LIBS) $(TLS_LIBS) $(PCRE_LIBS) $(LDFLAGS)
@@ -904,7 +903,6 @@ dane.o: $(HDRS) dane.c dane-openssl.c
dcc.o: $(HDRS) dcc.h dcc.c
dmarc.o: $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c
imap_utf7.o: $(HDRS) imap_utf7.c
-spf.o: $(HDRS) spf.h spf.c
utf8.o: $(HDRS) utf8.c
xclient.o: $(HDRS) xclient.c
@@ -1018,10 +1016,11 @@ $(MONBIN): $(HDRS)
# Copies of modules built as dynamic-load libraries
-dynmodules: buildlookups buildrouters buildtransports buildauths
+dynmodules: buildlookups buildrouters buildtransports buildauths \
+ buildmisc
rm -fr dynmodules
mkdir dynmodules
- for d in lookup router transport auth; do \
+ for d in lookup router transport auth miscmod; do \
for f in $${d}s/*.so; do \
[ -e $$f ] && ln $$f dynmodules/`basename $$f .so`_$$d.so; \
done; \
@@ -1073,6 +1072,14 @@ pdkim/pdkim.a: config
INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
@echo " "
+buildmisc: config
+ @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \
+ CC="$(CC)" CFLAGS="$(CFLAGS)" \
+ CFLAGS_DYNAMIC="$(CFLAGS_DYNAMIC)" HDRS="../version.h $(PHDRS)" \
+ FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" \
+ INCLUDE="$(INCLUDE) $(IPV6_INCLUDE)"
+ @echo " "
+
# The "clean", "install", and "makefile" targets just pass themselves back to
# the main Exim makefile. These targets will be obeyed only if "make" is obeyed
# for them in the build directory.
diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile
index 9179392f3..427ce0cb7 100755
--- a/src/scripts/Configure-Makefile
+++ b/src/scripts/Configure-Makefile
@@ -301,6 +301,7 @@ done <<-END
routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT
transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP
auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS
+ miscmods SUPPORT SPF
END
# See if there is a definition of EXIM_PERL in what we have built so far.
diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks
index 76859ce9a..09d18b63c 100755
--- a/src/scripts/MakeLinks
+++ b/src/scripts/MakeLinks
@@ -89,6 +89,17 @@ do
done
cd ..
+# miscellaneous modules
+d="miscmods"
+mkdir $d
+cd $d
+# Makefile is generated
+for f in spf.c spf.h
+do
+ ln -s ../../src/$d/$f $f
+done
+cd ..
+
# and the hintsdb implementations
d="hintsdb"
mkdir $d
diff --git a/src/scripts/lookups-Makefile b/src/scripts/lookups-Makefile
index 40cca603f..188c22d14 100755
--- a/src/scripts/lookups-Makefile
+++ b/src/scripts/lookups-Makefile
@@ -169,8 +169,8 @@ do
emit_module_rule $name_mod
done
-# Because the variable is EXPERIMENTAL_SPF and not LOOKUP_SPF we
-# always include spf.o and compile a dummy if EXPERIMENTAL_SPF is not
+# Because the variable is SUPPORT_SPF and not LOOKUP_SPF we
+# always include spf.o and compile a dummy if SUPPORT_SPF is not
# defined.
OBJ="${OBJ} spf.o"
diff --git a/src/src/EDITME b/src/src/EDITME
index 7d0b07331..e507ab3cd 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -832,8 +832,8 @@ FIXED_NEVER_USERS=root
# is historic).
# You need to add -export-dynamic -rdynamic to EXTRALIBS. You may also need to
# add -ldl to EXTRALIBS so that dlopen() is available to Exim. You need to
-# define CFLAGS_DYNAIC and LOOKUP_MODULE_DIR below so the builds are done right,
-# and so the exim binary actually loads dynamic lookup modules.
+# define CFLAGS_DYNAMIC and LOOKUP_MODULE_DIR below so the builds are done
+# right and so the exim binary actually loads dynamic lookup modules.
#
# Libraries being built as modules should be added to respective
# LOOKUP_*_INCLUDE and LOOKUP_*_LIBS rather than the the ones for the
@@ -1119,6 +1119,11 @@ ZCAT_COMMAND=/usr/bin/zcat
# Uncomment the following lines to add SPF support. You need to have libspf2
# installed on your system (
www.libspf2.org). Depending on where it is installed
# you may have to edit the CFLAGS and LDFLAGS lines.
+#
+# If set to "2" instead of "yes" then the support will be
+# built as a module and must be installed into LOOKUP_MODULE_DIR (the name
+# is historic). The same rules as for other module builds apply; use
+# SUPPORT_SPF_{INCLUDE,LIBS}.
# SUPPORT_SPF=yes
# CFLAGS += -I/usr/local/include
diff --git a/src/src/acl.c b/src/src/acl.c
index 533dcd60a..e285da65c 100644
--- a/src/src/acl.c
+++ b/src/src/acl.c
@@ -129,14 +129,16 @@ being the prefix of another; the binary-search in the list will go wrong. */
typedef struct condition_def {
uschar *name;
+ /* Flags for actions or checks to do during readconf for this condition */
unsigned flags;
#define ACD_EXP BIT(0) /* do expansion at outer level*/
#define ACD_MOD BIT(1) /* is a modifier */
+#define ACD_LOAD BIT(2) /* supported by a dynamic-load module */
-/* Bit map vector of which conditions and modifiers are not allowed at certain
-times. For each condition and modifier, there's a bitmap of dis-allowed times.
-For some, it is easier to specify the negation of a small number of allowed
-times. */
+ /* Bit map vector of which conditions and modifiers are not allowed at certain
+ times. For each condition and modifier, there's a bitmap of dis-allowed times.
+ For some, it is easier to specify the negation of a small number of allowed
+ times. */
unsigned forbids;
#define FORBIDDEN(times) (times)
#define PERMITTED(times) ((unsigned) ~(times))
@@ -339,14 +341,22 @@ static condition_def conditions[] = {
},
#endif
#ifdef SUPPORT_SPF
- [ACLC_SPF] = { US"spf", ACD_EXP,
+ [ACLC_SPF] = { US"spf",
+# if SUPPORT_SPF==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO | ACL_BIT_MAILAUTH |
ACL_BIT_ETRN | ACL_BIT_EXPN |
ACL_BIT_STARTTLS | ACL_BIT_VRFY |
ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START),
},
- [ACLC_SPF_GUESS] = { US"spf_guess", ACD_EXP,
+ [ACLC_SPF_GUESS] = { US"spf_guess",
+# if SUPPORT_SPF==2
+ ACD_LOAD |
+# endif
+ ACD_EXP,
FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT |
ACL_BIT_HELO | ACL_BIT_MAILAUTH |
ACL_BIT_ETRN | ACL_BIT_EXPN |
@@ -383,6 +393,25 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++)
#ifndef MACRO_PREDEF
+# ifdef LOOKUP_MODULE_DIR
+typedef struct condition_module {
+ const uschar * mod_name; /* module for the givien conditions */
+ misc_module_info * info; /* NULL when not loaded */
+ const int * conditions; /* array of ACLC_*, -1 terminated */
+} condition_module;
+
+# if SUPPORT_SPF==2
+static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 };
+# endif
+
+static condition_module condition_modules[] = {
+# if SUPPORT_SPF==2
+ {.mod_name = US"spf", .conditions = spf_condx},
+# endif
+};
+
+# endif
+
/* Return values from decode_control() */
enum {
@@ -936,7 +965,7 @@ while ((s = (*func)()))
/* The modifiers may not be negated */
- if (negated && conditions[c].flags & ACD_MOD )
+ if (negated && conditions[c].flags & ACD_MOD)
{
*error = string_sprintf("ACL error: negation is not allowed with "
"\"%s\"", conditions[c].name);
@@ -954,6 +983,34 @@ while ((s = (*func)()))
return NULL;
}
+#ifdef LOOKUP_MODULE_DIR
+ if (conditions[c].flags & ACD_LOAD)
+ { /* a loadable module supports this condition */
+ condition_module * cm;
+ uschar * s = NULL;
+
+ for (cm = condition_modules;
+ cm < condition_modules + nelem(condition_modules); cm++)
+ for (const int * cond = cm->conditions; *cond != -1; cond++)
+ if (*cond == c) goto found;
+ found:
+
+ if (cm >= condition_modules + nelem(condition_modules))
+ { /* shouldn't happen */
+ *error = string_sprintf("ACL error: failed to locate support for '%s'",
+ conditions[c].name);
+ return NULL;
+ }
+ if ( !cm->info /* module not loaded */
+ && !(cm->info = misc_mod_find(cm->mod_name, &s)))
+ {
+ *error = string_sprintf("ACL error: failed to find module for '%s': %s",
+ conditions[c].name, s);
+ return NULL;
+ }
+ }
+#endif
+
cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED);
cond->next = NULL;
cond->type = c;
@@ -4127,12 +4184,23 @@ for (; cb; cb = cb->next)
#ifdef SUPPORT_SPF
case ACLC_SPF:
- rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL);
- break;
-
case ACLC_SPF_GUESS:
- rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS);
+ /* Hardwire the offset of the function in the module functions table
+ for now. Work out a more general mech later. */
+ {
+ misc_module_info * mi = misc_mod_find(US"spf", &log_message);
+ typedef int (*fn_t)(const uschar **, const uschar *, int);
+ fn_t fn;
+
+ if (!mi)
+ { rc = DEFER; break; } /* shouldn't happen */
+
+ fn = ((fn_t *) mi->functions)[1];
+
+ rc = fn(&arg, sender_address,
+ cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS);
break;
+ }
#endif
case ACLC_UDPSEND:
diff --git a/src/src/daemon.c b/src/src/daemon.c
index 4088cb532..49bf74a11 100644
--- a/src/src/daemon.c
+++ b/src/src/daemon.c
@@ -2578,8 +2578,8 @@ smtp_deliver_init(); /* Used for callouts */
#ifdef WITH_CONTENT_SCAN
malware_init();
#endif
-#ifdef SUPPORT_SPF
-spf_init();
+#ifdef SUPPORT_DMARC
+dmarc_init();
#endif
#ifndef DISABLE_TLS
tls_daemon_init();
diff --git a/src/src/dmarc.c b/src/src/dmarc.c
index 28fce0624..664daa737 100644
--- a/src/src/dmarc.c
+++ b/src/src/dmarc.c
@@ -31,7 +31,9 @@ OPENDMARC_STATUS_T da, sa, action;
BOOL dmarc_abort = FALSE;
uschar *dmarc_pass_fail = US"skipped";
header_line *from_header = NULL;
-extern SPF_response_t *spf_response;
+
+misc_module_info * spf_mod_info;
+SPF_response_t *spf_response_p;
int dmarc_spf_ares_result = 0;
uschar *spf_sender_domain = NULL;
uschar *spf_human_readable = NULL;
@@ -53,6 +55,16 @@ static dmarc_exim_p dmarc_policy_description[] = {
};
+int
+dmarc_init(void)
+{
+uschar * errstr;
+if (!(spf_mod_info = misc_mod_find(US"spf", &errstr)))
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
+ "dmarc: failed to find SPF module: %s", errstr);
+return TRUE;
+}
+
gstring *
dmarc_version_report(gstring * g)
{
@@ -86,12 +98,12 @@ eb->next = NULL;
return eblock;
}
-/* dmarc_init sets up a context that can be re-used for several
+/* dmarc_conn_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) */
int
-dmarc_init(void)
+dmarc_conn_init(void)
{
int *netmask = NULL; /* Ignored */
int is_ipv6 = 0;
@@ -106,11 +118,12 @@ dmarc_pass_fail = US"skipped";
dmarc_used_domain = US"";
f.dmarc_has_been_checked = FALSE;
header_from_sender = NULL;
+spf_response_p = NULL;
spf_sender_domain = NULL;
spf_human_readable = NULL;
/* ACLs have "control=dmarc_disable_verify" */
-if (f.dmarc_disable_verify == TRUE)
+if (f.dmarc_disable_verify)
return OK;
(void) memset(&dmarc_ctx, '\0', sizeof dmarc_ctx);
@@ -274,7 +287,7 @@ g = string_fmt_append(NULL,
message_id, primary_hostname, time(NULL), sender_host_address,
header_from_sender, expand_string(US"$sender_address_domain"));
-if (spf_response)
+if (spf_response_p)
g = string_fmt_append(g, "spf %d\n", dmarc_spf_ares_result);
if (dkim_history_buffer)
@@ -418,7 +431,15 @@ if (!dmarc_abort && !sender_host_authenticated)
/* Use the envelope sender domain for this part of DMARC */
spf_sender_domain = expand_string(US"$sender_address_domain");
- if (!spf_response)
+
+ {
+ misc_module_info * mi = misc_mod_findonly(US"spf");
+ typedef SPF_response_t * (*fn_t)(void);
+ if (mi)
+ spf_response_p = ((fn_t *) mi->functions)[3](); /* spf_get_response */
+ }
+
+ if (!spf_response_p)
{
/* No spf data means null envelope sender so generate a domain name
from the sender_helo_name */
@@ -439,7 +460,7 @@ if (!dmarc_abort && !sender_host_authenticated)
}
else
{
- sr = spf_response->result;
+ sr = spf_response_p->result;
dmarc_spf_result = sr == SPF_RESULT_NEUTRAL ? DMARC_POLICY_SPF_OUTCOME_NONE :
sr == SPF_RESULT_PASS ? DMARC_POLICY_SPF_OUTCOME_PASS :
sr == SPF_RESULT_FAIL ? DMARC_POLICY_SPF_OUTCOME_FAIL :
@@ -454,7 +475,7 @@ if (!dmarc_abort && !sender_host_authenticated)
sr == SPF_RESULT_PERMERROR ? ARES_RESULT_PERMERROR :
ARES_RESULT_UNKNOWN;
origin = DMARC_POLICY_SPF_ORIGIN_MAILFROM;
- spf_human_readable = US spf_response->header_comment;
+ spf_human_readable = US spf_response_p->header_comment;
DEBUG(D_receive)
debug_printf_indent("DMARC using SPF sender domain = %s\n", spf_sender_domain);
}
diff --git a/src/src/dmarc.h b/src/src/dmarc.h
index fa366dd06..dcf289f2d 100644
--- a/src/src/dmarc.h
+++ b/src/src/dmarc.h
@@ -21,6 +21,7 @@
/* prototypes */
gstring * dmarc_version_report(gstring *);
int dmarc_init(void);
+int dmarc_conn_init(void);
int dmarc_store_data(header_line *);
int dmarc_process(void);
uschar *dmarc_exim_expand_query(int);
diff --git a/src/src/drtables.c b/src/src/drtables.c
index a144085c5..35a376dd1 100644
--- a/src/src/drtables.c
+++ b/src/src/drtables.c
@@ -325,7 +325,7 @@ extern lookup_module_info redis_lookup_module_info;
extern lookup_module_info lmdb_lookup_module_info;
#endif
#if defined(SUPPORT_SPF)
-extern lookup_module_info spf_lookup_module_info;
+extern lookup_module_info spf_lookup_module_info; /* see below */
#endif
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
extern lookup_module_info sqlite_lookup_module_info;
@@ -341,6 +341,31 @@ extern lookup_module_info readsock_lookup_module_info;
#ifdef LOOKUP_MODULE_DIR
+static void *
+mod_open(const uschar * name, const uschar * class, uschar ** errstr)
+{
+const uschar * path = string_sprintf(
+ LOOKUP_MODULE_DIR "/%s_%s." DYNLIB_FN_EXT, name, class);
+void * dl;
+if (!(dl = dlopen(CS path, RTLD_NOW)))
+ {
+ if (errstr)
+ *errstr = string_sprintf("Error loading %s: %s", name, dlerror());
+ else
+ (void) dlerror(); /* clear out error state */
+ return NULL;
+ }
+
+/* FreeBSD nsdispatch() can trigger dlerror() errors about
+_nss_cache_cycle_prevention_function; we need to clear the dlerror()
+state before calling dlsym(), so that any error afterwards only comes
+from dlsym(). */
+
+(void) dlerror();
+return dl;
+}
+
+
/* Try to load a lookup module with the given name.
Arguments:
@@ -353,27 +378,12 @@ Return: boolean success
static BOOL
lookup_mod_load(const uschar * name, uschar ** errstr)
{
-const uschar * path = string_sprintf(
- LOOKUP_MODULE_DIR "/%s_lookup." DYNLIB_FN_EXT, name);
void * dl;
struct lookup_module_info * info;
const char * errormsg;
-if (!(dl = dlopen(CS path, RTLD_NOW)))
- {
- if (errstr)
- *errstr = string_sprintf("Error loading %s: %s", name, dlerror());
- else
- (void) dlerror(); /* clear out error state */
+if (!(dl = mod_open(name, US"lookup", errstr)))
return FALSE;
- }
-
-/* FreeBSD nsdispatch() can trigger dlerror() errors about
-_nss_cache_cycle_prevention_function; we need to clear the dlerror()
-state before calling dlsym(), so that any error afterwards only comes
-from dlsym(). */
-
-errormsg = dlerror();
info = (struct lookup_module_info *) dlsym(dl, "_lookup_module_info");
if ((errormsg = dlerror()))
@@ -416,6 +426,89 @@ return TRUE;
+misc_module_info * misc_module_list = NULL;
+
+static void
+misc_mod_add(misc_module_info * mi)
+{
+if (mi->init) mi->init(mi);
+DEBUG(D_lookup) if (mi->lib_vers_report)
+ debug_printf_indent("%Y\n", mi->lib_vers_report(NULL));
+
+mi->next = misc_module_list;
+misc_module_list = mi;
+}
+
+
+#ifdef LOOKUP_MODULE_DIR
+
+/* Load a "misc" module, and add to list */
+
+static misc_module_info *
+misc_mod_load(const uschar * name, uschar ** errstr)
+{
+void * dl;
+struct misc_module_info * mi;
+const char * errormsg;
+
+DEBUG(D_any) debug_printf_indent("loading module '%s'\n", name);
+if (!(dl = mod_open(name, US"miscmod", errstr)))
+ return NULL;
+
+mi = (struct misc_module_info *) dlsym(dl,
+ CS string_sprintf("%s_module_info", name));
+if ((errormsg = dlerror()))
+ {
+ fprintf(stderr, "%s does not appear to be an spf module (%s)\n", name, errormsg);
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s does not appear to be an spf module (%s)", name, errormsg);
+ dlclose(dl);
+ return NULL;
+ }
+if (mi->dyn_magic != MISC_MODULE_MAGIC)
+ {
+ fprintf(stderr, "Module %s is not compatible with this version of Exim\n", name);
+ log_write(0, LOG_MAIN|LOG_PANIC, "Module %s is not compatible with this version of Exim", name);
+ dlclose(dl);
+ return FALSE;
+ }
+
+DEBUG(D_lookup) debug_printf_indent("Loaded \"%s\"\n", name);
+misc_mod_add(mi);
+return mi;
+}
+
+#endif /*LOOKUP_MODULE_DIR*/
+
+
+/* Find a "misc" module by name, if loaded.
+For now use a linear search down a linked list. If the number of
+modules gets large, we might consider a tree.
+*/
+
+misc_module_info *
+misc_mod_findonly(const uschar * name)
+{
+for (misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+ if (Ustrcmp(name, mi->name) == 0)
+ return mi;
+}
+
+/* Find a "misc" module, possibly already loaded, by name. */
+
+misc_module_info *
+misc_mod_find(const uschar * name, uschar ** errstr)
+{
+misc_module_info * mi;
+if ((mi = misc_mod_findonly(name))) return mi;
+#ifdef LOOKUP_MODULE_DIR
+return misc_mod_load(name, errstr);
+#else
+return NULL;
+#endif /*LOOKUP_MODULE_DIR*/
+}
+
+
+
void
@@ -495,10 +588,6 @@ addlookupmodule(&redis_lookup_module_info);
addlookupmodule(&lmdb_lookup_module_info);
#endif
-#ifdef SUPPORT_SPF
-addlookupmodule(&spf_lookup_module_info);
-#endif
-
#if defined(LOOKUP_SQLITE) && LOOKUP_SQLITE!=2
addlookupmodule(&sqlite_lookup_module_info);
#endif
@@ -511,6 +600,14 @@ addlookupmodule(&testdb_lookup_module_info);
addlookupmodule(&whoson_lookup_module_info);
#endif
+/* This is provided by the spf "misc" module, and the lookup aspect is always
+linked statically whether or not the "misc" module (and hence libspf2) is
+dynamic-load. */
+
+#if defined(SUPPORT_SPF)
+addlookupmodule(&spf_lookup_module_info);
+#endif
+
/* This is a custom expansion, and not available as either
a list-syntax lookup or a lookup expansion. However, it is
implemented by a lookup module. */
@@ -558,8 +655,22 @@ else
DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
#endif
+}
+#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
+extern misc_module_info spf_module_info;
+#endif
+
+void
+init_misc_mod_list(void)
+{
+static BOOL onetime = FALSE;
+if (onetime) return;
+#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
+misc_mod_add(&spf_module_info);
+#endif
+onetime = TRUE;
}
diff --git a/src/src/exim.c b/src/src/exim.c
index de1f48434..ecc25d6bc 100644
--- a/src/src/exim.c
+++ b/src/src/exim.c
@@ -33,6 +33,7 @@ Also a few functions that don't naturally fit elsewhere. */
#endif
extern void init_lookup_list(void);
+extern void init_misc_mod_list(void);
@@ -1155,6 +1156,13 @@ gstring * b = NULL, * d = NULL;
d = string_cat(d, US" redis");
# endif
#endif
+#ifdef SUPPORT_SPF
+# if SUPPORT_SPF!=2
+ b = string_cat(b, US" spf");
+# else
+ d = string_cat(d, US" spf");
+# endif
+#endif
#ifdef LOOKUP_SQLITE
# if LOOKUP_SQLITE!=2
b = string_cat(b, US" sqlite");
@@ -1390,9 +1398,6 @@ DEBUG(D_any)
#ifdef SUPPORT_DMARC
g = dmarc_version_report(g);
#endif
-#ifdef SUPPORT_SPF
- g = spf_lib_version_report(g);
-#endif
show_string(is_stdout, g);
g = NULL;
@@ -1428,6 +1433,7 @@ DEBUG(D_any)
tree_walk(lookups_tree, lookup_version_report_cb, &g);
show_string(is_stdout, g);
g = NULL;
+ init_misc_mod_list();
#ifdef WHITELIST_D_MACROS
g = string_fmt_append(g, "WHITELIST_D_MACROS: \"%s\"\n", WHITELIST_D_MACROS);
@@ -4204,6 +4210,7 @@ is equivalent to the ability to modify a setuid binary!
This needs to happen before we read the main configuration. */
init_lookup_list();
+init_misc_mod_list();
/*XXX this excrescence could move to the testsuite standard config setup file */
#ifdef SUPPORT_I18N
diff --git a/src/src/exim.h b/src/src/exim.h
index c4d80c694..470adb351 100644
--- a/src/src/exim.h
+++ b/src/src/exim.h
@@ -543,7 +543,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
# include "bmi_spam.h"
#endif
#ifdef SUPPORT_SPF
-# include "spf.h"
+# include "miscmods/spf.h"
#endif
#ifndef DISABLE_DKIM
# include "dkim.h"
diff --git a/src/src/expand.c b/src/src/expand.c
index 08d72f213..b3a1575a7 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -421,51 +421,6 @@ enum {
};
-/* Types of table entry */
-
-enum vtypes {
- vtype_int, /* value is address of int */
- vtype_filter_int, /* ditto, but recognized only when filtering */
- vtype_ino, /* value is address of ino_t (not always an int) */
- vtype_uid, /* value is address of uid_t (not always an int) */
- vtype_gid, /* value is address of gid_t (not always an int) */
- vtype_bool, /* value is address of bool */
- vtype_stringptr, /* value is address of pointer to string */
- vtype_msgbody, /* as stringptr, but read when first required */
- vtype_msgbody_end, /* ditto, the end of the message */
- vtype_msgheaders, /* the message's headers, processed */
- vtype_msgheaders_raw, /* the message's headers, unprocessed */
- vtype_localpart, /* extract local part from string */
- vtype_domain, /* extract domain from string */
- vtype_string_func, /* value is string returned by given function */
- vtype_todbsdin, /* value not used; generate BSD inbox tod */
- vtype_tode, /* value not used; generate tod in epoch format */
- vtype_todel, /* value not used; generate tod in epoch/usec format */
- vtype_todf, /* value not used; generate full tod */
- vtype_todl, /* value not used; generate log tod */
- vtype_todlf, /* value not used; generate log file datestamp tod */
- vtype_todzone, /* value not used; generate time zone only */
- vtype_todzulu, /* value not used; generate zulu tod */
- vtype_reply, /* value not used; get reply from headers */
- vtype_pid, /* value not used; result is pid */
- vtype_host_lookup, /* value not used; get host name */
- vtype_load_avg, /* value not used; result is int from os_getloadavg */
- vtype_pspace, /* partition space; value is T/F for spool/log */
- vtype_pinodes, /* partition inodes; value is T/F for spool/log */
- vtype_cert /* SSL certificate */
-#ifndef DISABLE_DKIM
- ,vtype_dkim /* Lookup of value in DKIM signature */
-#endif
-};
-
-/* Type for main variable table */
-
-typedef struct {
- const char *name;
- enum vtypes type;
- void *value;
-} var_entry;
-
/* Type for entries pointing to address/length pairs. Not currently
in use. */
@@ -754,12 +709,12 @@ static var_entry var_table[] = {
{ "spam_score_int", vtype_stringptr, &spam_score_int },
#endif
#ifdef SUPPORT_SPF
- { "spf_guess", vtype_stringptr, &spf_guess },
- { "spf_header_comment", vtype_stringptr, &spf_header_comment },
- { "spf_received", vtype_stringptr, &spf_received },
- { "spf_result", vtype_stringptr, &spf_result },
- { "spf_result_guessed", vtype_bool, &spf_result_guessed },
- { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment },
+ { "spf_guess", vtype_module, US"spf" },
+ { "spf_header_comment", vtype_module, US"spf" },
+ { "spf_received", vtype_module, US"spf" },
+ { "spf_result", vtype_module, US"spf" },
+ { "spf_result_guessed", vtype_module, US"spf" },
+ { "spf_smtp_comment", vtype_module, US"spf" },
#endif
{ "spool_directory", vtype_stringptr, &spool_directory },
{ "spool_inodes", vtype_pinodes, (void *)TRUE },
@@ -1281,19 +1236,19 @@ return NULL;
static var_entry *
-find_var_ent(uschar * name)
+find_var_ent(uschar * name, var_entry * table, unsigned nent)
{
int first = 0;
-int last = nelem(var_table);
+int last = nent;
while (last > first)
{
int middle = (first + last)/2;
- int c = Ustrcmp(name, var_table[middle].name);
+ int c = Ustrcmp(name, table[middle].name);
if (c > 0) { first = middle + 1; continue; }
if (c < 0) { last = middle; continue; }
- return &var_table[middle];
+ return &table[middle];
}
return NULL;
}
@@ -1420,7 +1375,7 @@ expand_getcertele(uschar * field, uschar * certvar)
{
var_entry * vp;
-if (!(vp = find_var_ent(certvar)))
+if (!(vp = find_var_ent(certvar, var_table, nelem(var_table))))
{
expand_string_message =
string_sprintf("no variable named \"%s\"", certvar);
@@ -1935,9 +1890,11 @@ static const uschar *
find_variable(uschar * name, esi_flags flags, int * newsize)
{
var_entry * vp;
-uschar *s, *domain;
-uschar **ss;
+uschar * s, * domain;
+uschar ** ss;
void * val;
+var_entry * table = var_table;
+unsigned table_count = nelem(var_table);
/* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx.
Originally, xxx had to be a number in the range 0-9 (later 0-19), but from
@@ -1982,9 +1939,11 @@ else if (Ustrncmp(name, "regex", 5) == 0)
}
#endif
+sublist:
+
/* For all other variables, search the table */
-if (!(vp = find_var_ent(name)))
+if (!(vp = find_var_ent(name, table, table_count)))
return NULL; /* Unknown variable name */
/* Found an existing variable. If in skipping state, the value isn't needed,
@@ -2175,6 +2134,20 @@ switch (vp->type)
return dkim_exim_expand_query((int)(long)val);
#endif
+ case vtype_module:
+ {
+ uschar * errstr;
+ misc_module_info * mi = misc_mod_find(val, &errstr);
+ if (mi)
+ {
+ table = mi->variables;
+ table_count = mi->variables_count;
+ goto sublist;
+ }
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to find %s module for %s: %s", US val, name, errstr);
+ return US"";
+ }
}
return NULL; /* Unknown variable. Silences static checkers. */
@@ -2187,7 +2160,8 @@ void
modify_variable(uschar *name, void * value)
{
var_entry * vp;
-if ((vp = find_var_ent(name))) vp->value = value;
+if ((vp = find_var_ent(name, var_table, nelem(var_table))))
+ vp->value = value;
return; /* Unknown variable name, fail silently */
}
@@ -4909,7 +4883,15 @@ while (*s)
yield = authres_iprev(yield);
yield = authres_smtpauth(yield);
#ifdef SUPPORT_SPF
- yield = authres_spf(yield);
+ {
+ misc_module_info * mi = misc_mod_findonly(US"spf");
+ if (mi)
+ {
+ typedef gstring * (*fn_t)(gstring *);
+ fn_t fn = ((fn_t *) mi->functions)[2]; /* authres_spf */
+ yield = fn(yield);
+ }
+ }
#endif
#ifndef DISABLE_DKIM
yield = authres_dkim(yield);
@@ -7225,7 +7207,8 @@ NOT_ITEM: ;
string_sprintf("missing '}' closing cert arg of %s", name);
goto EXPAND_FAILED_CURLY;
}
- if ((vp = find_var_ent(sub)) && vp->type == vtype_cert)
+ if ( (vp = find_var_ent(sub, var_table, nelem(var_table)))
+ && vp->type == vtype_cert)
{
s = s1+1;
break;
diff --git a/src/src/functions.h b/src/src/functions.h
index be60d5fc9..aaec6461f 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -151,9 +151,6 @@ extern gstring *authres_dkim(gstring *);
extern gstring *authres_dmarc(gstring *);
#endif
extern gstring *authres_smtpauth(gstring *);
-#ifdef SUPPORT_SPF
-extern gstring *authres_spf(gstring *);
-#endif
extern uschar *b64encode(const uschar *, int);
extern uschar *b64encode_taint(const uschar *, int, const void *);
@@ -380,6 +377,8 @@ extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *);
extern int mime_regex(const uschar **, BOOL);
extern void mime_set_anomaly(int);
#endif
+extern misc_module_info * misc_mod_find(const uschar * modname, uschar **);
+extern misc_module_info * misc_mod_findonly(const uschar * modname);
extern uschar *moan_check_errorcopy(const uschar *);
extern BOOL moan_skipped_syntax_errors(uschar *, error_block *, uschar *,
BOOL, uschar *);
diff --git a/src/src/globals.c b/src/src/globals.c
index 9efc389a6..f2287d41c 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -411,9 +411,6 @@ BOOL smtp_enforce_sync = TRUE;
BOOL smtp_etrn_serialize = TRUE;
BOOL smtp_input = FALSE;
BOOL smtp_return_error_details = FALSE;
-#ifdef SUPPORT_SPF
-BOOL spf_result_guessed = FALSE;
-#endif
BOOL split_spool_directory = FALSE;
BOOL spool_wireformat = FALSE;
BOOL strict_acl_vars = FALSE;
@@ -1501,17 +1498,6 @@ uschar *spam_action = NULL;
uschar *spam_score = NULL;
uschar *spam_score_int = NULL;
#endif
-#ifdef SUPPORT_SPF
-uschar *spf_guess = US"v=spf1 a/24 mx/24 ptr ?all";
-uschar *spf_header_comment = NULL;
-uschar *spf_received = NULL;
-uschar *spf_result = NULL;
-uschar *spf_smtp_comment = NULL;
-uschar *spf_smtp_comment_template
- /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */
- = US"Please%_see%_http://www.open-spf.org/Why";
-
-#endif
FILE *spool_data_file = NULL;
uschar *spool_directory = US SPOOL_DIRECTORY
diff --git a/src/src/globals.h b/src/src/globals.h
index a542a179f..9b30e502c 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -1049,16 +1049,6 @@ extern uschar *spam_action; /* the spamd recommended-action */
extern uschar *spam_score; /* the spam score (float) */
extern uschar *spam_score_int; /* spam_score * 10 (int) */
#endif
-#ifdef SUPPORT_SPF
-extern uschar *spf_guess; /* spf best-guess record */
-extern uschar *spf_header_comment; /* spf header comment */
-extern uschar *spf_received; /* Received-SPF: header */
-extern uschar *spf_result; /* spf result in string form */
-extern BOOL spf_result_guessed; /* spf result is of best-guess operation */
-extern uschar *spf_smtp_comment; /* spf comment to include in SMTP reply */
-extern uschar *spf_smtp_comment_template;
- /* template to construct the spf comment by libspf2 */
-#endif
extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */
extern FILE *spool_data_file; /* handle for -D file */
extern uschar *spool_directory; /* Name of spool directory */
diff --git a/src/src/lookups/Makefile b/src/src/lookups/Makefile
index 5193520f9..2bfd691a1 100644
--- a/src/src/lookups/Makefile
+++ b/src/src/lookups/Makefile
@@ -10,8 +10,6 @@
# MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
-OBJ += spf.o
-
all: lookups.a $(MODS)
lookups.a: $(OBJ)
diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c
index a1052d7fc..4e0824911 100644
--- a/src/src/lookups/spf.c
+++ b/src/src/lookups/spf.c
@@ -31,92 +31,46 @@ static void dummy(int x) { dummy2(x-1); }
#include <spf2/spf_dns_resolv.h>
#include <spf2/spf_dns_cache.h>
-extern SPF_dns_server_t * SPF_dns_exim_new(int);
-
static void *
spf_open(const uschar * filename, uschar ** errmsg)
{
-SPF_dns_server_t * dc;
-SPF_server_t *spf_server = NULL;
-int debug = 0;
-
-DEBUG(D_lookup) debug = 1;
-
-if ((dc = SPF_dns_exim_new(debug)))
- if ((dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
- spf_server = SPF_server_new_dns(dc, debug);
-
-if (!spf_server)
+misc_module_info * mi = misc_mod_find(US"spf", errmsg);
+if (mi)
{
- *errmsg = US"SPF_dns_exim_nnew() failed";
- return NULL;
+ typedef void * (*fn_t)(const uschar *, uschar **);
+ return (((fn_t *) mi->functions)[5]) (filename, errmsg);
}
-return (void *) spf_server;
+return NULL;
}
static void
-spf_close(void *handle)
+spf_close(void * handle)
{
-SPF_server_t *spf_server = handle;
-if (spf_server) SPF_server_free(spf_server);
+misc_module_info * mi = misc_mod_find(US"spf", NULL);
+if (mi)
+ {
+ typedef void (*fn_t)(void *);
+ return (((fn_t *) mi->functions)[6]) (handle);
+ }
}
+
static int
spf_find(void * handle, const uschar * filename, const uschar * keystring,
int key_len, uschar ** result, uschar ** errmsg, uint * do_cache,
const uschar * opts)
{
-SPF_server_t *spf_server = handle;
-SPF_request_t *spf_request;
-SPF_response_t *spf_response = NULL;
-
-if (!(spf_request = SPF_request_new(spf_server)))
- {
- *errmsg = US"SPF_request_new() failed";
- return FAIL;
- }
-
-#if HAVE_IPV6
-switch (string_is_ip_address(filename, NULL))
-#else
-switch (4)
-#endif
+misc_module_info * mi = misc_mod_find(US"spf", errmsg);
+if (mi)
{
- case 4:
- if (!SPF_request_set_ipv4_str(spf_request, CS filename))
- break;
- *errmsg = string_sprintf("invalid IPv4 address '%s'", filename);
- return FAIL;
-#if HAVE_IPV6
-
- case 6:
- if (!SPF_request_set_ipv6_str(spf_request, CS filename))
- break;
- *errmsg = string_sprintf("invalid IPv6 address '%s'", filename);
- return FAIL;
-
- default:
- *errmsg = string_sprintf("invalid IP address '%s'", filename);
- return FAIL;
-#endif
+ typedef int (*fn_t) (void *, const uschar *, const uschar *,
+ int, uschar **, uschar **, uint *, const uschar *);
+ return (((fn_t *) mi->functions)[7]) (handle, filename, keystring, key_len,
+ result, errmsg, do_cache, opts);
}
-
-if (SPF_request_set_env_from(spf_request, CS keystring))
- {
- *errmsg = string_sprintf("invalid envelope from address '%s'", keystring);
- return FAIL;
-}
-
-SPF_request_query_mailfrom(spf_request, &spf_response);
-*result = string_copy(US SPF_strresult(SPF_response_result(spf_response)));
-
-DEBUG(D_lookup) spf_response_debug(spf_response);
-
-SPF_response_free(spf_response);
-SPF_request_free(spf_request);
-return OK;
+return FAIL;
}
@@ -138,7 +92,7 @@ return g;
}
-static lookup_info _lookup_info = {
+static lookup_info spf_lookup_info = {
.name = US"spf", /* lookup name */
.type = 0, /* not absfile, not query style */
.open = spf_open, /* open function */
@@ -150,11 +104,11 @@ static lookup_info _lookup_info = {
.version_report = spf_version_report /* version reporting */
};
-#ifdef DYNLOOKUP
+#ifdef notdef_DYNLOOKUP
#define spf_lookup_module_info _lookup_module_info
#endif
-static lookup_info *_lookup_list[] = { &_lookup_info };
+static lookup_info *_lookup_list[] = { &spf_lookup_info };
lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
#endif /* SUPPORT_SPF */
diff --git a/src/src/macros.h b/src/src/macros.h
index 3bfcf51d5..7bcc7cd04 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -704,7 +704,7 @@ can be easily tested as a group. That is the only use of opt_bool_last. */
enum { opt_bit = 32, opt_bool_verify, opt_bool_set, opt_expand_bool,
opt_bool_last,
opt_rewrite, opt_timelist, opt_uid, opt_gid, opt_uidlist, opt_gidlist,
- opt_expand_uid, opt_expand_gid, opt_func, opt_void };
+ opt_expand_uid, opt_expand_gid, opt_func, opt_void, opt_module };
/* There's a high-ish bit which is used to flag duplicate options, kept
for compatibility, which shouldn't be output. Also used for hidden options
diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile
new file mode 100644
index 000000000..59bf29836
--- /dev/null
+++ b/src/src/miscmods/Makefile
@@ -0,0 +1,31 @@
+# Make file for building Exim's lookup modules.
+# This is called from the main make file, after cd'ing
+# to the misc_modulessubdirectory.
+#
+# Copyright (c) The Exim Maintainers 2024
+
+# nb: at build time, the version of this file used will have had some
+# extra variable definitions and prepended to it and module build rules
+# interpolated below. This is done by scripts/lookups-Makefile.
+
+# MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
+
+
+all: miscmods.a $(MODS)
+
+miscmods.a: $(OBJ)
+ @$(RM_COMMAND) -f miscmods.a
+ @echo "$(AR) miscmods.a"
+ @$(AR) miscmods.a $(OBJ)
+ $(RANLIB) $@
+
+.SUFFIXES: .o .c .so
+.c.o:; @echo "$(CC) $*.c"
+ $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
+
+.c.so:; @echo "$(CC) -shared $*.c"
+ $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
+
+spf.o spf.so: $(HDRS) spf.h spf.c
+
+# End
diff --git a/src/src/spf.c b/src/src/miscmods/spf.c
similarity index 71%
rename from src/src/spf.c
rename to src/src/miscmods/spf.c
index 9abe18fc3..a7b6c6a8d 100644
--- a/src/src/spf.c
+++ b/src/src/miscmods/spf.c
@@ -11,7 +11,7 @@
/* Code for calling spf checks via libspf-alt. Called from acl.c. */
-#include "exim.h"
+#include "../exim.h"
#ifdef SUPPORT_SPF
/* must be kept in numeric order */
@@ -34,8 +34,20 @@ SPF_response_t *spf_response_2mx = NULL;
SPF_dns_rr_t * spf_nxdomain = NULL;
+uschar * spf_guess = US"v=spf1 a/24 mx/24 ptr ?all";
+uschar * spf_header_comment = NULL;
+uschar * spf_received = NULL;
+uschar * spf_result = NULL;
+uschar * spf_smtp_comment = NULL;
+uschar * spf_smtp_comment_template
+ /* Used to be: "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}" */
+ = US"Please%_see%_http://www.open-spf.org/Why";
+BOOL spf_result_guessed = FALSE;
-gstring *
+
+
+
+static gstring *
spf_lib_version_report(gstring * g)
{
int maj, min, patch;
@@ -188,12 +200,12 @@ return spfrr;
-SPF_dns_server_t *
+static SPF_dns_server_t *
SPF_dns_exim_new(int debug)
{
SPF_dns_server_t * spf_dns_server = store_malloc(sizeof(SPF_dns_server_t));
-DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n");
+/* DEBUG(D_receive) debug_printf("SPF_dns_exim_new\n"); */
memset(spf_dns_server, 0, sizeof(SPF_dns_server_t));
spf_dns_server->destroy = NULL;
@@ -225,8 +237,8 @@ return spf_dns_server;
Return: Boolean success.
*/
-BOOL
-spf_init(void)
+static BOOL
+spf_init(void * dummy_ctx)
{
SPF_dns_server_t * dc;
int debug = 0;
@@ -278,13 +290,13 @@ return TRUE;
Return: Boolean success
*/
-BOOL
+static BOOL
spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr)
{
DEBUG(D_receive)
debug_printf("spf_conn_init: %s %s\n", spf_helo_domain, spf_remote_addr);
-if (!spf_server && !spf_init()) return FALSE;
+if (!spf_server && !spf_init(NULL)) return FALSE;
if (SPF_server_set_rec_dom(spf_server, CS primary_hostname))
{
@@ -320,8 +332,15 @@ if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain))
return TRUE;
}
+static void
+spf_smtp_reset(void)
+{
+spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
+spf_result_guessed = FALSE;
+}
+
-void
+static void
spf_response_debug(SPF_response_t * spf_response)
{
if (SPF_response_messages(spf_response) == 0)
@@ -343,7 +362,7 @@ else for (int i = 0; i < SPF_response_messages(spf_response); i++)
Return: OK/FAIL */
-int
+static int
spf_process(const uschar ** listptr, const uschar * spf_envelope_sender,
int action)
{
@@ -407,7 +426,7 @@ return FAIL;
-gstring *
+static gstring *
authres_spf(gstring * g)
{
uschar * s;
@@ -439,4 +458,149 @@ return g;
}
+/* Ugly; used only by dmarc (peeking into our data!)
+Exposure of values as $variables might be better? */
+
+static SPF_response_t *
+spf_get_response(void)
+{
+return spf_response;
+}
+
+/******************************************************************************/
+/* Lookup support */
+
+static void *
+spf_lookup_open(const uschar * filename, uschar ** errmsg)
+{
+SPF_dns_server_t * dc;
+SPF_server_t * spf_server = NULL;
+int debug = 0;
+
+DEBUG(D_lookup) debug = 1;
+
+if ((dc = SPF_dns_exim_new(debug)))
+ if ((dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
+ spf_server = SPF_server_new_dns(dc, debug);
+
+if (!spf_server)
+ {
+ *errmsg = US"SPF_dns_exim_nnew() failed";
+ return NULL;
+ }
+return (void *) spf_server;
+}
+
+static void
+spf_lookup_close(void * handle)
+{
+SPF_server_t * spf_server = handle;
+if (spf_server) SPF_server_free(spf_server);
+}
+
+static int
+spf_lookup_find(void * handle, const uschar * filename,
+ const uschar * keystring, int key_len, uschar ** result, uschar ** errmsg,
+ uint * do_cache, const uschar * opts)
+{
+SPF_server_t *spf_server = handle;
+SPF_request_t *spf_request;
+SPF_response_t *spf_response = NULL;
+
+if (!(spf_request = SPF_request_new(spf_server)))
+ {
+ *errmsg = US"SPF_request_new() failed";
+ return FAIL;
+ }
+
+#if HAVE_IPV6
+switch (string_is_ip_address(filename, NULL))
+#else
+switch (4)
+#endif
+ {
+ case 4:
+ if (!SPF_request_set_ipv4_str(spf_request, CS filename))
+ break;
+ *errmsg = string_sprintf("invalid IPv4 address '%s'", filename);
+ return FAIL;
+#if HAVE_IPV6
+
+ case 6:
+ if (!SPF_request_set_ipv6_str(spf_request, CS filename))
+ break;
+ *errmsg = string_sprintf("invalid IPv6 address '%s'", filename);
+ return FAIL;
+
+ default:
+ *errmsg = string_sprintf("invalid IP address '%s'", filename);
+ return FAIL;
#endif
+ }
+
+if (SPF_request_set_env_from(spf_request, CS keystring))
+ {
+ *errmsg = string_sprintf("invalid envelope from address '%s'", keystring);
+ return FAIL;
+}
+
+SPF_request_query_mailfrom(spf_request, &spf_response);
+*result = string_copy(US SPF_strresult(SPF_response_result(spf_response)));
+
+DEBUG(D_lookup) spf_response_debug(spf_response);
+
+SPF_response_free(spf_response);
+SPF_request_free(spf_request);
+return OK;
+}
+
+
+/******************************************************************************/
+/* Module API */
+
+static optionlist spf_options[] = {
+ { "spf_guess", opt_stringptr, {&spf_guess} },
+ { "spf_smtp_comment_template",opt_stringptr, {&spf_smtp_comment_template} },
+};
+
+static void * spf_functions[] = {
+ spf_conn_init,
+ spf_process,
+ authres_spf,
+ spf_get_response, /* ugly; for dmarc */
+ spf_smtp_reset,
+
+ spf_lookup_open,
+ spf_lookup_close,
+ spf_lookup_find,
+};
+
+static var_entry spf_variables[] = {
+ { "spf_guess", vtype_stringptr, &spf_guess },
+ { "spf_header_comment", vtype_stringptr, &spf_header_comment },
+ { "spf_received", vtype_stringptr, &spf_received },
+ { "spf_result", vtype_stringptr, &spf_result },
+ { "spf_result_guessed", vtype_bool, &spf_result_guessed },
+ { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment },
+};
+
+misc_module_info spf_module_info =
+{
+ .name = US"spf",
+# if SUPPORT_SPF==2
+ .dyn_magic = MISC_MODULE_MAGIC,
+# endif
+ .init = spf_init,
+ .lib_vers_report = spf_lib_version_report,
+
+ .options = spf_options,
+ .options_count = nelem(spf_options),
+
+ .functions = spf_functions,
+ .functions_count = nelem(spf_functions),
+
+ .variables = spf_variables,
+ .variables_count = nelem(spf_variables),
+};
+
+#endif /* almost all the file */
diff --git a/src/src/spf.h b/src/src/miscmods/spf.h
similarity index 77%
rename from src/src/spf.h
rename to src/src/miscmods/spf.h
index 7e9a6ca2a..679eab44b 100644
--- a/src/src/spf.h
+++ b/src/src/miscmods/spf.h
@@ -25,13 +25,6 @@ typedef struct spf_result_id {
int value;
} spf_result_id;
-/* prototypes */
-gstring * spf_lib_version_report(gstring *);
-BOOL spf_init(void);
-BOOL spf_conn_init(uschar *, uschar *);
-int spf_process(const uschar **, const uschar *, int);
-void spf_response_debug(SPF_response_t *);
-
#define SPF_PROCESS_NORMAL 0
#define SPF_PROCESS_GUESS 1
#define SPF_PROCESS_FALLBACK 2
diff --git a/src/src/readconf.c b/src/src/readconf.c
index a090cfb30..1fe6b7341 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -344,8 +344,8 @@ static optionlist optionlist_config[] = {
{ "spamd_address", opt_stringptr, {&spamd_address} },
#endif
#ifdef SUPPORT_SPF
- { "spf_guess", opt_stringptr, {&spf_guess} },
- { "spf_smtp_comment_template",opt_stringptr, {&spf_smtp_comment_template} },
+ { "spf_guess", opt_module, {US"spf"} },
+ { "spf_smtp_comment_template",opt_module, {US"spf"} },
#endif
{ "split_spool_directory", opt_bool, {&split_spool_directory} },
{ "spool_directory", opt_stringptr, {&spool_directory} },
@@ -1764,6 +1764,8 @@ if (Ustrncmp(name, "not_", 4) == 0)
offset = 4;
}
+sublist:
+
/* Search the list for the given name. A non-existent name, or an option that
is set twice, is a disaster. */
@@ -2443,6 +2445,20 @@ switch (type)
case opt_func:
ol->v.fn(name, s, 0);
break;
+
+ case opt_module:
+ {
+ uschar * errstr;
+ misc_module_info * mi = misc_mod_find(US ol->v.value, &errstr);
+ if (!mi)
+ log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+ "failed to find %s module for %s: %s", US ol->v.value, name, errstr);
+
+debug_printf("hunting for option %s in module %s\n", name, mi->name);
+ oltop = mi->options;
+ last = mi->options_count;
+ goto sublist;
+ }
}
return TRUE;
diff --git a/src/src/receive.c b/src/src/receive.c
index 1ee02c5b7..336b37410 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -1836,7 +1836,7 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
#endif
#ifdef SUPPORT_DMARC
-if (sender_host_address) dmarc_init(); /* initialize libopendmarc */
+if (sender_host_address) dmarc_conn_init(); /* initialize libopendmarc */
#endif
/* In SMTP sessions we may receive several messages in one connection. Before
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 7c2b4a292..f9bd3ece8 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -1682,8 +1682,14 @@ bmi_verdicts = NULL;
#endif
dnslist_domain = dnslist_matched = NULL;
#ifdef SUPPORT_SPF
-spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
-spf_result_guessed = FALSE;
+ {
+ misc_module_info * mi = misc_mod_findonly(US"spf");
+ if (mi)
+ {
+ typedef void (*fn_t)(void);
+ (((fn_t *) mi->functions)[4])(); /* spf_smtp_reset*/
+ }
+ }
#endif
#ifndef DISABLE_DKIM
dkim_cur_signer = dkim_signers =
@@ -4020,8 +4026,22 @@ while (done <= 0)
}
#ifdef SUPPORT_SPF
- /* set up SPF context */
- spf_conn_init(sender_helo_name, sender_host_address);
+ /* If we have an spf module, set up SPF context */
+ {
+ misc_module_info * mi = misc_mod_findonly(US"spf");
+ if (mi)
+ {
+ /* We have hardwired function-call numbers, and also prototypes for the
+ functions. We could do a function name table search for the number
+ but I can't see how to deal with prototypes. Is a K&R non-prototyped
+ function still usable with today's compilers? */
+
+ typedef BOOL (*fn_t)(uschar *, uschar *);
+ fn_t fn = ((fn_t *) mi->functions)[0]; /* spf_conn_init */
+
+ (void) fn(sender_helo_name, sender_host_address);
+ }
+ }
#endif
/* Apply an ACL check if one is defined; afterwards, recheck
diff --git a/src/src/structs.h b/src/src/structs.h
index 9492dbac2..2c8c77c43 100644
--- a/src/src/structs.h
+++ b/src/src/structs.h
@@ -965,4 +965,73 @@ typedef struct qrunner {
BOOL queue_2stage :1;
} qrunner;
+
+/* Types of variable table entry */
+
+enum vtypes {
+ vtype_int, /* value is address of int */
+ vtype_filter_int, /* ditto, but recognized only when filtering */
+ vtype_ino, /* value is address of ino_t (not always an int) */
+ vtype_uid, /* value is address of uid_t (not always an int) */
+ vtype_gid, /* value is address of gid_t (not always an int) */
+ vtype_bool, /* value is address of bool */
+ vtype_stringptr, /* value is address of pointer to string */
+ vtype_msgbody, /* as stringptr, but read when first required */
+ vtype_msgbody_end, /* ditto, the end of the message */
+ vtype_msgheaders, /* the message's headers, processed */
+ vtype_msgheaders_raw, /* the message's headers, unprocessed */
+ vtype_localpart, /* extract local part from string */
+ vtype_domain, /* extract domain from string */
+ vtype_string_func, /* value is string returned by given function */
+ vtype_todbsdin, /* value not used; generate BSD inbox tod */
+ vtype_tode, /* value not used; generate tod in epoch format */
+ vtype_todel, /* value not used; generate tod in epoch/usec format */
+ vtype_todf, /* value not used; generate full tod */
+ vtype_todl, /* value not used; generate log tod */
+ vtype_todlf, /* value not used; generate log file datestamp tod */
+ vtype_todzone, /* value not used; generate time zone only */
+ vtype_todzulu, /* value not used; generate zulu tod */
+ vtype_reply, /* value not used; get reply from headers */
+ vtype_pid, /* value not used; result is pid */
+ vtype_host_lookup, /* value not used; get host name */
+ vtype_load_avg, /* value not used; result is int from os_getloadavg */
+ vtype_pspace, /* partition space; value is T/F for spool/log */
+ vtype_pinodes, /* partition inodes; value is T/F for spool/log */
+ vtype_cert, /* SSL certificate */
+#ifndef DISABLE_DKIM
+ vtype_dkim, /* Lookup of value in DKIM signature */
+#endif
+ vtype_module, /* variable lives in a module; value is module name */
+};
+
+/* Type for main variable table */
+
+typedef struct {
+ const char *name;
+ enum vtypes type;
+ void *value;
+} var_entry;
+
+
+
+/* dynamic-load module info */
+
+typedef struct misc_module_info {
+ struct misc_module_info * next;
+
+ const uschar * name;
+ unsigned dyn_magic;
+ BOOL (*init)(void *); /* arg is the misc_module_info ptr */
+ gstring * (*lib_vers_report)(gstring *); /* underlying library */
+
+ void * options;
+ unsigned options_count;
+ void * functions;
+ unsigned functions_count;
+ void * variables;
+ unsigned variables_count;
+} misc_module_info;
+
+#define MISC_MODULE_MAGIC 0x4d4d4d31 /* MMM1 */
+
/* End of structs.h */
diff --git a/test/runtest b/test/runtest
index 3e10a5ab7..dcf6d76b2 100755
--- a/test/runtest
+++ b/test/runtest
@@ -383,7 +383,7 @@ $date = "\\d{2}-\\w{3}-\\d{4}\\s\\d{2}:\\d{2}:\\d{2}";
# Debug time & pid
-$time_pid = "(?:\\d{2}:\\d{2}:\\d{2}\\s+\\d+\\s)";
+$time_pid = "(?:(?:\\d{2}:\\d{2}:\\d{2}\\s+)?\\d+\\s)";
# Pattern for matching pids at start of stderr lines; initially something
# that won't match.
@@ -1315,11 +1315,14 @@ RESET_AFTER_EXTRA_LINE_READ:
# different libraries will have different numbers (possibly 0) of follow-up
# lines, indenting with more data
if (/^$time_pid?Library version:/) {
- while (1) {
+ $_ = <IN>;
+ if (/^$time_pid?\s/) {
$_ = <IN>;
- next if /^$time_pid?\s/;
- goto RESET_AFTER_EXTRA_LINE_READ;
+ if (/^$time_pid?\s/) {
+ $_ = <IN>;
+ }
}
+ goto RESET_AFTER_EXTRA_LINE_READ;
}
# drop other build-time controls emitted for debugging
@@ -1556,8 +1559,10 @@ RESET_AFTER_EXTRA_LINE_READ:
next if /^DKIM >> Body data for hash, canonicalized/;
# Not all platforms build with SPF enabled
- next if /(^spf_conn_init|^SPF_dns_exim_new|spf_compile\.c)/;
+ next if /(^$time_pid?spf_conn_init|spf_compile\.c)/;
next if /try option spf_smtp_comment_template$/;
+ next if /loading module 'spf'$/;
+ next if /^Loaded "spf"$/;
# Not all platforms have sendfile support
next if /^cannot use sendfile for body: no support$/;
diff --git a/test/stderr/2610 b/test/stderr/2610
index c762e0340..564fb7b11 100644
--- a/test/stderr/2610
+++ b/test/stderr/2610
@@ -304,8 +304,6 @@ close MYSQL connection: 127.0.0.1:PORT_N/test/root
01:01:01 p1235 sender_fullhost = (test) [10.0.0.0]
01:01:01 p1235 sender_rcvhost = [10.0.0.0] (helo=test)
01:01:01 p1235 set_process_info: pppp handling incoming connection from (test) [10.0.0.0]
-01:01:01 p1235 spf_conn_init: test 10.0.0.0
-01:01:01 p1235 SPF_dns_exim_new
01:01:01 p1235 try option acl_smtp_helo
01:01:01 p1235 SMTP>> 250 myhost.test.ex Hello test [10.0.0.0]
01:01:01 p1235 SMTP<< mail from:<a@b>
--
## subscription configuration (requires account):
##
https://lists.exim.org/mailman3/postorius/lists/exim-cvs.lists.exim.org/
## unsubscribe (doesn't require an account):
## exim-cvs-unsubscribe@???
## Exim details at
http://www.exim.org/
## Please use the Wiki with this list -
http://wiki.exim.org/