spf dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 27 Aug 2024 23:16:43 +0000 (00:16 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Wed, 28 Aug 2024 10:17:07 +0000 (11:17 +0100)
32 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/scripts/lookups-Makefile
src/src/EDITME
src/src/acl.c
src/src/daemon.c
src/src/dmarc.c
src/src/dmarc.h
src/src/drtables.c
src/src/exim.c
src/src/exim.h
src/src/expand.c
src/src/functions.h
src/src/globals.c
src/src/globals.h
src/src/lookups/Makefile
src/src/lookups/spf.c
src/src/macros.h
src/src/miscmods/Makefile [new file with mode: 0644]
src/src/miscmods/spf.c [new file with mode: 0644]
src/src/miscmods/spf.h [new file with mode: 0644]
src/src/readconf.c
src/src/receive.c
src/src/smtp_in.c
src/src/spf.c [deleted file]
src/src/spf.h [deleted file]
src/src/structs.h
test/runtest
test/stderr/2610

index 600eee4afbef200a9de892f55e82b0b51263c0ac..428cbc07967074676d787edc324cb3be8625b163 100644 (file)
@@ -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.
 
index 9a34f8ac2a8304ef7dc5640fae7d1f2266beec2f..640bd58cd00e41385227dbd3bf63348e5a99946f 100644 (file)
@@ -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
 ------------
index 2e8728ae2c5fa6c09922a55c957f7a8934573f79..caae1e536bf841ea334a9b0965741827eaa04250 100644 (file)
@@ -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.
index 9179392f3123233925bffdc4834773ec3a0f1974..427ce0cb7549604afc7878138bf08578eda1a2e2 100755 (executable)
@@ -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.
index 76859ce9abf2bca8f06875813111be91268e1fd7..09d18b63cf89a5263e6effa8cb9f5b9ec244010c 100755 (executable)
@@ -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
index 40cca603f52886eb9ce13b7264f881d6dab1d41a..188c22d14fa7d95762c995821ea39db2749f1060 100755 (executable)
@@ -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"
index 7d0b07331ac4480ac5d64dc9033443d853478eed..e507ab3cd53a5eccb1c5826c58f6dc817cab2ce1 100644 (file)
@@ -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
index 533dcd60a39c77de9b3dfa9b5020743a1a019720..e285da65ca430719fcc04524783735b39f81ab18 100644 (file)
@@ -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:
index 4088cb53210935ae2ac4ed73a746c234d546e346..49bf74a11d3e7c43795826d05ce503f2849bb85f 100644 (file)
@@ -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();
index 28fce06244f889c5c9518361ee884baaa1c875b5..664daa737319e56037bf8104dc0089fda02611af 100644 (file)
@@ -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);
     }
index fa366dd06b3818f81f3f0480d4647e978bbbf747..dcf289f2d49c6bbf377ca638e198d5fb00377bcf 100644 (file)
@@ -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);
index a144085c5a797397c356ecce68ad46b45fab503b..35a376dd1aeb92ef8e51a9070f3f2dac2b5da427 100644 (file)
@@ -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;
 }
 
 
index de1f484341b9f3b917aff2c25074ca8586208a8f..ecc25d6bc5920474681428bc1de2d1e5931757bd 100644 (file)
@@ -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
index c4d80c694602046d69e633fef261a2503c2a9628..470adb351321547d022dd099d37e6d83b704aedf 100644 (file)
@@ -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"
index 08d72f213dd56938af10b6b609378f99a6ea677b..b3a1575a7e0e5d9ba346f0efdb11de069481042f 100644 (file)
@@ -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;
index be60d5fc9a49ad7dbb793687f19e4b475d2a1f5f..aaec6461f61fd683d8a472b868ca283fea8ccaf7 100644 (file)
@@ -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 *);
index 9efc389a643a67b3d89b4d309e3a60d8c903f4a0..f2287d41cf5599dfeb7df28052de55062d3092b5 100644 (file)
@@ -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
index a542a179f243964f1c14792f2a13da7c8827c749..9b30e502c1ff5f48814914c2415d7373015c64d7 100644 (file)
@@ -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 */
index 5193520f996533a0e237ec63d798af89238241f5..2bfd691a1a7b5db582135bc27f84f7679fec28f9 100644 (file)
@@ -10,8 +10,6 @@
 
 # MAGIC-TAG-MODS-OBJ-RULES-GO-HERE
 
-OBJ += spf.o
-
 all:             lookups.a $(MODS)
 
 lookups.a:       $(OBJ)
index a1052d7fc4c752efffac3295fc6981c6c3ed9f22..4e0824911d96b798c250a4f6466b76309297c074 100644 (file)
@@ -31,92 +31,46 @@ static void dummy(int x) { dummy2(x-1); }
 #include <spf2/spf_dns_resolv.h>
 #include <spf2/spf_dns_cache.h>
 
