From: Jeremy Harris Date: Tue, 27 Aug 2024 23:16:43 +0000 (+0100) Subject: spf dynamic module X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/cccb2c06bbb96fa98edf6867f0eba4059aa757a0 spf dynamic module --- 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 #include -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/miscmods/spf.c b/src/src/miscmods/spf.c new file mode 100644 index 000000000..a7b6c6a8d --- /dev/null +++ b/src/src/miscmods/spf.c @@ -0,0 +1,606 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* SPF support. + Copyright (c) The Exim Maintainers 2015 - 2024 + Copyright (c) Tom Kistner 2004 - 2014 + License: GPL + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +/* Code for calling spf checks via libspf-alt. Called from acl.c. */ + +#include "../exim.h" +#ifdef SUPPORT_SPF + +/* must be kept in numeric order */ +static spf_result_id spf_result_id_list[] = { + /* name value */ + { US"invalid", 0}, + { US"neutral", 1 }, + { US"pass", 2 }, + { US"fail", 3 }, + { US"softfail", 4 }, + { US"none", 5 }, + { US"temperror", 6 }, /* RFC 4408 defined */ + { US"permerror", 7 } /* RFC 4408 defined */ +}; + +SPF_server_t *spf_server = NULL; +SPF_request_t *spf_request = NULL; +SPF_response_t *spf_response = NULL; +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; + + + + +static gstring * +spf_lib_version_report(gstring * g) +{ +int maj, min, patch; + +SPF_get_lib_version(&maj, &min, &patch); +g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n", + SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH); +g = string_fmt_append(g, " Runtime: %d.%d.%d\n", + maj, min, patch); +return g; +} + + + +static SPF_dns_rr_t * +SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server, + const char *domain, ns_type rr_type, int should_cache) +{ +dns_answer * dnsa = store_get_dns_answer(); +dns_scan dnss; +SPF_dns_rr_t * spfrr; +unsigned found = 0; + +SPF_dns_rr_t srr = { + .domain = CS domain, /* query information */ + .domain_buf_len = 0, + .rr_type = rr_type, + + .rr_buf_len = 0, /* answer information */ + .rr_buf_num = 0, /* no free of s */ + .utc_ttl = 0, + + .hook = NULL, /* misc information */ + .source = spf_dns_server +}; + +DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain); + +/* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by +RFC 6686/7208 years ago. see bug #1294 */ + +if (rr_type == T_SPF) + { + HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n"); + srr.herrno = NO_DATA; + SPF_dns_rr_dup(&spfrr, &srr); + store_free_dns_answer(dnsa); + return spfrr; + } + +switch (dns_lookup(dnsa, US domain, rr_type, NULL)) + { + case DNS_AGAIN: srr.herrno = TRY_AGAIN; break; + case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break; + case DNS_NODATA: srr.herrno = NO_DATA; break; + case DNS_FAIL: + default: srr.herrno = NO_RECOVERY; break; + case DNS_SUCCEED: + srr.herrno = NETDB_SUCCESS; + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + /* Need to alloc space for all records, so no early-out */ + if (rr->type == rr_type) found++; + break; + } + +if (found == 0) + { + SPF_dns_rr_dup(&spfrr, &srr); + store_free_dns_answer(dnsa); + return spfrr; + } + +srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found); + +found = 0; +for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type == rr_type) + { + const uschar * s = rr->data; + + srr.ttl = rr->ttl; + switch(rr_type) + { + case T_MX: + if (rr->size < 2) continue; + s += 2; /* skip the MX precedence field */ + case T_PTR: + { + uschar * buf = store_malloc(256); + (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, + (DN_EXPAND_ARG4_TYPE)buf, 256); + s = buf; + break; + } + + case T_TXT: + { + gstring * g = NULL; + uschar chunk_len; + + if (rr->size < 1+6) continue; /* min for version str */ + if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0) + { + HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n", + (int) s[0], s+1); + continue; + } + + /* require 1 byte for the chunk_len */ + for (int off = 0; off < rr->size - 1; off += chunk_len) + { + if ( !(chunk_len = s[off++]) + || rr->size < off + chunk_len /* ignore bogus size chunks */ + ) break; + g = string_catn(g, s+off, chunk_len); + } + if (!g) + continue; + gstring_release_unused(g); + s = string_copy_malloc(string_from_gstring(g)); + DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s); + break; + } + + case T_A: + case T_AAAA: + default: + { + uschar * buf = store_malloc(dnsa->answerlen + 1); + s = memcpy(buf, s, dnsa->answerlen + 1); + break; + } + } + srr.rr[found++] = (void *) s; + } + +/* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with +empty ANSWER section. */ + +if (!(srr.num_rr = found)) + srr.herrno = NO_DATA; + +/* spfrr->rr must have been malloc()d for this */ +SPF_dns_rr_dup(&spfrr, &srr); +store_free_dns_answer(dnsa); +return spfrr; +} + + + +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"); */ + +memset(spf_dns_server, 0, sizeof(SPF_dns_server_t)); +spf_dns_server->destroy = NULL; +spf_dns_server->lookup = SPF_dns_exim_lookup; +spf_dns_server->get_spf = NULL; +spf_dns_server->get_exp = NULL; +spf_dns_server->add_cache = NULL; +spf_dns_server->layer_below = NULL; +spf_dns_server->name = "exim"; +spf_dns_server->debug = debug; + +/* XXX This might have to return NO_DATA sometimes. */ + +spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server, + "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND); +if (!spf_nxdomain) + { + store_free(spf_dns_server); + return NULL; + } + +return spf_dns_server; +} + + + + +/* Construct the SPF library stack. + Return: Boolean success. +*/ + +static BOOL +spf_init(void * dummy_ctx) +{ +SPF_dns_server_t * dc; +int debug = 0; +const uschar *s; + +DEBUG(D_receive) debug = 1; + +/* We insert our own DNS access layer rather than letting the spf library +do it, so that our dns access path is used for debug tracing and for the +testsuite. */ + +if (!(dc = SPF_dns_exim_new(debug))) + { + DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n"); + return FALSE; + } +if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8))) + { + DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n"); + return FALSE; + } +if (!(spf_server = SPF_server_new_dns(dc, debug))) + { + DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n"); + return FALSE; + } + +/* Override the outdated explanation URL. +See https://www.mail-archive.com/mailop@mailop.org/msg08019.html +Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", +but is broken now (May 18th, 2020) */ + +GET_OPTION("spf_smtp_comment_template"); +if (!(s = expand_string(spf_smtp_comment_template))) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed"); + +SPF_server_set_explanation(spf_server, CCS s, &spf_response); +if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response))); + +return TRUE; +} + + +/* Set 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). + +Return: Boolean success +*/ + +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(NULL)) return FALSE; + +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; + } + +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) + ) + { + DEBUG(D_receive) + debug_printf("spf: SPF_request_set_ipv4_str() and " + "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr); + spf_server = NULL; + spf_request = NULL; + return FALSE; + } + +if (SPF_request_set_helo_dom(spf_request, CS 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 TRUE; +} + +static void +spf_smtp_reset(void) +{ +spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL; +spf_result_guessed = FALSE; +} + + +static void +spf_response_debug(SPF_response_t * spf_response) +{ +if (SPF_response_messages(spf_response) == 0) + debug_printf(" (no errors)\n"); +else for (int i = 0; i < SPF_response_messages(spf_response); i++) + { + SPF_error_t * err = SPF_response_message(spf_response, i); + debug_printf( "%s_msg = (%d) %s\n", + (SPF_error_errorp(err) ? "warn" : "err"), + SPF_error_code(err), + SPF_error_message(err)); + } +} + + +/* spf_process adds the envelope sender address to the existing + context (if any), retrieves the result, sets up expansion + strings and evaluates the condition outcome. + +Return: OK/FAIL */ + +static int +spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, + int action) +{ +int sep = 0; +const uschar *list = *listptr; +uschar *spf_result_id; +int rc = SPF_RESULT_PERMERROR; + +DEBUG(D_receive) debug_printf("spf_process\n"); + +if (!(spf_server && spf_request)) + /* no global context, assume temp error and skip to evaluation */ + rc = SPF_RESULT_PERMERROR; + +else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender)) + /* Invalid sender address. This should be a real rare occurrence */ + rc = SPF_RESULT_PERMERROR; + +else + { + /* get SPF result */ + if (action == SPF_PROCESS_FALLBACK) + { + SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess); + spf_result_guessed = TRUE; + } + else + SPF_request_query_mailfrom(spf_request, &spf_response); + + /* set up expansion items */ + spf_header_comment = US SPF_response_get_header_comment(spf_response); + spf_received = US SPF_response_get_received_spf(spf_response); + spf_result = US SPF_strresult(SPF_response_result(spf_response)); + spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response); + + rc = SPF_response_result(spf_response); + + DEBUG(D_acl) spf_response_debug(spf_response); + } + +/* We got a result. Now see if we should return OK or FAIL for it */ +DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc); + +if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none"))) + return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK); + +while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0))) + { + BOOL negate, result; + + if ((negate = spf_result_id[0] == '!')) + spf_result_id++; + + result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0; + if (negate != result) return OK; + } + +/* no match */ +return FAIL; +} + + + +static gstring * +authres_spf(gstring * g) +{ +uschar * s; +if (spf_result) + { + int start = 0; /* Compiler quietening */ + DEBUG(D_acl) start = gstring_length(g); + + g = string_append(g, 2, US";\n\tspf=", spf_result); + if (spf_result_guessed) + g = string_cat(g, US" (best guess record for domain)"); + + s = expand_string(US"$sender_address_domain"); + if (s && *s) + g = string_append(g, 2, US" smtp.mailfrom=", s); + else + { + s = sender_helo_name; + g = s && *s + ? string_append(g, 2, US" smtp.helo=", s) + : string_cat(g, US" smtp.mailfrom=<>"); + } + DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n", + gstring_length(g) - start - 3, g->s + start + 3); + } +else + DEBUG(D_acl) debug_printf("SPF:\tno authres\n"); +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/miscmods/spf.h b/src/src/miscmods/spf.h new file mode 100644 index 000000000..679eab44b --- /dev/null +++ b/src/src/miscmods/spf.h @@ -0,0 +1,32 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* SPF support. + Copyright (c) The Exim Maintainers 2016 - 2024 + Copyright (c) Tom Kistner 2004 + License: GPL + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifdef SUPPORT_SPF + +/* Yes, we do have ns_type. spf.h redefines it if we don't set this. Doh */ +#if !defined(HAVE_NS_TYPE) && defined(NS_INADDRSZ) +# define HAVE_NS_TYPE +#endif +#include + +#include +#include + +typedef struct spf_result_id { + uschar *name; + int value; +} spf_result_id; + +#define SPF_PROCESS_NORMAL 0 +#define SPF_PROCESS_GUESS 1 +#define SPF_PROCESS_FALLBACK 2 + +#endif 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/spf.c b/src/src/spf.c deleted file mode 100644 index 9abe18fc3..000000000 --- a/src/src/spf.c +++ /dev/null @@ -1,442 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* SPF support. - Copyright (c) The Exim Maintainers 2015 - 2024 - Copyright (c) Tom Kistner 2004 - 2014 - License: GPL - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -/* Code for calling spf checks via libspf-alt. Called from acl.c. */ - -#include "exim.h" -#ifdef SUPPORT_SPF - -/* must be kept in numeric order */ -static spf_result_id spf_result_id_list[] = { - /* name value */ - { US"invalid", 0}, - { US"neutral", 1 }, - { US"pass", 2 }, - { US"fail", 3 }, - { US"softfail", 4 }, - { US"none", 5 }, - { US"temperror", 6 }, /* RFC 4408 defined */ - { US"permerror", 7 } /* RFC 4408 defined */ -}; - -SPF_server_t *spf_server = NULL; -SPF_request_t *spf_request = NULL; -SPF_response_t *spf_response = NULL; -SPF_response_t *spf_response_2mx = NULL; - -SPF_dns_rr_t * spf_nxdomain = NULL; - - -gstring * -spf_lib_version_report(gstring * g) -{ -int maj, min, patch; - -SPF_get_lib_version(&maj, &min, &patch); -g = string_fmt_append(g, "Library version: spf2: Compile: %d.%d.%d\n", - SPF_LIB_VERSION_MAJOR, SPF_LIB_VERSION_MINOR, SPF_LIB_VERSION_PATCH); -g = string_fmt_append(g, " Runtime: %d.%d.%d\n", - maj, min, patch); -return g; -} - - - -static SPF_dns_rr_t * -SPF_dns_exim_lookup(SPF_dns_server_t *spf_dns_server, - const char *domain, ns_type rr_type, int should_cache) -{ -dns_answer * dnsa = store_get_dns_answer(); -dns_scan dnss; -SPF_dns_rr_t * spfrr; -unsigned found = 0; - -SPF_dns_rr_t srr = { - .domain = CS domain, /* query information */ - .domain_buf_len = 0, - .rr_type = rr_type, - - .rr_buf_len = 0, /* answer information */ - .rr_buf_num = 0, /* no free of s */ - .utc_ttl = 0, - - .hook = NULL, /* misc information */ - .source = spf_dns_server -}; - -DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", domain); - -/* Shortcircuit SPF RR lookups by returning NO_DATA. They were obsoleted by -RFC 6686/7208 years ago. see bug #1294 */ - -if (rr_type == T_SPF) - { - HDEBUG(D_host_lookup) debug_printf("faking NO_DATA for SPF RR(99) lookup\n"); - srr.herrno = NO_DATA; - SPF_dns_rr_dup(&spfrr, &srr); - store_free_dns_answer(dnsa); - return spfrr; - } - -switch (dns_lookup(dnsa, US domain, rr_type, NULL)) - { - case DNS_AGAIN: srr.herrno = TRY_AGAIN; break; - case DNS_NOMATCH: srr.herrno = HOST_NOT_FOUND; break; - case DNS_NODATA: srr.herrno = NO_DATA; break; - case DNS_FAIL: - default: srr.herrno = NO_RECOVERY; break; - case DNS_SUCCEED: - srr.herrno = NETDB_SUCCESS; - for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - /* Need to alloc space for all records, so no early-out */ - if (rr->type == rr_type) found++; - break; - } - -if (found == 0) - { - SPF_dns_rr_dup(&spfrr, &srr); - store_free_dns_answer(dnsa); - return spfrr; - } - -srr.rr = store_malloc(sizeof(SPF_dns_rr_data_t) * found); - -found = 0; -for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; - rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) - if (rr->type == rr_type) - { - const uschar * s = rr->data; - - srr.ttl = rr->ttl; - switch(rr_type) - { - case T_MX: - if (rr->size < 2) continue; - s += 2; /* skip the MX precedence field */ - case T_PTR: - { - uschar * buf = store_malloc(256); - (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, - (DN_EXPAND_ARG4_TYPE)buf, 256); - s = buf; - break; - } - - case T_TXT: - { - gstring * g = NULL; - uschar chunk_len; - - if (rr->size < 1+6) continue; /* min for version str */ - if (strncmpic(rr->data+1, US SPF_VER_STR, 6) != 0) - { - HDEBUG(D_host_lookup) debug_printf("not an spf record: %.*s\n", - (int) s[0], s+1); - continue; - } - - /* require 1 byte for the chunk_len */ - for (int off = 0; off < rr->size - 1; off += chunk_len) - { - if ( !(chunk_len = s[off++]) - || rr->size < off + chunk_len /* ignore bogus size chunks */ - ) break; - g = string_catn(g, s+off, chunk_len); - } - if (!g) - continue; - gstring_release_unused(g); - s = string_copy_malloc(string_from_gstring(g)); - DEBUG(D_receive) debug_printf("SPF_dns_exim_lookup '%s'\n", s); - break; - } - - case T_A: - case T_AAAA: - default: - { - uschar * buf = store_malloc(dnsa->answerlen + 1); - s = memcpy(buf, s, dnsa->answerlen + 1); - break; - } - } - srr.rr[found++] = (void *) s; - } - -/* Did we filter out all TXT RRs? Return NO_DATA instead of SUCCESS with -empty ANSWER section. */ - -if (!(srr.num_rr = found)) - srr.herrno = NO_DATA; - -/* spfrr->rr must have been malloc()d for this */ -SPF_dns_rr_dup(&spfrr, &srr); -store_free_dns_answer(dnsa); -return spfrr; -} - - - -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"); - -memset(spf_dns_server, 0, sizeof(SPF_dns_server_t)); -spf_dns_server->destroy = NULL; -spf_dns_server->lookup = SPF_dns_exim_lookup; -spf_dns_server->get_spf = NULL; -spf_dns_server->get_exp = NULL; -spf_dns_server->add_cache = NULL; -spf_dns_server->layer_below = NULL; -spf_dns_server->name = "exim"; -spf_dns_server->debug = debug; - -/* XXX This might have to return NO_DATA sometimes. */ - -spf_nxdomain = SPF_dns_rr_new_init(spf_dns_server, - "", ns_t_any, 24 * 60 * 60, HOST_NOT_FOUND); -if (!spf_nxdomain) - { - store_free(spf_dns_server); - return NULL; - } - -return spf_dns_server; -} - - - - -/* Construct the SPF library stack. - Return: Boolean success. -*/ - -BOOL -spf_init(void) -{ -SPF_dns_server_t * dc; -int debug = 0; -const uschar *s; - -DEBUG(D_receive) debug = 1; - -/* We insert our own DNS access layer rather than letting the spf library -do it, so that our dns access path is used for debug tracing and for the -testsuite. */ - -if (!(dc = SPF_dns_exim_new(debug))) - { - DEBUG(D_receive) debug_printf("spf: SPF_dns_exim_new() failed\n"); - return FALSE; - } -if (!(dc = SPF_dns_cache_new(dc, NULL, debug, 8))) - { - DEBUG(D_receive) debug_printf("spf: SPF_dns_cache_new() failed\n"); - return FALSE; - } -if (!(spf_server = SPF_server_new_dns(dc, debug))) - { - DEBUG(D_receive) debug_printf("spf: SPF_server_new() failed.\n"); - return FALSE; - } - -/* Override the outdated explanation URL. -See https://www.mail-archive.com/mailop@mailop.org/msg08019.html -Used to work as "Please%_see%_http://www.open-spf.org/Why?id=%{S}&ip=%{C}&receiver=%{R}", -but is broken now (May 18th, 2020) */ - -GET_OPTION("spf_smtp_comment_template"); -if (!(s = expand_string(spf_smtp_comment_template))) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "expansion of spf_smtp_comment_template failed"); - -SPF_server_set_explanation(spf_server, CCS s, &spf_response); -if (SPF_response_errcode(spf_response) != SPF_E_SUCCESS) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", SPF_strerror(SPF_response_errcode(spf_response))); - -return TRUE; -} - - -/* Set 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). - -Return: Boolean success -*/ - -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_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; - } - -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) - ) - { - DEBUG(D_receive) - debug_printf("spf: SPF_request_set_ipv4_str() and " - "SPF_request_set_ipv6_str() failed [%s]\n", spf_remote_addr); - spf_server = NULL; - spf_request = NULL; - return FALSE; - } - -if (SPF_request_set_helo_dom(spf_request, CS 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 TRUE; -} - - -void -spf_response_debug(SPF_response_t * spf_response) -{ -if (SPF_response_messages(spf_response) == 0) - debug_printf(" (no errors)\n"); -else for (int i = 0; i < SPF_response_messages(spf_response); i++) - { - SPF_error_t * err = SPF_response_message(spf_response, i); - debug_printf( "%s_msg = (%d) %s\n", - (SPF_error_errorp(err) ? "warn" : "err"), - SPF_error_code(err), - SPF_error_message(err)); - } -} - - -/* spf_process adds the envelope sender address to the existing - context (if any), retrieves the result, sets up expansion - strings and evaluates the condition outcome. - -Return: OK/FAIL */ - -int -spf_process(const uschar ** listptr, const uschar * spf_envelope_sender, - int action) -{ -int sep = 0; -const uschar *list = *listptr; -uschar *spf_result_id; -int rc = SPF_RESULT_PERMERROR; - -DEBUG(D_receive) debug_printf("spf_process\n"); - -if (!(spf_server && spf_request)) - /* no global context, assume temp error and skip to evaluation */ - rc = SPF_RESULT_PERMERROR; - -else if (SPF_request_set_env_from(spf_request, CS spf_envelope_sender)) - /* Invalid sender address. This should be a real rare occurrence */ - rc = SPF_RESULT_PERMERROR; - -else - { - /* get SPF result */ - if (action == SPF_PROCESS_FALLBACK) - { - SPF_request_query_fallback(spf_request, &spf_response, CS spf_guess); - spf_result_guessed = TRUE; - } - else - SPF_request_query_mailfrom(spf_request, &spf_response); - - /* set up expansion items */ - spf_header_comment = US SPF_response_get_header_comment(spf_response); - spf_received = US SPF_response_get_received_spf(spf_response); - spf_result = US SPF_strresult(SPF_response_result(spf_response)); - spf_smtp_comment = US SPF_response_get_smtp_comment(spf_response); - - rc = SPF_response_result(spf_response); - - DEBUG(D_acl) spf_response_debug(spf_response); - } - -/* We got a result. Now see if we should return OK or FAIL for it */ -DEBUG(D_acl) debug_printf("SPF result is %s (%d)\n", SPF_strresult(rc), rc); - -if (action == SPF_PROCESS_GUESS && (!strcmp (SPF_strresult(rc), "none"))) - return spf_process(listptr, spf_envelope_sender, SPF_PROCESS_FALLBACK); - -while ((spf_result_id = string_nextinlist(&list, &sep, NULL, 0))) - { - BOOL negate, result; - - if ((negate = spf_result_id[0] == '!')) - spf_result_id++; - - result = Ustrcmp(spf_result_id, spf_result_id_list[rc].name) == 0; - if (negate != result) return OK; - } - -/* no match */ -return FAIL; -} - - - -gstring * -authres_spf(gstring * g) -{ -uschar * s; -if (spf_result) - { - int start = 0; /* Compiler quietening */ - DEBUG(D_acl) start = gstring_length(g); - - g = string_append(g, 2, US";\n\tspf=", spf_result); - if (spf_result_guessed) - g = string_cat(g, US" (best guess record for domain)"); - - s = expand_string(US"$sender_address_domain"); - if (s && *s) - g = string_append(g, 2, US" smtp.mailfrom=", s); - else - { - s = sender_helo_name; - g = s && *s - ? string_append(g, 2, US" smtp.helo=", s) - : string_cat(g, US" smtp.mailfrom=<>"); - } - DEBUG(D_acl) debug_printf("SPF:\tauthres '%.*s'\n", - gstring_length(g) - start - 3, g->s + start + 3); - } -else - DEBUG(D_acl) debug_printf("SPF:\tno authres\n"); -return g; -} - - -#endif diff --git a/src/src/spf.h b/src/src/spf.h deleted file mode 100644 index 7e9a6ca2a..000000000 --- a/src/src/spf.h +++ /dev/null @@ -1,39 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* SPF support. - Copyright (c) The Exim Maintainers 2016 - 2024 - Copyright (c) Tom Kistner 2004 - License: GPL - SPDX-License-Identifier: GPL-2.0-or-later -*/ - -#ifdef SUPPORT_SPF - -/* Yes, we do have ns_type. spf.h redefines it if we don't set this. Doh */ -#if !defined(HAVE_NS_TYPE) && defined(NS_INADDRSZ) -# define HAVE_NS_TYPE -#endif -#include - -#include -#include - -typedef struct spf_result_id { - uschar *name; - 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 - -#endif 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) { + $_ = ; + if (/^$time_pid?\s/) { $_ = ; - next if /^$time_pid?\s/; - goto RESET_AFTER_EXTRA_LINE_READ; + if (/^$time_pid?\s/) { + $_ = ; + } } + 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: