From 7dc8d146a675f52b441310e731314d86c66b2114 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Wed, 28 Aug 2024 22:01:24 +0100 Subject: [PATCH] dmarc dynamic module --- doc/doc-docbook/spec.xfpt | 14 +++- src/OS/Makefile-Base | 2 - src/scripts/Configure-Makefile | 12 +++- src/scripts/MakeLinks | 4 +- src/src/EDITME | 7 ++ src/src/acl.c | 51 +++++++++++-- src/src/arc.c | 2 +- src/src/daemon.c | 3 - src/src/drtables.c | 56 +++++++++++++-- src/src/exim.c | 6 +- src/src/exim.h | 2 +- src/src/expand.c | 21 ++++-- src/src/functions.h | 8 ++- src/src/globals.c | 9 --- src/src/globals.h | 9 --- src/src/lookups/spf.c | 6 +- src/src/miscmods/Makefile | 3 +- src/src/{ => miscmods}/dmarc.c | 128 ++++++++++++++++++++++++++------- src/src/{ => miscmods}/dmarc.h | 13 +--- src/src/miscmods/spf.c | 27 +++---- src/src/moan.c | 3 +- src/src/readconf.c | 21 +++--- src/src/receive.c | 16 +++-- src/src/smtp_in.c | 35 ++------- src/src/structs.h | 3 + test/runtest | 4 +- test/stderr/0437 | 1 + 27 files changed, 312 insertions(+), 154 deletions(-) rename src/src/{ => miscmods}/dmarc.c (87%) rename src/src/{ => miscmods}/dmarc.h (84%) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 428cbc079..90fb6dd47 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -32055,12 +32055,20 @@ This control turns off DKIM verification processing entirely. For details on the operation and configuration of DKIM, see section &<>&. -.vitem &*control&~=&~dmarc_disable_verify*& +.vitem &*control&~=&~enforce_sync*& &&& + &*control&~=&~no_enforce_sync*& + +.vitem &*control&~=&~dmarc_disable_verify*& &&& + &*control&~=&~dmarc_enable_forensic*& .cindex "disable DMARC verify" -.cindex "DMARC" "disable verify" -This control turns off DMARC verification processing entirely. For details on +.cindex DMARC "disable verify" +.cindex DMARC controls +.cindex DMARC "forensic mails" +These control affect DMARC processing. For details on the operation and configuration of DMARC, see section &<>&. +The &"disable"& turns off DMARC verification processing entirely. + .vitem &*control&~=&~dscp/*&<&'value'&> .cindex "&ACL;" "setting DSCP value" diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index caae1e536..43cec361c 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -499,7 +499,6 @@ OBJ_EXPERIMENTAL = arc.o \ bmi_spam.o \ dane.o \ dcc.o \ - dmarc.o \ imap_utf7.o \ utf8.o \ xclient.o @@ -901,7 +900,6 @@ arc.o: $(HDRS) pdkim/pdkim.h arc.c bmi_spam.o: $(HDRS) bmi_spam.c 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 utf8.o: $(HDRS) utf8.c xclient.o: $(HDRS) xclient.c diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index af0de26e4..2b8a9bcb5 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -29,6 +29,16 @@ fi archtype=`../scripts/arch-type` || exit 1 +# Linux now whines about egrep, saying "use grep -E". +# Solarix doesn't support -E on grep. Thanks so much for +# going non-back-compatible, Linux. +if echo 1 | grep -E 1 >/dev/null; then + egrep="grep -E" +else + egrep="egrep" +fi + + # Now test for either the non-existence of Makefile, or for any of its # components being newer. Note that the "newer" script gives the right # answer (for our purposes) when the first file is non-existent. @@ -311,7 +321,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 + miscmods SUPPORT SPF DMARC 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 09d18b63c..e45097243 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -94,7 +94,7 @@ d="miscmods" mkdir $d cd $d # Makefile is generated -for f in spf.c spf.h +for f in dmarc.c dmarc.h spf.c spf.h do ln -s ../../src/$d/$f $f done @@ -140,7 +140,7 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \ string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \ tls-gnu.c tls-openssl.c \ tod.c transport.c tree.c verify.c version.c xtextencode.c \ - dkim.c dkim.h dkim_transport.c dmarc.c dmarc.h \ + dkim.c dkim.h dkim_transport.c \ valgrind.h memcheck.h \ macro_predef.c macro_predef.h do diff --git a/src/src/EDITME b/src/src/EDITME index e507ab3cd..35c497697 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -642,9 +642,16 @@ DISABLE_MAL_MKS=yes # Uncomment the following line to add DMARC checking capability, implemented # using libopendmarc libraries. You must have SPF and DKIM support enabled also. +# +# 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_DMARC_{INCLUDE,LIBS}. +# # SUPPORT_DMARC=yes # CFLAGS += -I/usr/local/include # LDFLAGS += -lopendmarc +# # Uncomment the following if you need to change the default. You can # override it at runtime (main config option dmarc_tld_file) # DMARC_TLD_FILE=/etc/exim/opendmarc.tlds diff --git a/src/src/acl.c b/src/src/acl.c index e285da65c..30fc09174 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -218,7 +218,11 @@ static condition_def conditions[] = { }, #endif #ifdef SUPPORT_DMARC - [ACLC_DMARC_STATUS] = { US"dmarc_status", ACD_EXP, + [ACLC_DMARC_STATUS] = { US"dmarc_status", +# if SUPPORT_DMARC==2 + ACD_LOAD | +# endif + ACD_EXP, PERMITTED(ACL_BIT_DATA) }, #endif @@ -393,6 +397,9 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) #ifndef MACRO_PREDEF +/* These tables support loading of dynamic modules triggered by an ACL +condition use, spotted during readconf. See acl_read(). */ + # ifdef LOOKUP_MODULE_DIR typedef struct condition_module { const uschar * mod_name; /* module for the givien conditions */ @@ -403,11 +410,17 @@ typedef struct condition_module { # if SUPPORT_SPF==2 static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 }; # endif +# if SUPPORT_DMARC==2 +static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 }; +# endif static condition_module condition_modules[] = { # if SUPPORT_SPF==2 {.mod_name = US"spf", .conditions = spf_condx}, # endif +# if SUPPORT_SPF==2 + {.mod_name = US"dmarc", .conditions = dmarc_condx}, +# endif }; # endif @@ -3942,14 +3955,36 @@ for (; cb; cb = cb->next) #ifdef SUPPORT_DMARC case ACLC_DMARC_STATUS: + /* See comment on ACLC_SPF wrt. coding issues */ + { + misc_module_info * mi = misc_mod_find(US"dmarc", &log_message); + typedef uschar * (*efn_t)(int); + uschar * expanded_query; + +debug_printf("%s %d\n", __FUNCTION__, __LINE__); + if (!mi) + { rc = DEFER; break; } /* shouldn't happen */ + +debug_printf("%s %d: mi %p\n", __FUNCTION__, __LINE__, mi); if (!f.dmarc_has_been_checked) - dmarc_process(); - f.dmarc_has_been_checked = TRUE; + { + typedef int (*pfn_t)(void); + (void) (((pfn_t *) mi->functions)[0]) (); /* dmarc_process */ + f.dmarc_has_been_checked = TRUE; + } +debug_printf("%s %d\n", __FUNCTION__, __LINE__); /* used long way of dmarc_exim_expand_query() in case we need more view into the process in the future. */ - rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS), + + /*XXX is this call used with any other arg? */ + expanded_query = (((efn_t *) mi->functions)[1]) (DMARC_VERIFY_STATUS); + +debug_printf("%s %d\n", __FUNCTION__, __LINE__); + rc = match_isinlist(expanded_query, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); +debug_printf("%s %d\n", __FUNCTION__, __LINE__); + } break; #endif @@ -4185,8 +4220,10 @@ for (; cb; cb = cb->next) #ifdef SUPPORT_SPF case ACLC_SPF: case ACLC_SPF_GUESS: - /* Hardwire the offset of the function in the module functions table - for now. Work out a more general mech later. */ + /* 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? */ { misc_module_info * mi = misc_mod_find(US"spf", &log_message); typedef int (*fn_t)(const uschar **, const uschar *, int); @@ -4195,7 +4232,7 @@ for (; cb; cb = cb->next) if (!mi) { rc = DEFER; break; } /* shouldn't happen */ - fn = ((fn_t *) mi->functions)[1]; + fn = ((fn_t *) mi->functions)[0]; /* spf_process() */ rc = fn(&arg, sender_address, cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS); diff --git a/src/src/arc.c b/src/src/arc.c index 48f69a8cf..2dcbf2efb 100644 --- a/src/src/arc.c +++ b/src/src/arc.c @@ -19,7 +19,7 @@ # include "pdkim/signing.h" # ifdef SUPPORT_DMARC -# include "dmarc.h" +# include "miscmods/dmarc.h" # endif extern pdkim_ctx * dkim_verify_ctx; diff --git a/src/src/daemon.c b/src/src/daemon.c index 49bf74a11..456c586da 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -2578,9 +2578,6 @@ smtp_deliver_init(); /* Used for callouts */ #ifdef WITH_CONTENT_SCAN malware_init(); #endif -#ifdef SUPPORT_DMARC -dmarc_init(); -#endif #ifndef DISABLE_TLS tls_daemon_init(); #endif diff --git a/src/src/drtables.c b/src/src/drtables.c index 35a376dd1..9ed55e29a 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -432,8 +432,8 @@ 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)); +DEBUG(D_any) if (mi->lib_vers_report) + debug_printf_indent("%Y", mi->lib_vers_report(NULL)); mi->next = misc_module_list; misc_module_list = mi; @@ -459,8 +459,10 @@ 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); + fprintf(stderr, "%s does not appear to be a '%s' module (%s)\n", + name, name, errormsg); + log_write(0, LOG_MAIN|LOG_PANIC, + "%s does not contain the expected module info symbol (%s)", name, errormsg); dlclose(dl); return NULL; } @@ -491,6 +493,7 @@ 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; +return NULL; } /* Find a "misc" module, possibly already loaded, by name. */ @@ -508,6 +511,42 @@ return NULL; } +/* For any "misc" module having a connection-init routine, call it. */ + +int +misc_mod_conn_init(const uschar * sender_helo_name, + const uschar * sender_host_address) +{ +for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next) + if (mi->conn_init) + if ((mi->conn_init) (sender_helo_name, sender_host_address) != OK) + return FAIL; +return OK; +} + +/* Ditto, smtp-reset */ + +void +misc_mod_smtp_reset(void) +{ +for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next) + if (mi->smtp_reset) + (mi->smtp_reset)(); +} + +/* Ditto, msg-init */ + +int +misc_mod_msg_init(void) +{ +for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next) + if (mi->msg_init) + if ((mi->msg_init)() != OK) + return FAIL; +return OK; +} + + @@ -658,6 +697,9 @@ DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules); } +#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2 +extern misc_module_info dmarc_module_info; +#endif #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2 extern misc_module_info spf_module_info; #endif @@ -667,9 +709,15 @@ init_misc_mod_list(void) { static BOOL onetime = FALSE; if (onetime) return; + #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2 +/* dmarc depends on spf so this add must go first, for the dmarc-static case */ misc_mod_add(&spf_module_info); #endif +#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2 +misc_mod_add(&dmarc_module_info); +#endif + onetime = TRUE; } diff --git a/src/src/exim.c b/src/src/exim.c index ecc25d6bc..5ad54ffc1 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -1395,9 +1395,9 @@ DEBUG(D_any) #ifdef SUPPORT_I18N g = utf8_version_report(g); #endif -#ifdef SUPPORT_DMARC - g = dmarc_version_report(g); -#endif + +/*XXX do we need a "show misc-mods version-report" ? +Currently they are output in misc_mod_add() */ show_string(is_stdout, g); g = NULL; diff --git a/src/src/exim.h b/src/src/exim.h index 470adb351..284748c5d 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -549,7 +549,7 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. # include "dkim.h" #endif #ifdef SUPPORT_DMARC -# include "dmarc.h" +# include "miscmods/dmarc.h" # include #endif diff --git a/src/src/expand.c b/src/src/expand.c index b3a1575a7..a6b05bd87 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -513,10 +513,10 @@ static var_entry var_table[] = { { "dkim_verify_status", vtype_stringptr, &dkim_verify_status }, #endif #ifdef SUPPORT_DMARC - { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, - { "dmarc_status", vtype_stringptr, &dmarc_status }, - { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, - { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, + { "dmarc_domain_policy", vtype_module, US"dmarc" }, + { "dmarc_status", vtype_module, US"dmarc" }, + { "dmarc_status_text", vtype_module, US"dmarc" }, + { "dmarc_used_domain", vtype_module, US"dmarc" }, #endif { "dnslist_domain", vtype_stringptr, &dnslist_domain }, { "dnslist_matched", vtype_stringptr, &dnslist_matched }, @@ -4888,7 +4888,7 @@ while (*s) if (mi) { typedef gstring * (*fn_t)(gstring *); - fn_t fn = ((fn_t *) mi->functions)[2]; /* authres_spf */ + fn_t fn = ((fn_t *) mi->functions)[1]; /* authres_spf */ yield = fn(yield); } } @@ -4897,7 +4897,16 @@ while (*s) yield = authres_dkim(yield); #endif #ifdef SUPPORT_DMARC - yield = authres_dmarc(yield); + { + misc_module_info * mi = misc_mod_findonly(US"dmarc"); + if (mi) + { + /*XXX is authres common enough to be generic? */ + typedef gstring * (*fn_t)(gstring *); + fn_t fn = ((fn_t *) mi->functions)[2]; /* authres_dmarc*/ + yield = fn(yield); + } + } #endif #ifdef EXPERIMENTAL_ARC yield = authres_arc(yield); diff --git a/src/src/functions.h b/src/src/functions.h index aaec6461f..3a980318f 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -147,9 +147,6 @@ extern gstring *authres_arc(gstring *); #ifndef DISABLE_DKIM extern gstring *authres_dkim(gstring *); #endif -#ifdef SUPPORT_DMARC -extern gstring *authres_dmarc(gstring *); -#endif extern gstring *authres_smtpauth(gstring *); extern uschar *b64encode(const uschar *, int); @@ -377,8 +374,13 @@ extern ssize_t mime_decode_base64(FILE *, FILE *, uschar *); extern int mime_regex(const uschar **, BOOL); extern void mime_set_anomaly(int); #endif + +extern int misc_mod_conn_init(const uschar *, const uschar *); extern misc_module_info * misc_mod_find(const uschar * modname, uschar **); extern misc_module_info * misc_mod_findonly(const uschar * modname); +extern int misc_mod_msg_init(void); +extern void misc_mod_smtp_reset(void); + 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 f2287d41c..cfa75f1d7 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -882,15 +882,6 @@ uschar *dkim_verify_signers = US"$dkim_signers"; uschar *dkim_verify_status = NULL; uschar *dkim_verify_reason = NULL; #endif -#ifdef SUPPORT_DMARC -uschar *dmarc_domain_policy = NULL; -uschar *dmarc_forensic_sender = NULL; -uschar *dmarc_history_file = NULL; -uschar *dmarc_status = NULL; -uschar *dmarc_status_text = NULL; -uschar *dmarc_tld_file = NULL; -uschar *dmarc_used_domain = NULL; -#endif uschar *dns_again_means_nonexist = NULL; int dns_csa_search_limit = 5; diff --git a/src/src/globals.h b/src/src/globals.h index 9b30e502c..8173d771e 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -563,15 +563,6 @@ extern uschar *dkim_verify_signers; /* Colon-separated list of domains for ea extern uschar *dkim_verify_status; /* result for this signature */ extern uschar *dkim_verify_reason; /* result for this signature */ #endif -#ifdef SUPPORT_DMARC -extern uschar *dmarc_domain_policy; /* Expansion for declared policy of used domain */ -extern uschar *dmarc_forensic_sender; /* Set sender address for forensic reports */ -extern uschar *dmarc_history_file; /* Expansion variable, file to store dmarc results */ -extern uschar *dmarc_status; /* Expansion variable, one word value */ -extern uschar *dmarc_status_text; /* Expansion variable, human readable value */ -extern uschar *dmarc_tld_file; /* Mozilla TLDs text file */ -extern uschar *dmarc_used_domain; /* Expansion variable, domain libopendmarc chose for DMARC policy lookup */ -#endif extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */ extern int dns_csa_search_limit; /* How deep to search for CSA SRV records */ diff --git a/src/src/lookups/spf.c b/src/src/lookups/spf.c index 4e0824911..a06f9117d 100644 --- a/src/src/lookups/spf.c +++ b/src/src/lookups/spf.c @@ -39,7 +39,7 @@ misc_module_info * mi = misc_mod_find(US"spf", errmsg); if (mi) { typedef void * (*fn_t)(const uschar *, uschar **); - return (((fn_t *) mi->functions)[5]) (filename, errmsg); + return (((fn_t *) mi->functions)[3]) (filename, errmsg); } return NULL; } @@ -52,7 +52,7 @@ misc_module_info * mi = misc_mod_find(US"spf", NULL); if (mi) { typedef void (*fn_t)(void *); - return (((fn_t *) mi->functions)[6]) (handle); + return (((fn_t *) mi->functions)[4]) (handle); } } @@ -67,7 +67,7 @@ if (mi) { 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, + return (((fn_t *) mi->functions)[5]) (handle, filename, keystring, key_len, result, errmsg, do_cache, opts); } return FAIL; diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index 59bf29836..d20b7a9f1 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -26,6 +26,7 @@ miscmods.a: $(OBJ) .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 +spf.o spf.so: $(HDRS) spf.h spf.c +dmarc.o dmarc.so: $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c # End diff --git a/src/src/dmarc.c b/src/src/miscmods/dmarc.c similarity index 87% rename from src/src/dmarc.c rename to src/src/miscmods/dmarc.c index 664daa737..0a97bf6b7 100644 --- a/src/src/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -12,7 +12,7 @@ /* Code for calling dmarc checks via libopendmarc. Called from acl.c. */ -#include "exim.h" +#include "../exim.h" #ifdef SUPPORT_DMARC # if !defined SUPPORT_SPF # error SPF must also be enabled for DMARC @@ -20,9 +20,9 @@ # error DKIM must also be enabled for DMARC # else -# include "functions.h" +# include "../functions.h" # include "dmarc.h" -# include "pdkim/pdkim.h" +# include "../pdkim/pdkim.h" OPENDMARC_LIB_T dmarc_ctx; DMARC_POLICY_T *dmarc_pctx = NULL; @@ -55,8 +55,22 @@ static dmarc_exim_p dmarc_policy_description[] = { }; -int -dmarc_init(void) +/* $variables */ +uschar * dmarc_domain_policy = NULL; /* Declared policy of used domain */ +uschar * dmarc_status = NULL; /* One word value */ +uschar * dmarc_status_text = NULL; /* Human readable value */ +uschar * dmarc_used_domain = NULL; /* Domain libopendmarc chose for DMARC policy lookup */ + +/* options */ +uschar * dmarc_forensic_sender = NULL; /* Set sender address for forensic reports */ +uschar * dmarc_history_file = NULL; /* File to store dmarc results */ +uschar * dmarc_tld_file = NULL; /* Mozilla TLDs text file */ + + +/* One-time initialisation for dmarc. Ensure the spf module is available. */ + +static BOOL +dmarc_init(void *) { uschar * errstr; if (!(spf_mod_info = misc_mod_find(US"spf", &errstr))) @@ -65,12 +79,14 @@ if (!(spf_mod_info = misc_mod_find(US"spf", &errstr))) return TRUE; } -gstring * +static gstring * dmarc_version_report(gstring * g) { return string_fmt_append(g, "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); + (OPENDMARC_LIB_VERSION & 0xff000000) >> 24, + (OPENDMARC_LIB_VERSION & 0x00ff0000) >> 16, + (OPENDMARC_LIB_VERSION & 0x0000ff00) >> 8, + (OPENDMARC_LIB_VERSION & 0x000000ff)); } @@ -98,12 +114,14 @@ eb->next = NULL; return eblock; } -/* dmarc_conn_init sets up a context that can be re-used for several +/* dmarc_msg_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) */ +same host with the same HELO string). +However, we seem to only use it for one; we destroy some sort of context +at the tail end of dmarc_process(). */ -int -dmarc_conn_init(void) +static int +dmarc_msg_init() { int *netmask = NULL; /* Ignored */ int is_ipv6 = 0; @@ -167,11 +185,19 @@ return OK; } +static void +dmarc_smtp_reset(void) +{ +dmarc_domain_policy = dmarc_status = dmarc_status_text = +dmarc_used_domain = NULL; +} + + /* dmarc_store_data stores the header data so that subsequent dmarc_process can access the data. Called after the entire message has been received, with the From: header. */ -int +static int dmarc_store_data(header_line * hdr) { /* No debug output because would change every test debug output */ @@ -362,7 +388,7 @@ context (if any), retrieves the result, sets up expansion strings and evaluates the condition outcome. Called for the first ACL dmarc= condition. */ -int +static int dmarc_process(void) { int sr, origin; /* used in SPF section */ @@ -433,10 +459,9 @@ if (!dmarc_abort && !sender_host_authenticated) spf_sender_domain = expand_string(US"$sender_address_domain"); { - 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_mod_info) + spf_response_p = ((fn_t *) spf_mod_info->functions)[2](); /* spf_get_response */ } if (!spf_response_p) @@ -673,27 +698,27 @@ if (!f.dmarc_disable_verify) return OK; } -uschar * -dmarc_exim_expand_query(int what) +static uschar * +dmarc_exim_expand_defaults(int what) { -if (f.dmarc_disable_verify || !dmarc_pctx) - return dmarc_exim_expand_defaults(what); - if (what == DMARC_VERIFY_STATUS) - return dmarc_status; + return f.dmarc_disable_verify ? US"off" : US"none"; return US""; } -uschar * -dmarc_exim_expand_defaults(int what) +static uschar * +dmarc_exim_expand_query(int what) { +if (f.dmarc_disable_verify || !dmarc_pctx) + return dmarc_exim_expand_defaults(what); + if (what == DMARC_VERIFY_STATUS) - return f.dmarc_disable_verify ? US"off" : US"none"; + return dmarc_status; return US""; } -gstring * +static gstring * authres_dmarc(gstring * g) { if (f.dmarc_has_been_checked) @@ -711,6 +736,55 @@ else return g; } +/******************************************************************************/ +/* Module API */ + +static optionlist dmarc_options[] = { + { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} }, + { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} }, + { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} }, +}; + +static void * dmarc_functions[] = { + dmarc_process, + dmarc_exim_expand_query, + authres_dmarc, + dmarc_store_data, +}; + +/* dmarc_forensic_sender is provided for visibility of the the option setting +by moan_send_message. We do not document it as a config-visible $variable. +We could provide it via a function but there's little advantage. */ + +static var_entry dmarc_variables[] = { + { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, + { "dmarc_forensic_sender", vtype_stringptr, &dmarc_forensic_sender }, + { "dmarc_status", vtype_stringptr, &dmarc_status }, + { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, + { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, +}; + +misc_module_info dmarc_module_info = +{ + .name = US"dmarc", +# if SUPPORT_SPF==2 + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .init = dmarc_init, + .lib_vers_report = dmarc_version_report, + .smtp_reset = dmarc_smtp_reset, + .msg_init = dmarc_msg_init, + + .options = dmarc_options, + .options_count = nelem(dmarc_options), + + .functions = dmarc_functions, + .functions_count = nelem(dmarc_functions), + + .variables = dmarc_variables, + .variables_count = nelem(dmarc_variables), +}; + # endif /* SUPPORT_SPF */ #endif /* SUPPORT_DMARC */ /* vi: aw ai sw=2 diff --git a/src/src/dmarc.h b/src/src/miscmods/dmarc.h similarity index 84% rename from src/src/dmarc.h rename to src/src/miscmods/dmarc.h index dcf289f2d..c1cafd0d1 100644 --- a/src/src/dmarc.h +++ b/src/src/miscmods/dmarc.h @@ -13,20 +13,11 @@ #ifdef SUPPORT_DMARC -# include "opendmarc/dmarc.h" +# include # ifdef SUPPORT_SPF -# include "spf2/spf.h" +# include # endif /* SUPPORT_SPF */ -/* 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); -uschar *dmarc_exim_expand_defaults(int); - #define DMARC_VERIFY_STATUS 1 #define DMARC_HIST_OK 1 diff --git a/src/src/miscmods/spf.c b/src/src/miscmods/spf.c index a7b6c6a8d..f28fd0cbf 100644 --- a/src/src/miscmods/spf.c +++ b/src/src/miscmods/spf.c @@ -287,29 +287,30 @@ return TRUE; messages on the same SMTP connection (that come from the same host with the same HELO string). -Return: Boolean success +Return: OK/FAIL */ -static BOOL -spf_conn_init(uschar * spf_helo_domain, uschar * spf_remote_addr) +static int +spf_conn_init(const uschar * spf_helo_domain, const 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(NULL)) return FALSE; +if (!spf_server && !spf_init(NULL)) + return FAIL; if (SPF_server_set_rec_dom(spf_server, CS primary_hostname)) { DEBUG(D_receive) debug_printf("spf: SPF_server_set_rec_dom(\"%s\") failed.\n", primary_hostname); spf_server = NULL; - return FALSE; + return FAIL; } spf_request = SPF_request_new(spf_server); -if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr) - && SPF_request_set_ipv6_str(spf_request, CS spf_remote_addr) +if ( SPF_request_set_ipv4_str(spf_request, CCS spf_remote_addr) + && SPF_request_set_ipv6_str(spf_request, CCS spf_remote_addr) ) { DEBUG(D_receive) @@ -317,19 +318,19 @@ if ( SPF_request_set_ipv4_str(spf_request, CS spf_remote_addr) "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr); spf_server = NULL; spf_request = NULL; - return FALSE; + return FAIL; } -if (SPF_request_set_helo_dom(spf_request, CS spf_helo_domain)) +if (SPF_request_set_helo_dom(spf_request, CCS spf_helo_domain)) { DEBUG(D_receive) debug_printf("spf: SPF_set_helo_dom(\"%s\") failed.\n", spf_helo_domain); spf_server = NULL; spf_request = NULL; - return FALSE; + return FAIL; } -return TRUE; +return OK; } static void @@ -564,11 +565,9 @@ static optionlist spf_options[] = { }; 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, @@ -592,6 +591,8 @@ misc_module_info spf_module_info = # endif .init = spf_init, .lib_vers_report = spf_lib_version_report, + .conn_init = spf_conn_init, + .smtp_reset = spf_smtp_reset, .options = spf_options, .options_count = nelem(spf_options), diff --git a/src/src/moan.c b/src/src/moan.c index 19d29190b..08258f5d1 100644 --- a/src/src/moan.c +++ b/src/src/moan.c @@ -178,8 +178,7 @@ header From: and grab the address from that for the envelope FROM. */ GET_OPTION("dmarc_forensic_sender"); if ( ident == ERRMESS_DMARC_FORENSIC - && dmarc_forensic_sender - && (s = expand_string(dmarc_forensic_sender)) + && (s = expand_string(US"$dmarc_forensic_sender")) /* a hack... */ && *s && (s2 = expand_string(string_sprintf("${address:%s}", s))) && *s2 diff --git a/src/src/readconf.c b/src/src/readconf.c index 1fe6b7341..ae7073229 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -129,9 +129,9 @@ static optionlist optionlist_config[] = { { "dkim_verify_signers", opt_stringptr, {&dkim_verify_signers} }, #endif #ifdef SUPPORT_DMARC - { "dmarc_forensic_sender", opt_stringptr, {&dmarc_forensic_sender} }, - { "dmarc_history_file", opt_stringptr, {&dmarc_history_file} }, - { "dmarc_tld_file", opt_stringptr, {&dmarc_tld_file} }, + { "dmarc_forensic_sender", opt_module, {US"dmarc"} }, + { "dmarc_history_file", opt_module, {US"dmarc"} }, + { "dmarc_tld_file", opt_module, {US"dmarc"} }, #endif { "dns_again_means_nonexist", opt_stringptr, {&dns_again_means_nonexist} }, { "dns_check_names_pattern", opt_stringptr, {&check_dns_names_pattern} }, @@ -1707,7 +1707,7 @@ static BOOL readconf_handle_option(uschar *buffer, optionlist *oltop, int last, void *data_block, uschar *unknown_txt) { -int ptr = 0; +int ptr; int offset = 0; int count, type, value; int issecure = 0; @@ -1721,11 +1721,16 @@ rmark reset_point; int intbase = 0; uschar *inttype = US""; uschar *sptr; -const uschar * s = buffer; +const uschar * s; uschar **str_target; uschar name[EXIM_DRIVERNAME_MAX]; uschar name2[EXIM_DRIVERNAME_MAX]; +sublist: + +s = buffer; +ptr = 0; + /* There may be leading spaces; thereafter, we expect an option name starting with a letter. */ @@ -1764,8 +1769,6 @@ 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. */ @@ -2454,7 +2457,6 @@ switch (type) 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; @@ -3516,6 +3518,9 @@ if (!*spool_directory) /* Expand the spool directory name; it may, for example, contain the primary host name. Same comment about failure. */ +DEBUG(D_any) if (Ustrchr(spool_directory, '$')) + debug_printf("Expanding spool_directory option\n"); + if (!(s = expand_string(spool_directory))) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand spool_directory " "\"%s\": %s", spool_directory, expand_string_message); diff --git a/src/src/receive.c b/src/src/receive.c index 336b37410..37b152f48 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -17,7 +17,7 @@ extern int dcc_ok; #endif #ifdef SUPPORT_DMARC -# include "dmarc.h" +# include "miscmods/dmarc.h" #endif /************************************************* @@ -1835,9 +1835,8 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify) dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); #endif -#ifdef SUPPORT_DMARC -if (sender_host_address) dmarc_conn_init(); /* initialize libopendmarc */ -#endif +if (misc_mod_msg_init() != OK) + goto TIDYUP; /* In SMTP sessions we may receive several messages in one connection. Before each subsequent one, we wait for the clock to tick at the level of message-id @@ -3627,7 +3626,14 @@ else #endif /* WITH_CONTENT_SCAN */ #ifdef SUPPORT_DMARC - dmarc_store_data(dmarc_from_header); + { + misc_module_info * mi = misc_mod_findonly(US"dmarc"); + if (mi) + { + typedef int (*fn_t)(header_line *); + (((fn_t *) mi->functions)[3]) (dmarc_from_header); + } + } #endif #ifndef DISABLE_PRDR diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index f9bd3ece8..adf6c59cb 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1681,16 +1681,6 @@ bmi_run = 0; bmi_verdicts = NULL; #endif dnslist_domain = dnslist_matched = NULL; -#ifdef SUPPORT_SPF - { - 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 = dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL; @@ -1701,8 +1691,6 @@ dkim_key_length = 0; #endif #ifdef SUPPORT_DMARC f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE; -dmarc_domain_policy = dmarc_status = dmarc_status_text = -dmarc_used_domain = NULL; #endif #ifdef EXPERIMENTAL_ARC arc_state = arc_state_reason = NULL; @@ -1742,6 +1730,7 @@ while (acl_warn_logged) store_free(this); } +misc_mod_smtp_reset(); message_tidyup(); store_reset(reset_point); @@ -4025,24 +4014,14 @@ while (done <= 0) } } -#ifdef SUPPORT_SPF - /* If we have an spf module, set up SPF context */ - { - misc_module_info * mi = misc_mod_findonly(US"spf"); - if (mi) + /* For any misc-module having a connection-init routine, call it. */ + + if (misc_mod_conn_init(sender_helo_name, sender_host_address) != OK) { - /* 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); + DEBUG(D_receive) debug_printf("A module conn-init routine failed\n"); + done = 1; + break; } - } -#endif /* Apply an ACL check if one is defined; afterwards, recheck synchronization in case the client started sending in a delay. */ diff --git a/src/src/structs.h b/src/src/structs.h index 2c8c77c43..46abac728 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -1023,6 +1023,9 @@ typedef struct misc_module_info { unsigned dyn_magic; BOOL (*init)(void *); /* arg is the misc_module_info ptr */ gstring * (*lib_vers_report)(gstring *); /* underlying library */ + int (*conn_init)(const uschar *, const uschar *); + void (*smtp_reset)(void); + int (*msg_init)(void); void * options; unsigned options_count; diff --git a/test/runtest b/test/runtest index dcf6d76b2..ae227810c 100755 --- a/test/runtest +++ b/test/runtest @@ -1561,8 +1561,8 @@ RESET_AFTER_EXTRA_LINE_READ: # Not all platforms build with SPF enabled 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"$/; + next if /loading module '(?:dmarc|spf)'$/; + next if /^$time_pid?Loaded "(?:dmarc|spf)"$/; # Not all platforms have sendfile support next if /^cannot use sendfile for body: no support$/; diff --git a/test/stderr/0437 b/test/stderr/0437 index 29241cfd3..514a770f8 100644 --- a/test/stderr/0437 +++ b/test/stderr/0437 @@ -1,5 +1,6 @@ Exim version x.yz .... Hints DB: +Expanding spool_directory option search_open: lsearch "TESTSUITE/aux-fixed/0437.ls" search_find: file="TESTSUITE/aux-fixed/0437.ls" key="spool" partial=-1 affix=NULL starflags=0 opts=NULL -- 2.30.2