-extern SPF_dns_server_t * SPF_dns_exim_new(int);
-
 
 static void *
 spf_open(const uschar * filename, uschar ** errmsg)
 {
-SPF_dns_server_t * dc;
-SPF_server_t *spf_server = NULL;
-int debug = 0;
-
-DEBUG(D_lookup) debug = 1;
-
-if ((dc = SPF_dns_exim_new(debug)))
-  if ((dc = SPF_dns_cache_new(dc, NULL, debug, 8)))
-    spf_server = SPF_server_new_dns(dc, debug);
-
-if (!spf_server)
+misc_module_info * mi = misc_mod_find(US"spf", errmsg);
+if (mi)
   {
-  *errmsg = US"SPF_dns_exim_nnew() failed";
-  return NULL;
+  typedef void * (*fn_t)(const uschar *, uschar **);
+  return (((fn_t *) mi->functions)[5]) (filename, errmsg);
   }
-return (void *) spf_server;
+return NULL;
 }
 
 
 static void
-spf_close(void *handle)
+spf_close(void * handle)
 {
-SPF_server_t *spf_server = handle;
-if (spf_server) SPF_server_free(spf_server);
+misc_module_info * mi = misc_mod_find(US"spf", NULL);
+if (mi)
+  {
+  typedef void (*fn_t)(void *);
+  return (((fn_t *) mi->functions)[6]) (handle);
+  }
 }
 
+
 static int
 spf_find(void * handle, const uschar * filename, const uschar * keystring,
   int key_len, uschar ** result, uschar ** errmsg, uint * do_cache,
   const uschar * opts)
 {
-SPF_server_t *spf_server = handle;
-SPF_request_t *spf_request;
-SPF_response_t *spf_response = NULL;
-
-if (!(spf_request = SPF_request_new(spf_server)))
-  {
-  *errmsg = US"SPF_request_new() failed";
-  return FAIL;
-  }
-
-#if HAVE_IPV6
-switch (string_is_ip_address(filename, NULL))
-#else
-switch (4)
-#endif
+misc_module_info * mi = misc_mod_find(US"spf", errmsg);
+if (mi)
   {
-  case 4:
-    if (!SPF_request_set_ipv4_str(spf_request, CS filename))
-      break;
-    *errmsg = string_sprintf("invalid IPv4 address '%s'", filename);
-    return FAIL;
-#if HAVE_IPV6
-
-  case 6:
-    if (!SPF_request_set_ipv6_str(spf_request, CS filename))
-      break;
-    *errmsg = string_sprintf("invalid IPv6 address '%s'", filename);
-    return FAIL;
-
-  default:
-    *errmsg = string_sprintf("invalid IP address '%s'", filename);
-    return FAIL;
-#endif
+  typedef int (*fn_t) (void *, const uschar *, const uschar *,
+                     int, uschar **, uschar **, uint *, const uschar *);
+  return (((fn_t *) mi->functions)[7]) (handle, filename, keystring, key_len,
+                                     result, errmsg, do_cache, opts);
   }
-
-if (SPF_request_set_env_from(spf_request, CS keystring))
-    {
-  *errmsg = string_sprintf("invalid envelope from address '%s'", keystring);
-  return FAIL;
-}
-
-SPF_request_query_mailfrom(spf_request, &spf_response);
-*result = string_copy(US SPF_strresult(SPF_response_result(spf_response)));
-
-DEBUG(D_lookup) spf_response_debug(spf_response);
-
-SPF_response_free(spf_response);
-SPF_request_free(spf_request);
-return OK;
+return FAIL;
 }
 
 
@@ -138,7 +92,7 @@ return g;
 }
 
 
-static lookup_info _lookup_info = {
+static lookup_info spf_lookup_info = {
   .name = US"spf",                     /* lookup name */
   .type = 0,                           /* not absfile, not query style */
   .open = spf_open,                    /* open function */
@@ -150,11 +104,11 @@ static lookup_info _lookup_info = {
   .version_report = spf_version_report             /* version reporting */
 };
 
-#ifdef DYNLOOKUP
+#ifdef notdef_DYNLOOKUP
 #define spf_lookup_module_info _lookup_module_info
 #endif
 
-static lookup_info *_lookup_list[] = { &_lookup_info };
+static lookup_info *_lookup_list[] = { &spf_lookup_info };
 lookup_module_info spf_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
 
 #endif /* SUPPORT_SPF */
index 3bfcf51d5e2a3d0b75303d86751dde688c7f8ca6..7bcc7cd049391977430fefb45377ec12502aebe1 100644 (file)
@@ -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 (file)
index 0000000..59bf298
--- /dev/null
@@ -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 (file)
index 0000000..a7b6c6a
--- /dev/null
@@ -0,0 +1,606 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* SPF support.
+   Copyright (c) The Exim Maintainers 2015 - 2024
+   Copyright (c) Tom Kistner <tom@duncanthrax.net> 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 (file)
index 0000000..679eab4
--- /dev/null
@@ -0,0 +1,32 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* SPF support.
+   Copyright (c) The Exim Maintainers 2016 - 2024
+   Copyright (c) Tom Kistner <tom@duncanthrax.net> 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 <spf2/spf.h>
+
+#include <spf2/spf_dns_resolv.h>
+#include <spf2/spf_dns_cache.h>
+
+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
index a090cfb30c1109eda3ddf937e7fbf5cf397731f7..1fe6b7341b95ccad538e29caa592327106d84d10 100644 (file)
@@ -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;
index 1ee02c5b700fdbd7d8f2ea8e2c4baec42398d6c8..336b374102cef35ff325056edfdc69821cac9769 100644 (file)
@@ -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
index 7c2b4a2922467c1a9f7883b759f7ad17449f7154..f9bd3ece81a96a65f63f24cfbcf1fe3680c3bb99 100644 (file)
@@ -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 (file)
index 9abe18f..0000000
+++ /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 <tom@duncanthrax.net> 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 (file)
index 7e9a6ca..0000000
+++ /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 <tom@duncanthrax.net> 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 <spf2/spf.h>
-
-#include <spf2/spf_dns_resolv.h>
-#include <spf2/spf_dns_cache.h>
-
-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
index 9492dbac23454e9fd202fbcc0df848a887010986..2c8c77c430e0237ace2eed565f4be9e033fd56c4 100644 (file)
@@ -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 */
index 3e10a5ab7e5dcb88764d4a4c75d502cefbc1e2e3..dcf6d76b23db726ead86f090e6452d638bc6fda9 100755 (executable)
@@ -383,7 +383,7 @@ $date = "\\d{2}-\\w{3}-\\d{4}\\s\\d{2}:\\d{2}:\\d{2}";
 
 # Debug time & pid
 
-$time_pid = "(?:\\d{2}:\\d{2}:\\d{2}\\s+\\d+\\s)";
+$time_pid = "(?:(?:\\d{2}:\\d{2}:\\d{2}\\s+)?\\d+\\s)";
 
 # Pattern for matching pids at start of stderr lines; initially something
 # that won't match.
@@ -1315,11 +1315,14 @@ RESET_AFTER_EXTRA_LINE_READ:
     # different libraries will have different numbers (possibly 0) of follow-up
     # lines, indenting with more data
     if (/^$time_pid?Library version:/) {
-      while (1) {
+      $_ = <IN>;
+      if (/^$time_pid?\s/) {
        $_ = <IN>;
-       next if /^$time_pid?\s/;
-       goto RESET_AFTER_EXTRA_LINE_READ;
+       if (/^$time_pid?\s/) {
+         $_ = <IN>;
+       }
       }
+      goto RESET_AFTER_EXTRA_LINE_READ;
     }
 
     # drop other build-time controls emitted for debugging
@@ -1556,8 +1559,10 @@ RESET_AFTER_EXTRA_LINE_READ:
     next if /^DKIM >> Body data for hash, canonicalized/;
 
     # Not all platforms build with SPF enabled
-    next if /(^spf_conn_init|^SPF_dns_exim_new|spf_compile\.c)/;
+    next if /(^$time_pid?spf_conn_init|spf_compile\.c)/;
     next if /try option spf_smtp_comment_template$/;
+    next if /loading module 'spf'$/;
+    next if /^Loaded "spf"$/;
 
     # Not all platforms have sendfile support
     next if /^cannot use sendfile for body: no support$/;
index c762e0340a33adb8e440e4f9b5f05da6b8c55557..564fb7b11257976f2bb094a14fa0ce263bea3457 100644 (file)
@@ -304,8 +304,6 @@ close MYSQL connection: 127.0.0.1:PORT_N/test/root
 01:01:01 p1235  sender_fullhost = (test) [10.0.0.0]
 01:01:01 p1235  sender_rcvhost = [10.0.0.0] (helo=test)
 01:01:01 p1235  set_process_info: pppp handling incoming connection from (test) [10.0.0.0]
-01:01:01 p1235  spf_conn_init: test 10.0.0.0
-01:01:01 p1235  SPF_dns_exim_new
 01:01:01 p1235  try option acl_smtp_helo
 01:01:01 p1235  SMTP>> 250 myhost.test.ex Hello test [10.0.0.0]
 01:01:01 p1235  SMTP<< mail from:<a@b>