dkim dynamic module
authorJeremy Harris <jgh146exb@wizmail.org>
Wed, 4 Sep 2024 20:46:26 +0000 (21:46 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Wed, 4 Sep 2024 20:46:26 +0000 (21:46 +0100)
59 files changed:
doc/doc-txt/NewStuff
src/OS/Makefile-Base
src/scripts/Configure-Makefile
src/scripts/MakeLinks
src/scripts/drivers-Makefile
src/src/EDITME
src/src/acl.c
src/src/arc.c
src/src/config.h.defaults
src/src/daemon.c
src/src/dkim.c [deleted file]
src/src/dkim.h [deleted file]
src/src/dkim_transport.c [deleted file]
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/hash.c
src/src/hash.h
src/src/miscmods/Makefile
src/src/miscmods/README
src/src/miscmods/dkim.c [new file with mode: 0644]
src/src/miscmods/dkim.h [new file with mode: 0644]
src/src/miscmods/dkim_api.h [new file with mode: 0644]
src/src/miscmods/dkim_transport.c [new file with mode: 0644]
src/src/miscmods/dmarc.c
src/src/miscmods/dmarc_api.h
src/src/miscmods/pdkim/Makefile [new file with mode: 0644]
src/src/miscmods/pdkim/README [new file with mode: 0644]
src/src/miscmods/pdkim/crypt_ver.h [new file with mode: 0644]
src/src/miscmods/pdkim/pdkim.c [new file with mode: 0644]
src/src/miscmods/pdkim/pdkim.h [new file with mode: 0644]
src/src/miscmods/pdkim/pdkim_hash.h [new file with mode: 0644]
src/src/miscmods/pdkim/signing.c [new file with mode: 0644]
src/src/miscmods/pdkim/signing.h [new file with mode: 0644]
src/src/miscmods/spf.c
src/src/miscmods/spf_api.h
src/src/pdkim/Makefile [deleted file]
src/src/pdkim/README [deleted file]
src/src/pdkim/config.h [deleted file]
src/src/pdkim/crypt_ver.h [deleted file]
src/src/pdkim/pdkim.c [deleted file]
src/src/pdkim/pdkim.h [deleted file]
src/src/pdkim/pdkim_hash.h [deleted file]
src/src/pdkim/signing.c [deleted file]
src/src/pdkim/signing.h [deleted file]
src/src/readconf.c
src/src/receive.c
src/src/smtp_in.c
src/src/spool_in.c
src/src/string.c
src/src/structs.h
src/src/tls-gnu.c
src/src/tls-openssl.c
src/src/transports/smtp.c
test/runtest

index 640bd58cd00e41385227dbd3bf63348e5a99946f..1189ce3f322d0db3c08eda236fad3b9488e335c2 100644 (file)
@@ -14,9 +14,9 @@ Version 4.98
 
  3. Events smtp:fail:protocol and smtp:fail:syntax
 
- 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
+ 4. JSON and LDAP lookup support, SPF, DKIM and DMARC 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 591b4261c006a18fc05ee5321e6d1d9229eea378..12319967eb534e5650510c1606f488c56b842b7a 100644 (file)
@@ -221,15 +221,15 @@ macro-spa.o :             auths/spa.c
 macro-authtls.o:       auths/tls.c
        @echo "$(CC) -DMACRO_PREDEF auths/tls.c"
        $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/tls.c
-macro-dkim.o:          dkim.c
-       @echo "$(CC) -DMACRO_PREDEF dkim.c"
-       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ dkim.c
+macro-dkim.o:          miscmods/dkim.c
+       @echo "$(CC) -DMACRO_PREDEF miscmods/dkim.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/dkim.c
 macro-malware.o:       malware.c
        @echo "$(CC) -DMACRO_PREDEF malware.c"
        $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ malware.c
-macro-signing.o:       pdkim/signing.c
-       @echo "$(CC) -DMACRO_PREDEF pdkim/signing.c"
-       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ pdkim/signing.c
+macro-signing.o:       miscmods/signing.c
+       @echo "$(CC) -DMACRO_PREDEF miscmods/signing.c"
+       $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/signing.c
 
 macro_predef: $(OBJ_MACRO)
        @echo "$(LNCC) -o $@"
@@ -244,7 +244,7 @@ macro.c: macro_predef
 # problem, but it does no harm. Other make programs will just ignore this.
 
 .PHONY: all config utils \
-       buildauths buildlookups buildpdkim buildrouters \
+       buildauths buildlookups buildrouters \
         buildtransports buildmisc dynmodules checklocalmake clean
 
 
@@ -515,7 +515,7 @@ OBJ_AUTHS = call_pam.o call_pwcheck.o call_radius.o check_serv_cond.o \
 
 OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         directory.o dns.o drtables.o enq.o exim.o expand.o filter.o \
-        filtertest.o globals.o dkim.o dkim_transport.o dnsbl.o hash.o \
+        filtertest.o globals.o dnsbl.o hash.o \
         header.o host.o host_address.o ip.o log.o lss.o match.o md5.o moan.o \
         os.o parse.o priv.o proxy.o queue.o \
         rda.o readconf.o receive.o retry.o rewrite.o rfc2047.o regex_cache.o \
@@ -526,13 +526,13 @@ OBJ_EXIM = acl.o base64.o child.o crypt16.o daemon.o dbfn.o debug.o deliver.o \
         local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
         $(OBJ_EXPERIMENTAL)
 
-exim:   buildlookups buildauths pdkim/pdkim.a \
+exim:   buildlookups buildauths \
         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 miscmods/miscmods.a \
+         auths/auths.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)
@@ -685,6 +685,7 @@ HDRS  =     blob.h \
        hintsdb/hints_tdb.h \
        local_scan.h \
        macros.h \
+       miscmods/dkim_api.h \
        miscmods/dmarc_api.h \
        miscmods/spf_api.h \
        mytypes.h \
@@ -706,6 +707,7 @@ PHDRS = ../config.h \
        ../hintsdb/hints_tdb.h \
        ../local_scan.h \
        ../macros.h \
+       ../miscmods/dkim_api.h \
        ../miscmods/dmarc_api.h \
        ../miscmods/spf_api.h \
        ../mytypes.h \
@@ -886,8 +888,6 @@ transport.o:     $(HDRS) transport.c
 tree.o:          $(HDRS) tree.c
 verify.o:        $(HDRS) transports/smtp.h verify.c
 xtextencode.o:   $(HDRS) xtextencode.c
-dkim.o:          $(HDRS) pdkim/pdkim.h dkim.c
-dkim_transport.o: $(HDRS) dkim_transport.c
 
 # Dependencies for WITH_CONTENT_SCAN modules
 
@@ -900,7 +900,7 @@ spool_mbox.o:    $(HDRS) spool_mbox.c
 
 # Dependencies for EXPERIMENTAL_* modules
 
-arc.o:         $(HDRS) pdkim/pdkim.h arc.c
+arc.o:         $(HDRS) miscmods/pdkim.h arc.c
 bmi_spam.o:    $(HDRS) bmi_spam.c
 dane.o:                $(HDRS) dane.c dane-openssl.c
 dcc.o:         $(HDRS) dcc.h dcc.c
@@ -1065,15 +1065,6 @@ buildauths: config
           INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
         @echo " "
 
-# The PDKIM library
-
-buildpdkim: pdkim/pdkim.a
-pdkim/pdkim.a: config
-        @cd pdkim && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) CC="$(CC)" CFLAGS="$(CFLAGS)" \
-          FE="$(FE)" RANLIB="$(RANLIB)" RM_COMMAND="$(RM_COMMAND)" HDRS="$(PHDRS)" \
-          INCLUDE="$(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE)"
-        @echo " "
-
 buildmisc: config
         @cd miscmods && $(MAKE) SHELL=$(SHELL) AR="$(AR)" $(MFLAGS) \
           CC="$(CC)" CFLAGS="$(CFLAGS)" \
index c3019f84677e83cababec4bdc07c06642f67b2ae..12f0ddd9c1f82454127972b38ec42125bc32a7f7 100755 (executable)
@@ -311,7 +311,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 DMARC
+ miscmods   SUPPORT    _DKIM DMARC SPF
 END
 
 # See if there is a definition of EXIM_PERL in what we have built so far.
index f657abd5b7f99f6bed20e1766d1fb8be53d884c9..a6521a95e0d72b0c8449fee10e00e857635e817a 100755 (executable)
@@ -90,13 +90,20 @@ done
 cd ..
 
 # miscellaneous modules
+# Note that the file in the miscmods/pdkim/ source subdir get linked to the 
+# destination miscmods/ dir
 d="miscmods"
 mkdir $d
 cd $d
 # Makefile is generated
-for f in dmarc.c dmarc.h dmarc_api.h dummy.c spf.c spf.h spf_api.h
+for f in dummy.c \
+       dkim.c dkim_transport.c dkim.h dkim_api.h \
+       pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \
+       pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \
+       dmarc.c dmarc.h dmarc_api.h \
+       spf.c spf.h spf_api.h
 do
-  ln -s ../../src/$d/$f $f
+  ln -s ../../src/$d/$f `basename $f`
 done
 cd ..
 
@@ -110,17 +117,6 @@ do
 done
 cd ..
 
-# Likewise for the code for the PDKIM library
-d="pdkim"
-mkdir $d
-cd $d
-for f in README Makefile crypt_ver.h pdkim.c \
-  pdkim.h hash.c hash.h signing.c signing.h blob.h
-do
-  ln -s ../../src/$d/$f $f
-done
-cd ..
-
 # The basic source files for Exim and utilities. NB local_scan.h gets linked,
 # but local_scan.c does not, because its location is taken from the build-time
 # configuration. Likewise for the os.c file, which gets build dynamically.
@@ -140,7 +136,6 @@ for f in blob.h dbfunctions.h exim.h functions.h globals.h \
   string.c tls.c tlscert-gnu.c tlscert-openssl.c tls-cipher-stdname.c \
   tls-gnu.c tls-openssl.c \
   tod.c transport.c tree.c verify.c version.c xtextencode.c \
-  dkim.c dkim.h dkim_transport.c \
   valgrind.h memcheck.h \
   macro_predef.c macro_predef.h
 do
@@ -155,7 +150,7 @@ done
 
 # EXPERIMENTAL_*
 for f in  arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \
-  danessl.h imap_utf7.c spf.c spf.h utf8.c xclient.c
+  danessl.h imap_utf7.c utf8.c xclient.c
 do
   ln -s ../src/$f $f
 done
index 2dd9580438342c42d8349b5d77c7de53813307ae..085eedbda968fbf24b66713c73b20520886d6add 100755 (executable)
@@ -95,13 +95,25 @@ fi
 # command-line, not just check the Makefile.
 
 want_dynamic() {
-  local dyn_name="$1"
+  local dyn_name="${1#_}"
   local re="(${classdef}|EXPERIMENTAL)_${dyn_name}[ $tab]*=[ $tab]*2"
+  #XXX Solaris does not support -E on grep.  Must use egrep.
   env | grep -E -q "^$re"
   if [ $? -eq 0 ]; then return 0; fi
   grep -E -q "^[ $tab]*$re" "$defs_source"
 }
 
+want_not_disabled() {
+  local want_name="${1#_}"
+  [ "$local_want_name" = "$1" ] && return 0;
+  local re="DISABLED_${want_name}[ $tab]*=[ $tab]*."
+  env | grep -E -q "^$re"
+  [ $? -ne 0 ] && return 0
+  grep -E -q "^[ $tab]*$re" "$defs_source"
+  [ $? -ne 0 ] && return 0
+  return 1
+}
+
 want_at_all() {
   local want_name="$1"
   local re="(${classdef}|EXPERIMENTAL)_${want_name}[ $tab]*=[ $tab]*."
@@ -135,20 +147,21 @@ emit_module_rule() {
       echo >&2 "Missing CFLAGS_DYNAMIC prevents building dynamic $name"
       exit 1
     fi
-    MODS="${MODS} ${mod_name}.so"
+    MODS="${MODS} ${mod_name#_}.so"
     grep "^${classdef}_${name}_PC" "$defs_source" 1>&2
     pkgconf=$(grep "^${classdef}_${name}_PC" "$defs_source")
     if [ $? -eq 0 ]; then
       pkgconf=$(echo $pkgconf | sed 's/^.*= *//')
-      echo "${classdef}_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)"
-      echo "${classdef}_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)"
+      echo "${classdef}_${mod_name#_}_INCLUDE = $(pkg-config --cflags $pkgconf)"
+      echo "${classdef}_${mod_name#_}_LIBS = $(pkg-config --libs $pkgconf)"
     else
       grep "^${classdef}_${name}_" "$defs_source"
-      echo "${classdef}_${mod_name}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
-      echo "${classdef}_${mod_name}_LIBS = \$(${classdef}_${name}_LIBS)"
+      echo "${classdef}_${mod_name#_}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
+      echo "${classdef}_${mod_name#_}_LIBS = \$(${classdef}_${name}_LIBS)"
     fi
-  elif want_at_all "$name"
-  then
+  elif want_not_disabled "$name"; then
+    OBJ="${OBJ} ${mod_name#_}.o"
+  elif want_at_all "$name"; then
     OBJ="${OBJ} ${mod_name}.o"
   fi
 }
index 35c497697a75447764c5152c8693d196993ca4e5..9d458842affaa9143acc95dbcbab8ce55ed638d8 100644 (file)
@@ -586,6 +586,10 @@ DISABLE_MAL_MKS=yes
 # turned on by default.  See the spec for information on conditionally
 # disabling it.  To disable the inclusion of the entire feature, set
 # DISABLE_DKIM to "yes"
+#
+# It is possible to build the support as a dynamic-load module. In addition
+# to not defining DISABLE_DKIM, define SUPPORT_DKIM=2.  The usual rules on
+# defines for includes and libs apply.
 
 # DISABLE_DKIM=yes
 
index 023ac2ff6ee1d8afa08a02ae1722e9873702015b..878278313931c2d4024e2861fe947616e20adbd8 100644 (file)
@@ -3934,23 +3934,19 @@ for (; cb; cb = cb->next)
 
 #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
-      if (dkim_cur_signer)
-       rc = match_isinlist(dkim_cur_signer,
-                          &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
-      else
-       rc = FAIL;
-      break;
-
     case ACLC_DKIM_STATUS:
-      {                /* return good for any match */
-      const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
-      int sep = 0;
-      for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
-       if (   (rc = match_isinlist(ss, &arg,
-                                   0, NULL, NULL, MCL_STRING, TRUE, NULL))
-           == OK) break;
-      }
+      /* See comment on ACLC_SPF wrt. coding issues */
+      {
+      misc_module_info * mi = misc_mod_find(US"dkim", &log_message);
+      typedef int (*fn_t)(const uschar *);
+      rc = mi
+       ? (((fn_t *) mi->functions)
+                     [cb->type == ACLC_DKIM_SIGNER
+                       ? DKIM_SIGNER_ISINLIST
+                       : DKIM_STATUS_LISTMATCH]) (arg)
+       : DEFER;
       break;
+      }
 #endif
 
 #ifdef SUPPORT_DMARC
@@ -4183,11 +4179,19 @@ for (; cb; cb = cb->next)
 #endif
         )
         store_pool = POOL_PERM;
+
 #ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
-      if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
-       dkim_verify_status = string_copy(arg);
-      else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
-       dkim_verify_reason = string_copy(arg);
+      if (  Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+        || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0
+         )
+         {
+         misc_module_info * mi = misc_mod_findonly(US"dkim");
+         typedef void (*fn_t)(const uschar *, void *);
+         
+         if (mi)
+           (((fn_t *) mi->functions)[DKIM_SETVAR])
+                                       (cb->u.varname, string_copy(arg));
+         }
       else
 #endif
        acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
@@ -4216,7 +4220,8 @@ for (; cb; cb = cb->next)
     case ACLC_SPF:
     case ACLC_SPF_GUESS:
       /* We have hardwired function-call numbers, and also prototypes for the
-      functions.  We could do a function name table search for the number
+      functions.  We could do a function name table search or (simpler)
+      a module include file with defines for the numbers
       but I can't see how to deal with prototypes.  Is a K&R non-prototyped
       function still usable with today's compilers (but we would lose on
       type-checking)?  We could macroize the typedef, and even the function
index d24b61114b21b399904d8d93c8a67e0f12b32469..a065ca8e382b22e54f03c02a8c87ba432d8ddc9a 100644 (file)
 # else
 
 #  include "functions.h"
-#  include "pdkim/pdkim.h"
-#  include "pdkim/signing.h"
+#  include "miscmods/pdkim.h"
+#  include "miscmods/signing.h"
 
 #  ifdef SUPPORT_DMARC
 #   include "miscmods/dmarc.h"
 #  endif
 
-extern pdkim_ctx * dkim_verify_ctx;
-extern pdkim_ctx dkim_sign_ctx;
-
 #define ARC_SIGN_OPT_TSTAMP    BIT(0)
 #define ARC_SIGN_OPT_EXPIRE    BIT(1)
 
@@ -100,6 +97,8 @@ typedef enum line_extract {
   le_all
 } line_extract_t;
 
+static misc_module_info * arc_dkim_mod_info;
+
 static time_t now;
 static time_t expire;
 static hdr_rlist * headers_rlist;
@@ -132,6 +131,23 @@ arc_parse_line() gathering only the 'i' tag (instance) information.
 */
 
 
+/******************************************************************************/
+
+/* We need a module init function, to check on the dkim module being present
+(and we may as well stack it's modinfo ptr)
+
+For now (until we do an arc module), called from exim.c main().
+*/
+BOOL
+arc_init(void)
+{
+uschar * errstr = NULL;
+if ((arc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
+  return TRUE;
+log_write(0, LOG_MAIN|LOG_PANIC, "arc: %s", errstr);
+return FALSE;
+}
+
 /******************************************************************************/
 
 
@@ -586,6 +602,39 @@ return Ustrncmp(s, al->cv.data, al->cv.len) == 0;
 }
 
 /******************************************************************************/
+/* Service routines provided by the dkim module */
+
+static int
+arc_dkim_hashname_blob_to_type(const blob * name)
+{
+typedef int (*fn_t)(const blob *);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_TYPE]) (name);
+}
+static hashmethod
+arc_dkim_hashtype_to_method(int hashtype)
+{
+typedef hashmethod (*fn_t)(int);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHTYPE_TO_METHOD]) (hashtype);
+}
+static hashmethod
+arc_dkim_hashname_blob_to_method(const blob * name)
+{
+typedef hashmethod (*fn_t)(const blob *);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HASHNAME_TO_METHOD]) (name);
+}
+
+/******************************************************************************/
+
+/* Do a "relaxed" canonicalization of a header */
+static uschar *
+arc_relax_header_n(const uschar * text, int len, BOOL append_crlf)
+{
+typedef uschar * (*fn_t)(const uschar *, int, BOOL);
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_HEADER_RELAX])
+                                               (text, len, append_crlf);
+}
+
+
 
 /* Return the hash of headers from the message that the AMS claims it
 signed.
@@ -599,14 +648,12 @@ const uschar * hn;
 int sep = ':';
 hdr_rlist * r;
 BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
-int hashtype = pdkim_hashname_to_hashtype(
-                   ams->a_hash.data, ams->a_hash.len);
+hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
 hctx hhash_ctx;
 const uschar * s;
 int len;
 
-if (  hashtype == -1
-   || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
   DEBUG(D_acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
@@ -628,7 +675,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
        && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0
        )
       {
-      if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE);
+      if (relaxed) s = arc_relax_header_n(s, r->h->slen, TRUE);
 
       DEBUG(D_acl) debug_printf("%Z\n", s);
       exim_sha_update_string(&hhash_ctx, s);
@@ -640,7 +687,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0)))
 
 s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len;
 if (relaxed)
-  len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE));
+  len = Ustrlen(s = arc_relax_header_n(s, len, FALSE));
 DEBUG(D_acl) debug_printf("%.*Z\n", len, s);
 exim_sha_update(&hhash_ctx, s, len);
 
@@ -653,33 +700,34 @@ return;
 
 
 
-static pdkim_pubkey *
-arc_line_to_pubkey(arc_line * al)
+static blob *
+arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
 {
-uschar * dns_txt;
-pdkim_pubkey * p;
-
-if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
-         (int)al->s.len, al->s.data, (int)al->d.len, al->d.data))))
-  {
-  DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
-  return NULL;
-  }
-
-if (  !(p = pdkim_parse_pubkey_record(dns_txt))
-   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
-   )
+typedef const uschar * (*fn_t)(const uschar *, blob **, const uschar **);
+blob * pubkey;
+const uschar * hashes;
+const uschar * srvtype =
+  (((fn_t *) arc_dkim_mod_info->functions)[DKIM_DNS_PUBKEY])
+    (string_sprintf("%.*s._domainkey.%.*s",
+                 (int)al->s.len, al->s.data, (int)al->d.len, al->d.data),
+    &pubkey, &hashes);
+
+/*XXX do we need a blob-string printf %handler?  Other types of blob? */
+
+if (!srvtype)
+  { *errstr = US"pubkey dns lookup fail"; return NULL; }
+if ((Ustrcmp(srvtype, "*") != 0 && Ustrcmp(srvtype, "email") != 0))
   {
-  DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+  *errstr = string_sprintf("pubkey format error: srvtype '%s'", srvtype);
   return NULL;
   }
 
 /* If the pubkey limits use to specified hashes, reject unusable
 signatures. XXX should we have looked for multiple dns records? */
 
-if (p->hashes)
+if (hashes)
   {
-  const uschar * list = p->hashes, * ele;
+  const uschar * list = hashes, * ele;
   int sep = ':';
 
   while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
@@ -687,11 +735,38 @@ if (p->hashes)
   if (!ele)
     {
     DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
-                             p->hashes, (int)al->a.len, al->a.data);
+                             hashes, (int)al->a.len, al->a.data);
+    *errstr = US"no usable sig for this pubkey hash list";
     return NULL;
     }
   }
-return p;
+return pubkey;
+}
+
+
+
+
+/* Set up a body hashing method on the given signature-context
+(creates a new one if needed, or uses an already-present one).
+
+Arguments:
+       signing         TRUE for signing, FALSE for verification
+       c               canonicalization spec, text form
+       ah              hash, text form
+       bodylen         byte count for message body
+
+Return:        pointer to hashing method struct
+*/
+
+static pdkim_bodyhash *
+arc_set_bodyhash(BOOL signing,
+  const blob * c, const blob * ah, long bodylen)
+{
+typedef pdkim_bodyhash * (*fn_t)(BOOL,
+  const blob * canon, const blob * hash, long bodylen);
+
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SET_BODYHASH])
+                   (signing, c, ah, bodylen);
 }
 
 
@@ -700,22 +775,72 @@ return p;
 static pdkim_bodyhash *
 arc_ams_setup_vfy_bodyhash(arc_line * ams)
 {
-int canon_head = -1, canon_body = -1;
-long bodylen;
-
-if (!ams->c.data) ams->c.data = US"simple";    /* RFC 6376 (DKIM) default */
-pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body);
-bodylen = ams->l.data
-       ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1;
-
-return pdkim_set_bodyhash(dkim_verify_ctx,
-       pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len),
-       canon_body,
-       bodylen);
+blob * c = &ams->c;
+long bodylen = ams->l.data
+       ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10)
+       : -1;
+
+if (!c->data)
+  {
+  c->data = US"simple";        /* RFC 6376 (DKIM) default */
+  c->len = 6;
+  }
+
+return arc_set_bodyhash(FALSE, c, &ams->a_hash, bodylen);
 }
 
 
 
+static void
+arc_decode_base64(const uschar * str, blob * b)
+{ 
+int dlen = b64decode(str, &b->data, str);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+
+
+static int
+arc_sig_verify(arc_set * as, arc_line * al, hashmethod hm,
+  blob * hhash_computed, blob * sighash,
+  const uschar * why, const uschar ** errstr_p)
+{
+blob * pubkey;
+const uschar * errstr = NULL;
+int rc;
+typedef int (*fn_t)
+       (const blob *, const blob *, hashmethod, const blob *, const uschar **);
+
+/* Get the public key from DNS */
+
+/*XXX dkim module */
+if (!(pubkey = arc_line_to_pubkey(al, &errstr)))
+  {
+  *errstr_p = string_sprintf("%s (%s)", errstr, why);
+  return ERROR;
+  }
+
+rc = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIG_VERIFY])
+                         (sighash, hhash_computed, hm, pubkey, &errstr);
+switch (rc)
+  {
+  case OK:
+    break;
+  case FAIL:
+    DEBUG(D_acl)
+      debug_printf("ARC i=%d %s verify %s\n", as->instance, why, errstr);
+    break;
+  case ERROR:
+    DEBUG(D_acl) debug_printf("ARC verify %s init: %s\n", why, errstr);
+    break;
+  }
+return rc;
+}
+
+
+
+
 /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag
 and without a DKIM v= tag.
 */
@@ -725,12 +850,11 @@ arc_ams_verify(arc_ctx * ctx, arc_set * as)
 {
 arc_line * ams = as->hdr_ams;
 pdkim_bodyhash * b;
-pdkim_pubkey * p;
 blob sighash;
-blob hhash;
-ev_ctx vctx;
-int hashtype;
+blob hhash_computed;
+hashmethod hm;
 const uschar * errstr;
+int rc;
 
 as->ams_verify_done = US"in-progress";
 
@@ -771,7 +895,7 @@ DEBUG(D_acl)
 /* We know the bh-tag blob is of a nul-term string, so safe as a string */
 
 if (  !ams->bh.data
-   || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
+   || (arc_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len)
    || memcmp(sighash.data, b->bh.data, b->bh.len) != 0
    )
   {
@@ -786,38 +910,21 @@ if (  !ams->bh.data
 
 DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
 
-/* Get the public key from DNS */
-
-if (!(p = arc_line_to_pubkey(ams)))
-  return as->ams_verify_done = arc_state_reason = US"pubkey problem";
-
 /* We know the b-tag blob is of a nul-term string, so safe as a string */
-pdkim_decode_base64(ams->b.data, &sighash);
+arc_decode_base64(ams->b.data, &sighash);
 
-arc_get_verify_hhash(ctx, ams, &hhash);
+arc_get_verify_hhash(ctx, ams, &hhash_computed);
 
-/* Setup the interface to the signing library */
-
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
-  {
-  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
-  as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
-  return US"fail";
-  }
-
-hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
-if (hashtype == -1)
+if ((hm = arc_dkim_hashname_blob_to_method(&ams->a_hash)) < 0)
   {
   DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
   return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
   }
 
-if ((errstr = exim_dkim_verify(&vctx,
-         pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
-  {
-  DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
-  return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
-  }
+rc = arc_sig_verify(as, ams, hm, &hhash_computed, &sighash, US"AMS", &errstr);
+if (rc != OK)
+  return as->ams_verify_done = arc_state_reason =
+    rc == FAIL ? US"AMS sig nonverify" : errstr;
 
 DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
 as->ams_verify_passed = TRUE;
@@ -901,13 +1008,12 @@ arc_seal_verify(arc_ctx * ctx, arc_set * as)
 {
 arc_line * hdr_as = as->hdr_as;
 arc_set * as2;
-int hashtype;
+hashmethod hm;
 hctx hhash_ctx;
 blob hhash_computed;
 blob sighash;
-ev_ctx vctx;
-pdkim_pubkey * p;
 const uschar * errstr;
+int rc;
 
 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
 /*
@@ -935,10 +1041,9 @@ if (  as->instance == 1 && !arc_cv_match(hdr_as, US"none")
            the ARC-Seal.
 */
 
-hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len);
+hm = arc_dkim_hashname_blob_to_method(&hdr_as->a_hash);
 
-if (  hashtype == -1
-   || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
   {
   DEBUG(D_acl)
       debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
@@ -967,7 +1072,8 @@ for (as2 = ctx->arcset_chain;
 
   al = as2->hdr_aar;
   if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                                            al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -975,7 +1081,8 @@ for (as2 = ctx->arcset_chain;
 
   al = as2->hdr_ams;
   if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                                            al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -983,10 +1090,12 @@ for (as2 = ctx->arcset_chain;
 
   al = as2->hdr_as;
   if (as2->instance == as->instance)
-    s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+    /*XXX dkim module */
+    s = arc_relax_header_n(al->rawsig_no_b_val.data,
                                        al->rawsig_no_b_val.len, FALSE);
   else if (!(s = al->relaxed))
-    al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+    /*XXX dkim module */
+    al->relaxed = s = arc_relax_header_n(al->complete->text,
                                            al->complete->slen, TRUE);
   len = Ustrlen(s);
   DEBUG(D_acl) debug_printf("%Z\n", s);
@@ -1009,12 +1118,9 @@ DEBUG(D_acl)
 /*
        6.  Retrieve the public key identified by the "s" and "d" tags in
            the ARC-Seal, as described in Section 4.1.6.
-*/
 
-if (!(p = arc_line_to_pubkey(hdr_as)))
-  return US"pubkey problem";
+Done below, in arc_sig_verify().
 
-/*
        7.  Determine whether the signature portion ("b" tag) of the ARC-
            Seal and the digest computed above are valid according to the
            public key.  (See also Section Section 8.4 for failure case
@@ -1025,21 +1131,12 @@ if (!(p = arc_line_to_pubkey(hdr_as)))
 */
 
 /* We know the b-tag blob is of a nul-term string, so safe as a string */
-pdkim_decode_base64(hdr_as->b.data, &sighash);
-
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
-  {
-  DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
-  return US"fail";
-  }
+arc_decode_base64(hdr_as->b.data, &sighash);
 
-if ((errstr = exim_dkim_verify(&vctx,
-             pdkim_hashes[hashtype].exim_hashmethod,
-             &hhash_computed, &sighash)))
+rc = arc_sig_verify(as, hdr_as, hm, &hhash_computed, &sighash, US"AS", &errstr);
+if (rc != OK)
   {
-  DEBUG(D_acl)
-    debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr);
-  arc_state_reason = US"seal sigverify error";
+  if (rc == FAIL) arc_state_reason = US"seal sigverify error";
   return US"fail";
   }
 
@@ -1076,12 +1173,6 @@ const uschar * res;
 
 memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
 
-if (!dkim_verify_ctx)
-  {
-  DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n");
-  return NULL;
-  }
-
 /* AS evaluation, per
 https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6
 */
@@ -1285,10 +1376,13 @@ arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey,
   blob * sig, const uschar * why)
 {
 hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
-  ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
+  ? HASH_SHA2_512
+  : arc_dkim_hashtype_to_method(hashtype);
+
 blob hhash;
-es_ctx sctx;
 const uschar * errstr;
+typedef const uschar * (*fn_t)
+                         (const blob *, hashmethod, const uschar *, blob *);
 
 DEBUG(D_transport)
   {
@@ -1296,7 +1390,7 @@ DEBUG(D_transport)
   debug_printf("ARC: %s header data for signing:\n", why);
   debug_printf("%.*Z\n", hdata->ptr, hdata->s);
 
-  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  (void) exim_sha_init(&hhash_ctx, hm);
   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
   exim_sha_finish(&hhash_ctx, &hhash);
   debug_printf("ARC: header hash: %.*H\n", hhash.len, hhash.data);
@@ -1305,7 +1399,7 @@ DEBUG(D_transport)
 if (FALSE /*need hash for Ed25519 or GCrypt signing*/ )
   {
   hctx hhash_ctx;
-  (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod);
+  (void) exim_sha_init(&hhash_ctx, arc_dkim_hashtype_to_method(hashtype));
   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
   exim_sha_finish(&hhash_ctx, &hhash);
   }
@@ -1315,8 +1409,9 @@ else
   hhash.len = hdata->ptr;
   }
 
-if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
-   || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig)))
+errstr = (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SIGN_DATA])
+                                                 (&hhash, hm, privkey, sig);
+if (errstr)
   {
   log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr);
   DEBUG(D_transport)
@@ -1324,6 +1419,7 @@ if (  (errstr = exim_dkim_signing_init(privkey, &sctx))
       privkey);
   return FALSE;
   }
+
 return TRUE;
 }
 
@@ -1333,7 +1429,7 @@ static gstring *
 arc_sign_append_sig(gstring * g, blob * sig)
 {
 /*debug_printf("%s: raw sig %.*H\n", __FUNCTION__, sig->len, sig->data);*/
-sig->data = pdkim_encode_base64(sig);
+sig->data = b64encode(sig->data, sig->len);
 sig->len = Ustrlen(sig->data);
 for (;;)
   {
@@ -1360,7 +1456,8 @@ arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance,
 uschar * s;
 gstring * hdata = NULL;
 int col;
-int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+const blob ams_h = {.data = US"sha256", .len = 6};     /*XXX hardwired */
+int hashtype = arc_dkim_hashname_blob_to_type(&ams_h);
 blob sig;
 int ams_off;
 arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED);
@@ -1378,7 +1475,7 @@ if (options & ARC_SIGN_OPT_TSTAMP)
 if (options & ARC_SIGN_OPT_EXPIRE)
   g = string_fmt_append(g, "; x=%lu", (u_long)expire);
 g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=",
-      pdkim_encode_base64(bodyhash));
+      b64encode(bodyhash->data, bodyhash->len));
 
 for(col = 3; rheaders; rheaders = rheaders->prev)
   {
@@ -1406,7 +1503,8 @@ for(col = 3; rheaders; rheaders = rheaders->prev)
       /* Accumulate header for hashing/signing */
 
       hdata = string_cat(hdata,
-               pdkim_relax_header_n(htext, rheaders->h->slen, TRUE));  /*XXX hardwired */
+               /*XXX dkim module */
+               arc_relax_header_n(htext, rheaders->h->slen, TRUE));    /*XXX hardwired */
       break;
       }
     }
@@ -1420,7 +1518,8 @@ g = string_catn(g, US";\r\n\tb=;", 7);
 
 /* Include the pseudo-header in the accumulation */
 
-s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
+/*XXX dkim module */
+s = arc_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE);
 hdata = string_cat(hdata, s);
 
 /* Calculate the signature from the accumulation */
@@ -1483,7 +1582,8 @@ header_line * h = (header_line *)(al+1);
 uschar * badline_str;
 
 gstring * hdata = NULL;
-int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6);      /*XXX hardwired */
+const blob as_h = {.data = US"sha256", .len = 6};      /*XXX hardwired */
+int hashtype = arc_dkim_hashname_blob_to_type(&as_h);
 blob sig;
 
 /*
@@ -1533,15 +1633,18 @@ for (arc_set * as = Ustrcmp(status, US"fail") == 0
   badline_str = US"aar";
   if (!(l = as->hdr_aar)) goto badline;
   h = l->complete;
-  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  /*XXX dkim module */
+  hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
   badline_str = US"ams";
   if (!(l = as->hdr_ams)) goto badline;
   h = l->complete;
-  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+  /*XXX dkim module */
+  hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
   badline_str = US"as";
   if (!(l = as->hdr_as)) goto badline;
   h = l->complete;
-  hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
+  /*XXX dkim module */
+  hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next));
   }
 
 /* Calculate the signature from the accumulation */
@@ -1567,21 +1670,19 @@ badline:
 
 /**************************************/
 
-/* Return pointer to pdkim_bodyhash for given hash method, creating new
-method if needed.
-*/
+/*XXX not static currently as the smtp tpt calls us */
+/* Really returns pdkim_bodyhash* - but there's an ordering
+problem for functions.h so call it void* */
 
 void *
 arc_ams_setup_sign_bodyhash(void)
 {
-int canon_head, canon_body;
+blob canon = {.data = US"relaxed", .len = 7};  /*XXX hardwired */
+blob hash =  {.data = US"sha256",  .len = 6};  /*XXX hardwired */
 
 DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
-pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body);     /*XXX hardwired */
-return pdkim_set_bodyhash(&dkim_sign_ctx,
-       pdkim_hashname_to_hashtype(US"sha256", 6),                      /*XXX hardwired */
-       canon_body,
-       -1);
+
+return arc_set_bodyhash(TRUE, &canon, &hash, -1);
 }
 
 
@@ -1767,6 +1868,10 @@ g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar);
     - ? oversigning?
   - Covers the data
   - we must have requested a suitable bodyhash previously
+XXX so where was that done?  I don't see it!
+XXX ah, ok - the smtp tpt calls arc_ams_setup_sign_bodyhash() directly, early
+       -> should pref use a better named call to make the point, but that
+       can wait until arc becomes a module
 */
 
 b = arc_ams_setup_sign_bodyhash();
@@ -1827,8 +1932,6 @@ arc_line al;
 pdkim_bodyhash * b;
 uschar * errstr;
 
-if (!dkim_verify_ctx) return US"no dkim context";
-
 if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS";
 
 DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n");
index 13b203e803ea358ae06d5b3d2f8b9ef674a2d32d..d602886a0084710d4bfa2eccca6890c0ea9a8c46 100644 (file)
@@ -167,6 +167,9 @@ Do not put spaces between # and the 'define'.
 #define SUPPORT_SRS
 #define SUPPORT_TRANSLATE_IP_ADDRESS
 
+/* Required to support dynamic-module build */
+#define SUPPORT_DKIM
+
 #define SYSLOG_LOG_PID
 #define SYSLOG_LONG_LINES
 
index 456c586da1632b2b2b16ed54929c40c9fa800ecf..fc8c7fdd220fea6e906c60a7f7ec230408714ccc 100644 (file)
@@ -2562,19 +2562,6 @@ else     /* no listening sockets, only queue-runs */
 dns_pattern_init();
 smtp_deliver_init();   /* Used for callouts */
 
-#ifndef DISABLE_DKIM
-  {
-# ifdef MEASURE_TIMING
-  struct timeval t0;
-  gettimeofday(&t0, NULL);
-# endif
-  dkim_exim_init();
-# ifdef MEASURE_TIMING
-  report_time_since(&t0, US"dkim_exim_init (delta)");
-# endif
-  }
-#endif
-
 #ifdef WITH_CONTENT_SCAN
 malware_init();
 #endif
diff --git a/src/src/dkim.c b/src/src/dkim.c
deleted file mode 100644 (file)
index 68f0748..0000000
+++ /dev/null
@@ -1,914 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) The Exim Maintainers 2020 - 2024 */
-/* Copyright (c) University of Cambridge, 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Code for DKIM support. Other DKIM relevant code is in
-   receive.c, transport.c and transports/smtp.c */
-
-#include "exim.h"
-
-#ifndef DISABLE_DKIM
-
-# include "pdkim/pdkim.h"
-
-# ifdef MACRO_PREDEF
-#  include "macro_predef.h"
-
-void
-params_dkim(void)
-{
-builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
-builtin_macro_create_var(US"_DKIM_OVERSIGN_HEADERS", US PDKIM_OVERSIGN_HEADERS);
-}
-# else /*!MACRO_PREDEF*/
-
-
-
-pdkim_ctx dkim_sign_ctx;
-
-int dkim_verify_oldpool;
-pdkim_ctx *dkim_verify_ctx = NULL;
-pdkim_signature *dkim_cur_sig = NULL;
-static const uschar * dkim_collect_error = NULL;
-
-#define DKIM_MAX_SIGNATURES 20
-
-
-
-/* Look up the DKIM record in DNS for the given hostname.
-Will use the first found if there are multiple.
-The return string is tainted, having come from off-site.
-*/
-
-uschar *
-dkim_exim_query_dns_txt(const uschar * name)
-{
-dns_answer * dnsa = store_get_dns_answer();
-dns_scan dnss;
-rmark reset_point = store_mark();
-gstring * g = string_get_tainted(256, GET_TAINTED);
-
-lookup_dnssec_authenticated = NULL;
-if (dns_lookup(dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
-  goto bad;
-
-/* Search for TXT record */
-
-for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-     rr;
-     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
-  if (rr->type == T_TXT)
-    {                  /* Copy record content to the answer buffer */
-    for (int rr_offset = 0; rr_offset < rr->size; )
-      {
-      uschar len = rr->data[rr_offset++];
-
-      g = string_catn(g, US(rr->data + rr_offset), len);
-      if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
-       goto bad;
-
-      rr_offset += len;
-      }
-
-    /* Check if this looks like a DKIM record */
-    if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
-      {
-      store_free_dns_answer(dnsa);
-      gstring_release_unused(g);
-      return string_from_gstring(g);
-      }
-
-    gstring_reset(g);          /* overwrite previous record */
-    }
-
-bad:
-store_reset(reset_point);
-store_free_dns_answer(dnsa);
-return NULL;   /*XXX better error detail?  logging? */
-}
-
-
-void
-dkim_exim_init(void)
-{
-if (f.dkim_init_done) return;
-f.dkim_init_done = TRUE;
-pdkim_init();
-}
-
-
-
-void
-dkim_exim_verify_init(BOOL dot_stuffing)
-{
-dkim_exim_init();
-
-/* There is a store-reset between header & body reception for the main pool
-(actually, after every header line) so cannot use that as we need the data we
-store per-header, during header processing, at the end of body reception
-for evaluating the signature.  Any allocs done for dkim verify
-memory-handling must use a different pool.  We use a separate one that we
-can reset per message. */
-
-dkim_verify_oldpool = store_pool;
-store_pool = POOL_MESSAGE;
-
-/* Free previous context if there is one */
-
-if (dkim_verify_ctx)
-  pdkim_free_ctx(dkim_verify_ctx);
-
-/* Create new context */
-
-dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
-dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
-dkim_collect_error = NULL;
-
-/* Start feed up with any cached data, but limited to message data */
-receive_get_cache(chunking_state == CHUNKING_LAST
-                 ? chunking_data_left : GETC_BUFFER_UNLIMITED);
-
-store_pool = dkim_verify_oldpool;
-}
-
-
-/* Submit a chunk of data for verification input.
-Only use the data when the feed is activated. */
-void
-dkim_exim_verify_feed(uschar * data, int len)
-{
-int rc;
-
-store_pool = POOL_MESSAGE;
-if (  dkim_collect_input
-   && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
-  {
-  dkim_collect_error = pdkim_errstr(rc);
-  log_write(0, LOG_MAIN,
-            "DKIM: validation error: %.100s", dkim_collect_error);
-  dkim_collect_input = 0;
-  }
-store_pool = dkim_verify_oldpool;
-}
-
-
-/* Log the result for the given signature */
-static void
-dkim_exim_verify_log_sig(pdkim_signature * sig)
-{
-gstring * logmsg;
-uschar * s;
-
-if (!sig) return;
-
-/* Remember the domain for the first pass result */
-
-if (  !dkim_verify_overall
-   && dkim_verify_status
-      ? Ustrcmp(dkim_verify_status, US"pass") == 0
-      : sig->verify_status == PDKIM_VERIFY_PASS
-   )
-  dkim_verify_overall = string_copy(sig->domain);
-
-/* Rewrite the sig result if the ACL overrode it.  This is only
-needed because the DMARC code (sigh) peeks at the dkim sigs.
-Mark the sig for this having been done. */
-
-if (  dkim_verify_status
-   && (  dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
-      || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
-   )  )
-  {                    /* overridden by ACL */
-  sig->verify_ext_status = -1;
-  if (Ustrcmp(dkim_verify_status, US"fail") == 0)
-    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_FAIL;
-  else if (Ustrcmp(dkim_verify_status, US"invalid") == 0)
-    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_INVALID;
-  else if (Ustrcmp(dkim_verify_status, US"none") == 0)
-    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_NONE;
-  else if (Ustrcmp(dkim_verify_status, US"pass") == 0)
-    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_PASS;
-  else
-    sig->verify_status = -1;
-  }
-
-if (!LOGGING(dkim_verbose)) return;
-
-
-logmsg = string_catn(NULL, US"DKIM: ", 6);
-if (!(s = sig->domain)) s = US"<UNSET>";
-logmsg = string_append(logmsg, 2, "d=", s);
-if (!(s = sig->selector)) s = US"<UNSET>";
-logmsg = string_append(logmsg, 2, " s=", s);
-logmsg = string_fmt_append(logmsg, " c=%s/%s a=%s b=" SIZE_T_FMT,
-         sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
-         sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
-         dkim_sig_to_a_tag(sig),
-         (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : (size_t)0);
-if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
-if (sig->created > 0) logmsg = string_fmt_append(logmsg, " t=%lu",
-                                   sig->created);
-if (sig->expires > 0) logmsg = string_fmt_append(logmsg, " x=%lu",
-                                   sig->expires);
-if (sig->bodylength > -1) logmsg = string_fmt_append(logmsg, " l=%lu",
-                                   sig->bodylength);
-
-if (sig->verify_status & PDKIM_VERIFY_POLICY)
-  logmsg = string_append(logmsg, 5,
-           US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
-else
-  switch (sig->verify_status)
-    {
-    case PDKIM_VERIFY_NONE:
-      logmsg = string_cat(logmsg, US" [not verified]");
-      break;
-
-    case PDKIM_VERIFY_INVALID:
-      logmsg = string_cat(logmsg, US" [invalid - ");
-      switch (sig->verify_ext_status)
-       {
-       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-         logmsg = string_cat(logmsg,
-                       US"public key record (currently?) unavailable]");
-         break;
-
-       case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
-         logmsg = string_cat(logmsg, US"overlong public key record]");
-         break;
-
-       case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
-       case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
-         logmsg = string_cat(logmsg, US"syntax error in public key record]");
-         break;
-
-       case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
-         logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
-         break;
-
-       case PDKIM_VERIFY_INVALID_DKIM_VERSION:
-         logmsg = string_cat(logmsg, US"unsupported DKIM version]");
-         break;
-
-       default:
-         logmsg = string_cat(logmsg, US"unspecified problem]");
-       }
-      break;
-
-    case PDKIM_VERIFY_FAIL:
-      logmsg = string_cat(logmsg, US" [verification failed - ");
-      switch (sig->verify_ext_status)
-       {
-       case PDKIM_VERIFY_FAIL_BODY:
-         logmsg = string_cat(logmsg,
-              US"body hash mismatch (body probably modified in transit)]");
-         break;
-
-       case PDKIM_VERIFY_FAIL_MESSAGE:
-         logmsg = string_cat(logmsg,
-               US"signature did not verify "
-               "(headers probably modified in transit)]");
-         break;
-
-       case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:
-         logmsg = string_cat(logmsg,
-               US"signature invalid (key too short)]");
-         break;
-
-       default:
-         logmsg = string_cat(logmsg, US"unspecified reason]");
-       }
-      break;
-
-    case PDKIM_VERIFY_PASS:
-      logmsg = string_cat(logmsg, US" [verification succeeded]");
-      break;
-    }
-
-log_write(0, LOG_MAIN, "%Y", logmsg);
-return;
-}
-
-
-/* Log a line for each signature */
-void
-dkim_exim_verify_log_all(void)
-{
-for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-  dkim_exim_verify_log_sig(sig);
-}
-
-
-void
-dkim_exim_verify_finish(void)
-{
-int rc;
-gstring * g = NULL;
-const uschar * errstr = NULL;
-
-store_pool = POOL_MESSAGE;
-
-/* Delete eventual previous signature chain */
-
-dkim_signers = NULL;
-dkim_signatures = NULL;
-
-if (dkim_collect_error)
-  {
-  log_write(0, LOG_MAIN,
-      "DKIM: Error during validation, disabling signature verification: %.100s",
-      dkim_collect_error);
-  f.dkim_disable_verify = TRUE;
-  goto out;
-  }
-
-dkim_collect_input = 0;
-
-/* Finish DKIM operation and fetch link to signatures chain */
-
-rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
-                       &errstr);
-if (rc != PDKIM_OK && errstr)
-  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
-
-/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
-
-for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-  {
-  if (sig->domain)   g = string_append_listele(g, ':', sig->domain);
-  if (sig->identity) g = string_append_listele(g, ':', sig->identity);
-  }
-gstring_release_unused(g);
-dkim_signers = string_from_gstring(g);
-
-out:
-store_pool = dkim_verify_oldpool;
-}
-
-
-
-/* Args as per dkim_exim_acl_run() below */
-static int
-dkim_acl_call(uschar * id, gstring ** res_ptr,
-  uschar ** user_msgptr, uschar ** log_msgptr)
-{
-int rc;
-DEBUG(D_receive)
-  debug_printf("calling acl_smtp_dkim for identity '%s' domain '%s' sel '%s'\n",
-             id, dkim_signing_domain, dkim_signing_selector);
-
-rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
-dkim_exim_verify_log_sig(dkim_cur_sig);
-*res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
-return rc;
-}
-
-
-
-/* For the given identity, run the DKIM ACL once for each matching signature.
-If none match, run it once.
-
-Arguments
- id            Identity to look for in dkim signatures
- res_ptr       ptr to growable string-list of status results,
-               appended to per ACL run
- user_msgptr   where to put a user error (for SMTP response)
- log_msgptr    where to put a logging message (not for SMTP response)
-
-Returns:       OK         access is granted by an ACCEPT verb
-               DISCARD    access is granted by a DISCARD verb
-               FAIL       access is denied
-               FAIL_DROP  access is denied; drop the connection
-               DEFER      can't tell at the moment
-               ERROR      disaster
-*/
-
-int
-dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
-  uschar ** user_msgptr, uschar ** log_msgptr)
-{
-uschar * cmp_val;
-int rc = -1;
-
-dkim_verify_status = US"none";
-dkim_verify_reason = US"";
-dkim_cur_signer = id;
-
-if (f.dkim_disable_verify || !id || !dkim_verify_ctx)
-  return OK;
-
-/* Find signatures to run ACL on */
-
-for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-  if (  (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
-     && strcmpic(cmp_val, id) == 0
-     )
-    {
-    /* The "dkim_domain" and "dkim_selector" expansion variables have
-    related globals, since they are used in the signing code too.
-    Instead of inventing separate names for verification, we set
-    them here. This is easy since a domain and selector is guaranteed
-    to be in a signature. The other dkim_* expansion items are
-    dynamically fetched from dkim_cur_sig at expansion time (see
-    dkim_exim_expand_query() below). */
-
-    dkim_cur_sig = sig;
-    dkim_signing_domain = US sig->domain;
-    dkim_signing_selector = US sig->selector;
-    dkim_key_length = sig->keybits;
-
-    /* These two return static strings, so we can compare the addr
-    later to see if the ACL overwrote them.  Check that when logging */
-
-    dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
-    dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
-
-    if (  (rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK
-       || dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
-      return rc;
-    }
-
-if (rc != -1)
-  return rc;
-
-/* No matching sig found.  Call ACL once anyway. */
-
-dkim_cur_sig = NULL;
-return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
-}
-
-
-static uschar *
-dkim_exim_expand_defaults(int what)
-{
-switch (what)
-  {
-  case DKIM_ALGO:              return US"";
-  case DKIM_BODYLENGTH:                return US"9999999999999";
-  case DKIM_CANON_BODY:                return US"";
-  case DKIM_CANON_HEADERS:     return US"";
-  case DKIM_COPIEDHEADERS:     return US"";
-  case DKIM_CREATED:           return US"0";
-  case DKIM_EXPIRES:           return US"9999999999999";
-  case DKIM_HEADERNAMES:       return US"";
-  case DKIM_IDENTITY:          return US"";
-  case DKIM_KEY_GRANULARITY:   return US"*";
-  case DKIM_KEY_SRVTYPE:       return US"*";
-  case DKIM_KEY_NOTES:         return US"";
-  case DKIM_KEY_TESTING:       return US"0";
-  case DKIM_NOSUBDOMAINS:      return US"0";
-  case DKIM_VERIFY_STATUS:     return US"none";
-  case DKIM_VERIFY_REASON:     return US"";
-  default:                     return US"";
-  }
-}
-
-
-uschar *
-dkim_exim_expand_query(int what)
-{
-if (!dkim_verify_ctx || f.dkim_disable_verify || !dkim_cur_sig)
-  return dkim_exim_expand_defaults(what);
-
-switch (what)
-  {
-  case DKIM_ALGO:
-    return dkim_sig_to_a_tag(dkim_cur_sig);
-
-  case DKIM_BODYLENGTH:
-    return dkim_cur_sig->bodylength >= 0
-      ? string_sprintf("%ld", dkim_cur_sig->bodylength)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_CANON_BODY:
-    switch (dkim_cur_sig->canon_body)
-      {
-      case PDKIM_CANON_RELAXED:        return US"relaxed";
-      case PDKIM_CANON_SIMPLE:
-      default:                 return US"simple";
-      }
-
-  case DKIM_CANON_HEADERS:
-    switch (dkim_cur_sig->canon_headers)
-      {
-      case PDKIM_CANON_RELAXED:        return US"relaxed";
-      case PDKIM_CANON_SIMPLE:
-      default:                 return US"simple";
-      }
-
-  case DKIM_COPIEDHEADERS:
-    return dkim_cur_sig->copiedheaders
-      ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
-
-  case DKIM_CREATED:
-    return dkim_cur_sig->created > 0
-      ? string_sprintf("%lu", dkim_cur_sig->created)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_EXPIRES:
-    return dkim_cur_sig->expires > 0
-      ? string_sprintf("%lu", dkim_cur_sig->expires)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_HEADERNAMES:
-    return dkim_cur_sig->headernames
-      ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
-
-  case DKIM_IDENTITY:
-    return dkim_cur_sig->identity
-      ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
-
-  case DKIM_KEY_GRANULARITY:
-    return dkim_cur_sig->pubkey
-      ? dkim_cur_sig->pubkey->granularity
-      ? US dkim_cur_sig->pubkey->granularity
-      : dkim_exim_expand_defaults(what)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_KEY_SRVTYPE:
-    return dkim_cur_sig->pubkey
-      ? dkim_cur_sig->pubkey->srvtype
-      ? US dkim_cur_sig->pubkey->srvtype
-      : dkim_exim_expand_defaults(what)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_KEY_NOTES:
-    return dkim_cur_sig->pubkey
-      ? dkim_cur_sig->pubkey->notes
-      ? US dkim_cur_sig->pubkey->notes
-      : dkim_exim_expand_defaults(what)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_KEY_TESTING:
-    return dkim_cur_sig->pubkey
-      ? dkim_cur_sig->pubkey->testing
-      ? US"1"
-      : dkim_exim_expand_defaults(what)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_NOSUBDOMAINS:
-    return dkim_cur_sig->pubkey
-      ? dkim_cur_sig->pubkey->no_subdomaining
-      ? US"1"
-      : dkim_exim_expand_defaults(what)
-      : dkim_exim_expand_defaults(what);
-
-  case DKIM_VERIFY_STATUS:
-    switch (dkim_cur_sig->verify_status)
-      {
-      case PDKIM_VERIFY_INVALID:       return US"invalid";
-      case PDKIM_VERIFY_FAIL:          return US"fail";
-      case PDKIM_VERIFY_PASS:          return US"pass";
-      case PDKIM_VERIFY_NONE:
-      default:                         return US"none";
-      }
-
-  case DKIM_VERIFY_REASON:
-    switch (dkim_cur_sig->verify_ext_status)
-      {
-      case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-                                               return US"pubkey_unavailable";
-      case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
-      case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
-      case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:        return US"pubkey_too_short";
-      case PDKIM_VERIFY_FAIL_BODY:             return US"bodyhash_mismatch";
-      case PDKIM_VERIFY_FAIL_MESSAGE:          return US"signature_incorrect";
-      }
-
-  default:
-    return US"";
-  }
-}
-
-
-void
-dkim_exim_sign_init(void)
-{
-int old_pool = store_pool;
-
-dkim_exim_init();
-store_pool = POOL_MAIN;
-pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
-store_pool = old_pool;
-}
-
-
-/* Generate signatures for the given file.
-If a prefix is given, prepend it to the file for the calculations.
-
-Return:
-  NULL:                error; error string written
-  string:      signature header(s), or a zero-length string (not an error)
-*/
-
-gstring *
-dkim_exim_sign(int fd, off_t off, uschar * prefix,
-  struct ob_dkim * dkim, const uschar ** errstr)
-{
-const uschar * dkim_domain = NULL;
-int sep = 0;
-gstring * seen_doms = NULL;
-pdkim_signature * sig;
-gstring * sigbuf;
-int pdkim_rc;
-int sread;
-uschar buf[4096];
-int save_errno = 0;
-int old_pool = store_pool;
-uschar * errwhen;
-const uschar * s;
-
-if (dkim->dot_stuffed)
-  dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
-
-store_pool = POOL_MAIN;
-
-GET_OPTION("dkim_domain");
-if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
-  /* expansion error, do not send message. */
-  { errwhen = US"dkim_domain"; goto expand_bad; }
-
-/* Set $dkim_domain expansion variable to each unique domain in list. */
-
-if (dkim_domain)
-  while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
-  {
-  const uschar * dkim_sel;
-  int sel_sep = 0;
-
-  if (dkim_signing_domain[0] == '\0')
-    continue;
-
-  /* Only sign once for each domain, no matter how often it
-  appears in the expanded list. */
-
-  dkim_signing_domain = string_copylc(dkim_signing_domain);
-  if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
-      0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
-    continue;
-
-  seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
-
-  /* Set $dkim_selector expansion variable to each selector in list,
-  for this domain. */
-
-  GET_OPTION("dkim_selector");
-  if (!(dkim_sel = expand_string(dkim->dkim_selector)))
-    { errwhen = US"dkim_selector"; goto expand_bad; }
-
-  while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
-         NULL, 0)))
-    {
-    uschar * dkim_canon_expanded;
-    int pdkim_canon;
-    uschar * dkim_sign_headers_expanded = NULL;
-    uschar * dkim_private_key_expanded;
-    uschar * dkim_hash_expanded;
-    uschar * dkim_identity_expanded = NULL;
-    uschar * dkim_timestamps_expanded = NULL;
-    unsigned long tval = 0, xval = 0;
-
-    /* Get canonicalization to use */
-
-    GET_OPTION("dkim_canon");
-    dkim_canon_expanded = dkim->dkim_canon
-      ? expand_string(dkim->dkim_canon) : US"relaxed";
-    if (!dkim_canon_expanded)  /* expansion error, do not send message. */
-      { errwhen = US"dkim_canon"; goto expand_bad; }
-
-    if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
-      pdkim_canon = PDKIM_CANON_RELAXED;
-    else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
-      pdkim_canon = PDKIM_CANON_SIMPLE;
-    else
-      {
-      log_write(0, LOG_MAIN,
-                "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
-                dkim_canon_expanded);
-      pdkim_canon = PDKIM_CANON_RELAXED;
-      }
-
-    GET_OPTION("dkim_sign_headers");
-    if (  dkim->dkim_sign_headers
-       && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
-      { errwhen = US"dkim_sign_header"; goto expand_bad; }
-    /* else pass NULL, which means default header list */
-
-    /* Get private key to use. */
-
-    GET_OPTION("dkim_private_key");
-    if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
-      { errwhen = US"dkim_private_key"; goto expand_bad; }
-
-    if (  Ustrlen(dkim_private_key_expanded) == 0
-       || Ustrcmp(dkim_private_key_expanded, "0") == 0
-       || Ustrcmp(dkim_private_key_expanded, "false") == 0
-       )
-      continue;                /* don't sign, but no error */
-
-    if (  dkim_private_key_expanded[0] == '/'
-       && !(dkim_private_key_expanded =
-            expand_file_big_buffer(dkim_private_key_expanded)))
-      goto bad;
-
-    GET_OPTION("dkim_hash");
-    if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
-      { errwhen = US"dkim_hash"; goto expand_bad; }
-
-    GET_OPTION("dkim_identity");
-    if (dkim->dkim_identity)
-      if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
-       { errwhen = US"dkim_identity"; goto expand_bad; }
-      else if (!*dkim_identity_expanded)
-       dkim_identity_expanded = NULL;
-
-    GET_OPTION("dkim_timestamps");
-    if (dkim->dkim_timestamps)
-      if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
-       { errwhen = US"dkim_timestamps"; goto expand_bad; }
-      else
-        {
-        tval = (unsigned long) time(NULL);
-        xval = strtoul(CCS dkim_timestamps_expanded, NULL, 10);
-        if (xval > 0)
-          xval += tval;
-        }
-
-    if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
-                         dkim_signing_selector,
-                         dkim_private_key_expanded,
-                         dkim_hash_expanded,
-                         errstr
-                         )))
-      goto bad;
-    dkim_private_key_expanded[0] = '\0';
-
-    pdkim_set_optional(sig,
-                       CS dkim_sign_headers_expanded,
-                       CS dkim_identity_expanded,
-                       pdkim_canon,
-                       pdkim_canon, -1, tval, xval);
-
-    if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
-      goto bad;
-
-    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_domain);
-    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_selector);
-
-    if (!dkim_sign_ctx.sig)            /* link sig to context chain */
-      dkim_sign_ctx.sig = sig;
-    else
-      {
-      pdkim_signature * n = dkim_sign_ctx.sig;
-      while (n->next) n = n->next;
-      n->next = sig;
-      }
-    }
-  }
-
-/* We may need to carry on with the data-feed even if there are no DKIM sigs to
-produce, if some other package (eg. ARC) is signing. */
-
-if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
-  {
-  DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
-  sigbuf = string_get(1);      /* return a zero-len string */
-  }
-else
-  {
-  if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
-    goto pk_bad;
-
-  if (lseek(fd, off, SEEK_SET) < 0)
-    sread = -1;
-  else
-    while ((sread = read(fd, &buf, sizeof(buf))) > 0)
-      if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK)
-       goto pk_bad;
-
-  /* Handle failed read above. */
-  if (sread == -1)
-    {
-    debug_printf("DKIM: Error reading -K file.\n");
-    save_errno = errno;
-    goto bad;
-    }
-
-  /* Build string of headers, one per signature */
-
-  if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
-    goto pk_bad;
-
-  if (!sig)
-    {
-    DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
-    sigbuf = string_get(1);    /* return a zero-len string */
-    }
-  else for (sigbuf = NULL; sig; sig = sig->next)
-    sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
-  }
-
-CLEANUP:
-  (void) string_from_gstring(sigbuf);
-  store_pool = old_pool;
-  errno = save_errno;
-  return sigbuf;
-
-pk_bad:
-  log_write(0, LOG_MAIN|LOG_PANIC,
-               "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
-bad:
-  sigbuf = NULL;
-  goto CLEANUP;
-
-expand_bad:
-  *errstr = string_sprintf("failed to expand %s: %s",
-              errwhen, expand_string_message);
-  log_write(0, LOG_MAIN | LOG_PANIC, "%s", *errstr);
-  goto bad;
-}
-
-
-
-
-gstring *
-authres_dkim(gstring * g)
-{
-int start = 0;         /* compiler quietening */
-
-DEBUG(D_acl) start = gstring_length(g);
-
-for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
-  {
-  g = string_catn(g, US";\n\tdkim=", 8);
-
-  if (sig->verify_status & PDKIM_VERIFY_POLICY)
-    g = string_append(g, 5,
-      US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
-  else switch(sig->verify_status)
-    {
-    case PDKIM_VERIFY_NONE:    g = string_cat(g, US"none"); break;
-    case PDKIM_VERIFY_INVALID:
-      switch (sig->verify_ext_status)
-       {
-       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
-          g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
-        case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
-          g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
-        case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
-        case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
-          g = string_cat(g, US"neutral (public key record import problem)\n\t\t");
-          break;
-        case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
-          g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
-          break;
-        case PDKIM_VERIFY_INVALID_DKIM_VERSION:
-          g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
-          break;
-        default:
-          g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
-       }
-      break;
-    case PDKIM_VERIFY_FAIL:
-      switch (sig->verify_ext_status)
-       {
-       case PDKIM_VERIFY_FAIL_BODY:
-          g = string_cat(g,
-           US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
-         break;
-        case PDKIM_VERIFY_FAIL_MESSAGE:
-          g = string_cat(g,
-           US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
-         break;
-        case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:      /* should this really be "polcy"? */
-          g = string_fmt_append(g, "fail (public key too short: %u bits)\n\t\t", sig->keybits);
-          break;
-        default:
-          g = string_cat(g, US"fail (unspecified reason)\n\t\t");
-         break;
-       }
-      break;
-    case PDKIM_VERIFY_PASS:    g = string_cat(g, US"pass"); break;
-    default:                   g = string_cat(g, US"permerror"); break;
-    }
-  if (sig->domain)   g = string_append(g, 2, US" header.d=", sig->domain);
-  if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
-  if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
-  g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
-  }
-
-DEBUG(D_acl)
-  if (gstring_length(g) == start)
-    debug_printf("DKIM:\tno authres\n");
-  else
-    debug_printf("DKIM:\tauthres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
-return g;
-}
-
-
-# endif        /*!MACRO_PREDEF*/
-#endif /*!DISABLE_DKIM*/
diff --git a/src/src/dkim.h b/src/src/dkim.h
deleted file mode 100644 (file)
index 915c6c7..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) University of Cambridge, 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-void    dkim_exim_init(void);
-gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
-void    dkim_exim_verify_init(BOOL);
-void    dkim_exim_verify_feed(uschar *, int);
-void    dkim_exim_verify_finish(void);
-void    dkim_exim_verify_log_all(void);
-int     dkim_exim_acl_run(uschar *, gstring **, uschar **, uschar **);
-uschar *dkim_exim_expand_query(int);
-
-#define DKIM_ALGO               1
-#define DKIM_BODYLENGTH         2
-#define DKIM_CANON_BODY         3
-#define DKIM_CANON_HEADERS      4
-#define DKIM_COPIEDHEADERS      5
-#define DKIM_CREATED            6
-#define DKIM_EXPIRES            7
-#define DKIM_HEADERNAMES        8
-#define DKIM_IDENTITY           9
-#define DKIM_KEY_GRANULARITY   10
-#define DKIM_KEY_SRVTYPE       11
-#define DKIM_KEY_NOTES         12
-#define DKIM_KEY_TESTING       13
-#define DKIM_NOSUBDOMAINS      14
-#define DKIM_VERIFY_STATUS     15
-#define DKIM_VERIFY_REASON     16
diff --git a/src/src/dkim_transport.c b/src/src/dkim_transport.c
deleted file mode 100644 (file)
index 63870c5..0000000
+++ /dev/null
@@ -1,420 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) The Exim Maintainers 2022 - 2024 */
-/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Transport shim for dkim signing */
-
-
-#include "exim.h"
-
-#ifndef DISABLE_DKIM   /* rest of file */
-
-
-static BOOL
-dkt_sign_fail(struct ob_dkim * dkim, int * errp)
-{
-GET_OPTION("dkim_strict");
-if (dkim->dkim_strict)
-  {
-  uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
-
-  if (dkim_strict_result)
-    if (  strcmpic(dkim_strict_result, US"1") == 0
-       || strcmpic(dkim_strict_result, US"true") == 0)
-      {
-      /* Set errno to something halfway meaningful */
-      *errp = EACCES;
-      log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
-       " and dkim_strict is set. Deferring message delivery.");
-      return FALSE;
-      }
-  }
-return TRUE;
-}
-
-/* Send the file at in_fd down the output fd */
-
-static BOOL
-dkt_send_file(int out_fd, int in_fd, off_t off
-#ifdef OS_SENDFILE
-  , size_t size
-#endif
-  )
-{
-#ifdef OS_SENDFILE
-DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
-#else
-DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
-#endif
-
-/*XXX should implement timeout, like transport_write_block_fd() ? */
-
-#ifdef OS_SENDFILE
-/* We can use sendfile() to shove the file contents
-   to the socket. However only if we don't use TLS,
-   as then there's another layer of indirection
-   before the data finally hits the socket. */
-if (tls_out.active.sock != out_fd)
-  {
-  ssize_t copied = 0;
-
-  while(copied >= 0 && off < size)
-    copied = os_sendfile(out_fd, in_fd, &off, size - off);
-  if (copied < 0)
-    return FALSE;
-  }
-else
-
-#endif
-
-  {
-  int sread, wwritten;
-
-  /* Rewind file */
-  if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
-
-  /* Send file down the original fd */
-  while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
-    {
-    uschar * p = deliver_out_buffer;
-    /* write the chunk */
-
-    while (sread)
-      {
-#ifndef DISABLE_TLS
-      wwritten = tls_out.active.sock == out_fd
-       ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
-       : write(out_fd, CS p, sread);
-#else
-      wwritten = write(out_fd, CS p, sread);
-#endif
-      if (wwritten == -1)
-       return FALSE;
-      p += wwritten;
-      sread -= wwritten;
-      }
-    }
-
-  if (sread == -1)
-    return FALSE;
-  }
-
-return TRUE;
-}
-
-
-
-
-/* This function is a wrapper around transport_write_message().
-   It is only called from the smtp transport if DKIM or Domainkeys support
-   is active and no transport filter is to be used.
-
-Arguments:
-  As for transport_write_message() in transort.c, with additional arguments
-  for DKIM.
-
-Returns:       TRUE on success; FALSE (with errno) for any failure
-*/
-
-static BOOL
-dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
-  const uschar ** err)
-{
-int save_fd = tctx->u.fd;
-int save_options = tctx->options;
-BOOL save_wireformat = f.spool_file_wireformat;
-uschar * hdrs;
-gstring * dkim_signature;
-int hsize;
-const uschar * errstr;
-BOOL rc;
-
-DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
-
-/* Get headers in string for signing and transmission.  Do CRLF
-and dotstuffing (but no body nor dot-termination) */
-
-tctx->u.msg = NULL;
-tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
-  | topt_output_string | topt_no_body;
-
-rc = transport_write_message(tctx, 0);
-hdrs = string_from_gstring(tctx->u.msg);
-hsize = tctx->u.msg->ptr;
-
-tctx->u.fd = save_fd;
-tctx->options = save_options;
-if (!rc) return FALSE;
-
-/* Get signatures for headers plus spool data file */
-
-#ifdef EXPERIMENTAL_ARC
-arc_sign_init();
-#endif
-
-/* The dotstuffed status of the datafile depends on whether it was stored
-in wireformat. */
-
-dkim->dot_stuffed = f.spool_file_wireformat;
-if (!(dkim_signature = dkim_exim_sign(deliver_datafile,
-             spool_data_start_offset(message_id), hdrs, dkim, &errstr)))
-  if (!(rc = dkt_sign_fail(dkim, &errno)))
-    {
-    *err = errstr;
-    return FALSE;
-    }
-
-#ifdef EXPERIMENTAL_ARC
-if (dkim->arc_signspec)                        /* Prepend ARC headers */
-  {
-  uschar * e = NULL;
-  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
-    {
-    *err = e;
-    return FALSE;
-    }
-  }
-#endif
-
-/* Write the signature and headers into the deliver-out-buffer.  This should
-mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
-(transport_write_message() sizes the BDAT for the buffered amount) - for short
-messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
-having already been done - but we have to say we want CRLF output format, and
-temporarily set the marker for possible already-CRLF input. */
-
-tctx->options &= ~topt_escape_headers;
-f.spool_file_wireformat = TRUE;
-transport_write_reset(0);
-if (  (  dkim_signature
-      && dkim_signature->ptr > 0
-      && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
-      )
-   || !write_chunk(tctx, hdrs, hsize)
-   )
-  return FALSE;
-
-f.spool_file_wireformat = save_wireformat;
-tctx->options = save_options | topt_no_headers | topt_continuation;
-
-if (!(transport_write_message(tctx, 0)))
-  return FALSE;
-
-tctx->options = save_options;
-return TRUE;
-}
-
-
-/* This function is a wrapper around transport_write_message().
-   It is only called from the smtp transport if DKIM or Domainkeys support
-   is active and a transport filter is to be used.  The function sets up a
-   replacement fd into a -K file, then calls the normal function. This way, the
-   exact bits that exim would have put "on the wire" will end up in the file
-   (except for TLS encapsulation, which is the very very last thing). When we
-   are done signing the file, send the signed message down the original fd (or
-   TLS fd).
-
-Arguments:
-  As for transport_write_message() in transort.c, with additional arguments
-  for DKIM.
-
-Returns:       TRUE on success; FALSE (with errno) for any failure
-*/
-
-static BOOL
-dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
-{
-int dkim_fd;
-int save_errno = 0;
-BOOL rc;
-uschar * dkim_spool_name;
-gstring * dkim_signature;
-int options, dlen;
-off_t k_file_size;
-const uschar * errstr;
-
-dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
-                   string_sprintf("-%d-K", (int)getpid()));
-
-DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
-
-if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
-  {
-  /* Can't create spool file. Ugh. */
-  rc = FALSE;
-  save_errno = errno;
-  *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
-  goto CLEANUP;
-  }
-
-/* Call transport utility function to write the -K file; does the CRLF expansion
-(but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
-
-  {
-  int save_fd = tctx->u.fd;
-  tctx->u.fd = dkim_fd;
-  options = tctx->options;
-  tctx->options &= ~topt_use_bdat;
-
-  rc = transport_write_message(tctx, 0);
-
-  tctx->u.fd = save_fd;
-  tctx->options = options;
-  }
-
-/* Save error state. We must clean up before returning. */
-if (!rc)
-  {
-  save_errno = errno;
-  goto CLEANUP;
-  }
-
-#ifdef EXPERIMENTAL_ARC
-arc_sign_init();
-#endif
-
-/* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
-status of the file depends on the output of transport_write_message() just
-above, which should be the result of the end_dot flag in tctx->options. */
-
-dkim->dot_stuffed = !!(options & topt_end_dot);
-if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
-  {
-  dlen = 0;
-  if (!(rc = dkt_sign_fail(dkim, &save_errno)))
-    {
-    *err = errstr;
-    goto CLEANUP;
-    }
-  }
-else
-  dlen = dkim_signature->ptr;
-
-#ifdef EXPERIMENTAL_ARC
-if (dkim->arc_signspec)                                /* Prepend ARC headers */
-  {
-  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
-    goto CLEANUP;
-  dlen = dkim_signature->ptr;
-  }
-#endif
-
-#ifndef OS_SENDFILE
-if (options & topt_use_bdat)
-#endif
-  if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
-    {
-    *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
-    goto CLEANUP;
-    }
-
-if (options & topt_use_bdat)
-  {
-  /* On big messages output a precursor chunk to get any pipelined
-  MAIL & RCPT commands flushed, then reap the responses so we can
-  error out on RCPT rejects before sending megabytes. */
-
-  if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
-     && dlen > 0)
-    {
-    if (  tctx->chunk_cb(tctx, dlen, 0) != OK
-       || !transport_write_block(tctx,
-                   dkim_signature->s, dlen, FALSE)
-       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
-       )
-      goto err;
-    dlen = 0;
-    }
-
-  /* Send the BDAT command for the entire message, as a single LAST-marked
-  chunk. */
-
-  if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
-    goto err;
-  }
-
-if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
-  goto err;
-
-if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
-#ifdef OS_SENDFILE
-  , k_file_size
-#endif
-  ))
-  {
-  save_errno = errno;
-  rc = FALSE;
-  }
-
-CLEANUP:
-  /* unlink -K file */
-  if (dkim_fd >= 0) (void)close(dkim_fd);
-  Uunlink(dkim_spool_name);
-  errno = save_errno;
-  return rc;
-
-err:
-  save_errno = errno;
-  rc = FALSE;
-  goto CLEANUP;
-}
-
-
-
-/***************************************************************************************************
-*    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
-***************************************************************************************************/
-
-/* This function is a wrapper around transport_write_message().
-   It is only called from the smtp transport if DKIM or Domainkeys support
-   is compiled in.
-
-Arguments:
-  As for transport_write_message() in transort.c, with additional arguments
-  for DKIM.
-
-Returns:       TRUE on success; FALSE (with errno) for any failure
-*/
-
-BOOL
-dkim_transport_write_message(transport_ctx * tctx,
-  struct ob_dkim * dkim, const uschar ** err)
-{
-BOOL yield;
-
-/* If we can't sign, just call the original function. */
-
-if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
-   && !dkim->force_bodyhash)
-  return transport_write_message(tctx, 0);
-
-/* If there is no filter command set up, construct the message and calculate
-a dkim signature of it, send the signature and a reconstructed message. This
-avoids using a temprary file. */
-
-if (  !transport_filter_argv
-   || !*transport_filter_argv
-   || !**transport_filter_argv
-   )
-  yield = dkt_direct(tctx, dkim, err);
-
-else
-  /* Use the transport path to write a file, calculate a dkim signature,
-  send the signature and then send the file. */
-
-  yield = dkt_via_kfile(tctx, dkim, err);
-
-tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
-return yield;
-}
-
-#endif /* whole file */
-
-/* vi: aw ai sw=2
-*/
-/* End of dkim_transport.c */
index 9ed55e29a02c9bc64dad53d6f2801865eae81e3a..61ced3e6a5e92944e841d70ca0263022cd8e36af 100644 (file)
@@ -431,12 +431,16 @@ misc_module_info * misc_module_list = NULL;
 static void
 misc_mod_add(misc_module_info * mi)
 {
-if (mi->init) mi->init(mi);
-DEBUG(D_any) if (mi->lib_vers_report)
-  debug_printf_indent("%Y", mi->lib_vers_report(NULL));
+if (mi->init && mi->init(mi))
+  {
+  DEBUG(D_any) if (mi->lib_vers_report)
+    debug_printf_indent("%Y", mi->lib_vers_report(NULL));
 
-mi->next = misc_module_list;
-misc_module_list = mi;
+  mi->next = misc_module_list;
+  misc_module_list = mi;
+  }
+else DEBUG(D_any)
+  debug_printf_indent("module init call failed for %s\n", mi->name);
 }
 
 
@@ -453,7 +457,10 @@ const char * errormsg;
 
 DEBUG(D_any) debug_printf_indent("loading module '%s'\n", name);
 if (!(dl = mod_open(name, US"miscmod", errstr)))
+  {
+  DEBUG(D_any) debug_printf_indent(" mod_open: %s\n", *errstr);
   return NULL;
+  }
 
 mi = (struct misc_module_info *) dlsym(dl,
                                    CS string_sprintf("%s_module_info", name));
@@ -546,6 +553,39 @@ for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
 return OK;
 }
 
+/* Ditto, authres.  Having to sort the responses (mainly for the testsuite)
+is pretty painful - maybe we should sort the modules on insertion to
+the list? */
+
+gstring *
+misc_mod_authres(gstring * g)
+{
+typedef struct {
+  const uschar * name;
+  gstring *     res;
+} pref;
+pref prefs[] = {
+  {US"spf", NULL}, {US"dkim", NULL}, {US"dmarc", NULL}, {US"arc", NULL}
+};
+gstring * others = NULL;
+
+for (const misc_module_info * mi = misc_module_list; mi; mi = mi->next)
+  if (mi->authres)
+    {
+    pref * p;
+    for (p = prefs; p < prefs + nelem(prefs); p++)
+      if (Ustrcmp(p->name, mi->name) == 0) break;
+
+    if (p) p->res = (mi->authres)(NULL);
+    else   others = (mi->authres)(others);
+    }
+
+for (pref * p = prefs; p < prefs + nelem(prefs); p++)
+  g = gstring_append(g, p->res);
+return gstring_append(g, others);
+}
+
+
 
 
 
@@ -697,6 +737,9 @@ DEBUG(D_lookup) debug_printf("Loaded %d lookup modules\n", countmodules);
 }
 
 
+#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
+extern misc_module_info dkim_module_info;
+#endif
 #if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
 extern misc_module_info dmarc_module_info;
 #endif
@@ -709,16 +752,18 @@ init_misc_mod_list(void)
 {
 static BOOL onetime = FALSE;
 if (onetime) return;
+onetime = TRUE;
 
+#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
+misc_mod_add(&dkim_module_info);
+#endif
 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
-/* dmarc depends on spf so this add must go first, for the dmarc-static case */
 misc_mod_add(&spf_module_info);
 #endif
 #if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+/* dmarc depends on spf so this add must go after, for the both-static case */
 misc_mod_add(&dmarc_module_info);
 #endif
-
-onetime = TRUE;
 }
 
 
index 5ad54ffc102a943885087ed059c4f9ebeea4e059..ca98e25def56ba9c2e258cf8b176878310ea296a 100644 (file)
@@ -4211,6 +4211,9 @@ 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();
+#ifdef EXPERIMENTAL_ARC
+arc_init();    /*XXX temporary, until we do an arc module */
+#endif
 
 /*XXX this excrescence could move to the testsuite standard config setup file */
 #ifdef SUPPORT_I18N
@@ -5610,9 +5613,6 @@ if (host_checking)
 
       return_path = sender_address = NULL;
       dnslist_domain = dnslist_matched = NULL;
-#ifndef DISABLE_DKIM
-      dkim_cur_signer = NULL;
-#endif
       acl_var_m = NULL;
       deliver_localpart_orig = NULL;
       deliver_domain_orig = NULL;
index c996a2f8cb28980954b9ed46b06e6381509b8433..8260dc75f56769ef2bf378a5f5f3df509e8bdce3 100644 (file)
@@ -547,7 +547,8 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly.
 # include "miscmods/spf_api.h"
 #endif
 #ifndef DISABLE_DKIM
-# include "dkim.h"
+# include "miscmods/dkim.h"
+# include "miscmods/dkim_api.h"
 #endif
 #ifdef SUPPORT_DMARC
 # include "miscmods/dmarc.h"
index af38160516afa9ab98a3960b63cb3d775fdba4c6..02680771f5269520f41c9e874250061781b5983c 100644 (file)
@@ -490,27 +490,28 @@ static var_entry var_table[] = {
   { "dcc_result",          vtype_stringptr,   &dcc_result },
 #endif
 #ifndef DISABLE_DKIM
-  { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
-  { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
-  { "dkim_canon_body",     vtype_dkim,        (void *)DKIM_CANON_BODY },
-  { "dkim_canon_headers",  vtype_dkim,        (void *)DKIM_CANON_HEADERS },
-  { "dkim_copiedheaders",  vtype_dkim,        (void *)DKIM_COPIEDHEADERS },
-  { "dkim_created",        vtype_dkim,        (void *)DKIM_CREATED },
-  { "dkim_cur_signer",     vtype_stringptr,   &dkim_cur_signer },
-  { "dkim_domain",         vtype_stringptr,   &dkim_signing_domain },
-  { "dkim_expires",        vtype_dkim,        (void *)DKIM_EXPIRES },
-  { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
-  { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
-  { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
-  { "dkim_key_length",     vtype_int,         &dkim_key_length },
-  { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
-  { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
-  { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
-  { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
-  { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
-  { "dkim_signers",        vtype_stringptr,   &dkim_signers },
-  { "dkim_verify_reason",  vtype_stringptr,   &dkim_verify_reason },
-  { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
+  { "dkim_algo",           vtype_module,       US"dkim" },
+  { "dkim_bodylength",     vtype_module,       US"dkim" },
+  { "dkim_canon_body",     vtype_module,       US"dkim" },
+  { "dkim_canon_headers",  vtype_module,       US"dkim" },
+  { "dkim_copiedheaders",  vtype_module,       US"dkim" },
+  { "dkim_created",        vtype_module,       US"dkim" },
+  { "dkim_cur_signer",     vtype_module,       US"dkim" },
+  { "dkim_domain",         vtype_module,       US"dkim" },
+  { "dkim_expires",        vtype_module,       US"dkim" },
+  { "dkim_headernames",    vtype_module,       US"dkim" },
+  { "dkim_identity",       vtype_module,       US"dkim" },
+  { "dkim_key_granularity",vtype_module,       US"dkim" },
+  { "dkim_key_length",     vtype_module,       US"dkim" },
+  { "dkim_key_nosubdomains",vtype_module,      US"dkim" },
+  { "dkim_key_notes",      vtype_module,       US"dkim" },
+  { "dkim_key_srvtype",    vtype_module,       US"dkim" },
+  { "dkim_key_testing",    vtype_module,       US"dkim" },
+  { "dkim_selector",       vtype_module,       US"dkim" },
+  { "dkim_signers",        vtype_module,       US"dkim" },
+  { "dkim_verify_reason",  vtype_module,       US"dkim" },
+  { "dkim_verify_signers", vtype_module,       US"dkim" },
+  { "dkim_verify_status",  vtype_module,       US"dkim" },
 #endif
 #ifdef SUPPORT_DMARC
   { "dmarc_domain_policy", vtype_module,       US"dmarc" },
@@ -2131,7 +2132,13 @@ switch (vp->type)
 
 #ifndef DISABLE_DKIM
   case vtype_dkim:
-    return dkim_exim_expand_query((int)(long)val);
+    {
+    misc_module_info * mi = misc_mod_findonly(US"dkim");
+    typedef uschar * (*fn_t)(int);
+    return mi
+      ? (((fn_t *) mi->functions)[DKIM_EXPAND_QUERY]) ((int)(long)val)
+      : US"";
+    }
 #endif
 
   case vtype_module:
@@ -4882,32 +4889,7 @@ while (*s)
       yield = authres_local(yield, sub_arg[0]);
       yield = authres_iprev(yield);
       yield = authres_smtpauth(yield);
-#ifdef SUPPORT_SPF
-       {
-       misc_module_info * mi = misc_mod_findonly(US"spf");
-       if (mi)
-         {
-         typedef gstring * (*fn_t)(gstring *);
-         fn_t fn = ((fn_t *) mi->functions)[SPF_AUTHRES];
-         yield = fn(yield);
-         }
-       }
-#endif
-#ifndef DISABLE_DKIM
-      yield = authres_dkim(yield);
-#endif
-#ifdef SUPPORT_DMARC
-       {
-       misc_module_info * mi = misc_mod_findonly(US"dmarc");
-       if (mi)
-         {
-         /*XXX is authres common enough to be generic? */
-         typedef gstring * (*fn_t)(gstring *);
-         fn_t fn = ((fn_t *) mi->functions)[DMARC_AUTHRES];
-         yield = fn(yield);
-         }
-       }
-#endif
+      yield = misc_mod_authres(yield);
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
 #endif
index 3a980318f37534e05b2d358c8d2e453b5c3a781d..fba5ec688523f6fbdaac1c197d09e0713e804331 100644 (file)
@@ -1,3 +1,4 @@
+extern BOOL arc_init(void);
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
@@ -144,9 +145,6 @@ extern uschar *authenticator_current_name(void);
 #ifdef EXPERIMENTAL_ARC
 extern gstring *authres_arc(gstring *);
 #endif
-#ifndef DISABLE_DKIM
-extern gstring *authres_dkim(gstring *);
-#endif
 extern gstring *authres_smtpauth(gstring *);
 
 extern uschar *b64encode(const uschar *, int);
@@ -222,13 +220,6 @@ extern void    delivery_re_exec(int);
 
 extern void    die_tainted(const uschar *, const uschar *, int);
 extern BOOL    directory_make(const uschar *, const uschar *, int, BOOL);
-#ifndef DISABLE_DKIM
-extern uschar *dkim_exim_query_dns_txt(const uschar *);
-extern void    dkim_exim_sign_init(void);
-
-extern BOOL    dkim_transport_write_message(transport_ctx *,
-                 struct ob_dkim *, const uschar ** errstr);
-#endif
 extern dns_address *dns_address_from_rr(dns_answer *, dns_record *);
 extern int     dns_basic_lookup(dns_answer *, const uschar *, int);
 extern uschar *dns_build_reverse(const uschar *);
@@ -375,6 +366,7 @@ extern int     mime_regex(const uschar **, BOOL);
 extern void    mime_set_anomaly(int);
 #endif
 
+extern gstring *misc_mod_authres(gstring *);
 extern int     misc_mod_conn_init(const uschar *, const uschar *);
 extern misc_module_info * misc_mod_find(const uschar * modname, uschar **);
 extern misc_module_info * misc_mod_findonly(const uschar * modname);
@@ -552,6 +544,7 @@ extern int     smtp_setup_msg(void);
 extern int     smtp_sock_connect(smtp_connect_args *, int, const blob *);
 extern BOOL    smtp_start_session(void);
 extern int     smtp_ungetc(int);
+extern void    smtp_verify_feed(const uschar *, unsigned);
 extern BOOL    smtp_verify_helo(void);
 extern int     smtp_write_command(void *, int, const char *, ...) PRINTF_FUNCTION(3,4);
 #ifdef WITH_CONTENT_SCAN
@@ -1112,7 +1105,7 @@ g->s = s;
 static inline gstring *
 gstring_append(gstring * dest, gstring * item)
 {
-return string_catn(dest, item->s, item->ptr);
+return item ? string_catn(dest, item->s, item->ptr) : dest;
 }
 
 
index cfa75f1d7bac98e198239ef98d874e279bf5514c..6fae1582f430d4af2eaef4460e8e99ceef8eef50 100644 (file)
@@ -864,25 +864,6 @@ address_item  *deliver_recipients = NULL;
 uschar *deliver_selectstring   = NULL;
 uschar *deliver_selectstring_sender = NULL;
 
-#ifndef DISABLE_DKIM
-unsigned dkim_collect_input      = 0;
-uschar *dkim_cur_signer          = NULL;
-int     dkim_key_length          = 0;
-void   *dkim_signatures                 = NULL;
-uschar *dkim_signers             = NULL;
-uschar *dkim_signing_domain      = NULL;
-uschar *dkim_signing_selector    = NULL;
-gstring *dkim_signing_record    = NULL;
-uschar *dkim_verify_hashes       = US"sha256:sha512";
-uschar *dkim_verify_keytypes     = US"ed25519:rsa";
-uschar *dkim_verify_min_keysizes = US"rsa=1024 ed25519=250";
-BOOL   dkim_verify_minimal      = FALSE;
-uschar *dkim_verify_overall      = NULL;
-uschar *dkim_verify_signers      = US"$dkim_signers";
-uschar *dkim_verify_status      = NULL;
-uschar *dkim_verify_reason      = NULL;
-#endif
-
 uschar *dns_again_means_nonexist = NULL;
 int     dns_csa_search_limit   = 5;
 int    dns_cname_loops        = 1;
index 8173d771e9fba0675074612462ec1cb6c39951ba..1f03cefee6f9211bb3315ef71efff4ef7ab9b72e 100644 (file)
@@ -545,25 +545,6 @@ extern BOOL    disable_fsync;          /* Not for normal use */
 #endif
 extern BOOL    disable_ipv6;           /* Don't do any IPv6 things */
 
-#ifndef DISABLE_DKIM
-extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks whether SMTP input is fed to DKIM validation */
-extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
-extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
-extern void   *dkim_signatures;               /* Actually a (pdkim_signature *) but most files do not need to know */
-extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
-extern gstring *dkim_signing_record;   /* domains+selectors used */
-extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
-extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
-extern uschar *dkim_verify_hashes;     /* Preference order for signatures */
-extern uschar *dkim_verify_keytypes;   /* Preference order for signatures */
-extern uschar *dkim_verify_min_keysizes; /* list of minimum key sizes, keyed by algo */
-extern BOOL    dkim_verify_minimal;    /* Shortcircuit signature verification */
-extern uschar *dkim_verify_overall;    /* First successful domain verified, or null */
-extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
-extern uschar *dkim_verify_status;     /* result for this signature */
-extern uschar *dkim_verify_reason;     /* result for this signature */
-#endif
-
 extern uschar *dns_again_means_nonexist; /* Domains that are badly set up */
 extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
 extern BOOL    dns_csa_use_reverse;    /* Check CSA in reverse DNS? (non-standard) */
index 17a52fe43effc6b5976546410d5d19e968c48fa7..d629a52bdf97b59b9073c6bbb9f0ebb55617d0a5 100644 (file)
@@ -227,49 +227,6 @@ memcpy(b->data, gcry_md_read(h->sha, 0), h->hashlen);
 
 
 
-#elif defined(SHA_POLARSSL)
-# define HAVE_PARTIAL_SHA
-/******************************************************************************/
-
-BOOL
-exim_sha_init(hctx * h, hashmethod m)
-{
-/*XXX extend for sha512 */
-switch (h->method = m)
-  {
-  case HASH_SHA1:   h->hashlen = 20; sha1_starts(&h->u.sha1);    break;
-  case HASH_SHA2_256: h->hashlen = 32; sha2_starts(&h->u.sha2, 0); break;
-  default:         h->hashlen = 0; return FALSE;
-  }
-return TRUE;
-}
-
-
-void
-exim_sha_update(hctx * h, const uschar * data, int len)
-{
-switch (h->method)
-  {
-  case HASH_SHA1:   sha1_update(h->u.sha1, US data, len); break;
-  case HASH_SHA2_256: sha2_update(h->u.sha2, US data, len); break;
-  }
-}
-
-
-void
-exim_sha_finish(hctx * h, blob * b)
-{
-b->data = store_get(b->len = h->hashlen, GET_INTAINTED);
-switch (h->method)
-  {
-  case HASH_SHA1:   sha1_finish(h->u.sha1, b->data); break;
-  case HASH_SHA2_256: sha2_finish(h->u.sha2, b->data); break;
-  }
-}
-
-
-
-
 #elif defined(SHA_NATIVE)
 /******************************************************************************/
 /* Only sha-1 supported */
index 788c9f0ad6571cb9700bcc4b905c744119362a44..9ad837b62958f0abc0f2d92d865b8a364e3cd1be 100644 (file)
 # include <gnutls/crypto.h>
 #elif defined(SHA_GCRYPT)
 # include <gcrypt.h>
-#elif defined(SHA_POLARSSL)
-# include "pdkim/pdkim.h"              /*XXX ugly */
-# include "pdkim/polarssl/sha1.h"
-# include "pdkim/polarssl/sha2.h"
 #endif
 
 
@@ -63,12 +59,6 @@ typedef struct {
 #elif defined(SHA_GCRYPT)
   gcry_md_hd_t sha;          /* Either SHA1 or SHA256 block               */
 
-#elif defined(SHA_POLARSSL)
-  union {
-    sha1_context sha1;       /* SHA1 block                                */
-    sha2_context sha2;       /* SHA256 block                              */
-  } u;
-
 #elif defined(SHA_NATIVE)
   sha1 sha1;
 #endif
index 3bc5357206d705640188fd262892ea4cc77bb007..d25d5ede0b3a7148d019e89a4352c91517d0813d 100644 (file)
@@ -25,10 +25,28 @@ miscmods.a: $(OBJ)
                $(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 $@
-
-dmarc.o dmarc.so:      $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c
+               $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+                       -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+                       $(DLFLAGS) $*.c -o $@
+
+# Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
+# by scripts/Makelinks.
+dkim.o  dkim.so:       $(HDRS) dkim.h dkim.c dkim_transport.c \
+                       crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
+                       signing.h signing.c
+dmarc.o dmarc.so:      $(HDRS) pdkim.h dmarc.h dmarc.c
 dummy.o:               dummy.c
-spf.o spf.so:          $(HDRS) spf.h spf.c
+spf.o   spf.so:                $(HDRS) spf.h spf.c
+
+dkim.o:
+               @echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
+               $(FE)$(CC) -r $(CFLAGS) $(INCLUDE) \
+                       dkim.c dkim_transport.c pdkim.c signing.c -o $@
+
+dkim.so:
+               @echo "$(CC) -shared dkim.c dkim_transport.c pdkim.c signing.c"
+               $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+                       -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+                       $(DLFLAGS) dkim.c dkim_transport.c pdkim.c signing.c -o $@
 
 # End
index e534a4eed1fd838764b807d04a214c2818313656..d1b7a163276e0470442ff51f99611d990f8d6287 100644 (file)
@@ -49,6 +49,7 @@ The variables table defins $variables for expansion, using the same
 definition entry struct as the main var_table in expand.c;
 entries here should have their proper vtype_<type> and should be duplicated
 in the main table but with vtype_module and the module name.
+Entries must be in order by the variable name.
 
 There are service functions to locate and to locate-or-load modules
 by name; these hide the static/dynamic aspect of a module.  Most
@@ -79,6 +80,14 @@ Write an include file with anything callers need to know, in particular
 and add it to HDRS and PHDRS in OS/Makefile-Base.
 Add a SUPPORT_<foo> line to Local/Makefile, and (if dynamic) any
 SUPPORT_<foo>_INCLUDE or SUPPORT_<foo>_LIBS required.
-Add the capitalised module name <foo> to the "miscmods" like in
+Add the capitalised module name <foo> to the "miscmods" line in
 scripts/Configure-Makefile.
 Add all the filenames to the "miscmods" list in scripts/Makelinks
+
+For statically-linked modules the SUPPORT_<foo> line should say "yes",
+for dynamic: "2".
+
+If include-by-default is wanted for the module, use DISABLE_<foo> instead
+of SUPPORT_<foo> (and leave it commented out as appropriate), and prefix
+the name in the "miscmods" line with an underbar ("_").
+For dynamic builds, a SUPPORT_ line is still needed.
diff --git a/src/src/miscmods/dkim.c b/src/src/miscmods/dkim.c
new file mode 100644 (file)
index 0000000..3867709
--- /dev/null
@@ -0,0 +1,1379 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Code for DKIM support. Other DKIM relevant code is in
+   receive.c, transport.c and transports/smtp.c */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM
+
+# include "pdkim.h"
+# include "signing.h"
+
+# ifdef MACRO_PREDEF
+#  include "../macro_predef.h"
+
+void
+params_dkim(void)
+{
+builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
+builtin_macro_create_var(US"_DKIM_OVERSIGN_HEADERS", US PDKIM_OVERSIGN_HEADERS);
+}
+# else /*!MACRO_PREDEF*/
+
+/* Options */
+
+uschar *dkim_verify_hashes     = US"sha256:sha512";
+uschar *dkim_verify_keytypes   = US"ed25519:rsa";
+uschar *dkim_verify_min_keysizes = US"rsa=1024 ed25519=250";
+BOOL    dkim_verify_minimal    = FALSE;
+uschar *dkim_verify_signers    = US"$dkim_signers";
+
+/* $variables */
+
+uschar *dkim_cur_signer                = NULL;
+int     dkim_key_length                = 0;
+uschar *dkim_signers           = NULL;
+uschar *dkim_signing_domain    = NULL;
+uschar *dkim_signing_selector  = NULL;
+uschar *dkim_verify_reason     = NULL;
+uschar *dkim_verify_status     = NULL;
+
+/* Working variables */
+
+unsigned dkim_collect_input    = 0;
+void   *dkim_signatures                = NULL;
+gstring *dkim_signing_record   = NULL;
+uschar *dkim_vdom_firstpass    = NULL;
+
+
+extern BOOL    dkim_transport_write_message(transport_ctx *,
+                  struct ob_dkim *, const uschar ** errstr);
+
+/****************************************/
+
+pdkim_ctx dkim_sign_ctx;
+
+int dkim_verify_oldpool;
+pdkim_ctx * dkim_verify_ctx = NULL;
+pdkim_signature *dkim_cur_sig = NULL;
+static const uschar * dkim_collect_error = NULL;
+
+#define DKIM_MAX_SIGNATURES 20
+static void dkim_exim_verify_pause(BOOL pause);
+
+
+/****************************************/
+
+/* Look up the DKIM record in DNS for the given hostname.
+Will use the first found if there are multiple.
+The return string is tainted, having come from off-site.
+*/
+
+static uschar *
+dkim_exim_query_dns_txt(const uschar * name)
+{
+dns_answer * dnsa = store_get_dns_answer();
+dns_scan dnss;
+rmark reset_point = store_mark();
+gstring * g = string_get_tainted(256, GET_TAINTED);
+
+lookup_dnssec_authenticated = NULL;
+if (dns_lookup(dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
+  goto bad;
+
+/* Search for TXT record */
+
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT))
+  if (rr->type == T_TXT)
+    {                  /* Copy record content to the answer buffer */
+    for (int rr_offset = 0; rr_offset < rr->size; )
+      {
+      uschar len = rr->data[rr_offset++];
+
+      g = string_catn(g, US(rr->data + rr_offset), len);
+      if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
+       goto bad;
+
+      rr_offset += len;
+      }
+
+    /* Check if this looks like a DKIM record */
+    if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
+      {
+      store_free_dns_answer(dnsa);
+      gstring_release_unused(g);
+      return string_from_gstring(g);
+      }
+
+    gstring_reset(g);          /* overwrite previous record */
+    }
+
+bad:
+store_reset(reset_point);
+store_free_dns_answer(dnsa);
+return NULL;   /*XXX better error detail?  logging? */
+}
+
+
+
+/* Module API:  Lookup a DNS DKIM record and parse the pubkey.
+
+Arguments:
+       dnsname         record to lookup in DNS
+       pubkey_p        pointer for return of pubkey
+       hashes_p        pointer for return of hashes
+
+Return: srvtype, or NULL on error
+*/
+
+static const uschar *
+dkim_exim_parse_dns_pubkey(const uschar * dnsname, blob ** pubkey_p,
+  const uschar ** hashes_p)
+{
+const uschar * dnstxt = dkim_exim_query_dns_txt(dnsname);
+pdkim_pubkey * p;
+
+if (!dnstxt)
+  {
+  DEBUG(D_acl) debug_printf_indent("pubkey dns lookup fail\n");
+  return NULL;
+  }
+if (!(p = pdkim_parse_pubkey_record(dnstxt)))
+  {
+  DEBUG(D_acl) debug_printf_indent("pubkey dns record format error\n");
+  return NULL;
+  }
+*pubkey_p = &p->key;
+*hashes_p = p->hashes;
+return p->srvtype;
+}
+
+
+
+
+/* Return:
+       OK      verify succesful
+       FAIL    verify did not pass
+       ERROR   problem setting up the pubkey
+*/
+
+static int
+dkim_exim_sig_verify(const blob * sighash, const blob * data_hash,
+  hashmethod hash, const blob * pubkey, const uschar ** errstr_p)
+{
+ev_ctx vctx;
+const uschar * errstr;
+int rc = OK;
+
+if ((errstr = exim_dkim_verify_init(pubkey, KEYFMT_DER, &vctx, NULL)))
+  rc = ERROR;
+else if ((errstr = exim_dkim_verify(&vctx, hash, data_hash, sighash)))
+  rc = FAIL;
+
+*errstr_p = errstr;
+return rc;
+}
+
+
+
+/****************************************/
+
+static BOOL
+dkim_exim_init(void * dummy)
+{
+if (f.dkim_init_done) return TRUE;
+f.dkim_init_done = TRUE;
+pdkim_init();
+return TRUE;
+}
+
+
+
+/* Module API: Set up for verification of a message being received.
+Always returns OK.
+*/
+
+static int
+dkim_exim_verify_init(void)
+{
+BOOL dot_stuffing = chunking_state <= CHUNKING_OFFERED;
+
+if (!smtp_input || smtp_batched_input || f.dkim_disable_verify)
+  return OK;
+
+dkim_exim_init(NULL);
+
+/* There is a store-reset between header & body reception for the main pool
+(actually, after every header line) so cannot use that as we need the data we
+store per-header, during header processing, at the end of body reception
+for evaluating the signature.  Any allocs done for dkim verify
+memory-handling must use a different pool.  We use a separate one that we
+can reset per message. */
+
+dkim_verify_oldpool = store_pool;
+store_pool = POOL_MESSAGE;
+
+/* Free previous context if there is one */
+
+if (dkim_verify_ctx)
+  pdkim_free_ctx(dkim_verify_ctx);
+
+/* Create new context */
+
+dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
+dkim_exim_verify_pause(FALSE);
+dkim_collect_input = dkim_verify_ctx ? DKIM_MAX_SIGNATURES : 0;
+dkim_collect_error = NULL;
+
+/* Start feed up with any cached data, but limited to message data */
+receive_get_cache(chunking_state == CHUNKING_LAST
+                 ? chunking_data_left : GETC_BUFFER_UNLIMITED);
+
+store_pool = dkim_verify_oldpool;
+return OK;
+}
+
+
+/* Module API : Submit a chunk of data for verification input.
+A NULL data pointer indicates end-of-message.
+Only use the data when the feed is activated. */
+
+static void
+dkim_exim_verify_feed(const uschar * data, unsigned len)
+{
+int rc;
+
+store_pool = POOL_MESSAGE;
+if (  (dkim_collect_input || !data)
+   && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
+  {
+  dkim_collect_error = pdkim_errstr(rc);
+  log_write(0, LOG_MAIN,
+            "DKIM: validation error: %.100s", dkim_collect_error);
+  dkim_collect_input = 0;
+  }
+store_pool = dkim_verify_oldpool;
+}
+
+
+/* Module API: pause/resume the verification data feed */
+
+static void
+dkim_exim_verify_pause(BOOL pause)
+{
+static unsigned save = 0;
+static BOOL paused = FALSE;
+
+if (!pause)
+  {
+  if (paused)
+    { dkim_collect_input = save; paused = FALSE; }
+  }
+else
+  if (!paused)
+    { save = dkim_collect_input; dkim_collect_input = 0; paused = TRUE; }
+}
+
+/* Module API: Finish off the body hashes, calculate sigs and do compares */
+
+static void
+dkim_exim_verify_finish(void)
+{
+int rc;
+gstring * g = NULL;
+const uschar * errstr = NULL;
+
+store_pool = POOL_MESSAGE;
+
+/* Delete eventual previous signature chain */
+
+dkim_signers = NULL;
+dkim_signatures = NULL;
+
+if (dkim_collect_error)
+  {
+  log_write(0, LOG_MAIN,
+      "DKIM: Error during validation, disabling signature verification: %.100s",
+      dkim_collect_error);
+  f.dkim_disable_verify = TRUE;
+  goto out;
+  }
+
+dkim_collect_input = 0;
+
+/* Finish DKIM operation and fetch link to signatures chain */
+
+rc = pdkim_feed_finish(dkim_verify_ctx, (pdkim_signature **)&dkim_signatures,
+                       &errstr);
+if (rc != PDKIM_OK && errstr)
+  log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
+
+/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
+
+for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  {
+  if (sig->domain)   g = string_append_listele(g, ':', sig->domain);
+  if (sig->identity) g = string_append_listele(g, ':', sig->identity);
+  }
+gstring_release_unused(g);
+dkim_signers = string_from_gstring(g);
+
+out:
+store_pool = dkim_verify_oldpool;
+}
+
+
+
+/* Log the result for the given signature */
+static void
+dkim_exim_verify_log_sig(pdkim_signature * sig)
+{
+gstring * logmsg;
+uschar * s;
+
+if (!sig) return;
+
+/* Remember the domain for the first pass result */
+
+if (  !dkim_vdom_firstpass
+   && dkim_verify_status
+      ? Ustrcmp(dkim_verify_status, US"pass") == 0
+      : sig->verify_status == PDKIM_VERIFY_PASS
+   )
+  dkim_vdom_firstpass= string_copy(sig->domain);
+
+/* Rewrite the sig result if the ACL overrode it.  This is only
+needed because the DMARC code (sigh) peeks at the dkim sigs.
+Mark the sig for this having been done. */
+
+if (  dkim_verify_status
+   && (  dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
+      || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
+   )  )
+  {                    /* overridden by ACL */
+  sig->verify_ext_status = -1;
+  if (Ustrcmp(dkim_verify_status, US"fail") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_FAIL;
+  else if (Ustrcmp(dkim_verify_status, US"invalid") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_INVALID;
+  else if (Ustrcmp(dkim_verify_status, US"none") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_NONE;
+  else if (Ustrcmp(dkim_verify_status, US"pass") == 0)
+    sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_PASS;
+  else
+    sig->verify_status = -1;
+  }
+
+if (!LOGGING(dkim_verbose)) return;
+
+
+logmsg = string_catn(NULL, US"DKIM: ", 6);
+if (!(s = sig->domain)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, "d=", s);
+if (!(s = sig->selector)) s = US"<UNSET>";
+logmsg = string_append(logmsg, 2, " s=", s);
+logmsg = string_fmt_append(logmsg, " c=%s/%s a=%s b=" SIZE_T_FMT,
+         sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+         sig->canon_body    == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
+         dkim_sig_to_a_tag(sig),
+         (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : (size_t)0);
+if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
+if (sig->created > 0) logmsg = string_fmt_append(logmsg, " t=%lu",
+                                   sig->created);
+if (sig->expires > 0) logmsg = string_fmt_append(logmsg, " x=%lu",
+                                   sig->expires);
+if (sig->bodylength > -1) logmsg = string_fmt_append(logmsg, " l=%lu",
+                                   sig->bodylength);
+
+if (sig->verify_status & PDKIM_VERIFY_POLICY)
+  logmsg = string_append(logmsg, 5,
+           US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
+else
+  switch (sig->verify_status)
+    {
+    case PDKIM_VERIFY_NONE:
+      logmsg = string_cat(logmsg, US" [not verified]");
+      break;
+
+    case PDKIM_VERIFY_INVALID:
+      logmsg = string_cat(logmsg, US" [invalid - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+         logmsg = string_cat(logmsg,
+                       US"public key record (currently?) unavailable]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+         logmsg = string_cat(logmsg, US"overlong public key record]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+       case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+         logmsg = string_cat(logmsg, US"syntax error in public key record]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+         logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+         logmsg = string_cat(logmsg, US"unsupported DKIM version]");
+         break;
+
+       default:
+         logmsg = string_cat(logmsg, US"unspecified problem]");
+       }
+      break;
+
+    case PDKIM_VERIFY_FAIL:
+      logmsg = string_cat(logmsg, US" [verification failed - ");
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_FAIL_BODY:
+         logmsg = string_cat(logmsg,
+              US"body hash mismatch (body probably modified in transit)]");
+         break;
+
+       case PDKIM_VERIFY_FAIL_MESSAGE:
+         logmsg = string_cat(logmsg,
+               US"signature did not verify "
+               "(headers probably modified in transit)]");
+         break;
+
+       case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:
+         logmsg = string_cat(logmsg,
+               US"signature invalid (key too short)]");
+         break;
+
+       default:
+         logmsg = string_cat(logmsg, US"unspecified reason]");
+       }
+      break;
+
+    case PDKIM_VERIFY_PASS:
+      logmsg = string_cat(logmsg, US" [verification succeeded]");
+      break;
+    }
+
+log_write(0, LOG_MAIN, "%Y", logmsg);
+return;
+}
+
+
+/* Module API:  Log a line for each signature */
+
+void
+dkim_exim_verify_log_all(void)
+{
+for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  dkim_exim_verify_log_sig(sig);
+}
+
+
+/* Module API: append a log element with domain for the first passing sig */
+
+gstring *
+dkim_exim_vdom_firstpass(gstring * g)
+{
+if (dkim_vdom_firstpass)
+  g = string_append(g, 2, US" DKIM=", dkim_vdom_firstpass);
+return g;
+}
+
+
+/* For one signature, run the DKIM ACL, log the sig result,
+and append ths sig status to the status list.
+
+Args as per dkim_exim_acl_run() below */
+
+static int
+dkim_acl_call(uschar * id, gstring ** res_ptr,
+  uschar ** user_msgptr, uschar ** log_msgptr)
+{
+int rc;
+DEBUG(D_receive)
+  debug_printf("calling acl_smtp_dkim for identity '%s' domain '%s' sel '%s'\n",
+             id, dkim_signing_domain, dkim_signing_selector);
+
+rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
+dkim_exim_verify_log_sig(dkim_cur_sig);
+*res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
+return rc;
+}
+
+
+
+/* For the given identity, run the DKIM ACL once for each matching signature.
+If none match, run it once.
+
+Arguments
+ id            Identity to look for in dkim signatures
+ res_ptr       ptr to growable string-list of status results,
+               appended to per ACL run
+ user_msgptr   where to put a user error (for SMTP response)
+ log_msgptr    where to put a logging message (not for SMTP response)
+
+Returns:       OK         access is granted by an ACCEPT verb
+               DISCARD    access is granted by a DISCARD verb
+               FAIL       access is denied
+               FAIL_DROP  access is denied; drop the connection
+               DEFER      can't tell at the moment
+               ERROR      disaster
+*/
+
+static int
+dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
+  uschar ** user_msgptr, uschar ** log_msgptr)
+{
+uschar * cmp_val;
+int rc = -1;
+
+dkim_verify_status = US"none";
+dkim_verify_reason = US"";
+dkim_cur_signer = id;
+
+if (f.dkim_disable_verify || !id || !dkim_verify_ctx)
+  return OK;
+
+/* Find signatures to run ACL on */
+
+for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  if (  (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
+     && strcmpic(cmp_val, id) == 0
+     )
+    {
+    /* The "dkim_domain" and "dkim_selector" expansion variables have
+    related globals, since they are used in the signing code too.
+    Instead of inventing separate names for verification, we set
+    them here. This is easy since a domain and selector is guaranteed
+    to be in a signature. The other dkim_* expansion items are
+    dynamically fetched from dkim_cur_sig at expansion time (see
+    dkim_exim_expand_query() below). */
+
+    dkim_cur_sig = sig;
+    dkim_signing_domain = US sig->domain;
+    dkim_signing_selector = US sig->selector;
+    dkim_key_length = sig->keybits;
+
+    /* These two return static strings, so we can compare the addr
+    later to see if the ACL overwrote them.  Check that when logging */
+
+    dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
+    dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
+
+    if (  (rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK
+       || dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
+      return rc;
+    }
+
+if (rc != -1)
+  return rc;
+
+/* No matching sig found.  Call ACL once anyway. */
+
+dkim_cur_sig = NULL;
+return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
+}
+
+
+/* Module API:
+Loop over dkim_verify_signers option doing ACL calls.  If one return any
+non-OK value stop and return that, else return OK.
+*/
+
+int
+    /*XXX need a user_msgptr */
+dkim_exim_acl_entry(uschar ** user_msgptr, uschar ** log_msgptr)
+{
+int rc = OK;
+
+GET_OPTION("dkim_verify_signers");
+if (dkim_verify_signers && *dkim_verify_signers)
+  {
+  const uschar * dkim_verify_signers_expanded =
+                     expand_cstring(dkim_verify_signers);
+  gstring * results = NULL, * seen_items = NULL;
+  int signer_sep = 0, old_pool = store_pool;
+
+  if (!dkim_verify_signers_expanded)
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "expansion of dkim_verify_signers option failed: %s",
+      expand_string_message);
+    return DEFER;
+    }
+
+  store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
+
+  /* Loop over signers we want to verify, calling ACL.  Default to OK
+  when no signers are present.  Each call from here expands to an ACL
+  call per matching sig in the message. */
+
+  for (uschar * item;
+      item = string_nextinlist(&dkim_verify_signers_expanded,
+                                   &signer_sep, NULL, 0); )
+    {
+    /* Prevent running ACL for an empty item */
+    if (!item || !*item) continue;
+
+    /* Only run ACL once for each domain or identity,
+    no matter how often it appears in the expanded list. */
+    if (seen_items)
+      {
+      uschar * seen_item;
+      const uschar * seen_items_list = string_from_gstring(seen_items);
+      int seen_sep = ':';
+      BOOL seen_this_item = FALSE;
+
+      while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
+                                           NULL, 0)))
+       if (Ustrcmp(seen_item, item) == 0)
+         {
+         seen_this_item = TRUE;
+         break;
+         }
+
+      if (seen_this_item)
+       {
+       DEBUG(D_receive)
+         debug_printf("acl_smtp_dkim: skipping signer %s, "
+           "already seen\n", item);
+       continue;
+       }
+
+      seen_items = string_catn(seen_items, US":", 1);
+      }
+    seen_items = string_cat(seen_items, item);
+
+    if ((rc = dkim_exim_acl_run(item, &results, user_msgptr, log_msgptr)) != OK)
+      {
+      DEBUG(D_receive)
+       debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
+         "skipping remaining items\n", rc, item);
+      break;
+      }
+    if (dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
+      break;
+    }                  /* signers loop */
+
+  dkim_verify_status = string_from_gstring(results);
+  store_pool = old_pool;
+  }
+else
+  dkim_exim_verify_log_all();
+
+return rc;
+}
+
+/******************************************************************************/
+
+/* Module API */
+
+static int
+dkim_exim_signer_isinlist(const uschar * l)
+{
+return dkim_cur_signer
+  ? match_isinlist(dkim_cur_signer, &l, 0, NULL, NULL, MCL_STRING, TRUE, NULL)
+  : FAIL;
+}
+
+/* Module API */
+
+static int
+dkim_exim_status_listmatch(const uschar * l)
+{                                              /* return good for any match */
+const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
+int sep = 0, rc = FAIL;
+for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
+  if (   (rc = match_isinlist(ss, &l, 0, NULL, NULL, MCL_STRING, TRUE, NULL))
+      == OK) break;
+return rc;
+}
+
+/* Module API: Overwriteable dkim result variables */
+
+static void
+dkim_exim_setvar(const uschar * name, void * val)
+{
+if (Ustrcmp(name, "dkim_verify_status") == 0)
+  dkim_verify_status = val;
+else if (Ustrcmp(name, "dkim_verify_reason") == 0)
+  dkim_verify_reason = val;
+}
+
+/******************************************************************************/
+
+static void
+dkim_smtp_reset(void)
+{
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
+dkim_vdom_firstpass = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+}
+
+/******************************************************************************/
+
+static uschar *
+dkim_exim_expand_defaults(int what)
+{
+switch (what)
+  {
+  case DKIM_ALGO:              return US"";
+  case DKIM_BODYLENGTH:                return US"9999999999999";
+  case DKIM_CANON_BODY:                return US"";
+  case DKIM_CANON_HEADERS:     return US"";
+  case DKIM_COPIEDHEADERS:     return US"";
+  case DKIM_CREATED:           return US"0";
+  case DKIM_EXPIRES:           return US"9999999999999";
+  case DKIM_HEADERNAMES:       return US"";
+  case DKIM_IDENTITY:          return US"";
+  case DKIM_KEY_GRANULARITY:   return US"*";
+  case DKIM_KEY_SRVTYPE:       return US"*";
+  case DKIM_KEY_NOTES:         return US"";
+  case DKIM_KEY_TESTING:       return US"0";
+  case DKIM_NOSUBDOMAINS:      return US"0";
+  case DKIM_VERIFY_STATUS:     return US"none";
+  case DKIM_VERIFY_REASON:     return US"";
+  default:                     return US"";
+  }
+}
+
+
+/* Module API: return a computed value for a variable expansion */
+
+uschar *
+dkim_exim_expand_query(int what)
+{
+if (!dkim_verify_ctx || f.dkim_disable_verify || !dkim_cur_sig)
+  return dkim_exim_expand_defaults(what);
+
+switch (what)
+  {
+  case DKIM_ALGO:
+    return dkim_sig_to_a_tag(dkim_cur_sig);
+
+  case DKIM_BODYLENGTH:
+    return dkim_cur_sig->bodylength >= 0
+      ? string_sprintf("%ld", dkim_cur_sig->bodylength)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_CANON_BODY:
+    switch (dkim_cur_sig->canon_body)
+      {
+      case PDKIM_CANON_RELAXED:        return US"relaxed";
+      case PDKIM_CANON_SIMPLE:
+      default:                 return US"simple";
+      }
+
+  case DKIM_CANON_HEADERS:
+    switch (dkim_cur_sig->canon_headers)
+      {
+      case PDKIM_CANON_RELAXED:        return US"relaxed";
+      case PDKIM_CANON_SIMPLE:
+      default:                 return US"simple";
+      }
+
+  case DKIM_COPIEDHEADERS:
+    return dkim_cur_sig->copiedheaders
+      ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
+
+  case DKIM_CREATED:
+    return dkim_cur_sig->created > 0
+      ? string_sprintf("%lu", dkim_cur_sig->created)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_EXPIRES:
+    return dkim_cur_sig->expires > 0
+      ? string_sprintf("%lu", dkim_cur_sig->expires)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_HEADERNAMES:
+    return dkim_cur_sig->headernames
+      ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
+
+  case DKIM_IDENTITY:
+    return dkim_cur_sig->identity
+      ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_GRANULARITY:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->granularity
+      ? US dkim_cur_sig->pubkey->granularity
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_SRVTYPE:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->srvtype
+      ? US dkim_cur_sig->pubkey->srvtype
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_NOTES:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->notes
+      ? US dkim_cur_sig->pubkey->notes
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_KEY_TESTING:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->testing
+      ? US"1"
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_NOSUBDOMAINS:
+    return dkim_cur_sig->pubkey
+      ? dkim_cur_sig->pubkey->no_subdomaining
+      ? US"1"
+      : dkim_exim_expand_defaults(what)
+      : dkim_exim_expand_defaults(what);
+
+  case DKIM_VERIFY_STATUS:
+    switch (dkim_cur_sig->verify_status)
+      {
+      case PDKIM_VERIFY_INVALID:       return US"invalid";
+      case PDKIM_VERIFY_FAIL:          return US"fail";
+      case PDKIM_VERIFY_PASS:          return US"pass";
+      case PDKIM_VERIFY_NONE:
+      default:                         return US"none";
+      }
+
+  case DKIM_VERIFY_REASON:
+    switch (dkim_cur_sig->verify_ext_status)
+      {
+      case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+                                               return US"pubkey_unavailable";
+      case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
+      case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
+      case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:        return US"pubkey_too_short";
+      case PDKIM_VERIFY_FAIL_BODY:             return US"bodyhash_mismatch";
+      case PDKIM_VERIFY_FAIL_MESSAGE:          return US"signature_incorrect";
+      }
+
+  default:
+    return US"";
+  }
+}
+
+
+/* Module API */
+
+static void
+dkim_exim_sign_init(void)
+{
+int old_pool = store_pool;
+
+dkim_exim_init(NULL);
+store_pool = POOL_MAIN;
+pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
+store_pool = old_pool;
+}
+
+
+/* Generate signatures for the given file.
+If a prefix is given, prepend it to the file for the calculations.
+
+Return:
+  NULL:                error; error string written
+  string:      signature header(s), or a zero-length string (not an error)
+*/
+
+gstring *
+dkim_exim_sign(int fd, off_t off, uschar * prefix,
+  struct ob_dkim * dkim, const uschar ** errstr)
+{
+const uschar * dkim_domain = NULL;
+int sep = 0;
+gstring * seen_doms = NULL;
+pdkim_signature * sig;
+gstring * sigbuf;
+int pdkim_rc;
+int sread;
+uschar buf[4096];
+int save_errno = 0;
+int old_pool = store_pool;
+uschar * errwhen;
+const uschar * s;
+
+if (dkim->dot_stuffed)
+  dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
+
+store_pool = POOL_MAIN;
+
+GET_OPTION("dkim_domain");
+if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
+  /* expansion error, do not send message. */
+  { errwhen = US"dkim_domain"; goto expand_bad; }
+
+/* Set $dkim_domain expansion variable to each unique domain in list. */
+
+if (dkim_domain)
+  while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
+  {
+  const uschar * dkim_sel;
+  int sel_sep = 0;
+
+  if (dkim_signing_domain[0] == '\0')
+    continue;
+
+  /* Only sign once for each domain, no matter how often it
+  appears in the expanded list. */
+
+  dkim_signing_domain = string_copylc(dkim_signing_domain);
+  if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
+      0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
+    continue;
+
+  seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
+
+  /* Set $dkim_selector expansion variable to each selector in list,
+  for this domain. */
+
+  GET_OPTION("dkim_selector");
+  if (!(dkim_sel = expand_string(dkim->dkim_selector)))
+    { errwhen = US"dkim_selector"; goto expand_bad; }
+
+  while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
+         NULL, 0)))
+    {
+    uschar * dkim_canon_expanded;
+    int pdkim_canon;
+    uschar * dkim_sign_headers_expanded = NULL;
+    uschar * dkim_private_key_expanded;
+    uschar * dkim_hash_expanded;
+    uschar * dkim_identity_expanded = NULL;
+    uschar * dkim_timestamps_expanded = NULL;
+    unsigned long tval = 0, xval = 0;
+
+    /* Get canonicalization to use */
+
+    GET_OPTION("dkim_canon");
+    dkim_canon_expanded = dkim->dkim_canon
+      ? expand_string(dkim->dkim_canon) : US"relaxed";
+    if (!dkim_canon_expanded)  /* expansion error, do not send message. */
+      { errwhen = US"dkim_canon"; goto expand_bad; }
+
+    if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
+      pdkim_canon = PDKIM_CANON_RELAXED;
+    else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
+      pdkim_canon = PDKIM_CANON_SIMPLE;
+    else
+      {
+      log_write(0, LOG_MAIN,
+                "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
+                dkim_canon_expanded);
+      pdkim_canon = PDKIM_CANON_RELAXED;
+      }
+
+    GET_OPTION("dkim_sign_headers");
+    if (  dkim->dkim_sign_headers
+       && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
+      { errwhen = US"dkim_sign_header"; goto expand_bad; }
+    /* else pass NULL, which means default header list */
+
+    /* Get private key to use. */
+
+    GET_OPTION("dkim_private_key");
+    if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
+      { errwhen = US"dkim_private_key"; goto expand_bad; }
+
+    if (  Ustrlen(dkim_private_key_expanded) == 0
+       || Ustrcmp(dkim_private_key_expanded, "0") == 0
+       || Ustrcmp(dkim_private_key_expanded, "false") == 0
+       )
+      continue;                /* don't sign, but no error */
+
+    if (  dkim_private_key_expanded[0] == '/'
+       && !(dkim_private_key_expanded =
+            expand_file_big_buffer(dkim_private_key_expanded)))
+      goto bad;
+
+    GET_OPTION("dkim_hash");
+    if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
+      { errwhen = US"dkim_hash"; goto expand_bad; }
+
+    GET_OPTION("dkim_identity");
+    if (dkim->dkim_identity)
+      if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
+       { errwhen = US"dkim_identity"; goto expand_bad; }
+      else if (!*dkim_identity_expanded)
+       dkim_identity_expanded = NULL;
+
+    GET_OPTION("dkim_timestamps");
+    if (dkim->dkim_timestamps)
+      if (!(dkim_timestamps_expanded = expand_string(dkim->dkim_timestamps)))
+       { errwhen = US"dkim_timestamps"; goto expand_bad; }
+      else
+        {
+        tval = (unsigned long) time(NULL);
+        xval = strtoul(CCS dkim_timestamps_expanded, NULL, 10);
+        if (xval > 0)
+          xval += tval;
+        }
+
+    if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
+                         dkim_signing_selector,
+                         dkim_private_key_expanded,
+                         dkim_hash_expanded,
+                         errstr
+                         )))
+      goto bad;
+    dkim_private_key_expanded[0] = '\0';
+
+    pdkim_set_optional(sig,
+                       CS dkim_sign_headers_expanded,
+                       CS dkim_identity_expanded,
+                       pdkim_canon,
+                       pdkim_canon, -1, tval, xval);
+
+    if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
+      goto bad;
+
+    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_domain);
+    dkim_signing_record = string_append_listele(dkim_signing_record, ':', dkim_signing_selector);
+
+    if (!dkim_sign_ctx.sig)            /* link sig to context chain */
+      dkim_sign_ctx.sig = sig;
+    else
+      {
+      pdkim_signature * n = dkim_sign_ctx.sig;
+      while (n->next) n = n->next;
+      n->next = sig;
+      }
+    }
+  }
+
+/* We may need to carry on with the data-feed even if there are no DKIM sigs to
+produce, if some other package (eg. ARC) is signing. */
+
+if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
+  {
+  DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
+  sigbuf = string_get(1);      /* return a zero-len string */
+  }
+else
+  {
+  if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
+    goto pk_bad;
+
+  if (lseek(fd, off, SEEK_SET) < 0)
+    sread = -1;
+  else
+    while ((sread = read(fd, &buf, sizeof(buf))) > 0)
+      if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK)
+       goto pk_bad;
+
+  /* Handle failed read above. */
+  if (sread == -1)
+    {
+    debug_printf("DKIM: Error reading -K file.\n");
+    save_errno = errno;
+    goto bad;
+    }
+
+  /* Build string of headers, one per signature */
+
+  if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
+    goto pk_bad;
+
+  if (!sig)
+    {
+    DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
+    sigbuf = string_get(1);    /* return a zero-len string */
+    }
+  else for (sigbuf = NULL; sig; sig = sig->next)
+    sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
+  }
+
+CLEANUP:
+  (void) string_from_gstring(sigbuf);
+  store_pool = old_pool;
+  errno = save_errno;
+  return sigbuf;
+
+pk_bad:
+  log_write(0, LOG_MAIN|LOG_PANIC,
+               "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
+bad:
+  sigbuf = NULL;
+  goto CLEANUP;
+
+expand_bad:
+  *errstr = string_sprintf("failed to expand %s: %s",
+              errwhen, expand_string_message);
+  log_write(0, LOG_MAIN | LOG_PANIC, "%s", *errstr);
+  goto bad;
+}
+
+
+
+#ifdef SUPPORT_DMARC
+
+/* Module API */
+
+static const pdkim_signature *
+dkim_sigs_list(void)
+{
+return dkim_signatures;
+}
+#endif
+
+#ifdef EXPERIMENTAL_ARC
+
+/* Module API */
+static int
+dkim_hashname_to_type(const blob * name)
+{
+return pdkim_hashname_to_hashtype(name->data, name->len);
+}
+
+/* Module API */
+hashmethod
+dkim_hashtype_to_method(int hashtype)
+{
+return hashtype >= 0 ? pdkim_hashes[hashtype].exim_hashmethod : -1;
+}
+
+/* Module API */
+hashmethod
+dkim_hashname_to_method(const blob * name)
+{
+return dkim_hashtype_to_method(dkim_hashname_to_type(name));
+}
+
+/*  Module API: Set up a body hashing method on the given signature-context
+(creates a new one if needed, or uses an already-present one).
+
+Arguments:
+       signing         TRUE to use dkim's signing context, else dkim_verify_ctx
+        canon          canonicalization spec, text form
+        hash           hash spec, text form
+        bodylen         byte count for message body
+
+Return: pointer to hashing method struct
+*/
+
+static pdkim_bodyhash *
+dkim_set_bodyhash(BOOL signing,
+  const blob * canon, const blob * hashname, long bodylen)
+{
+int canon_head = -1, canon_body = -1;
+
+pdkim_cstring_to_canons(canon->data, canon->len, &canon_head, &canon_body);
+return pdkim_set_bodyhash(signing ? &dkim_sign_ctx: dkim_verify_ctx,
+        dkim_hashname_to_type(hashname),
+        canon_body,
+        bodylen);
+}
+
+/* Module API: Sign a blob of data (which might already be a hash, if
+Ed25519 or GCrypt signing).
+
+Arguments:
+       data            to be signed
+       hm              hash to be applied to the data
+       privkey         private key for siging, PEM format
+       signature       pointer for result blob
+
+Return: NULL, or error string on failure
+*/
+
+static const uschar *
+dkim_sign_blob(const blob * data, hashmethod hm, const uschar * privkey,
+  blob * signature)
+{
+es_ctx sctx;
+const uschar * errstr;
+
+if ((errstr = exim_dkim_signing_init(privkey, &sctx)))
+  { DEBUG(D_transport) debug_printf("signing key setup: %s\n", errstr); }
+else errstr = exim_dkim_sign(&sctx, hm, data, signature);
+
+return errstr;
+}
+
+#endif /*EXPERIMENTAL_ARC*/
+
+
+/* Module API */
+
+gstring *
+authres_dkim(gstring * g)
+{
+int start = 0;         /* compiler quietening */
+
+DEBUG(D_acl) start = gstring_length(g);
+
+for (pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  {
+  g = string_catn(g, US";\n\tdkim=", 8);
+
+  if (sig->verify_status & PDKIM_VERIFY_POLICY)
+    g = string_append(g, 5,
+      US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
+  else switch(sig->verify_status)
+    {
+    case PDKIM_VERIFY_NONE:    g = string_cat(g, US"none"); break;
+    case PDKIM_VERIFY_INVALID:
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
+          g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
+        case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
+          g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
+        case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
+        case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
+          g = string_cat(g, US"neutral (public key record import problem)\n\t\t");
+          break;
+        case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
+          g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
+          break;
+        case PDKIM_VERIFY_INVALID_DKIM_VERSION:
+          g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
+          break;
+        default:
+          g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
+       }
+      break;
+    case PDKIM_VERIFY_FAIL:
+      switch (sig->verify_ext_status)
+       {
+       case PDKIM_VERIFY_FAIL_BODY:
+          g = string_cat(g,
+           US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
+         break;
+        case PDKIM_VERIFY_FAIL_MESSAGE:
+          g = string_cat(g,
+           US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
+         break;
+        case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE:      /* should this really be "polcy"? */
+          g = string_fmt_append(g, "fail (public key too short: %u bits)\n\t\t", sig->keybits);
+          break;
+        default:
+          g = string_cat(g, US"fail (unspecified reason)\n\t\t");
+         break;
+       }
+      break;
+    case PDKIM_VERIFY_PASS:    g = string_cat(g, US"pass"); break;
+    default:                   g = string_cat(g, US"permerror"); break;
+    }
+  if (sig->domain)   g = string_append(g, 2, US" header.d=", sig->domain);
+  if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
+  if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
+  g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
+  }
+
+DEBUG(D_acl)
+  if (gstring_length(g) == start)
+    debug_printf("DKIM:\tno authres\n");
+  else
+    debug_printf("DKIM:\tauthres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
+return g;
+}
+
+/******************************************************************************/
+/* Module API */
+
+static optionlist dkim_options[] = {
+  { "acl_smtp_dkim",           opt_stringptr,   {&acl_smtp_dkim} },
+  { "dkim_verify_hashes",       opt_stringptr,   {&dkim_verify_hashes} },
+  { "dkim_verify_keytypes",     opt_stringptr,   {&dkim_verify_keytypes} },
+  { "dkim_verify_min_keysizes", opt_stringptr,   {&dkim_verify_min_keysizes} },
+  { "dkim_verify_minimal",      opt_bool,        {&dkim_verify_minimal} },
+  { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
+};
+
+static void * dkim_functions[] = {
+  [DKIM_VERIFY_FEED] =         dkim_exim_verify_feed,
+  [DKIM_VERIFY_PAUSE] =                dkim_exim_verify_pause,
+  [DKIM_VERIFY_FINISH] =       dkim_exim_verify_finish,
+  [DKIM_ACL_ENTRY] =           dkim_exim_acl_entry,
+  [DKIM_VERIFY_LOG_ALL] =      dkim_exim_verify_log_all,
+  [DKIM_VDOM_FIRSTPASS] =      dkim_exim_vdom_firstpass,
+
+  [DKIM_SIGNER_ISINLIST] =     dkim_exim_signer_isinlist,
+  [DKIM_STATUS_LISTMATCH] =    dkim_exim_status_listmatch,
+
+  [DKIM_SETVAR] =              dkim_exim_setvar,
+  [DKIM_EXPAND_QUERY] =                dkim_exim_expand_query,
+
+  [DKIM_TRANSPORT_INIT] =      dkim_exim_sign_init,
+  [DKIM_TRANSPORT_WRITE] =     dkim_transport_write_message,
+
+#ifdef SUPPORT_DMARC
+  [DKIM_SIGS_LIST] =           dkim_sigs_list,
+#endif
+#ifdef EXPERIMENTAL_ARC
+  [DKIM_HASHNAME_TO_TYPE] =    dkim_hashname_to_type,
+  [DKIM_HASHTYPE_TO_METHOD] =  dkim_hashtype_to_method,
+  [DKIM_HASHNAME_TO_METHOD] =  dkim_hashname_to_method,
+  [DKIM_SET_BODYHASH] =                dkim_set_bodyhash,
+  [DKIM_DNS_PUBKEY] =          dkim_exim_parse_dns_pubkey,
+  [DKIM_SIG_VERIFY] =          dkim_exim_sig_verify,
+  [DKIM_HEADER_RELAX] =                pdkim_relax_header_n,
+  [DKIM_SIGN_DATA] =           dkim_sign_blob,
+#endif
+};
+
+static var_entry dkim_variables[] = {
+  { "dkim_algo",           vtype_dkim,        (void *)DKIM_ALGO },
+  { "dkim_bodylength",     vtype_dkim,        (void *)DKIM_BODYLENGTH },
+  { "dkim_canon_body",     vtype_dkim,        (void *)DKIM_CANON_BODY },
+  { "dkim_canon_headers",  vtype_dkim,        (void *)DKIM_CANON_HEADERS },
+  { "dkim_copiedheaders",  vtype_dkim,        (void *)DKIM_COPIEDHEADERS },
+  { "dkim_created",        vtype_dkim,        (void *)DKIM_CREATED },
+  { "dkim_cur_signer",     vtype_stringptr,   &dkim_cur_signer },
+  { "dkim_domain",         vtype_stringptr,   &dkim_signing_domain },
+  { "dkim_expires",        vtype_dkim,        (void *)DKIM_EXPIRES },
+  { "dkim_headernames",    vtype_dkim,        (void *)DKIM_HEADERNAMES },
+  { "dkim_identity",       vtype_dkim,        (void *)DKIM_IDENTITY },
+  { "dkim_key_granularity",vtype_dkim,        (void *)DKIM_KEY_GRANULARITY },
+  { "dkim_key_length",     vtype_int,         &dkim_key_length },
+  { "dkim_key_nosubdomains",vtype_dkim,       (void *)DKIM_NOSUBDOMAINS },
+  { "dkim_key_notes",      vtype_dkim,        (void *)DKIM_KEY_NOTES },
+  { "dkim_key_srvtype",    vtype_dkim,        (void *)DKIM_KEY_SRVTYPE },
+  { "dkim_key_testing",    vtype_dkim,        (void *)DKIM_KEY_TESTING },
+  { "dkim_selector",       vtype_stringptr,   &dkim_signing_selector },
+  { "dkim_signers",        vtype_stringptr,   &dkim_signers },
+  { "dkim_verify_reason",  vtype_stringptr,   &dkim_verify_reason },
+  { "dkim_verify_status",  vtype_stringptr,   &dkim_verify_status },
+};
+
+misc_module_info dkim_module_info = {
+  .name =              US"dkim",
+# if SUPPORT_DKIM==2
+  .dyn_magic =         MISC_MODULE_MAGIC,
+# endif
+  .init =              dkim_exim_init,
+  .msg_init =          dkim_exim_verify_init,
+  .authres =           authres_dkim,
+  .smtp_reset =                dkim_smtp_reset,
+
+  .options =           dkim_options,
+  .options_count =     nelem(dkim_options),
+
+  .functions =         dkim_functions,
+  .functions_count =   nelem(dkim_functions),
+
+  .variables =         dkim_variables,
+  .variables_count =   nelem(dkim_variables),
+};
+
+# endif        /*!MACRO_PREDEF*/
+#endif /*!DISABLE_DKIM*/
diff --git a/src/src/miscmods/dkim.h b/src/src/miscmods/dkim.h
new file mode 100644 (file)
index 0000000..aa14d58
--- /dev/null
@@ -0,0 +1,47 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+gstring * dkim_exim_sign(int, off_t, uschar *, struct ob_dkim *, const uschar **);
+uschar *dkim_exim_expand_query(int);
+
+
+#define DKIM_ALGO               1
+#define DKIM_BODYLENGTH         2
+#define DKIM_CANON_BODY         3
+#define DKIM_CANON_HEADERS      4
+#define DKIM_COPIEDHEADERS      5
+#define DKIM_CREATED            6
+#define DKIM_EXPIRES            7
+#define DKIM_HEADERNAMES        8
+#define DKIM_IDENTITY           9
+#define DKIM_KEY_GRANULARITY   10
+#define DKIM_KEY_SRVTYPE       11
+#define DKIM_KEY_NOTES         12
+#define DKIM_KEY_TESTING       13
+#define DKIM_NOSUBDOMAINS      14
+#define DKIM_VERIFY_STATUS     15
+#define DKIM_VERIFY_REASON     16
+
+
+extern unsigned dkim_collect_input;    /* Runtime count of dkim signtures; tracks whether SMTP input is fed to DKIM validation */
+extern uschar *dkim_cur_signer;        /* Expansion variable, holds the current "signer" domain or identity during a acl_smtp_dkim run */
+extern int     dkim_key_length;        /* Expansion variable, length of signing key in bits */
+extern void   *dkim_signatures;        /* Actually a (pdkim_signature *) but most files do not need to know */
+extern uschar *dkim_signers;           /* Expansion variable, holds colon-separated list of domains and identities that have signed a message */
+extern gstring *dkim_signing_record;   /* domains+selectors used */
+extern uschar *dkim_signing_domain;    /* Expansion variable, domain used for signing a message. */
+extern uschar *dkim_signing_selector;  /* Expansion variable, selector used for signing a message. */
+extern uschar *dkim_verify_hashes;     /* Preference order for signatures */
+extern uschar *dkim_verify_keytypes;   /* Preference order for signatures */
+extern uschar *dkim_verify_min_keysizes; /* list of minimum key sizes, keyed by algo */
+extern BOOL    dkim_verify_minimal;    /* Shortcircuit signature verification */
+extern uschar *dkim_vdom_firstpass;    /* First successful domain verified, or null */
+extern uschar *dkim_verify_signers;    /* Colon-separated list of domains for each of which we call the DKIM ACL */
+extern uschar *dkim_verify_status;     /* result for this signature */
+extern uschar *dkim_verify_reason;     /* result for this signature */
+
diff --git a/src/src/miscmods/dkim_api.h b/src/src/miscmods/dkim_api.h
new file mode 100644 (file)
index 0000000..54e1141
--- /dev/null
@@ -0,0 +1,36 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2024 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* API definitions for the dkim module */
+
+
+/* Function table entry numbers */
+
+#define DKIM_VERIFY_FEED       0
+#define DKIM_VERIFY_PAUSE      1
+#define DKIM_VERIFY_FINISH     2
+#define DKIM_ACL_ENTRY         3
+#define DKIM_VERIFY_LOG_ALL    4
+#define DKIM_VDOM_FIRSTPASS    5
+#define DKIM_SIGNER_ISINLIST   6
+#define DKIM_STATUS_LISTMATCH  7
+#define DKIM_SETVAR            8
+#define DKIM_EXPAND_QUERY      9
+#define DKIM_TRANSPORT_INIT    10
+#define DKIM_TRANSPORT_WRITE   11
+
+#define DKIM_SIGS_LIST         12
+
+#define DKIM_HASHNAME_TO_TYPE  13
+#define DKIM_HASHTYPE_TO_METHOD        14
+#define DKIM_HASHNAME_TO_METHOD        15
+#define DKIM_SET_BODYHASH      16
+#define DKIM_DNS_PUBKEY                17
+#define DKIM_SIG_VERIFY                18
+#define DKIM_HEADER_RELAX      19
+#define DKIM_SIGN_DATA         20
diff --git a/src/src/miscmods/dkim_transport.c b/src/src/miscmods/dkim_transport.c
new file mode 100644 (file)
index 0000000..0500da2
--- /dev/null
@@ -0,0 +1,421 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2022 - 2024 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Transport shim for dkim signing */
+
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM   /* rest of file */
+
+
+static BOOL
+dkt_sign_fail(struct ob_dkim * dkim, int * errp)
+{
+GET_OPTION("dkim_strict");
+if (dkim->dkim_strict)
+  {
+  uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
+
+  if (dkim_strict_result)
+    if (  strcmpic(dkim_strict_result, US"1") == 0
+       || strcmpic(dkim_strict_result, US"true") == 0)
+      {
+      /* Set errno to something halfway meaningful */
+      *errp = EACCES;
+      log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
+       " and dkim_strict is set. Deferring message delivery.");
+      return FALSE;
+      }
+  }
+return TRUE;
+}
+
+/* Send the file at in_fd down the output fd */
+
+static BOOL
+dkt_send_file(int out_fd, int in_fd, off_t off
+#ifdef OS_SENDFILE
+  , size_t size
+#endif
+  )
+{
+#ifdef OS_SENDFILE
+DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
+#else
+DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
+#endif
+
+/*XXX should implement timeout, like transport_write_block_fd() ? */
+
+#ifdef OS_SENDFILE
+/* We can use sendfile() to shove the file contents
+   to the socket. However only if we don't use TLS,
+   as then there's another layer of indirection
+   before the data finally hits the socket. */
+if (tls_out.active.sock != out_fd)
+  {
+  ssize_t copied = 0;
+
+  while(copied >= 0 && off < size)
+    copied = os_sendfile(out_fd, in_fd, &off, size - off);
+  if (copied < 0)
+    return FALSE;
+  }
+else
+
+#endif
+
+  {
+  int sread, wwritten;
+
+  /* Rewind file */
+  if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
+
+  /* Send file down the original fd */
+  while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
+    {
+    uschar * p = deliver_out_buffer;
+    /* write the chunk */
+
+    while (sread)
+      {
+#ifndef DISABLE_TLS
+      wwritten = tls_out.active.sock == out_fd
+       ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
+       : write(out_fd, CS p, sread);
+#else
+      wwritten = write(out_fd, CS p, sread);
+#endif
+      if (wwritten == -1)
+       return FALSE;
+      p += wwritten;
+      sread -= wwritten;
+      }
+    }
+
+  if (sread == -1)
+    return FALSE;
+  }
+
+return TRUE;
+}
+
+
+
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is active and no transport filter is to be used.
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
+  const uschar ** err)
+{
+int save_fd = tctx->u.fd;
+int save_options = tctx->options;
+BOOL save_wireformat = f.spool_file_wireformat;
+uschar * hdrs;
+gstring * dkim_signature;
+int hsize;
+const uschar * errstr;
+BOOL rc;
+
+DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
+
+/* Get headers in string for signing and transmission.  Do CRLF
+and dotstuffing (but no body nor dot-termination) */
+
+tctx->u.msg = NULL;
+tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
+  | topt_output_string | topt_no_body;
+
+rc = transport_write_message(tctx, 0);
+hdrs = string_from_gstring(tctx->u.msg);
+hsize = tctx->u.msg->ptr;
+
+tctx->u.fd = save_fd;
+tctx->options = save_options;
+if (!rc) return FALSE;
+
+/* Get signatures for headers plus spool data file */
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();       /*XXX perhaps move this call back to the smtp tpt
+             around where it currently calls arc_ams_setup_sign_bodyhash() ? */
+#endif
+
+/* The dotstuffed status of the datafile depends on whether it was stored
+in wireformat. */
+
+dkim->dot_stuffed = f.spool_file_wireformat;
+if (!(dkim_signature = dkim_exim_sign(deliver_datafile,
+             spool_data_start_offset(message_id), hdrs, dkim, &errstr)))
+  if (!(rc = dkt_sign_fail(dkim, &errno)))
+    {
+    *err = errstr;
+    return FALSE;
+    }
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                        /* Prepend ARC headers */
+  {
+  uschar * e = NULL;
+  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
+    {
+    *err = e;
+    return FALSE;
+    }
+  }
+#endif
+
+/* Write the signature and headers into the deliver-out-buffer.  This should
+mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
+(transport_write_message() sizes the BDAT for the buffered amount) - for short
+messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
+having already been done - but we have to say we want CRLF output format, and
+temporarily set the marker for possible already-CRLF input. */
+
+tctx->options &= ~topt_escape_headers;
+f.spool_file_wireformat = TRUE;
+transport_write_reset(0);
+if (  (  dkim_signature
+      && dkim_signature->ptr > 0
+      && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
+      )
+   || !write_chunk(tctx, hdrs, hsize)
+   )
+  return FALSE;
+
+f.spool_file_wireformat = save_wireformat;
+tctx->options = save_options | topt_no_headers | topt_continuation;
+
+if (!(transport_write_message(tctx, 0)))
+  return FALSE;
+
+tctx->options = save_options;
+return TRUE;
+}
+
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is active and a transport filter is to be used.  The function sets up a
+   replacement fd into a -K file, then calls the normal function. This way, the
+   exact bits that exim would have put "on the wire" will end up in the file
+   (except for TLS encapsulation, which is the very very last thing). When we
+   are done signing the file, send the signed message down the original fd (or
+   TLS fd).
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+static BOOL
+dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
+{
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar * dkim_spool_name;
+gstring * dkim_signature;
+int options, dlen;
+off_t k_file_size;
+const uschar * errstr;
+
+dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
+                   string_sprintf("-%d-K", (int)getpid()));
+
+DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
+
+if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
+  {
+  /* Can't create spool file. Ugh. */
+  rc = FALSE;
+  save_errno = errno;
+  *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
+  goto CLEANUP;
+  }
+
+/* Call transport utility function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
+
+  {
+  int save_fd = tctx->u.fd;
+  tctx->u.fd = dkim_fd;
+  options = tctx->options;
+  tctx->options &= ~topt_use_bdat;
+
+  rc = transport_write_message(tctx, 0);
+
+  tctx->u.fd = save_fd;
+  tctx->options = options;
+  }
+
+/* Save error state. We must clean up before returning. */
+if (!rc)
+  {
+  save_errno = errno;
+  goto CLEANUP;
+  }
+
+#ifdef EXPERIMENTAL_ARC
+arc_sign_init();
+#endif
+
+/* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
+status of the file depends on the output of transport_write_message() just
+above, which should be the result of the end_dot flag in tctx->options. */
+
+dkim->dot_stuffed = !!(options & topt_end_dot);
+if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
+  {
+  dlen = 0;
+  if (!(rc = dkt_sign_fail(dkim, &save_errno)))
+    {
+    *err = errstr;
+    goto CLEANUP;
+    }
+  }
+else
+  dlen = dkim_signature->ptr;
+
+#ifdef EXPERIMENTAL_ARC
+if (dkim->arc_signspec)                                /* Prepend ARC headers */
+  {
+  if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
+    goto CLEANUP;
+  dlen = dkim_signature->ptr;
+  }
+#endif
+
+#ifndef OS_SENDFILE
+if (options & topt_use_bdat)
+#endif
+  if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
+    {
+    *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
+    goto CLEANUP;
+    }
+
+if (options & topt_use_bdat)
+  {
+  /* On big messages output a precursor chunk to get any pipelined
+  MAIL & RCPT commands flushed, then reap the responses so we can
+  error out on RCPT rejects before sending megabytes. */
+
+  if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
+     && dlen > 0)
+    {
+    if (  tctx->chunk_cb(tctx, dlen, 0) != OK
+       || !transport_write_block(tctx,
+                   dkim_signature->s, dlen, FALSE)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+       )
+      goto err;
+    dlen = 0;
+    }
+
+  /* Send the BDAT command for the entire message, as a single LAST-marked
+  chunk. */
+
+  if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
+    goto err;
+  }
+
+if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
+  goto err;
+
+if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
+#ifdef OS_SENDFILE
+  , k_file_size
+#endif
+  ))
+  {
+  save_errno = errno;
+  rc = FALSE;
+  }
+
+CLEANUP:
+  /* unlink -K file */
+  if (dkim_fd >= 0) (void)close(dkim_fd);
+  Uunlink(dkim_spool_name);
+  errno = save_errno;
+  return rc;
+
+err:
+  save_errno = errno;
+  rc = FALSE;
+  goto CLEANUP;
+}
+
+
+
+/***************************************************************************************************
+*    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
+***************************************************************************************************/
+
+/* This function is a wrapper around transport_write_message().
+   It is only called from the smtp transport if DKIM or Domainkeys support
+   is compiled in.
+
+Arguments:
+  As for transport_write_message() in transort.c, with additional arguments
+  for DKIM.
+
+Returns:       TRUE on success; FALSE (with errno) for any failure
+*/
+
+BOOL
+dkim_transport_write_message(transport_ctx * tctx,
+  struct ob_dkim * dkim, const uschar ** err)
+{
+BOOL yield;
+
+/* If we can't sign, just call the original function. */
+
+if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+   && !dkim->force_bodyhash)
+  return transport_write_message(tctx, 0);
+
+/* If there is no filter command set up, construct the message and calculate
+a dkim signature of it, send the signature and a reconstructed message. This
+avoids using a temporary file. */
+
+if (  !transport_filter_argv
+   || !*transport_filter_argv
+   || !**transport_filter_argv
+   )
+  yield = dkt_direct(tctx, dkim, err);
+
+else
+  /* Use the transport path to write a file, calculate a dkim signature,
+  send the signature and then send the file. */
+
+  yield = dkt_via_kfile(tctx, dkim, err);
+
+tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
+return yield;
+}
+
+#endif /* whole file */
+
+/* vi: aw ai sw=2
+*/
+/* End of dkim_transport.c */
index 37648d0452c19c7a03b58fcfda61c803c1ff9074..4a8beab66d6606e2f2c087f84678a06e64e429f5 100644 (file)
@@ -22,7 +22,7 @@
 
 #  include "../functions.h"
 #  include "dmarc.h"
-#  include "../pdkim/pdkim.h"
+#  include "pdkim.h"
 
 OPENDMARC_LIB_T     dmarc_ctx;
 DMARC_POLICY_T     *dmarc_pctx = NULL;
@@ -32,7 +32,8 @@ BOOL dmarc_abort  = FALSE;
 uschar *dmarc_pass_fail = US"skipped";
 header_line *from_header   = NULL;
 
-misc_module_info * spf_mod_info;
+static misc_module_info * dmarc_dkim_mod_info;
+static misc_module_info * dmarc_spf_mod_info;
 SPF_response_t   *spf_response_p;
 int dmarc_spf_ares_result  = 0;
 uschar *spf_sender_domain  = NULL;
@@ -73,9 +74,19 @@ static BOOL
 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);
+if (!(dmarc_spf_mod_info = misc_mod_find(US"spf", &errstr)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
+/*XXX not yet used, but will be */
+if (!(dmarc_dkim_mod_info = misc_mod_find(US"dkim", &errstr)))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dmarc: %s", errstr);
+  return FALSE;
+  }
+
 return TRUE;
 }
 
@@ -188,6 +199,8 @@ return OK;
 static void
 dmarc_smtp_reset(void)
 {
+f.dmarc_has_been_checked = f.dmarc_disable_verify =
+f.dmarc_enable_forensic = FALSE;
 dmarc_domain_policy = dmarc_status = dmarc_status_text =
 dmarc_used_domain = NULL;
 }
@@ -394,7 +407,6 @@ dmarc_process(void)
 int sr, origin;             /* used in SPF section */
 int dmarc_spf_result  = 0;  /* stores spf into dmarc conn ctx */
 int tmp_ans, c;
-pdkim_signature * sig = dkim_signatures;
 uschar * rr;
 BOOL has_dmarc_record = TRUE;
 u_char ** ruf; /* forensic report addressees, if called for */
@@ -453,6 +465,7 @@ if (!dmarc_abort && !sender_host_authenticated)
   {
   uschar * dmarc_domain;
   gstring * dkim_history_buffer = NULL;
+  typedef const pdkim_signature * (*sigs_fn_t)(void);
 
   /* Use the envelope sender domain for this part of DMARC */
 
@@ -460,9 +473,9 @@ if (!dmarc_abort && !sender_host_authenticated)
 
     {
     typedef SPF_response_t * (*fn_t)(void);
-    if (spf_mod_info)
+    if (dmarc_spf_mod_info)
       /*XXX ugly use of a pointer */
-      spf_response_p = ((fn_t *) spf_mod_info->functions)[SPF_GET_RESPONSE]();
+      spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
     }
 
   if (!spf_response_p)
@@ -519,7 +532,9 @@ if (!dmarc_abort && !sender_host_authenticated)
   /* Now we cycle through the dkim signature results and put into
   the opendmarc context, further building the DMARC reply. */
 
-  for(pdkim_signature * sig = dkim_signatures; sig; sig = sig->next)
+  for(const pdkim_signature * sig =
+             (((sigs_fn_t *)dmarc_dkim_mod_info->functions)[DKIM_SIGS_LIST])();
+      sig; sig = sig->next)
     {
     int dkim_result, dkim_ares_result, vs, ves;
 
@@ -749,7 +764,6 @@ static optionlist dmarc_options[] = {
 static void * dmarc_functions[] = {
   [DMARC_PROCESS] =    dmarc_process,
   [DMARC_EXPAND_QUERY] = dmarc_exim_expand_query,
-  [DMARC_AUTHRES] =    authres_dmarc,
   [DMARC_STORE_DATA] = dmarc_store_data,
 };
 
@@ -775,6 +789,7 @@ misc_module_info dmarc_module_info =
   .lib_vers_report =   dmarc_version_report,
   .smtp_reset =                dmarc_smtp_reset,
   .msg_init =          dmarc_msg_init,
+  .authres =           authres_dmarc,
 
   .options =           dmarc_options,
   .options_count =     nelem(dmarc_options),
index 6ba8a5060ca189ef5ee29096dfdc64b4f80dd9a8..9d9ef62da345f8dd3375a9ea8cf5f2f9e3430749 100644 (file)
@@ -13,5 +13,4 @@
 
 #define        DMARC_PROCESS           0
 #define DMARC_EXPAND_QUERY     1
-#define DMARC_AUTHRES          2
-#define DMARC_STORE_DATA       3
+#define DMARC_STORE_DATA       2
diff --git a/src/src/miscmods/pdkim/Makefile b/src/src/miscmods/pdkim/Makefile
new file mode 100644 (file)
index 0000000..47f92ee
--- /dev/null
@@ -0,0 +1,19 @@
+# Make file for building the pdkim library.
+# Copyright (c) The Exim Maintainers 1995 - 2018
+
+OBJ = pdkim.o signing.o
+
+pdkim.a:         $(OBJ)
+                @$(RM_COMMAND) -f pdkim.a
+                @echo "$(AR) pdkim.a"
+                $(FE)$(AR) pdkim.a $(OBJ)
+                $(RANLIB) $@
+
+.SUFFIXES:       .o .c
+.c.o:;           @echo "$(CC) $*.c"
+                $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c
+
+pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c
+signing.o: $(HDRS) crypt_ver.h signing.h signing.c
+
+# End
diff --git a/src/src/miscmods/pdkim/README b/src/src/miscmods/pdkim/README
new file mode 100644 (file)
index 0000000..953e86e
--- /dev/null
@@ -0,0 +1,9 @@
+PDKIM - a RFC4871 (DKIM) implementation
+http://duncanthrax.net/pdkim/
+Copyright (C) 2009      Tom Kistner <tom@duncanthrax.net>
+
+No longer includes code from the PolarSSL project.
+Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
+
+This copy of PDKIM is included with Exim. For a standalone distribution,
+visit http://duncanthrax.net/pdkim/.
diff --git a/src/src/miscmods/pdkim/crypt_ver.h b/src/src/miscmods/pdkim/crypt_ver.h
new file mode 100644 (file)
index 0000000..56ae236
--- /dev/null
@@ -0,0 +1,34 @@
+/*************************************************
+*     Exim - an Internet mail transport agent    *
+*************************************************/
+
+/* Copyright (c) Jeremy Harris 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Signing and hashing routine selection for PDKIM */
+
+#include "../exim.h"
+#include "../sha_ver.h"
+
+
+#ifdef USE_GNUTLS
+# include <gnutls/gnutls.h>
+
+# if GNUTLS_VERSION_NUMBER >= 0x030000
+#  define SIGN_GNUTLS
+#  if GNUTLS_VERSION_NUMBER >= 0x030600
+#   define SIGN_HAVE_ED25519
+#  endif
+# else
+#  define SIGN_GCRYPT
+# endif
+#endif
+
+#ifdef USE_OPENSSL
+# define SIGN_OPENSSL
+# if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10101000L
+#  define SIGN_HAVE_ED25519
+# endif
+#endif
+
diff --git a/src/src/miscmods/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c
new file mode 100644 (file)
index 0000000..cdbdfc5
--- /dev/null
@@ -0,0 +1,2088 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (c) The Exim Maintainers 2021 - 2024
+ *  Copyright (C) 2016 - 2020  Jeremy Harris <jgh@exim.org>
+ *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  http://duncanthrax.net/pdkim/
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "../exim.h"
+
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#ifdef DISABLE_TLS
+# error Must not DISABLE_TLS, for DKIM
+#endif
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#include "pdkim.h"
+#include "signing.h"
+
+#define PDKIM_SIGNATURE_VERSION     "1"
+#define PDKIM_PUB_RECORD_VERSION    US "DKIM1"
+
+#define PDKIM_MAX_HEADER_LEN        65536
+#define PDKIM_MAX_HEADERS           512
+#define PDKIM_MAX_BODY_LINE_LEN     16384
+#define PDKIM_DNS_TXT_MAX_NAMELEN   1024
+
+/* -------------------------------------------------------------------------- */
+struct pdkim_stringlist {
+  uschar * value;
+  int      tag;
+  void *   next;
+};
+
+/* -------------------------------------------------------------------------- */
+/* A bunch of list constants */
+const uschar * pdkim_querymethods[] = {
+  US"dns/txt",
+  NULL
+};
+const uschar * pdkim_canons[] = {
+  US"simple",
+  US"relaxed",
+  NULL
+};
+
+const pdkim_hashtype pdkim_hashes[] = {
+  { US"sha1",   HASH_SHA1 },
+  { US"sha256", HASH_SHA2_256 },
+  { US"sha512", HASH_SHA2_512 }
+};
+
+const uschar * pdkim_keytypes[] = {
+  [KEYTYPE_RSA] =      US"rsa",
+#ifdef SIGN_HAVE_ED25519
+  [KEYTYPE_ED25519] =  US"ed25519",            /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
+#endif
+
+#ifdef notyet_EC_dkim_extensions       /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
+  US"eccp256",
+  US"eccp348",
+  US"ed448",
+#endif
+};
+
+typedef struct pdkim_combined_canon_entry {
+  const uschar *       str;
+  int                  canon_headers;
+  int                  canon_body;
+} pdkim_combined_canon_entry;
+
+pdkim_combined_canon_entry pdkim_combined_canons[] = {
+  { US"simple/simple",    PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
+  { US"simple/relaxed",   PDKIM_CANON_SIMPLE,   PDKIM_CANON_RELAXED },
+  { US"relaxed/simple",   PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
+  { US"relaxed/relaxed",  PDKIM_CANON_RELAXED,  PDKIM_CANON_RELAXED },
+  { US"simple",           PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
+  { US"relaxed",          PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
+  { NULL,                 0,                    0 }
+};
+
+
+static const blob lineending = {.data = US"\r\n", .len = 2};
+
+/* -------------------------------------------------------------------------- */
+uschar *
+dkim_sig_to_a_tag(const pdkim_signature * sig)
+{
+if (  sig->keytype < 0  || sig->keytype > nelem(pdkim_keytypes)
+   || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
+  return US"err";
+return string_sprintf("%s-%s",
+  pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
+}
+
+
+static int
+pdkim_keyname_to_keytype(const uschar * s)
+{
+for (int i = 0; i < nelem(pdkim_keytypes); i++)
+  if (Ustrcmp(s, pdkim_keytypes[i]) == 0) return i;
+return -1;
+}
+
+int
+pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; i < nelem(pdkim_hashes); i++)
+  if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
+    return i;
+return -1;
+}
+
+void
+pdkim_cstring_to_canons(const uschar * s, unsigned len,
+  int * canon_head, int * canon_body)
+{
+if (!len) len = Ustrlen(s);
+for (int i = 0; pdkim_combined_canons[i].str; i++)
+  if (  Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
+     && len == Ustrlen(pdkim_combined_canons[i].str))
+    {
+    *canon_head = pdkim_combined_canons[i].canon_headers;
+    *canon_body = pdkim_combined_canons[i].canon_body;
+    break;
+    }
+}
+
+
+
+const char *
+pdkim_verify_status_str(int status)
+{
+switch(status)
+  {
+  case PDKIM_VERIFY_NONE:    return "PDKIM_VERIFY_NONE";
+  case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
+  case PDKIM_VERIFY_FAIL:    return "PDKIM_VERIFY_FAIL";
+  case PDKIM_VERIFY_PASS:    return "PDKIM_VERIFY_PASS";
+  default:                   return "PDKIM_VERIFY_UNKNOWN";
+  }
+}
+
+const char *
+pdkim_verify_ext_status_str(int ext_status)
+{
+switch(ext_status)
+  {
+  case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
+  case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
+  case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
+  case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
+  case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
+  case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
+  case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
+  case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE: return "PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE";
+  case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
+  case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
+  default: return "PDKIM_VERIFY_UNKNOWN";
+  }
+}
+
+const uschar *
+pdkim_errstr(int status)
+{
+switch(status)
+  {
+  case PDKIM_OK:               return US"OK";
+  case PDKIM_FAIL:             return US"FAIL";
+  case PDKIM_ERR_RSA_PRIVKEY:  return US"PRIVKEY";
+  case PDKIM_ERR_RSA_SIGNING:  return US"SIGNING";
+  case PDKIM_ERR_LONG_LINE:    return US"LONG_LINE";
+  case PDKIM_ERR_BUFFER_TOO_SMALL:     return US"BUFFER_TOO_SMALL";
+  case PDKIM_ERR_EXCESS_SIGS:  return US"EXCESS_SIGS";
+  case PDKIM_SIGN_PRIVKEY_WRAP:        return US"PRIVKEY_WRAP";
+  case PDKIM_SIGN_PRIVKEY_B64D:        return US"PRIVKEY_B64D";
+  default: return US"(unknown)";
+  }
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+static pdkim_stringlist *
+pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
+{
+pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED);
+
+memset(new_entry, 0, sizeof(pdkim_stringlist));
+new_entry->value = string_copy(str);
+if (base) new_entry->next = base;
+return new_entry;
+}
+
+
+
+/* Trim whitespace fore & aft */
+
+static void
+pdkim_strtrim(gstring * str)
+{
+uschar * p = str->s;
+uschar * q;
+
+while (*p == '\t' || *p == ' ')                /* dump the leading whitespace */
+  { str->size--; str->ptr--; str->s++; }
+
+while (  str->ptr > 0
+      && ((q = str->s + str->ptr - 1),  (*q == '\t' || *q == ' '))
+      )
+  str->ptr--;                          /* dump trailing whitespace */
+
+(void) string_from_gstring(str);
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_free_ctx(pdkim_ctx *ctx)
+{
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Matches the name of the passed raw "header" against
+   the passed colon-separated "tick", and invalidates
+   the entry in tick.  Entries can be prefixed for multi- or over-signing,
+   in which case do not invalidate.
+
+   Returns OK for a match, or fail-code
+*/
+
+static int
+header_name_match(const uschar * header, uschar * tick)
+{
+const uschar * ticklist = tick;
+int sep = ':';
+BOOL multisign;
+uschar * hname, * p, * ele;
+uschar * hcolon = Ustrchr(header, ':');                /* Get header name */
+
+if (!hcolon)
+  return PDKIM_FAIL; /* This isn't a header */
+
+/* if we had strncmpic() we wouldn't need this copy */
+hname = string_copyn(header, hcolon-header);
+
+while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
+  {
+  switch (*ele)
+  {
+  case '=': case '+':  multisign = TRUE; ele++; break;
+  default:             multisign = FALSE; break;
+  }
+
+  if (strcmpic(ele, hname) == 0)
+    {
+    if (!multisign)
+      *p = '_';        /* Invalidate this header name instance in tick-off list */
+    return PDKIM_OK;
+    }
+  }
+return PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Module API */
+/* Performs "relaxed" canonicalization of a header. */
+
+uschar *
+pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
+{
+BOOL past_field_name = FALSE;
+BOOL seen_wsp = FALSE;
+uschar * relaxed = store_get(len+3, GET_TAINTED);
+uschar * q = relaxed;
+
+for (const uschar * p = header; p - header < len; p++)
+  {
+  uschar c = *p;
+
+  if (c == '\r' || c == '\n')  /* Ignore CR & LF */
+    continue;
+  if (c == '\t' || c == ' ')
+    {
+    if (seen_wsp)
+      continue;
+    c = ' ';                   /* Turns WSP into SP */
+    seen_wsp = TRUE;
+    }
+  else
+    if (!past_field_name && c == ':')
+      {
+      if (seen_wsp) q--;       /* This removes WSP immediately before the colon */
+      seen_wsp = TRUE;         /* This removes WSP immediately after the colon */
+      past_field_name = TRUE;
+      }
+    else
+      seen_wsp = FALSE;
+
+  /* Lowercase header name */
+  if (!past_field_name) c = tolower(c);
+  *q++ = c;
+  }
+
+if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
+
+if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
+*q = '\0';
+return relaxed;
+}
+
+
+uschar *
+pdkim_relax_header(const uschar * header, BOOL append_crlf)
+{
+return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_QP_ERROR_DECODE -1
+
+static const uschar *
+pdkim_decode_qp_char(const uschar *qp_p, int *c)
+{
+const uschar *initial_pos = qp_p;
+
+/* Advance one char */
+qp_p++;
+
+/* Check for two hex digits and decode them */
+if (isxdigit(*qp_p) && isxdigit(qp_p[1]))
+  {
+  /* Do hex conversion */
+  *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) << 4;
+  *c |= isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10;
+  return qp_p + 2;
+  }
+
+/* Illegal char here */
+*c = PDKIM_QP_ERROR_DECODE;
+return initial_pos;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static uschar *
+pdkim_decode_qp(const uschar * str)
+{
+int nchar = 0;
+uschar * q;
+const uschar * p = str;
+uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED);
+
+*n = '\0';
+q = n;
+while (*p)
+  {
+  if (*p == '=')
+    {
+    p = pdkim_decode_qp_char(p, &nchar);
+    if (nchar >= 0)
+      {
+      *q++ = nchar;
+      continue;
+      }
+    }
+  else
+    *q++ = *p;
+  p++;
+  }
+*q = '\0';
+return n;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+void
+pdkim_decode_base64(const uschar * str, blob * b)
+{
+int dlen = b64decode(str, &b->data, str);
+if (dlen < 0) b->data = NULL;
+b->len = dlen;
+}
+
+uschar *
+pdkim_encode_base64(blob * b)
+{
+return b64encode(b->data, b->len);
+}
+
+
+/* -------------------------------------------------------------------------- */
+#define PDKIM_HDR_LIMBO 0
+#define PDKIM_HDR_TAG   1
+#define PDKIM_HDR_VALUE 2
+
+static pdkim_signature *
+pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
+{
+pdkim_signature * sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
+uschar * q;
+gstring * cur_tag = NULL, * cur_val = NULL;
+BOOL past_hname = FALSE, in_b_val = FALSE;
+int where = PDKIM_HDR_LIMBO;
+
+memset(sig, 0, sizeof(pdkim_signature));
+sig->bodylength = -1;
+
+/* Set so invalid/missing data error display is accurate */
+sig->version = 0;
+sig->keytype = -1;
+sig->hashtype = -1;
+
+q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED);
+
+for (uschar * p = raw_hdr; ; p++)
+  {
+  char c = *p;
+
+  /* Ignore FWS */
+  if (c == '\r' || c == '\n')
+    goto NEXT_CHAR;
+
+  /* Fast-forward through header name */
+  if (!past_hname)
+    {
+    if (c == ':') past_hname = TRUE;
+    goto NEXT_CHAR;
+    }
+
+  if (where == PDKIM_HDR_LIMBO)
+    {
+    /* In limbo, just wait for a tag-char to appear */
+    if (!(c >= 'a' && c <= 'z'))
+      goto NEXT_CHAR;
+
+    where = PDKIM_HDR_TAG;
+    }
+
+  if (where == PDKIM_HDR_TAG)
+    if (c == '=')
+      {
+      if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
+        {
+       *q++ = '=';
+       in_b_val = TRUE;
+       }
+      where = PDKIM_HDR_VALUE;
+      goto NEXT_CHAR;
+      }
+    else if (!isspace(c))
+      cur_tag = string_catn(cur_tag, p, 1);
+
+  if (where == PDKIM_HDR_VALUE)
+    {
+    if (c == '\r' || c == '\n' || c == ' ' || c == '\t')
+      goto NEXT_CHAR;
+
+    if (c == ';' || c == '\0')
+      {
+      /* We must have both tag and value, and tags must be one char except
+      for the possibility of "bh". */
+
+      if (  cur_tag && cur_val
+        && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
+        )
+        {
+       (void) string_from_gstring(cur_val);
+       pdkim_strtrim(cur_val);
+
+       DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
+
+       switch (*cur_tag->s)
+         {
+         case 'b':                             /* sig-data or body-hash */
+           switch (cur_tag->s[1])
+             {
+             case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
+             case 'h':  if (cur_tag->ptr != 2) goto bad_tag;
+                        pdkim_decode_base64(cur_val->s, &sig->bodyhash);
+                        break;
+             default:   goto bad_tag;
+             }
+           break;
+         case 'v':                                     /* version */
+             /* We only support version 1, and that is currently the
+                only version there is. */
+           sig->version =
+               Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
+           break;
+         case 'a':                                     /* algorithm */
+           {
+           const uschar * list = cur_val->s;
+           int sep = '-';
+           uschar * elem;
+
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             sig->keytype = pdkim_keyname_to_keytype(elem);
+           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
+             for (int i = 0; i < nelem(pdkim_hashes); i++)
+               if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
+                 { sig->hashtype = i; break; }
+           }
+           break;
+
+         case 'c':                                     /* canonicalization */
+           pdkim_cstring_to_canons(cur_val->s, 0,
+                                   &sig->canon_headers, &sig->canon_body);
+           break;
+         case 'q':                             /* Query method (for pubkey)*/
+           for (int i = 0; pdkim_querymethods[i]; i++)
+             if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
+               {
+               sig->querymethod = i;   /* we never actually use this */
+               break;
+               }
+           break;
+         case 's':                                     /* Selector */
+           sig->selector = string_copy_from_gstring(cur_val); break;
+         case 'd':                                     /* SDID */
+           sig->domain = string_copy_from_gstring(cur_val); break;
+         case 'i':                                     /* AUID */
+           sig->identity = pdkim_decode_qp(cur_val->s); break;
+         case 't':                                     /* Timestamp */
+           sig->created = strtoul(CS cur_val->s, NULL, 10); break;
+         case 'x':                                     /* Expiration */
+           sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
+         case 'l':                                     /* Body length count */
+           sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
+         case 'h':                                     /* signed header fields */
+           sig->headernames = string_copy_from_gstring(cur_val); break;
+         case 'z':                                     /* Copied headfields */
+           sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
+/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
+for rsafp signatures.  But later discussion is dropping those. */
+         default:
+           goto bad_tag;
+         }
+       }
+       else
+bad_tag:  DEBUG(D_acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
+
+      cur_tag = cur_val = NULL;
+      in_b_val = FALSE;
+      where = PDKIM_HDR_LIMBO;
+      }
+    else
+      cur_val = string_catn(cur_val, p, 1);
+    }
+
+NEXT_CHAR:
+  if (c == '\0')
+    break;
+
+  if (!in_b_val)
+    *q++ = c;
+  }
+
+if (sig->keytype < 0 || sig->hashtype < 0)     /* Cannot verify this signature */
+  return NULL;
+
+*q = '\0';
+/* Chomp raw header. The final newline must not be added to the signature. */
+while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
+  *q = '\0';
+
+DEBUG(D_acl)
+  {
+  debug_printf(
+         "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  debug_printf("%Z\n", US sig->rawsig_no_b_val);
+  debug_printf(
+         "DKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
+  debug_printf(
+         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  }
+
+if (!pdkim_set_sig_bodyhash(ctx, sig))
+  return NULL;
+
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+pdkim_pubkey *
+pdkim_parse_pubkey_record(const uschar * raw_record)
+{
+pdkim_pubkey * pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
+
+memset(pub, 0, sizeof(pdkim_pubkey));
+
+for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
+  {
+  Uskip_whitespace(&ele);
+  end = Ustrchrnul(ele, ';');
+  tspec = string_copyn(ele, end - ele);
+  if (*end) end++;                     /* skip the ; */
+
+  if ((val = Ustrchr(tspec, '=')))
+    {
+    int taglen = val++ - tspec;
+
+    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
+    while (taglen > 1 && isspace(tspec[taglen-1]))
+      taglen--;                        /* Ignore whitespace before = */
+    Uskip_whitespace(&val);    /* Ignore whitespace after = */
+    if (isspace(val[ Ustrlen(val)-1 ]))
+      {                                /* Ignore whitespace after value */
+      gstring * g = string_cat(NULL, val);
+      while (isspace(gstring_last_char(g)))
+       gstring_trim(g, 1);
+      val = string_from_gstring(g);
+      }
+
+    if (taglen == 1) switch (tspec[0])
+      {
+      case 'v': pub->version = val;                    break;
+      case 'h': pub->hashes = val;                     break;
+      case 'k': pub->keytype = val;                    break;
+      case 'g': pub->granularity = val;                        break;
+      case 'n': pub->notes = pdkim_decode_qp(val);     break;
+      case 'p': pdkim_decode_base64(val, &pub->key);   break;
+      case 's': pub->srvtype = val;                    break;
+      case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
+               if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
+               break;
+      default:  goto bad_tag;
+      }
+    else
+bad_tag:
+       DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
+    }
+  }
+
+/* Set fallback defaults */
+if (!pub->version)
+  pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
+else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
+  {
+  DEBUG(D_acl) debug_printf(" Bad v= field\n");
+  return NULL;
+  }
+
+if (!pub->granularity) pub->granularity = US"*";
+if (!pub->keytype    ) pub->keytype     = US"rsa";
+if (!pub->srvtype    ) pub->srvtype     = US"*";
+
+/* p= is required */
+if (pub->key.data)
+  return pub;
+
+DEBUG(D_acl) debug_printf(" Missing p= field\n");
+return NULL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Update one bodyhash with some additional data.
+If we have to relax the data for this sig, return our copy of it. */
+
+static blob *
+pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
+{
+const blob * canon_data = orig_data;
+size_t left;
+
+/* Defaults to simple canon (no further treatment necessary) */
+
+if (b->canon_method == PDKIM_CANON_RELAXED)
+  {
+  /* Relax the line if not done already */
+  if (!relaxed_data)
+    {
+    BOOL seen_wsp = FALSE;
+    int q = 0;
+
+    /* We want to be able to free this else we allocate
+    for the entire message which could be many MB. Since
+    we don't know what allocations the SHA routines might
+    do, not safe to use store_get()/store_reset(). */
+
+    relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
+    relaxed_data->data = US (relaxed_data+1);
+
+    for (const uschar * p = orig_data->data, * r = p + orig_data->len; p < r; p++)
+      {
+      char c = *p;
+      if (c == '\r')
+       {
+       if (q > 0 && relaxed_data->data[q-1] == ' ')
+         q--;
+       }
+      else if (c == '\t' || c == ' ')
+       {
+       c = ' '; /* Turns WSP into SP */
+       if (seen_wsp)
+         continue;
+       seen_wsp = TRUE;
+       }
+      else
+       seen_wsp = FALSE;
+      relaxed_data->data[q++] = c;
+      }
+    relaxed_data->data[q] = '\0';
+    relaxed_data->len = q;
+    }
+  canon_data = relaxed_data;
+  }
+
+/* Make sure we don't exceed the to-be-signed body length */
+left = canon_data->len;
+if (  b->bodylength >= 0
+   && left > (unsigned long)b->bodylength - b->signed_body_bytes
+   )
+  left = (unsigned long)b->bodylength - b->signed_body_bytes;
+
+if (left > 0)
+  {
+  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
+  b->signed_body_bytes += left;
+  DEBUG(D_acl) debug_printf("%.*Z\n", left, canon_data->data);
+  }
+
+return relaxed_data;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+static void
+pdkim_finish_bodyhash(pdkim_ctx * ctx)
+{
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)     /* Finish hashes */
+  {
+  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
+      pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
+      b->bodylength, b->signed_body_bytes);
+  exim_sha_finish(&b->body_hash_ctx, &b->bh);
+  }
+
+/* Traverse all signatures */
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
+  {
+  pdkim_bodyhash * b = sig->calc_body_hash;
+
+  DEBUG(D_acl)
+    {
+    debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
+                "DKIM [%s]%s Body %s computed: ",
+       sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes,
+       sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname);
+    debug_printf("%.*H\n", b->bh.len, CUS b->bh.data);
+    }
+
+  /* SIGNING -------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    /* If bodylength limit is set, and we have received less bytes
+       than the requested amount, effectively remove the limit tag. */
+    if (b->signed_body_bytes < sig->bodylength)
+      sig->bodylength = -1;
+    }
+
+  else
+  /* VERIFICATION --------------------------------------------------------- */
+  /* Be careful that the header sig included a bodyash */
+
+    if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
+       && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
+      {
+      DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
+      }
+    else
+      {
+      DEBUG(D_acl)
+        {
+       debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
+       debug_printf("%.*H\n", sig->bodyhash.len, sig->bodyhash.data);
+       debug_printf("DKIM [%s] Body hash did NOT verify\n", sig->domain);
+       }
+      sig->verify_status     = PDKIM_VERIFY_FAIL;
+      sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
+      }
+  }
+}
+
+
+
+static void
+pdkim_body_complete(pdkim_ctx * ctx)
+{
+/* In simple body mode, if any empty lines were buffered,
+replace with one. rfc 4871 3.4.3 */
+/*XXX checking the signed-body-bytes is a gross hack; I think
+it indicates that all linebreaks should be buffered, including
+the one terminating a text line */
+
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+  if (  b->canon_method == PDKIM_CANON_SIMPLE
+     && b->signed_body_bytes == 0
+     && b->num_buffered_blanklines > 0
+     )
+    (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
+
+ctx->flags |= PDKIM_SEEN_EOD;
+ctx->linebuf_offset = 0;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+/* Call from pdkim_feed below for processing complete body lines */
+/* NOTE: the line is not NUL-terminated; but we have a count */
+
+static void
+pdkim_bodyline_complete(pdkim_ctx * ctx)
+{
+blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
+blob * rnl = NULL;
+blob * rline = NULL;
+
+/* Ignore extra data if we've seen the end-of-data marker */
+if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
+
+/* We've always got one extra byte to stuff a zero ... */
+ctx->linebuf[line.len] = '\0';
+
+/* Terminate on EOD marker */
+if (ctx->flags & PDKIM_DOT_TERM)
+  {
+  if (memcmp(line.data, ".\r\n", 3) == 0)
+    { pdkim_body_complete(ctx); return; }
+
+  /* Unstuff dots */
+  if (memcmp(line.data, "..", 2) == 0)
+    { line.data++; line.len--; }
+  }
+
+/* Empty lines need to be buffered until we find a non-empty line */
+if (memcmp(line.data, "\r\n", 2) == 0)
+  {
+  for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+    b->num_buffered_blanklines++;
+  goto all_skip;
+  }
+
+/* Process line for each bodyhash separately */
+for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+  {
+  if (b->canon_method == PDKIM_CANON_RELAXED)
+    {
+    /* Lines with just spaces need to be buffered too */
+    uschar * cp = line.data;
+    char c;
+
+    while ((c = *cp))
+      {
+      if (c == '\r' && cp[1] == '\n') break;
+      if (c != ' ' && c != '\t') goto hash_process;
+      cp++;
+      }
+
+    b->num_buffered_blanklines++;
+    goto hash_skip;
+    }
+
+hash_process:
+  /* At this point, we have a non-empty line, so release the buffered ones. */
+
+  while (b->num_buffered_blanklines)
+    {
+    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+    b->num_buffered_blanklines--;
+    }
+
+  rline = pdkim_update_ctx_bodyhash(b, &line, rline);
+hash_skip: ;
+  }
+
+if (rnl) store_free(rnl);
+if (rline) store_free(rline);
+
+all_skip:
+
+ctx->linebuf_offset = 0;
+return;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Callback from pdkim_feed below for processing complete headers */
+#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
+
+static int
+pdkim_header_complete(pdkim_ctx * ctx)
+{
+if (ctx->cur_header->ptr > 1)
+  gstring_trim_trailing(ctx->cur_header, '\r');
+(void) string_from_gstring(ctx->cur_header);
+
+#ifdef EXPERIMENTAL_ARC
+/* Feed the header line to ARC processing */
+(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
+#endif
+
+if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
+
+/* SIGNING -------------------------------------------------------------- */
+if (ctx->flags & PDKIM_MODE_SIGN)
+  for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)  /* Traverse all signatures */
+
+    /* Add header to the signed headers list (in reverse order) */
+    sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
+
+/* VERIFICATION ----------------------------------------------------------- */
+/* DKIM-Signature: headers are added to the verification list */
+else
+  {
+#ifdef notdef
+  DEBUG(D_acl) debug_printf("DKIM >> raw hdr: %.*Z\n",
+                           ctx->cur_head->ptr, CUS ctx->cur_header->s);
+#endif
+  if (strncasecmp(CCS ctx->cur_header->s,
+                 DKIM_SIGNATURE_HEADERNAME,
+                 Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
+    {
+    pdkim_signature * sig, * last_sig;
+    /* Create and chain new signature block.  We could error-check for all
+    required tags here, but prefer to create the internal sig and expicitly
+    fail verification of it later. */
+
+    DEBUG(D_acl) debug_printf(
+       "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+
+    sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
+
+    if (!(last_sig = ctx->sig))
+      ctx->sig = sig;
+    else
+      {
+      while (last_sig->next) last_sig = last_sig->next;
+      last_sig->next = sig;
+      }
+
+    if (dkim_collect_input && --dkim_collect_input == 0)
+      {
+      ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+      ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
+      return PDKIM_ERR_EXCESS_SIGS;
+      }
+    }
+
+  /* all headers are stored for signature verification */
+  ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
+  }
+
+BAIL:
+ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';   /* leave buffer for reuse */
+return PDKIM_OK;
+}
+
+
+
+/* -------------------------------------------------------------------------- */
+#define HEADER_BUFFER_FRAG_SIZE 256
+
+DLLEXPORT int
+pdkim_feed(pdkim_ctx * ctx, const uschar * data, unsigned len)
+{
+/* Alternate EOD signal, used in non-dotstuffing mode */
+if (!data)
+  pdkim_body_complete(ctx);
+
+else for (unsigned p = 0; p < len; p++)
+  {
+  uschar c = data[p];
+  int rc;
+
+  if (ctx->flags & PDKIM_PAST_HDRS)
+    {
+    if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR))    /* emulate the CR */
+      {
+      ctx->linebuf[ctx->linebuf_offset++] = '\r';
+      if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+       return PDKIM_ERR_LONG_LINE;
+      }
+
+    /* Processing body byte */
+    ctx->linebuf[ctx->linebuf_offset++] = c;
+    if (c == '\r')
+      ctx->flags |= PDKIM_SEEN_CR;
+    else if (c == '\n')
+      {
+      ctx->flags &= ~PDKIM_SEEN_CR;
+      pdkim_bodyline_complete(ctx);
+      }
+
+    if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
+      return PDKIM_ERR_LONG_LINE;
+    }
+  else
+    {
+    /* Processing header byte */
+    if (c == '\r')
+      ctx->flags |= PDKIM_SEEN_CR;
+    else if (c == '\n')
+      {
+      if (!(ctx->flags & PDKIM_SEEN_CR))               /* emulate the CR */
+       ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1);
+
+      if (ctx->flags & PDKIM_SEEN_LF)          /* Seen last header line */
+       {
+       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+         return rc;
+
+       ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
+       DEBUG(D_acl) debug_printf(
+           "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+       continue;
+       }
+      else
+       ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
+      }
+    else if (ctx->flags & PDKIM_SEEN_LF)
+      {
+      if (!(c == '\t' || c == ' '))                    /* End of header */
+       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+         return rc;
+      ctx->flags &= ~PDKIM_SEEN_LF;
+      }
+
+    if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
+      ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
+    }
+  }
+return PDKIM_OK;
+}
+
+
+
+/* Extend a growing header with a continuation-linebreak */
+static gstring *
+pdkim_hdr_cont(gstring * str, int * col)
+{
+*col = 1;
+return string_catn(str, US"\r\n\t", 3);
+}
+
+
+
+/*
+ * RFC 5322 specifies that header line length SHOULD be no more than 78
+ *  pdkim_headcat
+ *
+ * Returns gstring (not nul-terminated) appending to one supplied
+ *
+ * col: this int holds and receives column number (octets since last '\n')
+ * str: partial string to append to
+ * pad: padding, split line or space after before or after eg: ";".
+ *               Only the initial charater is used.
+ * intro: - must join to payload eg "h=", usually the tag name
+ * payload: eg base64 data - long data can be split arbitrarily.
+ *
+ * this code doesn't fold the header in some of the places that RFC4871
+ * allows: As per RFC5322(2.2.3) it only folds before or after tag-value
+ * pairs and inside long values. it also always spaces or breaks after the
+ * "pad"
+ *
+ * No guarantees are made for output given out-of range input. like tag
+ * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
+ */
+
+static gstring *
+pdkim_headcat(int * col, gstring * str,
+  const uschar * pad, const uschar * intro, const uschar * payload)
+{
+int len, chomp, padded = 0;
+
+/* If we can fit at least the pad at the end of current line, do it now.
+Otherwise, wrap if there is a pad. */
+
+if (pad)
+  if (*col + 1 <= 78)
+    {
+    str = string_catn(str, pad, 1);
+    (*col)++;
+    pad = NULL;
+    padded = 1;
+    }
+  else
+    str = pdkim_hdr_cont(str, col);
+
+/* Special case: if the whole addition does not fit at the end of the current
+line, but could fit on a new line, wrap to give it its full, dedicated line.  */
+
+len = (pad ? 2 : padded)
+    + (intro ? Ustrlen(intro) : 0)
+    + (payload ? Ustrlen(payload) : 0);
+if (len <= 77 && *col+len > 78)
+  {
+  str = pdkim_hdr_cont(str, col);
+  padded = 0;
+  }
+
+/* Either we already dealt with the pad or we know there is room */
+
+if (pad)
+  {
+  str = string_catn(str, pad, 1);
+  str = string_catn(str, US" ", 1);
+  *col += 2;
+  }
+else if (padded && *col < 78)
+  {
+  str = string_catn(str, US" ", 1);
+  (*col)++;
+  }
+
+/* Call recursively with intro as payload: it gets the same, special treatment
+(that is, not split if < 78).  */
+
+if (intro)
+  str = pdkim_headcat(col, str, NULL, NULL, intro);
+
+if (payload)
+  for (len = Ustrlen(payload); len; len -= chomp)
+    {
+    if (*col >= 78)
+      str = pdkim_hdr_cont(str, col);
+    chomp = *col+len > 78 ? 78 - *col : len;
+    str = string_catn(str, payload, chomp);
+    *col += chomp;
+    payload += chomp;
+    }
+
+return str;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* Signing: create signature header
+*/
+static uschar *
+pdkim_create_header(pdkim_signature * sig, BOOL final)
+{
+uschar * base64_bh;
+uschar * base64_b;
+int col = 0;
+gstring * hdr;
+gstring * canon_all;
+
+canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
+canon_all = string_catn(canon_all, US"/", 1);
+canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
+(void) string_from_gstring(canon_all);
+
+hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
+col = hdr->ptr;
+
+/* Required and static bits */
+hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
+hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
+hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
+hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
+hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector);
+
+/* list of header names can be split between items. */
+  {
+  uschar * n = string_copy(sig->headernames);
+  uschar * i = US"h=";
+  uschar * s = US";";
+
+  while (*n)
+    {
+    uschar * c = Ustrchr(n, ':');
+
+    if (c) *c ='\0';
+
+    if (!i)
+      hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":");
+
+    hdr = pdkim_headcat(&col, hdr, s, i, n);
+
+    if (!c)
+      break;
+
+    n = c+1;
+    s = NULL;
+    i = NULL;
+    }
+  }
+
+base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
+hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
+
+/* Optional bits */
+if (sig->identity)
+  hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity);
+
+if (sig->created > 0)
+  {
+  uschar minibuf[21];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
+  hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
+}
+
+if (sig->expires > 0)
+  {
+  uschar minibuf[21];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
+  hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
+  }
+
+if (sig->bodylength >= 0)
+  {
+  uschar minibuf[21];
+
+  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
+  hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
+  }
+
+/* Preliminary or final version? */
+if (final)
+  {
+  base64_b = pdkim_encode_base64(&sig->sighash);
+  hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
+
+  /* add trailing semicolon: I'm not sure if this is actually needed */
+  hdr = pdkim_headcat(&col, hdr, NULL, US";", US"");
+  }
+else
+  {
+  /* To satisfy the rule "all surrounding whitespace [...] deleted"
+  ( RFC 6376 section 3.7 ) we ensure there is no whitespace here.  Otherwise
+  the headcat routine could insert a linebreak which the relaxer would reduce
+  to a single space preceding the terminating semicolon, resulting in an
+  incorrect header-hash. */
+  hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US"");
+  }
+
+return string_from_gstring(hdr);
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
+to DNS, hence the pubkey).  Check for more than 32 bytes; if so assume the
+alternate possible representation (still) being discussed: a
+SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
+should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
+which could be any size).  We still rely on the crypto library for checking for
+undersize.
+
+When the RFC is published this should be re-addressed. */
+
+static void
+check_bare_ed25519_pubkey(pdkim_pubkey * p)
+{
+int excess = p->key.len - 32;
+if (excess > 0)
+  {
+  DEBUG(D_acl)
+    debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
+  p->key.data += excess; p->key.len = 32;
+  }
+}
+
+
+static pdkim_pubkey *
+pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
+  const uschar ** errstr)
+{
+uschar * dns_txt_name, * dns_txt_reply;
+pdkim_pubkey * p;
+
+/* Fetch public key for signing domain, from DNS */
+
+dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
+
+if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
+   || dns_txt_reply[0] == '\0'
+   )
+  {
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
+  return NULL;
+  }
+
+DEBUG(D_acl)
+  {
+  debug_printf(
+    "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
+    " %s\n"
+    " Raw record: %Z\n",
+    dns_txt_name,
+    CUS dns_txt_reply);
+  }
+
+if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
+   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
+   )
+  {
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
+
+  DEBUG(D_acl)
+    {
+    if (p)
+      debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
+    else
+      debug_printf(" Error while parsing public key record\n");
+    debug_printf(
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+  return NULL;
+  }
+
+DEBUG(D_acl) debug_printf(
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Import public key */
+
+/* Normally we use the signature a= tag to tell us the pubkey format.
+When signing under debug we do a test-import of the pubkey, and at that
+time we do not have a signature so we must interpret the pubkey k= tag
+instead.  Assume writing on the sig is ok in that case. */
+
+if (sig->keytype < 0)
+  if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
+    {
+    DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
+    sig->verify_status =      PDKIM_VERIFY_INVALID;
+    sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+    return NULL;
+    }
+
+if (sig->keytype == KEYTYPE_ED25519)
+  check_bare_ed25519_pubkey(p);
+
+if ((*errstr = exim_dkim_verify_init(&p->key,
+           sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
+           vctx, &sig->keybits)))
+  {
+  DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
+  sig->verify_status =      PDKIM_VERIFY_INVALID;
+  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
+  return NULL;
+  }
+
+vctx->keytype = sig->keytype;
+return p;
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* Sort and filter the sigs developed from the message */
+
+static pdkim_signature *
+sort_sig_methods(pdkim_signature * siglist)
+{
+pdkim_signature * yield, ** ss;
+const uschar * prefs;
+uschar * ele;
+int sep;
+
+if (!siglist) return NULL;
+
+/* first select in order of hashtypes */
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes   '%s'\n", dkim_verify_hashes);
+for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
+     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+  {
+  int i = pdkim_hashname_to_hashtype(CUS ele, 0);
+  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+       s = next)
+    {
+    next = s->next;
+    if (s->hashtype == i)
+      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+    else
+      prev = &s->next;
+    }
+  }
+
+/* then in order of keytypes */
+siglist = yield;
+DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
+for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
+     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
+  {
+  int i = pdkim_keyname_to_keytype(CUS ele);
+  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
+       s = next)
+    {
+    next = s->next;
+    if (s->keytype == i)
+      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
+    else
+      prev = &s->next;
+    }
+  }
+
+DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
+  debug_printf(" retain d=%s s=%s a=%s\n",
+    s->domain, s->selector, dkim_sig_to_a_tag(s));
+return yield;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT int
+pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
+  const uschar ** err)
+{
+BOOL verify_pass = FALSE;
+
+/* Check if we must still flush a (partial) header. If that is the
+   case, the message has no body, and we must compute a body hash
+   out of '<CR><LF>' */
+if (ctx->cur_header && ctx->cur_header->ptr > 0)
+  {
+  blob * rnl = NULL;
+  int rc;
+
+  if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
+    return rc;
+
+  for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
+    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
+  if (rnl) store_free(rnl);
+  }
+else
+  DEBUG(D_acl) debug_printf(
+      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+/* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
+have a hash to do for ARC. */
+
+pdkim_finish_bodyhash(ctx);
+
+/* Sort and filter the recived signatures */
+
+if (!(ctx->flags & PDKIM_MODE_SIGN))
+  ctx->sig = sort_sig_methods(ctx->sig);
+
+if (!ctx->sig)
+  {
+  DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
+  *return_signatures = NULL;
+  return PDKIM_OK;
+  }
+
+for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
+  {
+  hctx hhash_ctx;
+  uschar * sig_hdr = US"";
+  blob hhash;
+  gstring * hdata = NULL;
+  es_ctx sctx;
+
+  if (  !(ctx->flags & PDKIM_MODE_SIGN)
+     && sig->verify_status == PDKIM_VERIFY_FAIL)
+    {
+    DEBUG(D_acl)
+       debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
+    continue;
+    }
+
+  /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
+  signing only, as it happens) and for either GnuTLS and OpenSSL when we are
+  signing with EC (specifically, Ed25519).  The former is because the GCrypt
+  signing operation is pure (does not do its own hash) so we must hash.  The
+  latter is because we (stupidly, but this is what the IETF draft is saying)
+  must hash with the declared hash method, then pass the result to the library
+  hash-and-sign routine (because that's all the libraries are providing.  And
+  we're stuck with whatever that hidden hash method is, too).  We may as well
+  do this hash incrementally.
+  We don't need the hash we're calculating here for the GnuTLS and OpenSSL
+  cases of RSA signing, since those library routines can do hash-and-sign.
+
+  Some time in the future we could easily avoid doing the hash here for those
+  cases (which will be common for a long while.  We could also change from
+  the current copy-all-the-headers-into-one-block, then call the hash-and-sign
+  implementation  - to a proper incremental one.  Unfortunately, GnuTLS just
+  cannot do incremental - either signing or verification.  Unsure about GCrypt.
+  */
+
+  /*XXX The header hash is also used (so far) by the verify operation */
+
+  if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
+    {
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "DKIM: hash setup error, possibly nonhandled hashtype");
+    break;
+    }
+
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    DEBUG(D_acl) debug_printf(
+       "DKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
+       " %s\n",
+       sig->sign_headers);
+
+  DEBUG(D_acl) debug_printf(
+      "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
+       pdkim_canons[sig->canon_headers]);
+
+
+  /* SIGNING ---------------------------------------------------------------- */
+  /* When signing, walk through our header list and add them to the hash. As we
+     go, construct a list of the header's names to use for the h= parameter.
+     Then append to that list any remaining header names for which there was no
+     header to sign. */
+
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    gstring * g = NULL;
+    const uschar * l;
+    uschar * s;
+    int sep = 0;
+
+    /* Import private key, including the keytype which we need for building
+    the signature header  */
+
+    if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
+      return PDKIM_ERR_RSA_PRIVKEY;
+      }
+    sig->keytype = sctx.keytype;
+
+    sig->headernames = NULL;                   /* Collected signed header names */
+    for (pdkim_stringlist * p = sig->headers; p; p = p->next)
+      {
+      uschar * rh = p->value;
+
+      if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
+       {
+       /* Collect header names (Note: colon presence is guaranteed here) */
+       g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
+
+       if (sig->canon_headers == PDKIM_CANON_RELAXED)
+         rh = pdkim_relax_header(rh, TRUE);    /* cook header for relaxed canon */
+
+       /* Feed header to the hash algorithm */
+       exim_sha_update_string(&hhash_ctx, CUS rh);
+
+       /* Remember headers block for signing (when the library cannot do incremental)  */
+       /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
+       hdata = exim_dkim_data_append(hdata, rh);
+
+       DEBUG(D_acl) debug_printf("%Z\n", rh);
+       }
+      }
+
+    /* Any headers we wanted to sign but were not present must also be listed.
+    Ignore elements that have been ticked-off or are marked as never-oversign. */
+
+    l = sig->sign_headers;
+    while((s = string_nextinlist(&l, &sep, NULL, 0)))
+      {
+      if (*s == '+')                   /* skip oversigning marker */
+        s++;
+      if (*s != '_' && *s != '=')
+       g = string_append_listele(g, ':', s);
+      }
+    sig->headernames = string_from_gstring(g);
+
+    /* Create signature header with b= omitted */
+    sig_hdr = pdkim_create_header(sig, FALSE);
+    }
+
+  /* VERIFICATION ----------------------------------------------------------- */
+  /* When verifying, walk through the header name list in the h= parameter and
+     add the headers to the hash in that order. */
+  else
+    {
+    uschar * p = sig->headernames;
+    uschar * q;
+
+    if (p)
+      {
+      /* clear tags */
+      for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+       hdrs->tag = 0;
+
+      p = string_copy(p);
+      while(1)
+       {
+       if ((q = Ustrchr(p, ':')))
+         *q = '\0';
+
+  /*XXX walk the list of headers in same order as received. */
+       for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
+         if (  hdrs->tag == 0
+            && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0
+            && (hdrs->value)[Ustrlen(p)] == ':'
+            )
+           {
+           /* cook header for relaxed canon, or just copy it for simple  */
+
+           uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
+             ? pdkim_relax_header(hdrs->value, TRUE)
+             : string_copy(CUS hdrs->value);
+
+           /* Feed header to the hash algorithm */
+           exim_sha_update_string(&hhash_ctx, CUS rh);
+
+           DEBUG(D_acl) debug_printf("%Z\n", rh);
+           hdrs->tag = 1;
+           break;
+           }
+
+       if (!q) break;
+       p = q+1;
+       }
+
+      sig_hdr = string_copy(sig->rawsig_no_b_val);
+      }
+    }
+
+  DEBUG(D_acl) debug_printf(
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+
+  DEBUG(D_acl)
+    {
+    debug_printf(
+           "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
+    debug_printf("%Z\n", CUS sig_hdr);
+    debug_printf(
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+
+  /* Relax header if necessary */
+  if (sig->canon_headers == PDKIM_CANON_RELAXED)
+    sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
+
+  DEBUG(D_acl)
+    {
+    debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
+           pdkim_canons[sig->canon_headers]);
+    debug_printf("%Z\n", CUS sig_hdr);
+    debug_printf(
+           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+    }
+
+  /* Finalize header hash */
+  exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
+  exim_sha_finish(&hhash_ctx, &hhash);
+
+  DEBUG(D_acl)
+    {
+    debug_printf("DKIM [%s] Header %s computed: ",
+      sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
+    debug_printf("%.*H\n", hhash.len, hhash.data);
+    }
+
+  /* Remember headers block for signing (when the signing library cannot do
+  incremental)  */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    hdata = exim_dkim_data_append(hdata, US sig_hdr);
+
+  /* SIGNING ---------------------------------------------------------------- */
+  if (ctx->flags & PDKIM_MODE_SIGN)
+    {
+    hashmethod hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+      ? HASH_NULL
+#else
+      ? HASH_SHA2_512
+#endif
+      : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+#ifdef SIGN_HAVE_ED25519
+    /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
+    routine.  For anything else we just pass the headers. */
+
+    if (sig->keytype != KEYTYPE_ED25519)
+#endif
+      {
+      hhash.data = hdata->s;
+      hhash.len = hdata->ptr;
+      }
+
+    if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
+      {
+      log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
+      return PDKIM_ERR_RSA_SIGNING;
+      }
+
+    DEBUG(D_acl)
+      {
+      debug_printf( "DKIM [%s] b computed: ", sig->domain);
+      debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
+      }
+
+    sig->signature_header = pdkim_create_header(sig, TRUE);
+    }
+
+  /* VERIFICATION ----------------------------------------------------------- */
+  else
+    {
+    ev_ctx vctx;
+    hashmethod hm;
+
+    /* Make sure we have all required signature tags */
+    if (!(  sig->domain        && *sig->domain
+        && sig->selector      && *sig->selector
+        && sig->headernames   && *sig->headernames
+        && sig->bodyhash.data
+        && sig->sighash.data
+        && sig->keytype >= 0
+        && sig->hashtype >= 0
+        && sig->version
+       ) )
+      {
+      sig->verify_status     = PDKIM_VERIFY_INVALID;
+      sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
+
+      DEBUG(D_acl) debug_printf(
+         " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
+         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
+         !(sig->domain && *sig->domain) ? "d="
+         : !(sig->selector && *sig->selector) ? "s="
+         : !(sig->headernames && *sig->headernames) ? "h="
+         : !sig->bodyhash.data ? "bh="
+         : !sig->sighash.data ? "b="
+         : sig->keytype < 0 || sig->hashtype < 0 ? "a="
+         : "v="
+         );
+      goto NEXT_VERIFY;
+      }
+
+    /* Make sure sig uses supported DKIM version (only v1) */
+    if (sig->version != 1)
+      {
+      sig->verify_status     = PDKIM_VERIFY_INVALID;
+      sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
+
+      DEBUG(D_acl) debug_printf(
+          " Error in DKIM-Signature header: unsupported DKIM version\n"
+          "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+      goto NEXT_VERIFY;
+      }
+
+    DEBUG(D_acl)
+      {
+      debug_printf( "DKIM [%s] b from mail: ", sig->domain);
+      debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
+      }
+
+    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
+      {
+      log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
+       sig->domain   ? "d=" : "", sig->domain   ? sig->domain   : US"",
+       sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
+      goto NEXT_VERIFY;
+      }
+
+    /* If the pubkey limits to a list of specific hashes, ignore sigs that
+    do not have the hash part of the sig algorithm matching */
+
+    if (sig->pubkey->hashes)
+      {
+      const uschar * list = sig->pubkey->hashes, * ele;
+      int sep = ':';
+      while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
+       if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
+      if (!ele)
+       {
+       DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
+         sig->pubkey->hashes,
+         pdkim_keytypes[sig->keytype],
+         pdkim_hashes[sig->hashtype].dkim_hashname);
+       sig->verify_status =      PDKIM_VERIFY_FAIL;
+       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
+       goto NEXT_VERIFY;
+       }
+      }
+
+    hm = sig->keytype == KEYTYPE_ED25519
+#if defined(SIGN_OPENSSL)
+      ? HASH_NULL
+#else
+      ? HASH_SHA2_512
+#endif
+      : pdkim_hashes[sig->hashtype].exim_hashmethod;
+
+    /* Check the signature */
+
+    if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
+      {
+      DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
+      sig->verify_status =      PDKIM_VERIFY_FAIL;
+      sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
+      goto NEXT_VERIFY;
+      }
+    if (*dkim_verify_min_keysizes)
+      {
+      unsigned minbits;
+      const uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
+                                   dkim_verify_min_keysizes);
+      if (ss &&  (minbits = atoi(CCS ss)) > sig->keybits)
+       {
+       DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
+         pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
+       sig->verify_status =      PDKIM_VERIFY_FAIL;
+       sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE;
+       }
+      }
+
+
+    /* We have a winner! (if bodyhash was correct earlier) */
+    if (sig->verify_status == PDKIM_VERIFY_NONE)
+      {
+      sig->verify_status = PDKIM_VERIFY_PASS;
+      verify_pass = TRUE;
+      /*XXX We used to "break" here if dkim_verify_minimal, but that didn't
+      stop the ACL being called.  So move that test.  Unfortunately, we
+      need to eval all the sigs here only to possibly ignore some later,
+      because we don't know what verify options might say.
+      Could we change to a later eval of the sig?
+      Both bits are called from receive_msg().
+      Moving the test is also suboptimal for the case of no ACL (or no
+      signers to check!) so keep it for that case, but after debug output */
+      }
+
+NEXT_VERIFY:
+    DEBUG(D_acl)
+      {
+      debug_printf("DKIM [%s] %s signature status: %s",
+             sig->domain, dkim_sig_to_a_tag(sig),
+             pdkim_verify_status_str(sig->verify_status));
+      if (sig->verify_ext_status > 0)
+       debug_printf(" (%s)\n",
+               pdkim_verify_ext_status_str(sig->verify_ext_status));
+      else
+       debug_printf("\n");
+      }
+
+    if (  verify_pass && dkim_verify_minimal
+       && !(acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers))
+      break;
+    }
+  }
+
+/* If requested, set return pointer to signature(s) */
+if (return_signatures)
+  *return_signatures = ctx->sig;
+
+return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
+  ? PDKIM_OK : PDKIM_FAIL;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_ctx *
+pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing)
+{
+pdkim_ctx * ctx;
+
+ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED);
+memset(ctx, 0, sizeof(pdkim_ctx));
+
+if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
+/* The line-buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
+ctx->dns_txt_callback = dns_txt_callback;
+ctx->cur_header = string_get_tainted(36, GET_TAINTED);
+
+return ctx;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT pdkim_signature *
+pdkim_init_sign(pdkim_ctx * ctx,
+  uschar * domain, uschar * selector, uschar * privkey,
+  uschar * hashname, const uschar ** errstr)
+{
+int hashtype;
+pdkim_signature * sig;
+
+if (!domain || !selector || !privkey)
+  return NULL;
+
+/* Allocate & init one signature struct */
+
+sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
+memset(sig, 0, sizeof(pdkim_signature));
+
+sig->bodylength = -1;
+
+sig->domain = string_copy(US domain);
+sig->selector = string_copy(US selector);
+sig->privkey = string_copy(US privkey);
+sig->keytype = -1;
+
+for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
+  if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
+  { sig->hashtype = hashtype; break; }
+if (hashtype >= nelem(pdkim_hashes))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC,
+    "DKIM: unrecognised hashname '%s'", hashname);
+  return NULL;
+  }
+
+DEBUG(D_acl)
+  {
+  pdkim_signature s = *sig;
+  ev_ctx vctx;
+
+  debug_printf("DKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
+  if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
+    debug_printf("WARNING: bad dkim key in dns\n");
+  debug_printf("DKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
+  }
+return sig;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+DLLEXPORT void
+pdkim_set_optional(pdkim_signature * sig,
+                       char * sign_headers,
+                       char * identity,
+                       int canon_headers,
+                       int canon_body,
+                       long bodylength,
+                       unsigned long created,
+                       unsigned long expires)
+{
+if (identity)
+  sig->identity = string_copy(US identity);
+
+sig->sign_headers = string_copy(sign_headers
+       ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);
+
+sig->canon_headers = canon_headers;
+sig->canon_body = canon_body;
+sig->bodylength = bodylength;
+sig->created = created;
+sig->expires = expires;
+
+return;
+}
+
+
+
+/* Set up a blob for calculating the bodyhash according to the
+given needs.  Use an existing one if possible, or create a new one.
+
+Return: hashblob pointer, or NULL on error
+*/
+pdkim_bodyhash *
+pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
+       long bodylength)
+{
+pdkim_bodyhash * b;
+
+if (hashtype == -1 || canon_method == -1) return NULL;
+
+if (!ctx)
+  {
+  DEBUG(D_receive) debug_printf("pdkim_set_bodyhash: null context\n");
+  return NULL;
+  }
+
+for (b = ctx->bodyhash; b; b = b->next)
+  if (  hashtype == b->hashtype
+     && canon_method == b->canon_method
+     && bodylength == b->bodylength)
+    {
+    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
+      pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+    return b;
+    }
+
+DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
+    pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
+b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
+b->next = ctx->bodyhash;
+b->hashtype = hashtype;
+b->canon_method = canon_method;
+b->bodylength = bodylength;
+if (!exim_sha_init(&b->body_hash_ctx,          /*XXX hash method: extend for sha512 */
+                 pdkim_hashes[hashtype].exim_hashmethod))
+  {
+  DEBUG(D_acl)
+    debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
+  return NULL;
+  }
+b->signed_body_bytes = 0;
+b->num_buffered_blanklines = 0;
+ctx->bodyhash = b;
+return b;
+}
+
+
+/* Set up a blob for calculating the bodyhash according to the
+needs of this signature.  Use an existing one if possible, or
+create a new one.
+
+Return: hashblob pointer, or NULL on error (only used as a boolean).
+*/
+pdkim_bodyhash *
+pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
+{
+pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
+                       sig->hashtype, sig->canon_body, sig->bodylength);
+sig->calc_body_hash = b;
+return b;
+}
+
+
+/* -------------------------------------------------------------------------- */
+
+
+void
+pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
+  uschar * (*dns_txt_callback)(const uschar *))
+{
+memset(ctx, 0, sizeof(pdkim_ctx));
+ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
+/* The line buffer is for message data, hence tainted */
+ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
+DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
+}
+
+
+void
+pdkim_init(void)
+{
+exim_dkim_signers_init();
+}
+
+
+
+#endif /*DISABLE_DKIM*/
diff --git a/src/src/miscmods/pdkim/pdkim.h b/src/src/miscmods/pdkim/pdkim.h
new file mode 100644 (file)
index 0000000..b86014f
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
+ *  Copyright (c) 2016 - 2020  Jeremy Harris
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  http://duncanthrax.net/pdkim/
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef PDKIM_H
+#define PDKIM_H
+
+#include "../blob.h"
+#include "../hash.h"
+
+#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
+                             "Message-ID:To:Cc:MIME-Version:Content-Type:"\
+                             "Content-Transfer-Encoding:Content-ID:"\
+                             "Content-Description:Resent-Date:Resent-From:"\
+                             "Resent-Sender:Resent-To:Resent-Cc:"\
+                             "Resent-Message-ID:In-Reply-To:References:"\
+                             "List-Id:List-Help:List-Unsubscribe:"\
+                             "List-Subscribe:List-Post:List-Owner:List-Archive"
+
+#define PDKIM_OVERSIGN_HEADERS "+From:+Sender:+Reply-To:+Subject:+Date:"\
+                             "+Message-ID:+To:+Cc:+MIME-Version:+Content-Type:"\
+                             "+Content-Transfer-Encoding:+Content-ID:"\
+                             "+Content-Description:+Resent-Date:+Resent-From:"\
+                             "+Resent-Sender:+Resent-To:+Resent-Cc:"\
+                             "+Resent-Message-ID:+In-Reply-To:+References:"\
+                             "+List-Id:+List-Help:+List-Unsubscribe:"\
+                             "+List-Subscribe:+List-Post:+List-Owner:+List-Archive"
+
+/* -------------------------------------------------------------------------- */
+/* Length of the preallocated buffer for the "answer" from the dns/txt
+   callback function. This should match the maximum RDLENGTH from DNS. */
+#define PDKIM_DNS_TXT_MAX_RECLEN    (1 << 16)
+
+/* -------------------------------------------------------------------------- */
+/* Function success / error codes */
+#define PDKIM_OK                      0
+#define PDKIM_FAIL                   -1
+#define PDKIM_ERR_RSA_PRIVKEY      -101
+#define PDKIM_ERR_RSA_SIGNING      -102
+#define PDKIM_ERR_LONG_LINE        -103
+#define PDKIM_ERR_BUFFER_TOO_SMALL -104
+#define PDKIM_ERR_EXCESS_SIGS     -105
+#define PDKIM_SIGN_PRIVKEY_WRAP    -106
+#define PDKIM_SIGN_PRIVKEY_B64D    -107
+
+/* -------------------------------------------------------------------------- */
+/* Main/Extended verification status */
+#define PDKIM_VERIFY_NONE      0
+#define PDKIM_VERIFY_INVALID   1
+#define PDKIM_VERIFY_FAIL      2
+#define PDKIM_VERIFY_PASS      3
+#define PDKIM_VERIFY_POLICY    BIT(31)
+
+#define PDKIM_VERIFY_FAIL_BODY                    1
+#define PDKIM_VERIFY_FAIL_MESSAGE                 2
+#define PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH      3
+#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE   4
+#define PDKIM_VERIFY_INVALID_BUFFER_SIZE          5
+#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD     6
+#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT        7
+#define PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE       8
+#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR      9
+#define PDKIM_VERIFY_INVALID_DKIM_VERSION         10
+
+/* -------------------------------------------------------------------------- */
+/* Some parameter values */
+#define PDKIM_QUERYMETHOD_DNS_TXT 0
+
+#define PDKIM_CANON_SIMPLE        0
+#define PDKIM_CANON_RELAXED       1
+
+/* -------------------------------------------------------------------------- */
+/* Some required forward declarations, please ignore */
+typedef struct pdkim_stringlist pdkim_stringlist;
+typedef struct pdkim_str pdkim_str;
+typedef struct sha1_context sha1_context;
+typedef struct sha2_context sha2_context;
+#define HAVE_SHA1_CONTEXT
+#define HAVE_SHA2_CONTEXT
+
+/* -------------------------------------------------------------------------- */
+/* Some concessions towards Redmond */
+#ifdef WINDOWS
+#define snprintf _snprintf
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+#define DLLEXPORT __declspec(dllexport)
+#else
+#define DLLEXPORT
+#endif
+
+
+/* -------------------------------------------------------------------------- */
+/* Public key as (usually) fetched from DNS */
+typedef struct pdkim_pubkey {
+  const uschar * version;         /* v=  */
+  const uschar *granularity;      /* g=  */
+
+  const uschar * hashes;          /* h=  */
+  const uschar * keytype;         /* k=  */
+  const uschar * srvtype;         /* s=  */
+  uschar *notes;                  /* n=  */
+
+  blob  key;                      /* p=  */
+  int   testing;                  /* t=y */
+  int   no_subdomaining;          /* t=s */
+} pdkim_pubkey;
+
+/* -------------------------------------------------------------------------- */
+/* Body-hash to be calculated */
+typedef struct pdkim_bodyhash {
+  struct pdkim_bodyhash *      next;
+  int                          hashtype;
+  int                          canon_method;
+  long                         bodylength;
+
+  hctx                                 body_hash_ctx;
+  unsigned long                        signed_body_bytes;      /* done so far */
+  int                          num_buffered_blanklines;
+
+  blob                         bh;                     /* completed hash */
+} pdkim_bodyhash;
+
+/* -------------------------------------------------------------------------- */
+/* Signature as it appears in a DKIM-Signature header */
+typedef struct pdkim_signature {
+  struct pdkim_signature * next;
+
+  /* Bits stored in a DKIM signature header --------------------------- */
+
+  /* (v=) The version, as an integer. Currently, always "1" */
+  int version;
+
+  /* (a=) The signature algorithm. */
+  int          keytype;        /* pdkim_keytypes index */
+  unsigned     keybits;        /* size of the key */
+  int          hashtype;       /* pdkim_hashes index */
+
+  /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
+     or PDKIM_CANON_RELAXED */
+  int canon_headers;
+
+  /* (c=/x) Body canonicalization method. Either PDKIM_CANON_SIMPLE
+     or PDKIM_CANON_RELAXED */
+  int canon_body;
+
+  /* (q=) Query Method. Currently, only PDKIM_QUERYMETHOD_DNS_TXT
+     is specified */
+  int querymethod;
+
+  /* (s=) The selector string as given in the signature */
+  uschar *selector;
+
+  /* (d=) The domain as given in the signature */
+  uschar *domain;
+
+  /* (i=) The identity as given in the signature */
+  uschar *identity;
+
+  /* (t=) Timestamp of signature creation */
+  unsigned long created;
+
+  /* (x=) Timestamp of expiry of signature */
+  unsigned long expires;
+
+  /* (l=) Amount of hashed body bytes (after canonicalization). Default
+     is -1. Note: a value of 0 means that the body is unsigned! */
+  long bodylength;
+
+  /* (h=) Colon-separated list of header names that are included in the
+     signature */
+  uschar *headernames;
+
+  /* (z=) */
+  uschar *copiedheaders;
+
+  /* (b=) Raw signature data, along with its length in bytes */
+  blob sighash;
+
+  /* (bh=) Raw body hash data, along with its length in bytes */
+  blob bodyhash;
+
+  /* Folded DKIM-Signature: header. Signing only, NULL for verifying.
+     Ready for insertion into the message. Note: Folded using CRLFTB,
+     but final line terminator is NOT included. Note2: This buffer is
+     free()d when you call pdkim_free_ctx(). */
+  uschar *signature_header;
+
+  /* The main verification status. Verification only. One of:
+
+     PDKIM_VERIFY_NONE      Verification was not attempted. This status
+                            should not appear.
+
+     PDKIM_VERIFY_INVALID   There was an error while trying to verify
+                            the signature. A more precise description
+                            is available in verify_ext_status.
+
+     PDKIM_VERIFY_FAIL      Verification failed because either the body
+                            hash did not match, or the signature verification
+                            failed. This means the message was modified.
+                            Check verify_ext_status for the exact reason.
+
+     PDKIM_VERIFY_PASS      Verification succeeded.
+  */
+  int verify_status;
+
+  /* Extended verification status. Verification only. Depending on the value
+     of verify_status, it can contain:
+
+     For verify_status == PDKIM_VERIFY_INVALID:
+
+        PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE
+          Unable to retrieve a public key container.
+
+        PDKIM_VERIFY_INVALID_BUFFER_SIZE
+          Either the DNS name constructed to retrieve the public key record
+          does not fit into PDKIM_DNS_TXT_MAX_NAMELEN bytes, or the retrieved
+          record is longer than PDKIM_DNS_TXT_MAX_RECLEN bytes.
+
+        PDKIM_VERIFY_INVALID_PUBKEY_PARSING
+          (Syntax) error while parsing the retrieved public key record.
+
+
+     For verify_status == PDKIM_VERIFY_FAIL:
+
+        PDKIM_VERIFY_FAIL_BODY
+          The calculated body hash does not match the advertised body hash
+          from the bh= tag of the signature.
+
+        PDKIM_VERIFY_FAIL_MESSAGE
+          RSA verification of the signature (b= tag) failed.
+  */
+  int verify_ext_status;
+
+  /* Pointer to a public key record that was used to verify the signature.
+     See pdkim_pubkey declaration above for more information.
+     Caution: is NULL if signing or if no record was retrieved. */
+  pdkim_pubkey *pubkey;
+
+  /* Properties below this point are used internally only ------------- */
+
+  /* Per-signature helper variables ----------------------------------- */
+  pdkim_bodyhash *calc_body_hash;      /* hash to be / being calculated */
+
+  pdkim_stringlist *headers;           /* Raw headers included in the sig */
+
+  /* Signing specific ------------------------------------------------- */
+  uschar * privkey;         /* Private key                                 */
+  uschar * sign_headers;    /* To-be-signed header names                   */
+  uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
+} pdkim_signature;
+
+
+/* -------------------------------------------------------------------------- */
+/* Context to keep state between all operations. */
+typedef struct pdkim_ctx {
+
+#define PDKIM_MODE_SIGN   BIT(0)       /* if unset, mode==verify */
+#define PDKIM_DOT_TERM   BIT(1)        /* dot termination and unstuffing */
+#define PDKIM_SEEN_CR    BIT(2)
+#define PDKIM_SEEN_LF    BIT(3)
+#define PDKIM_PAST_HDRS          BIT(4)
+#define PDKIM_SEEN_EOD   BIT(5)
+  unsigned   flags;
+
+  /* One (signing) or several chained (verification) signatures */
+  pdkim_signature *sig;
+
+  /* One (signing) or several chained (verification) bodyhashes */
+  pdkim_bodyhash *bodyhash;
+
+  /* Callback for dns/txt query method (verification only) */
+  uschar * (*dns_txt_callback)(const uschar *);
+
+  /* Coder's little helpers */
+  gstring   *cur_header;
+  uschar    *linebuf;
+  int        linebuf_offset;
+  int        num_headers;
+  pdkim_stringlist *headers; /* Raw headers for verification         */
+} pdkim_ctx;
+
+
+/******************************************************************************/
+
+typedef struct {
+  const uschar * dkim_hashname;
+  hashmethod    exim_hashmethod;
+} pdkim_hashtype;
+extern const pdkim_hashtype pdkim_hashes[];
+
+/******************************************************************************/
+
+
+/* -------------------------------------------------------------------------- */
+/* API functions. Please see the sample code in sample/test_sign.c and
+   sample/test_verify.c for documentation.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void      pdkim_init         (void);
+
+void      pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(const uschar *));
+
+DLLEXPORT
+pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
+                              uschar *, uschar *, uschar *, uschar *,
+                              const uschar **);
+
+DLLEXPORT
+pdkim_ctx *pdkim_init_verify  (uschar * (*)(const uschar *), BOOL);
+
+DLLEXPORT
+void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
+                               long,
+                               unsigned long,
+                               unsigned long);
+
+int            pdkim_hashname_to_hashtype(const uschar *, unsigned);
+void           pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
+pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
+pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
+
+DLLEXPORT
+int        pdkim_feed         (pdkim_ctx *, const uschar *, unsigned);
+DLLEXPORT
+int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **, const uschar **);
+
+DLLEXPORT
+void       pdkim_free_ctx     (pdkim_ctx *);
+
+
+const uschar * pdkim_errstr(int);
+
+extern uschar *                pdkim_encode_base64(blob *);
+extern void            pdkim_decode_base64(const uschar *, blob *);
+extern pdkim_pubkey *  pdkim_parse_pubkey_record(const uschar *);
+extern uschar *                pdkim_relax_header_n(const uschar *, int, BOOL);
+extern uschar *                pdkim_relax_header(const uschar *, BOOL);
+extern uschar *                dkim_sig_to_a_tag(const pdkim_signature *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/src/miscmods/pdkim/pdkim_hash.h b/src/src/miscmods/pdkim/pdkim_hash.h
new file mode 100644 (file)
index 0000000..d56e3ce
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 1995 - 2018  Exim maintainers
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  Hash interface functions
+ */
+
+#include "../exim.h"
+
+#if !defined(HASH_H)   /* entire file */
+#define HASH_H
+
+#ifdef DISABLE_TLS
+# error Must not DISABLE_TLS, for DKIM
+#endif
+
+#include "crypt_ver.h"
+#include "../blob.h"
+#include "../hash.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+#endif
+
+#if defined(SHA_OPENSSL)
+# include "pdkim.h"
+#elif defined(SHA_GCRYPT)
+# include "pdkim.h"
+#endif
+
+#endif
+/* End of File */
diff --git a/src/src/miscmods/pdkim/signing.c b/src/src/miscmods/pdkim/signing.c
new file mode 100644 (file)
index 0000000..44f2e12
--- /dev/null
@@ -0,0 +1,913 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *  Copyright (c) The Exim Maintainers 1995 - 2024
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  signing/verification interface
+ */
+
+#include "../exim.h"
+#include "crypt_ver.h"
+#include "signing.h"
+
+
+#ifdef MACRO_PREDEF
+# include "../macro_predef.h"
+
+void
+features_crypto(void)
+{
+# ifdef SIGN_HAVE_ED25519
+  builtin_macro_create(US"_CRYPTO_SIGN_ED25519");
+# endif
+# ifdef EXIM_HAVE_SHA3
+  builtin_macro_create(US"_CRYPTO_HASH_SHA3");
+# endif
+}
+#else
+
+#ifndef DISABLE_DKIM   /* rest of file */
+
+#ifdef DISABLE_TLS
+# error Must no DISABLE_TLS, for DKIM
+#endif
+
+
+/******************************************************************************/
+#ifdef SIGN_GNUTLS
+# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
+
+# ifndef GNUTLS_VERIFY_ALLOW_BROKEN
+#  define GNUTLS_VERIFY_ALLOW_BROKEN 0
+# endif
+
+
+/* Logging function which can be registered with
+ *   gnutls_global_set_log_function()
+ *   gnutls_global_set_log_level() 0..9
+ */
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+static void
+exim_gnutls_logger_cb(int level, const char *message)
+{
+size_t len = strlen(message);
+if (len < 1)
+  {
+  DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
+  return;
+  }
+DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
+    message[len-1] == '\n' ? "" : "\n");
+}
+#endif
+
+
+
+void
+exim_dkim_signers_init(void)
+{
+#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
+DEBUG(D_tls)
+  {
+  gnutls_global_set_log_function(exim_gnutls_logger_cb);
+  /* arbitrarily chosen level; bump upto 9 for more */
+  gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
+  }
+#endif
+}
+
+
+/* accumulate data (gnutls-only).  String to be appended must be nul-terminated. */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) };
+gnutls_x509_privkey_t x509_key;
+const uschar * where;
+int rc;
+
+if (  (where = US"internal init", rc = gnutls_x509_privkey_init(&x509_key))
+   || (rc = gnutls_privkey_init(&sign_ctx->key))
+   || (where = US"privkey PEM-block import",
+       rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
+   || (where = US"internal privkey transfer",
+       rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
+   )
+  return string_sprintf("%s: %s", where, gnutls_strerror(rc));
+
+switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
+  {
+  case GNUTLS_PK_RSA:          sign_ctx->keytype = KEYTYPE_RSA;     break;
+#ifdef SIGN_HAVE_ED25519
+  case GNUTLS_PK_EDDSA_ED25519:        sign_ctx->keytype = KEYTYPE_ED25519; break;
+#endif
+  default: return rc < 0
+    ? CUS gnutls_strerror(rc)
+    : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
+  }
+
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data.   No way to do incremental.
+
+Arguments:
+       sign_ctx        library-specific context for a signature (incl. key)
+       hash            hash method to apply to data
+       data            data to be signed
+       sig             returned signature
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
+{
+gnutls_datum_t k_data = { .data = data->data, .size = data->len };
+gnutls_digest_algorithm_t dig;
+gnutls_datum_t k_sig;
+int rc;
+
+switch (hash)
+  {
+  case HASH_SHA1:      dig = GNUTLS_DIG_SHA1; break;
+  case HASH_SHA2_256:  dig = GNUTLS_DIG_SHA256; break;
+  case HASH_SHA2_512:  dig = GNUTLS_DIG_SHA512; break;
+  default:             return US"nonhandled hash type";
+  }
+
+if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
+  return CUS gnutls_strerror(rc);
+
+/* Don't care about deinit for the key; shortlived process */
+
+sig->data = k_sig.data;
+sig->len = k_sig.size;
+return NULL;
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
+{
+gnutls_datum_t k;
+int rc;
+const uschar * ret = NULL;
+
+gnutls_pubkey_init(&verify_ctx->key);
+k.data = pubkey->data;
+k.size = pubkey->len;
+
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
+      ret = US gnutls_strerror(rc);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
+                                         GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
+      ret = US gnutls_strerror(rc);
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
+if (!ret && bits) gnutls_pubkey_get_pk_algorithm(verify_ctx->key, bits);
+return ret;
+}
+
+
+/* verify signature (of hash if RSA sig, of data if EC sig.  No way to do incremental)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data_hash,
+  const blob * sig)
+{
+gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
+gnutls_datum_t s = { .data = sig->data,       .size = sig->len };
+int rc;
+const uschar * ret = NULL;
+
+#ifdef SIGN_HAVE_ED25519
+if (verify_ctx->keytype == KEYTYPE_ED25519)
+  {
+  if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
+                                     GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
+    ret = US gnutls_strerror(rc);
+  }
+else
+#endif
+  {
+  gnutls_sign_algorithm_t algo;
+  switch (hash)
+    {
+    case HASH_SHA1:    algo = GNUTLS_SIGN_RSA_SHA1;   break;
+    case HASH_SHA2_256:        algo = GNUTLS_SIGN_RSA_SHA256; break;
+    case HASH_SHA2_512:        algo = GNUTLS_SIGN_RSA_SHA512; break;
+    default:           return US"nonhandled hash type";
+    }
+
+  if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo,
+             GNUTLS_VERIFY_ALLOW_BROKEN, &k, &s)) < 0)
+    ret = US gnutls_strerror(rc);
+  }
+
+gnutls_pubkey_deinit(verify_ctx->key);
+return ret;
+}
+
+
+
+
+#elif defined(SIGN_GCRYPT)
+/******************************************************************************/
+/* This variant is used under pre-3.0.0 GnuTLS.  Only rsa-sha1 and rsa-sha256 */
+
+
+/* Internal service routine:
+Read and move past an asn.1 header, checking class & tag,
+optionally returning the data-length */
+
+static int
+as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
+{
+int rc;
+uschar tag_class;
+int taglen;
+long tag, len;
+
+debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
+       der->data[0], der->data[1], der->data[2], der->data[3]);
+
+if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
+    != ASN1_SUCCESS)
+  return rc;
+
+if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
+
+if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
+  return ASN1_DER_ERROR;
+if (alen) *alen = len;
+
+/* debug_printf_indent("as_tag:  tlen %d dlen %d\n", taglen, (int)len); */
+
+der->data += taglen;
+der->len -= taglen;
+return rc;
+}
+
+/* Internal service routine:
+Read and move over an asn.1 integer, setting an MPI to the value
+*/
+
+static uschar *
+as_mpi(blob * der, gcry_mpi_t * mpi)
+{
+long alen;
+int rc;
+gcry_error_t gerr;
+
+debug_printf_indent("%s\n", __FUNCTION__);
+
+/* integer; move past the header */
+if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+  return US asn1_strerror(rc);
+
+/* read to an MPI */
+if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
+  return US gcry_strerror(gerr);
+
+/* move over the data */
+der->data += alen; der->len -= alen;
+return NULL;
+}
+
+
+
+void
+exim_dkim_signers_init(void)
+{
+/* Version check should be the very first call because it
+makes sure that important subsystems are initialized. */
+if (!gcry_check_version (GCRYPT_VERSION))
+  {
+  fputs ("libgcrypt version mismatch\n", stderr);
+  exim_exit(2);
+  }
+
+/* We don't want to see any warnings, e.g. because we have not yet
+parsed program options which might be used to suppress such
+warnings. */
+gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
+
+/* ... If required, other initialization goes here.  Note that the
+process might still be running with increased privileges and that
+the secure memory has not been initialized.  */
+
+/* Allocate a pool of 16k secure memory.  This make the secure memory
+available and also drops privileges where needed.  */
+gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+
+/* It is now okay to let Libgcrypt complain when there was/is
+a problem with the secure memory. */
+gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
+
+/* ... If required, other initialization goes here.  */
+
+/* Tell Libgcrypt that initialization has completed. */
+gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+
+return;
+}
+
+
+
+
+/* Accumulate data (gnutls-only).
+String to be appended must be nul-terminated. */
+
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return g;      /*dummy*/
+}
+
+
+
+/* import private key from PEM string in memory.
+Only handles RSA keys.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+uschar * s1, * s2;
+blob der;
+long alen;
+int rc;
+
+/*XXX will need extension to _spot_ as well as handle a
+non-RSA key?  I think...
+So... this is not a PrivateKeyInfo - which would have a field
+identifying the keytype - PrivateKeyAlgorithmIdentifier -
+but a plain RSAPrivateKey (wrapped in PEM-headers.  Can we
+use those as a type tag?  What forms are there?  "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
+
+How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
+gnutls_x509_privkey_import() ?
+*/
+
+/*
+ *  RSAPrivateKey ::= SEQUENCE
+ *      version           Version,
+ *      modulus           INTEGER,  -- n
+ *      publicExponent    INTEGER,  -- e
+ *      privateExponent   INTEGER,  -- d
+ *      prime1            INTEGER,  -- p
+ *      prime2            INTEGER,  -- q
+ *      exponent1         INTEGER,  -- d mod (p-1)
+ *      exponent2         INTEGER,  -- d mod (q-1)
+ *      coefficient       INTEGER,  -- (inverse of q) mod p
+ *      otherPrimeInfos   OtherPrimeInfos OPTIONAL
+
+ * ECPrivateKey ::= SEQUENCE {
+ *     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+ *     privateKey     OCTET STRING,
+ *     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+ *     publicKey  [1] BIT STRING OPTIONAL
+ *   }
+ * Hmm, only 1 useful item, and not even an integer?  Wonder how we might use it...
+
+- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
+       value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
+
+
+Useful cmds:
+  ssh-keygen -t ecdsa -f foo.privkey
+  ssh-keygen -t ecdsa -b384 -f foo.privkey
+  ssh-keygen -t ecdsa -b521 -f foo.privkey
+  ssh-keygen -t ed25519 -f foo.privkey
+
+  < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
+
+  openssl asn1parse -in foo -inform PEM -dump
+  openssl asn1parse -in foo -inform PEM -dump -stroffset 24    (??)
+(not good for ed25519)
+
+ */
+
+if (  !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
+   || !(s2 = Ustrstr(CS (s1+=31),    "-----END RSA PRIVATE KEY-----" ))
+   )
+  return US"Bad PEM wrapper";
+
+*s2 = '\0';
+
+if ((rc = b64decode(s1, &der.data, s1) < 0))
+  return US"Bad PEM-DER b64 decode";
+der.len = rc;
+
+/* untangle asn.1 */
+
+/* sequence; just move past the header */
+if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* integer version; move past the header, check is zero */
+if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
+  goto asn_err;
+if (alen != 1 || *der.data != 0)
+  return US"Bad version number";
+der.data++; der.len--;
+
+if (  (s1 = as_mpi(&der, &sign_ctx->n))
+   || (s1 = as_mpi(&der, &sign_ctx->e))
+   || (s1 = as_mpi(&der, &sign_ctx->d))
+   || (s1 = as_mpi(&der, &sign_ctx->p))
+   || (s1 = as_mpi(&der, &sign_ctx->q))
+   || (s1 = as_mpi(&der, &sign_ctx->dp))
+   || (s1 = as_mpi(&der, &sign_ctx->dq))
+   || (s1 = as_mpi(&der, &sign_ctx->qp))
+   )
+  return s1;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
+  {
+  uschar * s;
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
+  debug_printf_indent(" N : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
+  debug_printf_indent(" E : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
+  debug_printf_indent(" D : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
+  debug_printf_indent(" P : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
+  debug_printf_indent(" Q : %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
+  debug_printf_indent(" DP: %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
+  debug_printf_indent(" DQ: %s\n", s);
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
+  debug_printf_indent(" QP: %s\n", s);
+  }
+#endif
+
+sign_ctx->keytype = KEYTYPE_RSA;
+return NULL;
+
+asn_err: return US asn1_strerror(rc);
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* sign already-hashed data.
+
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
+{
+char * sexp_hash;
+gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
+gcry_mpi_t m_sig;
+uschar * errstr;
+gcry_error_t gerr;
+
+/*XXX will need extension for hash types (though, possibly, should
+be re-specced to not rehash but take an already-hashed value? Actually
+current impl looks WRONG - it _is_ given a hash so should not be
+re-hashing.  Has this been tested?
+
+Will need extension for non-RSA sugning algos. */
+
+switch (hash)
+  {
+  case HASH_SHA1:      sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+  case HASH_SHA2_256:  sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+  default:             return US"nonhandled hash type";
+  }
+
+#define SIGSPACE 128
+sig->data = store_get(SIGSPACE, GET_UNTAINTED);
+
+if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
+  {
+  gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
+  gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
+  }
+
+if (  (gerr = gcry_sexp_build (&s_key, NULL,
+               "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+               sign_ctx->n, sign_ctx->e,
+               sign_ctx->d, sign_ctx->p,
+               sign_ctx->q, sign_ctx->qp))
+   || (gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+               (int) data->len, CS data->data))
+   ||  (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
+   )
+  return US gcry_strerror(gerr);
+
+/* gcry_sexp_dump(s_sig); */
+
+if (  !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
+   )
+  return US"no sig result";
+
+m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
+
+#ifdef extreme_debug
+DEBUG(D_acl)
+  {
+  uschar * s;
+  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
+  debug_printf_indent(" SG: %s\n", s);
+  }
+#endif
+
+gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
+if (gerr)
+  {
+  debug_printf_indent("signature conversion from MPI to buffer failed\n");
+  return US gcry_strerror(gerr);
+  }
+#undef SIGSPACE
+
+return NULL;
+}
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
+{
+/*
+in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
+*/
+uschar tag_class;
+int taglen;
+long alen;
+unsigned nbits;
+int rc;
+uschar * errstr;
+gcry_error_t gerr;
+uschar * stage = US"S1";
+
+if (fmt != KEYFMT_DER) return US"pubkey format not handled";
+
+/*
+sequence
+ sequence
+  OBJECT:rsaEncryption
+  NULL
+ BIT STRING:RSAPublicKey
+  sequence
+   INTEGER:Public modulus
+   INTEGER:Public exponent
+
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
+openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
+*/
+
+/* sequence; just move past the header */
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* sequence; skip the entire thing */
+DEBUG(D_acl) stage = US"S2";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
+   != ASN1_SUCCESS) goto asn_err;
+pubkey->data += alen; pubkey->len -= alen;
+
+
+/* bitstring: limit range to size of bitstring;
+move over header + content wrapper */
+DEBUG(D_acl) stage = US"BS";
+if ((rc = as_tag(pubkey, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
+  goto asn_err;
+pubkey->len = alen;
+pubkey->data++; pubkey->len--;
+
+/* sequence; just move past the header */
+DEBUG(D_acl) stage = US"S3";
+if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
+   != ASN1_SUCCESS) goto asn_err;
+
+/* read two integers */
+DEBUG(D_acl) stage = US"MPI";
+nbits = pubkey->len;
+if ((errstr = as_mpi(pubkey, &verify_ctx->n))) return errstr;
+nbits = (nbits - pubkey->len) * 8;
+if ((errstr = as_mpi(pubkey, &verify_ctx->e))) return errstr;
+
+#ifdef extreme_debug
+DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
+       {
+       uschar * s;
+       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
+       debug_printf_indent(" N : %s\n", s);
+       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
+       debug_printf_indent(" E : %s\n", s);
+       }
+
+#endif
+if (bits) *bits = nbits;
+return NULL;
+
+asn_err:
+DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
+            return US asn1_strerror(rc);
+}
+
+
+/* verify signature (of hash)
+XXX though we appear to be doing a hash, too!
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data_hash,
+  const blob * sig)
+{
+/*
+cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
+*/
+char * sexp_hash;
+gcry_mpi_t m_sig;
+gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+gcry_error_t gerr;
+uschar * stage;
+
+/*XXX needs extension for SHA512 */
+switch (hash)
+  {
+  case HASH_SHA1:     sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
+  case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
+  default:           return US"nonhandled hash type";
+  }
+
+if (  (stage = US"pkey sexp build",
+       gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
+                       verify_ctx->n, verify_ctx->e))
+   || (stage = US"data sexp build",
+       gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
+               (int) data_hash->len, CS data_hash->data))
+   || (stage = US"sig mpi scan",
+       gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
+   || (stage = US"sig sexp build",
+       gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
+   || (stage = US"verify",
+       gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
+   )
+  {
+  DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
+  return US gcry_strerror(gerr);
+  }
+
+if (s_sig) gcry_sexp_release (s_sig);
+if (s_hash) gcry_sexp_release (s_hash);
+if (s_pkey) gcry_sexp_release (s_pkey);
+gcry_mpi_release (m_sig);
+gcry_mpi_release (verify_ctx->n);
+gcry_mpi_release (verify_ctx->e);
+
+return NULL;
+}
+
+
+
+
+#elif defined(SIGN_OPENSSL)
+/******************************************************************************/
+
+void
+exim_dkim_signers_init(void)
+{
+ERR_load_crypto_strings();
+}
+
+
+/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
+because now using hash-and-sign interface) */
+gstring *
+exim_dkim_data_append(gstring * g, uschar * s)
+{
+return string_cat(g, s);
+}
+
+
+/* import private key from PEM string in memory.
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
+{
+BIO * bp = BIO_new_mem_buf((void *)privkey_pem, -1);
+
+if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
+  return string_sprintf("privkey PEM-block import: %s",
+                       ERR_error_string(ERR_get_error(), NULL));
+
+sign_ctx->keytype =
+#ifdef SIGN_HAVE_ED25519
+       EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519
+         ? KEYTYPE_ED25519 : KEYTYPE_RSA;
+#else
+       KEYTYPE_RSA;
+#endif
+return NULL;
+}
+
+
+
+/* allocate mem for signature (when signing) */
+/* hash & sign data.  Incremental not supported.
+
+Return: NULL for success with the signaature in the sig blob, or an error string */
+
+const uschar *
+exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, const blob * data, blob * sig)
+{
+const EVP_MD * md;
+EVP_MD_CTX * ctx;
+size_t siglen;
+
+switch (hash)
+  {
+  case HASH_NULL:      md = NULL;         break;       /* Ed25519 signing */
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
+  }
+
+#ifdef SIGN_HAVE_ED25519
+if (  (ctx = EVP_MD_CTX_new())
+   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+   && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
+   && (sig->data = store_get(siglen, GET_UNTAINTED))
+
+   /* Obtain the signature (slen could change here!) */
+   && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
+   )
+  {
+  EVP_MD_CTX_destroy(ctx);
+  sig->len = siglen;
+  return NULL;
+  }
+#else
+/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
+if (  (ctx = EVP_MD_CTX_create())
+   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
+   && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
+   && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
+   && (sig->data = store_get(siglen, GET_UNTAINTED))
+   /* Obtain the signature (slen could change here!) */
+   && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
+   )
+  {
+  EVP_MD_CTX_destroy(ctx);
+  sig->len = siglen;
+  return NULL;
+  }
+#endif
+
+if (ctx) EVP_MD_CTX_destroy(ctx);
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+/* import public key (from blob in memory)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify_init(const blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
+  unsigned * bits)
+{
+const uschar * s = pubkey->data;
+uschar * ret = NULL;
+
+switch(fmt)
+  {
+  case KEYFMT_DER:
+    /*XXX hmm, we never free this */
+    if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    break;
+#ifdef SIGN_HAVE_ED25519
+  case KEYFMT_ED25519_BARE:
+    if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
+                                                       s, pubkey->len)))
+      ret = US ERR_error_string(ERR_get_error(), NULL);
+    break;
+#endif
+  default:
+    ret = US"pubkey format not handled";
+    break;
+  }
+
+if (!ret && bits) *bits = EVP_PKEY_bits(verify_ctx->key);
+return ret;
+}
+
+
+
+
+/* verify signature (of hash, except Ed25519 where of-data)
+(given pubkey & alleged sig)
+Return: NULL for success, or an error string */
+
+const uschar *
+exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, const blob * data,
+  const blob * sig)
+{
+const EVP_MD * md;
+
+switch (hash)
+  {
+  case HASH_NULL:      md = NULL;         break;
+  case HASH_SHA1:      md = EVP_sha1();   break;
+  case HASH_SHA2_256:  md = EVP_sha256(); break;
+  case HASH_SHA2_512:  md = EVP_sha512(); break;
+  default:             return US"nonhandled hash type";
+  }
+
+#ifdef SIGN_HAVE_ED25519
+if (!md)
+  {
+  EVP_MD_CTX * ctx;
+
+  if ((ctx = EVP_MD_CTX_new()))
+    {
+    if (  EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
+       && EVP_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0
+       )
+      { EVP_MD_CTX_free(ctx); return NULL; }
+    EVP_MD_CTX_free(ctx);
+    }
+  }
+else
+#endif
+  {
+  EVP_PKEY_CTX * ctx;
+
+  if ((ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL)))
+    {
+    if (  EVP_PKEY_verify_init(ctx) > 0
+       && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
+       && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
+       && EVP_PKEY_verify(ctx, sig->data, sig->len,
+                                     data->data, data->len) == 1
+       )
+      { EVP_PKEY_CTX_free(ctx); return NULL; }
+    EVP_PKEY_CTX_free(ctx);
+
+    DEBUG(D_tls)
+      if (Ustrcmp(ERR_reason_error_string(ERR_peek_error()), "wrong signature length") == 0)
+       debug_printf("sig len (from msg hdr): %d, expected (from dns pubkey) %d\n",
+        (int) sig->len, EVP_PKEY_size(verify_ctx->key));
+    }
+  }
+
+return US ERR_error_string(ERR_get_error(), NULL);
+}
+
+
+
+#endif
+/******************************************************************************/
+
+#endif /*DISABLE_DKIM*/
+#endif /*MACRO_PREDEF*/
+/* End of File */
diff --git a/src/src/miscmods/pdkim/signing.h b/src/src/miscmods/pdkim/signing.h
new file mode 100644 (file)
index 0000000..ce801a3
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *  PDKIM - a RFC4871 (DKIM) implementation
+ *
+ *  Copyright (C) 1995 - 2020  Exim maintainers
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  RSA signing/verification interface
+ */
+
+#include "../exim.h"
+
+#ifndef DISABLE_DKIM   /* entire file */
+
+#include "crypt_ver.h"
+
+#ifdef SIGN_OPENSSL
+# include <openssl/rsa.h>
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+#elif defined(SIGN_GNUTLS)
+# include <gnutls/gnutls.h>
+# include <gnutls/x509.h>
+# include <gnutls/abstract.h>
+#elif defined(SIGN_GCRYPT)
+# include <gcrypt.h>
+# include <libtasn1.h>
+#endif
+
+#include "../blob.h"
+
+typedef enum {
+  KEYTYPE_RSA,
+  KEYTYPE_ED25519
+} keytype;
+
+typedef enum {
+  KEYFMT_DER,          /* an asn.1 structure */
+  KEYFMT_ED25519_BARE  /* just the key */
+} keyformat;
+
+
+#ifdef SIGN_OPENSSL
+
+typedef struct {
+  keytype      keytype;
+  EVP_PKEY *   key;
+} es_ctx;
+
+typedef struct {
+  keytype      keytype;
+  EVP_PKEY *   key;
+} ev_ctx;
+
+#elif defined(SIGN_GNUTLS)
+
+typedef struct {
+  keytype      keytype;
+  gnutls_privkey_t key;
+} es_ctx;
+
+typedef struct {
+  keytype      keytype;
+  gnutls_pubkey_t key;
+} ev_ctx;
+
+#elif defined(SIGN_GCRYPT)
+
+typedef struct {
+  keytype      keytype;
+  gcry_mpi_t n;
+  gcry_mpi_t e;
+  gcry_mpi_t d;
+  gcry_mpi_t p;
+  gcry_mpi_t q;
+  gcry_mpi_t dp;
+  gcry_mpi_t dq;
+  gcry_mpi_t qp;
+} es_ctx;
+
+typedef struct {
+  keytype      keytype;
+  gcry_mpi_t n;
+  gcry_mpi_t e;
+} ev_ctx;
+
+#endif
+
+
+extern void exim_dkim_signers_init(void);
+extern gstring * exim_dkim_data_append(gstring *, uschar *);
+
+extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
+extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, const blob *, blob *);
+extern const uschar * exim_dkim_verify_init(const blob *, keyformat, ev_ctx *,
+                       unsigned *);
+extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, const blob *,
+                       const blob *);
+
+#endif /*DISABLE_DKIM*/
+/* End of File */
index ea23c1c655ce546a0099900058589215212a163c..14937e7999fe1bc9e6931994ac7b06e124cb11c5 100644 (file)
@@ -566,7 +566,6 @@ static optionlist spf_options[] = {
 
 static void * spf_functions[] = {
   [SPF_PROCESS] =      spf_process,
-  [SPF_AUTHRES] =      authres_spf,
   [SPF_GET_RESPONSE] = spf_get_response,               /* ugly; for dmarc */
   
   [SPF_OPEN] =         spf_lookup_open,
@@ -593,6 +592,7 @@ misc_module_info spf_module_info =
   .lib_vers_report =   spf_lib_version_report,
   .conn_init =         spf_conn_init,
   .smtp_reset =                spf_smtp_reset,
+  .authres =           authres_spf,
 
   .options =           spf_options,
   .options_count =     nelem(spf_options),
index 3801e3e3fef64b1978b270866a4abb77cb2d3866..117a7ae6a002858150e3271e5c8b72819054f07c 100644 (file)
@@ -6,13 +6,12 @@
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
-/* API definitions for the spfmodule */
+/* API definitions for the spf module */
 
 
 /* Function table entry numbers */
 
 #define        SPF_PROCESS             0
-#define SPF_AUTHRES            1
 #define SPF_GET_RESPONSE       2
 #define SPF_OPEN               3
 #define SPF_CLOSE              4
diff --git a/src/src/pdkim/Makefile b/src/src/pdkim/Makefile
deleted file mode 100644 (file)
index 47f92ee..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-# Make file for building the pdkim library.
-# Copyright (c) The Exim Maintainers 1995 - 2018
-
-OBJ = pdkim.o signing.o
-
-pdkim.a:         $(OBJ)
-                @$(RM_COMMAND) -f pdkim.a
-                @echo "$(AR) pdkim.a"
-                $(FE)$(AR) pdkim.a $(OBJ)
-                $(RANLIB) $@
-
-.SUFFIXES:       .o .c
-.c.o:;           @echo "$(CC) $*.c"
-                $(FE)$(CC) -c $(CFLAGS) $(INCLUDE) -I. $*.c
-
-pdkim.o: $(HDRS) crypt_ver.h pdkim.h pdkim.c
-signing.o: $(HDRS) crypt_ver.h signing.h signing.c
-
-# End
diff --git a/src/src/pdkim/README b/src/src/pdkim/README
deleted file mode 100644 (file)
index 953e86e..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-PDKIM - a RFC4871 (DKIM) implementation
-http://duncanthrax.net/pdkim/
-Copyright (C) 2009      Tom Kistner <tom@duncanthrax.net>
-
-No longer includes code from the PolarSSL project.
-Copyright (C) 2016 Jeremy Harris <jgh@exim.org>
-
-This copy of PDKIM is included with Exim. For a standalone distribution,
-visit http://duncanthrax.net/pdkim/.
diff --git a/src/src/pdkim/config.h b/src/src/pdkim/config.h
deleted file mode 100644 (file)
index fdd4cfe..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#define POLARSSL_BASE64_C
-
-
-
diff --git a/src/src/pdkim/crypt_ver.h b/src/src/pdkim/crypt_ver.h
deleted file mode 100644 (file)
index 56ae236..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*************************************************
-*     Exim - an Internet mail transport agent    *
-*************************************************/
-
-/* Copyright (c) Jeremy Harris 1995 - 2018 */
-/* See the file NOTICE for conditions of use and distribution. */
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Signing and hashing routine selection for PDKIM */
-
-#include "../exim.h"
-#include "../sha_ver.h"
-
-
-#ifdef USE_GNUTLS
-# include <gnutls/gnutls.h>
-
-# if GNUTLS_VERSION_NUMBER >= 0x030000
-#  define SIGN_GNUTLS
-#  if GNUTLS_VERSION_NUMBER >= 0x030600
-#   define SIGN_HAVE_ED25519
-#  endif
-# else
-#  define SIGN_GCRYPT
-# endif
-#endif
-
-#ifdef USE_OPENSSL
-# define SIGN_OPENSSL
-# if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10101000L
-#  define SIGN_HAVE_ED25519
-# endif
-#endif
-
diff --git a/src/src/pdkim/pdkim.c b/src/src/pdkim/pdkim.c
deleted file mode 100644 (file)
index 42e67e6..0000000
+++ /dev/null
@@ -1,2081 +0,0 @@
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *
- *  Copyright (c) The Exim Maintainers 2021 - 2024
- *  Copyright (C) 2016 - 2020  Jeremy Harris <jgh@exim.org>
- *  Copyright (C) 2009 - 2016  Tom Kistner <tom@duncanthrax.net>
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *
- *  http://duncanthrax.net/pdkim/
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "../exim.h"
-
-
-#ifndef DISABLE_DKIM   /* entire file */
-
-#ifdef DISABLE_TLS
-# error Must not DISABLE_TLS, for DKIM
-#endif
-
-#include "crypt_ver.h"
-
-#ifdef SIGN_OPENSSL
-# include <openssl/rsa.h>
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#elif defined(SIGN_GNUTLS)
-# include <gnutls/gnutls.h>
-# include <gnutls/x509.h>
-#endif
-
-#include "pdkim.h"
-#include "signing.h"
-
-#define PDKIM_SIGNATURE_VERSION     "1"
-#define PDKIM_PUB_RECORD_VERSION    US "DKIM1"
-
-#define PDKIM_MAX_HEADER_LEN        65536
-#define PDKIM_MAX_HEADERS           512
-#define PDKIM_MAX_BODY_LINE_LEN     16384
-#define PDKIM_DNS_TXT_MAX_NAMELEN   1024
-
-/* -------------------------------------------------------------------------- */
-struct pdkim_stringlist {
-  uschar * value;
-  int      tag;
-  void *   next;
-};
-
-/* -------------------------------------------------------------------------- */
-/* A bunch of list constants */
-const uschar * pdkim_querymethods[] = {
-  US"dns/txt",
-  NULL
-};
-const uschar * pdkim_canons[] = {
-  US"simple",
-  US"relaxed",
-  NULL
-};
-
-const pdkim_hashtype pdkim_hashes[] = {
-  { US"sha1",   HASH_SHA1 },
-  { US"sha256", HASH_SHA2_256 },
-  { US"sha512", HASH_SHA2_512 }
-};
-
-const uschar * pdkim_keytypes[] = {
-  [KEYTYPE_RSA] =      US"rsa",
-#ifdef SIGN_HAVE_ED25519
-  [KEYTYPE_ED25519] =  US"ed25519",            /* Works for 3.6.0 GnuTLS, OpenSSL 1.1.1 */
-#endif
-
-#ifdef notyet_EC_dkim_extensions       /* https://tools.ietf.org/html/draft-srose-dkim-ecc-00 */
-  US"eccp256",
-  US"eccp348",
-  US"ed448",
-#endif
-};
-
-typedef struct pdkim_combined_canon_entry {
-  const uschar *       str;
-  int                  canon_headers;
-  int                  canon_body;
-} pdkim_combined_canon_entry;
-
-pdkim_combined_canon_entry pdkim_combined_canons[] = {
-  { US"simple/simple",    PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
-  { US"simple/relaxed",   PDKIM_CANON_SIMPLE,   PDKIM_CANON_RELAXED },
-  { US"relaxed/simple",   PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
-  { US"relaxed/relaxed",  PDKIM_CANON_RELAXED,  PDKIM_CANON_RELAXED },
-  { US"simple",           PDKIM_CANON_SIMPLE,   PDKIM_CANON_SIMPLE },
-  { US"relaxed",          PDKIM_CANON_RELAXED,  PDKIM_CANON_SIMPLE },
-  { NULL,                 0,                    0 }
-};
-
-
-static const blob lineending = {.data = US"\r\n", .len = 2};
-
-/* -------------------------------------------------------------------------- */
-uschar *
-dkim_sig_to_a_tag(const pdkim_signature * sig)
-{
-if (  sig->keytype < 0  || sig->keytype > nelem(pdkim_keytypes)
-   || sig->hashtype < 0 || sig->hashtype > nelem(pdkim_hashes))
-  return US"err";
-return string_sprintf("%s-%s",
-  pdkim_keytypes[sig->keytype], pdkim_hashes[sig->hashtype].dkim_hashname);
-}
-
-
-static int
-pdkim_keyname_to_keytype(const uschar * s)
-{
-for (int i = 0; i < nelem(pdkim_keytypes); i++)
-  if (Ustrcmp(s, pdkim_keytypes[i]) == 0) return i;
-return -1;
-}
-
-int
-pdkim_hashname_to_hashtype(const uschar * s, unsigned len)
-{
-if (!len) len = Ustrlen(s);
-for (int i = 0; i < nelem(pdkim_hashes); i++)
-  if (Ustrncmp(s, pdkim_hashes[i].dkim_hashname, len) == 0)
-    return i;
-return -1;
-}
-
-void
-pdkim_cstring_to_canons(const uschar * s, unsigned len,
-  int * canon_head, int * canon_body)
-{
-if (!len) len = Ustrlen(s);
-for (int i = 0; pdkim_combined_canons[i].str; i++)
-  if (  Ustrncmp(s, pdkim_combined_canons[i].str, len) == 0
-     && len == Ustrlen(pdkim_combined_canons[i].str))
-    {
-    *canon_head = pdkim_combined_canons[i].canon_headers;
-    *canon_body = pdkim_combined_canons[i].canon_body;
-    break;
-    }
-}
-
-
-
-const char *
-pdkim_verify_status_str(int status)
-{
-switch(status)
-  {
-  case PDKIM_VERIFY_NONE:    return "PDKIM_VERIFY_NONE";
-  case PDKIM_VERIFY_INVALID: return "PDKIM_VERIFY_INVALID";
-  case PDKIM_VERIFY_FAIL:    return "PDKIM_VERIFY_FAIL";
-  case PDKIM_VERIFY_PASS:    return "PDKIM_VERIFY_PASS";
-  default:                   return "PDKIM_VERIFY_UNKNOWN";
-  }
-}
-
-const char *
-pdkim_verify_ext_status_str(int ext_status)
-{
-switch(ext_status)
-  {
-  case PDKIM_VERIFY_FAIL_BODY: return "PDKIM_VERIFY_FAIL_BODY";
-  case PDKIM_VERIFY_FAIL_MESSAGE: return "PDKIM_VERIFY_FAIL_MESSAGE";
-  case PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH: return "PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH";
-  case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE: return "PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE";
-  case PDKIM_VERIFY_INVALID_BUFFER_SIZE: return "PDKIM_VERIFY_INVALID_BUFFER_SIZE";
-  case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD: return "PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD";
-  case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return "PDKIM_VERIFY_INVALID_PUBKEY_IMPORT";
-  case PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE: return "PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE";
-  case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR: return "PDKIM_VERIFY_INVALID_SIGNATURE_ERROR";
-  case PDKIM_VERIFY_INVALID_DKIM_VERSION: return "PDKIM_VERIFY_INVALID_DKIM_VERSION";
-  default: return "PDKIM_VERIFY_UNKNOWN";
-  }
-}
-
-const uschar *
-pdkim_errstr(int status)
-{
-switch(status)
-  {
-  case PDKIM_OK:               return US"OK";
-  case PDKIM_FAIL:             return US"FAIL";
-  case PDKIM_ERR_RSA_PRIVKEY:  return US"PRIVKEY";
-  case PDKIM_ERR_RSA_SIGNING:  return US"SIGNING";
-  case PDKIM_ERR_LONG_LINE:    return US"LONG_LINE";
-  case PDKIM_ERR_BUFFER_TOO_SMALL:     return US"BUFFER_TOO_SMALL";
-  case PDKIM_ERR_EXCESS_SIGS:  return US"EXCESS_SIGS";
-  case PDKIM_SIGN_PRIVKEY_WRAP:        return US"PRIVKEY_WRAP";
-  case PDKIM_SIGN_PRIVKEY_B64D:        return US"PRIVKEY_B64D";
-  default: return US"(unknown)";
-  }
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-static pdkim_stringlist *
-pdkim_prepend_stringlist(pdkim_stringlist * base, const uschar * str)
-{
-pdkim_stringlist * new_entry = store_get(sizeof(pdkim_stringlist), GET_UNTAINTED);
-
-memset(new_entry, 0, sizeof(pdkim_stringlist));
-new_entry->value = string_copy(str);
-if (base) new_entry->next = base;
-return new_entry;
-}
-
-
-
-/* Trim whitespace fore & aft */
-
-static void
-pdkim_strtrim(gstring * str)
-{
-uschar * p = str->s;
-uschar * q;
-
-while (*p == '\t' || *p == ' ')                /* dump the leading whitespace */
-  { str->size--; str->ptr--; str->s++; }
-
-while (  str->ptr > 0
-      && ((q = str->s + str->ptr - 1),  (*q == '\t' || *q == ' '))
-      )
-  str->ptr--;                          /* dump trailing whitespace */
-
-(void) string_from_gstring(str);
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-
-DLLEXPORT void
-pdkim_free_ctx(pdkim_ctx *ctx)
-{
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Matches the name of the passed raw "header" against
-   the passed colon-separated "tick", and invalidates
-   the entry in tick.  Entries can be prefixed for multi- or over-signing,
-   in which case do not invalidate.
-
-   Returns OK for a match, or fail-code
-*/
-
-static int
-header_name_match(const uschar * header, uschar * tick)
-{
-const uschar * ticklist = tick;
-int sep = ':';
-BOOL multisign;
-uschar * hname, * p, * ele;
-uschar * hcolon = Ustrchr(header, ':');                /* Get header name */
-
-if (!hcolon)
-  return PDKIM_FAIL; /* This isn't a header */
-
-/* if we had strncmpic() we wouldn't need this copy */
-hname = string_copyn(header, hcolon-header);
-
-while (p = US ticklist, ele = string_nextinlist(&ticklist, &sep, NULL, 0))
-  {
-  switch (*ele)
-  {
-  case '=': case '+':  multisign = TRUE; ele++; break;
-  default:             multisign = FALSE; break;
-  }
-
-  if (strcmpic(ele, hname) == 0)
-    {
-    if (!multisign)
-      *p = '_';        /* Invalidate this header name instance in tick-off list */
-    return PDKIM_OK;
-    }
-  }
-return PDKIM_FAIL;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Performs "relaxed" canonicalization of a header. */
-
-uschar *
-pdkim_relax_header_n(const uschar * header, int len, BOOL append_crlf)
-{
-BOOL past_field_name = FALSE;
-BOOL seen_wsp = FALSE;
-uschar * relaxed = store_get(len+3, GET_TAINTED);
-uschar * q = relaxed;
-
-for (const uschar * p = header; p - header < len; p++)
-  {
-  uschar c = *p;
-
-  if (c == '\r' || c == '\n')  /* Ignore CR & LF */
-    continue;
-  if (c == '\t' || c == ' ')
-    {
-    if (seen_wsp)
-      continue;
-    c = ' ';                   /* Turns WSP into SP */
-    seen_wsp = TRUE;
-    }
-  else
-    if (!past_field_name && c == ':')
-      {
-      if (seen_wsp) q--;       /* This removes WSP immediately before the colon */
-      seen_wsp = TRUE;         /* This removes WSP immediately after the colon */
-      past_field_name = TRUE;
-      }
-    else
-      seen_wsp = FALSE;
-
-  /* Lowercase header name */
-  if (!past_field_name) c = tolower(c);
-  *q++ = c;
-  }
-
-if (q > relaxed && q[-1] == ' ') q--; /* Squash eventual trailing SP */
-
-if (append_crlf) { *q++ = '\r'; *q++ = '\n'; }
-*q = '\0';
-return relaxed;
-}
-
-
-uschar *
-pdkim_relax_header(const uschar * header, BOOL append_crlf)
-{
-return pdkim_relax_header_n(header, Ustrlen(header), append_crlf);
-}
-
-
-/* -------------------------------------------------------------------------- */
-#define PDKIM_QP_ERROR_DECODE -1
-
-static const uschar *
-pdkim_decode_qp_char(const uschar *qp_p, int *c)
-{
-const uschar *initial_pos = qp_p;
-
-/* Advance one char */
-qp_p++;
-
-/* Check for two hex digits and decode them */
-if (isxdigit(*qp_p) && isxdigit(qp_p[1]))
-  {
-  /* Do hex conversion */
-  *c = (isdigit(*qp_p) ? *qp_p - '0' : toupper(*qp_p) - 'A' + 10) << 4;
-  *c |= isdigit(qp_p[1]) ? qp_p[1] - '0' : toupper(qp_p[1]) - 'A' + 10;
-  return qp_p + 2;
-  }
-
-/* Illegal char here */
-*c = PDKIM_QP_ERROR_DECODE;
-return initial_pos;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-static uschar *
-pdkim_decode_qp(const uschar * str)
-{
-int nchar = 0;
-uschar * q;
-const uschar * p = str;
-uschar * n = store_get(Ustrlen(str)+1, GET_TAINTED);
-
-*n = '\0';
-q = n;
-while (*p)
-  {
-  if (*p == '=')
-    {
-    p = pdkim_decode_qp_char(p, &nchar);
-    if (nchar >= 0)
-      {
-      *q++ = nchar;
-      continue;
-      }
-    }
-  else
-    *q++ = *p;
-  p++;
-  }
-*q = '\0';
-return n;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-void
-pdkim_decode_base64(const uschar * str, blob * b)
-{
-int dlen = b64decode(str, &b->data, str);
-if (dlen < 0) b->data = NULL;
-b->len = dlen;
-}
-
-uschar *
-pdkim_encode_base64(blob * b)
-{
-return b64encode(CUS b->data, b->len);
-}
-
-
-/* -------------------------------------------------------------------------- */
-#define PDKIM_HDR_LIMBO 0
-#define PDKIM_HDR_TAG   1
-#define PDKIM_HDR_VALUE 2
-
-static pdkim_signature *
-pdkim_parse_sig_header(pdkim_ctx * ctx, uschar * raw_hdr)
-{
-pdkim_signature * sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
-uschar * q;
-gstring * cur_tag = NULL, * cur_val = NULL;
-BOOL past_hname = FALSE, in_b_val = FALSE;
-int where = PDKIM_HDR_LIMBO;
-
-memset(sig, 0, sizeof(pdkim_signature));
-sig->bodylength = -1;
-
-/* Set so invalid/missing data error display is accurate */
-sig->version = 0;
-sig->keytype = -1;
-sig->hashtype = -1;
-
-q = sig->rawsig_no_b_val = store_get(Ustrlen(raw_hdr)+1, GET_TAINTED);
-
-for (uschar * p = raw_hdr; ; p++)
-  {
-  char c = *p;
-
-  /* Ignore FWS */
-  if (c == '\r' || c == '\n')
-    goto NEXT_CHAR;
-
-  /* Fast-forward through header name */
-  if (!past_hname)
-    {
-    if (c == ':') past_hname = TRUE;
-    goto NEXT_CHAR;
-    }
-
-  if (where == PDKIM_HDR_LIMBO)
-    {
-    /* In limbo, just wait for a tag-char to appear */
-    if (!(c >= 'a' && c <= 'z'))
-      goto NEXT_CHAR;
-
-    where = PDKIM_HDR_TAG;
-    }
-
-  if (where == PDKIM_HDR_TAG)
-    if (c == '=')
-      {
-      if (Ustrcmp(string_from_gstring(cur_tag), "b") == 0)
-        {
-       *q++ = '=';
-       in_b_val = TRUE;
-       }
-      where = PDKIM_HDR_VALUE;
-      goto NEXT_CHAR;
-      }
-    else if (!isspace(c))
-      cur_tag = string_catn(cur_tag, p, 1);
-
-  if (where == PDKIM_HDR_VALUE)
-    {
-    if (c == '\r' || c == '\n' || c == ' ' || c == '\t')
-      goto NEXT_CHAR;
-
-    if (c == ';' || c == '\0')
-      {
-      /* We must have both tag and value, and tags must be one char except
-      for the possibility of "bh". */
-
-      if (  cur_tag && cur_val
-        && (cur_tag->ptr == 1 || *cur_tag->s == 'b')
-        )
-        {
-       (void) string_from_gstring(cur_val);
-       pdkim_strtrim(cur_val);
-
-       DEBUG(D_acl) debug_printf(" %s=%s\n", cur_tag->s, cur_val->s);
-
-       switch (*cur_tag->s)
-         {
-         case 'b':                             /* sig-data or body-hash */
-           switch (cur_tag->s[1])
-             {
-             case '\0': pdkim_decode_base64(cur_val->s, &sig->sighash); break;
-             case 'h':  if (cur_tag->ptr != 2) goto bad_tag;
-                        pdkim_decode_base64(cur_val->s, &sig->bodyhash);
-                        break;
-             default:   goto bad_tag;
-             }
-           break;
-         case 'v':                                     /* version */
-             /* We only support version 1, and that is currently the
-                only version there is. */
-           sig->version =
-               Ustrcmp(cur_val->s, PDKIM_SIGNATURE_VERSION) == 0 ? 1 : -1;
-           break;
-         case 'a':                                     /* algorithm */
-           {
-           const uschar * list = cur_val->s;
-           int sep = '-';
-           uschar * elem;
-
-           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
-             sig->keytype = pdkim_keyname_to_keytype(elem);
-           if ((elem = string_nextinlist(&list, &sep, NULL, 0)))
-             for (int i = 0; i < nelem(pdkim_hashes); i++)
-               if (Ustrcmp(elem, pdkim_hashes[i].dkim_hashname) == 0)
-                 { sig->hashtype = i; break; }
-           }
-           break;
-
-         case 'c':                                     /* canonicalization */
-           pdkim_cstring_to_canons(cur_val->s, 0,
-                                   &sig->canon_headers, &sig->canon_body);
-           break;
-         case 'q':                             /* Query method (for pubkey)*/
-           for (int i = 0; pdkim_querymethods[i]; i++)
-             if (Ustrcmp(cur_val->s, pdkim_querymethods[i]) == 0)
-               {
-               sig->querymethod = i;   /* we never actually use this */
-               break;
-               }
-           break;
-         case 's':                                     /* Selector */
-           sig->selector = string_copy_from_gstring(cur_val); break;
-         case 'd':                                     /* SDID */
-           sig->domain = string_copy_from_gstring(cur_val); break;
-         case 'i':                                     /* AUID */
-           sig->identity = pdkim_decode_qp(cur_val->s); break;
-         case 't':                                     /* Timestamp */
-           sig->created = strtoul(CS cur_val->s, NULL, 10); break;
-         case 'x':                                     /* Expiration */
-           sig->expires = strtoul(CS cur_val->s, NULL, 10); break;
-         case 'l':                                     /* Body length count */
-           sig->bodylength = strtol(CS cur_val->s, NULL, 10); break;
-         case 'h':                                     /* signed header fields */
-           sig->headernames = string_copy_from_gstring(cur_val); break;
-         case 'z':                                     /* Copied headfields */
-           sig->copiedheaders = pdkim_decode_qp(cur_val->s); break;
-/*XXX draft-ietf-dcrup-dkim-crypto-05 would need 'p' tag support
-for rsafp signatures.  But later discussion is dropping those. */
-         default:
-           goto bad_tag;
-         }
-       }
-       else
-bad_tag:  DEBUG(D_acl) debug_printf(" Unknown tag encountered: %Y\n", cur_tag);
-
-      cur_tag = cur_val = NULL;
-      in_b_val = FALSE;
-      where = PDKIM_HDR_LIMBO;
-      }
-    else
-      cur_val = string_catn(cur_val, p, 1);
-    }
-
-NEXT_CHAR:
-  if (c == '\0')
-    break;
-
-  if (!in_b_val)
-    *q++ = c;
-  }
-
-if (sig->keytype < 0 || sig->hashtype < 0)     /* Cannot verify this signature */
-  return NULL;
-
-*q = '\0';
-/* Chomp raw header. The final newline must not be added to the signature. */
-while (--q > sig->rawsig_no_b_val  && (*q == '\r' || *q == '\n'))
-  *q = '\0';
-
-DEBUG(D_acl)
-  {
-  debug_printf(
-         "DKIM >> Raw signature w/o b= tag value >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-  debug_printf("%Z\n", US sig->rawsig_no_b_val);
-  debug_printf(
-         "DKIM >> Sig size: %4u bits\n", (unsigned) sig->sighash.len*8);
-  debug_printf(
-         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-  }
-
-if (!pdkim_set_sig_bodyhash(ctx, sig))
-  return NULL;
-
-return sig;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-pdkim_pubkey *
-pdkim_parse_pubkey_record(const uschar * raw_record)
-{
-pdkim_pubkey * pub = store_get(sizeof(pdkim_pubkey), GET_TAINTED);
-
-memset(pub, 0, sizeof(pdkim_pubkey));
-
-for (const uschar * ele = raw_record, * tspec, * end, * val; *ele; ele = end)
-  {
-  Uskip_whitespace(&ele);
-  end = Ustrchrnul(ele, ';');
-  tspec = string_copyn(ele, end - ele);
-  if (*end) end++;                     /* skip the ; */
-
-  if ((val = Ustrchr(tspec, '=')))
-    {
-    int taglen = val++ - tspec;
-
-    DEBUG(D_acl) debug_printf(" %.*s=%s\n", taglen, tspec, val);
-    while (taglen > 1 && isspace(tspec[taglen-1]))
-      taglen--;                        /* Ignore whitespace before = */
-    Uskip_whitespace(&val);    /* Ignore whitespace after = */
-    if (isspace(val[ Ustrlen(val)-1 ]))
-      {                                /* Ignore whitespace after value */
-      gstring * g = string_cat(NULL, val);
-      while (isspace(gstring_last_char(g)))
-       gstring_trim(g, 1);
-      val = string_from_gstring(g);
-      }
-
-    if (taglen == 1) switch (tspec[0])
-      {
-      case 'v': pub->version = val;                    break;
-      case 'h': pub->hashes = val;                     break;
-      case 'k': pub->keytype = val;                    break;
-      case 'g': pub->granularity = val;                        break;
-      case 'n': pub->notes = pdkim_decode_qp(val);     break;
-      case 'p': pdkim_decode_base64(val, &pub->key);   break;
-      case 's': pub->srvtype = val;                    break;
-      case 't': if (Ustrchr(val, 'y')) pub->testing = 1;
-               if (Ustrchr(val, 's')) pub->no_subdomaining = 1;
-               break;
-      default:  goto bad_tag;
-      }
-    else
-bad_tag:
-       DEBUG(D_acl) debug_printf(" Unknown tag encountered\n");
-    }
-  }
-
-/* Set fallback defaults */
-if (!pub->version)
-  pub->version = string_copy(PDKIM_PUB_RECORD_VERSION);
-else if (Ustrcmp(pub->version, PDKIM_PUB_RECORD_VERSION) != 0)
-  {
-  DEBUG(D_acl) debug_printf(" Bad v= field\n");
-  return NULL;
-  }
-
-if (!pub->granularity) pub->granularity = US"*";
-if (!pub->keytype    ) pub->keytype     = US"rsa";
-if (!pub->srvtype    ) pub->srvtype     = US"*";
-
-/* p= is required */
-if (pub->key.data)
-  return pub;
-
-DEBUG(D_acl) debug_printf(" Missing p= field\n");
-return NULL;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-/* Update one bodyhash with some additional data.
-If we have to relax the data for this sig, return our copy of it. */
-
-static blob *
-pdkim_update_ctx_bodyhash(pdkim_bodyhash * b, const blob * orig_data, blob * relaxed_data)
-{
-const blob * canon_data = orig_data;
-size_t left;
-
-/* Defaults to simple canon (no further treatment necessary) */
-
-if (b->canon_method == PDKIM_CANON_RELAXED)
-  {
-  /* Relax the line if not done already */
-  if (!relaxed_data)
-    {
-    BOOL seen_wsp = FALSE;
-    int q = 0;
-
-    /* We want to be able to free this else we allocate
-    for the entire message which could be many MB. Since
-    we don't know what allocations the SHA routines might
-    do, not safe to use store_get()/store_reset(). */
-
-    relaxed_data = store_malloc(sizeof(blob) + orig_data->len+1);
-    relaxed_data->data = US (relaxed_data+1);
-
-    for (const uschar * p = orig_data->data, * r = p + orig_data->len; p < r; p++)
-      {
-      char c = *p;
-      if (c == '\r')
-       {
-       if (q > 0 && relaxed_data->data[q-1] == ' ')
-         q--;
-       }
-      else if (c == '\t' || c == ' ')
-       {
-       c = ' '; /* Turns WSP into SP */
-       if (seen_wsp)
-         continue;
-       seen_wsp = TRUE;
-       }
-      else
-       seen_wsp = FALSE;
-      relaxed_data->data[q++] = c;
-      }
-    relaxed_data->data[q] = '\0';
-    relaxed_data->len = q;
-    }
-  canon_data = relaxed_data;
-  }
-
-/* Make sure we don't exceed the to-be-signed body length */
-left = canon_data->len;
-if (  b->bodylength >= 0
-   && left > (unsigned long)b->bodylength - b->signed_body_bytes
-   )
-  left = (unsigned long)b->bodylength - b->signed_body_bytes;
-
-if (left > 0)
-  {
-  exim_sha_update(&b->body_hash_ctx, CUS canon_data->data, left);
-  b->signed_body_bytes += left;
-  DEBUG(D_acl) debug_printf("%.*Z\n", left, canon_data->data);
-  }
-
-return relaxed_data;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-static void
-pdkim_finish_bodyhash(pdkim_ctx * ctx)
-{
-for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)     /* Finish hashes */
-  {
-  DEBUG(D_acl) debug_printf("DKIM: finish bodyhash %s/%s/%ld len %ld\n",
-      pdkim_hashes[b->hashtype].dkim_hashname, pdkim_canons[b->canon_method],
-      b->bodylength, b->signed_body_bytes);
-  exim_sha_finish(&b->body_hash_ctx, &b->bh);
-  }
-
-/* Traverse all signatures */
-for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
-  {
-  pdkim_bodyhash * b = sig->calc_body_hash;
-
-  DEBUG(D_acl)
-    {
-    debug_printf("DKIM [%s]%s Body bytes (%s) hashed: %lu\n"
-                "DKIM [%s]%s Body %s computed: ",
-       sig->domain, sig->selector, pdkim_canons[b->canon_method], b->signed_body_bytes,
-       sig->domain, sig->selector, pdkim_hashes[b->hashtype].dkim_hashname);
-    debug_printf("%.*H\n", b->bh.len, CUS b->bh.data);
-    }
-
-  /* SIGNING -------------------------------------------------------------- */
-  if (ctx->flags & PDKIM_MODE_SIGN)
-    {
-    /* If bodylength limit is set, and we have received less bytes
-       than the requested amount, effectively remove the limit tag. */
-    if (b->signed_body_bytes < sig->bodylength)
-      sig->bodylength = -1;
-    }
-
-  else
-  /* VERIFICATION --------------------------------------------------------- */
-  /* Be careful that the header sig included a bodyash */
-
-    if (sig->bodyhash.data && sig->bodyhash.len == b->bh.len
-       && memcmp(b->bh.data, sig->bodyhash.data, b->bh.len) == 0)
-      {
-      DEBUG(D_acl) debug_printf("DKIM [%s] Body hash compared OK\n", sig->domain);
-      }
-    else
-      {
-      DEBUG(D_acl)
-        {
-       debug_printf("DKIM [%s] Body hash signature from headers: ", sig->domain);
-       debug_printf("%.*H\n", sig->bodyhash.len, sig->bodyhash.data);
-       debug_printf("DKIM [%s] Body hash did NOT verify\n", sig->domain);
-       }
-      sig->verify_status     = PDKIM_VERIFY_FAIL;
-      sig->verify_ext_status = PDKIM_VERIFY_FAIL_BODY;
-      }
-  }
-}
-
-
-
-static void
-pdkim_body_complete(pdkim_ctx * ctx)
-{
-/* In simple body mode, if any empty lines were buffered,
-replace with one. rfc 4871 3.4.3 */
-/*XXX checking the signed-body-bytes is a gross hack; I think
-it indicates that all linebreaks should be buffered, including
-the one terminating a text line */
-
-for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
-  if (  b->canon_method == PDKIM_CANON_SIMPLE
-     && b->signed_body_bytes == 0
-     && b->num_buffered_blanklines > 0
-     )
-    (void) pdkim_update_ctx_bodyhash(b, &lineending, NULL);
-
-ctx->flags |= PDKIM_SEEN_EOD;
-ctx->linebuf_offset = 0;
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-/* Call from pdkim_feed below for processing complete body lines */
-/* NOTE: the line is not NUL-terminated; but we have a count */
-
-static void
-pdkim_bodyline_complete(pdkim_ctx * ctx)
-{
-blob line = {.data = ctx->linebuf, .len = ctx->linebuf_offset};
-blob * rnl = NULL;
-blob * rline = NULL;
-
-/* Ignore extra data if we've seen the end-of-data marker */
-if (ctx->flags & PDKIM_SEEN_EOD) goto all_skip;
-
-/* We've always got one extra byte to stuff a zero ... */
-ctx->linebuf[line.len] = '\0';
-
-/* Terminate on EOD marker */
-if (ctx->flags & PDKIM_DOT_TERM)
-  {
-  if (memcmp(line.data, ".\r\n", 3) == 0)
-    { pdkim_body_complete(ctx); return; }
-
-  /* Unstuff dots */
-  if (memcmp(line.data, "..", 2) == 0)
-    { line.data++; line.len--; }
-  }
-
-/* Empty lines need to be buffered until we find a non-empty line */
-if (memcmp(line.data, "\r\n", 2) == 0)
-  {
-  for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
-    b->num_buffered_blanklines++;
-  goto all_skip;
-  }
-
-/* Process line for each bodyhash separately */
-for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
-  {
-  if (b->canon_method == PDKIM_CANON_RELAXED)
-    {
-    /* Lines with just spaces need to be buffered too */
-    uschar * cp = line.data;
-    char c;
-
-    while ((c = *cp))
-      {
-      if (c == '\r' && cp[1] == '\n') break;
-      if (c != ' ' && c != '\t') goto hash_process;
-      cp++;
-      }
-
-    b->num_buffered_blanklines++;
-    goto hash_skip;
-    }
-
-hash_process:
-  /* At this point, we have a non-empty line, so release the buffered ones. */
-
-  while (b->num_buffered_blanklines)
-    {
-    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
-    b->num_buffered_blanklines--;
-    }
-
-  rline = pdkim_update_ctx_bodyhash(b, &line, rline);
-hash_skip: ;
-  }
-
-if (rnl) store_free(rnl);
-if (rline) store_free(rline);
-
-all_skip:
-
-ctx->linebuf_offset = 0;
-return;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Callback from pdkim_feed below for processing complete headers */
-#define DKIM_SIGNATURE_HEADERNAME "DKIM-Signature:"
-
-static int
-pdkim_header_complete(pdkim_ctx * ctx)
-{
-if (ctx->cur_header->ptr > 1)
-  gstring_trim_trailing(ctx->cur_header, '\r');
-(void) string_from_gstring(ctx->cur_header);
-
-#ifdef EXPERIMENTAL_ARC
-/* Feed the header line to ARC processing */
-(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN));
-#endif
-
-if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL;
-
-/* SIGNING -------------------------------------------------------------- */
-if (ctx->flags & PDKIM_MODE_SIGN)
-  for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)  /* Traverse all signatures */
-
-    /* Add header to the signed headers list (in reverse order) */
-    sig->headers = pdkim_prepend_stringlist(sig->headers, ctx->cur_header->s);
-
-/* VERIFICATION ----------------------------------------------------------- */
-/* DKIM-Signature: headers are added to the verification list */
-else
-  {
-#ifdef notdef
-  DEBUG(D_acl) debug_printf("DKIM >> raw hdr: %.*Z\n",
-                           ctx->cur_head->ptr, CUS ctx->cur_header->s);
-#endif
-  if (strncasecmp(CCS ctx->cur_header->s,
-                 DKIM_SIGNATURE_HEADERNAME,
-                 Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0)
-    {
-    pdkim_signature * sig, * last_sig;
-    /* Create and chain new signature block.  We could error-check for all
-    required tags here, but prefer to create the internal sig and expicitly
-    fail verification of it later. */
-
-    DEBUG(D_acl) debug_printf(
-       "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-
-    sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s);
-
-    if (!(last_sig = ctx->sig))
-      ctx->sig = sig;
-    else
-      {
-      while (last_sig->next) last_sig = last_sig->next;
-      last_sig->next = sig;
-      }
-
-    if (dkim_collect_input && --dkim_collect_input == 0)
-      {
-      ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
-      ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';
-      return PDKIM_ERR_EXCESS_SIGS;
-      }
-    }
-
-  /* all headers are stored for signature verification */
-  ctx->headers = pdkim_prepend_stringlist(ctx->headers, ctx->cur_header->s);
-  }
-
-BAIL:
-ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0';   /* leave buffer for reuse */
-return PDKIM_OK;
-}
-
-
-
-/* -------------------------------------------------------------------------- */
-#define HEADER_BUFFER_FRAG_SIZE 256
-
-DLLEXPORT int
-pdkim_feed(pdkim_ctx * ctx, uschar * data, int len)
-{
-/* Alternate EOD signal, used in non-dotstuffing mode */
-if (!data)
-  pdkim_body_complete(ctx);
-
-else for (int p = 0; p < len; p++)
-  {
-  uschar c = data[p];
-  int rc;
-
-  if (ctx->flags & PDKIM_PAST_HDRS)
-    {
-    if (c == '\n' && !(ctx->flags & PDKIM_SEEN_CR))    /* emulate the CR */
-      {
-      ctx->linebuf[ctx->linebuf_offset++] = '\r';
-      if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
-       return PDKIM_ERR_LONG_LINE;
-      }
-
-    /* Processing body byte */
-    ctx->linebuf[ctx->linebuf_offset++] = c;
-    if (c == '\r')
-      ctx->flags |= PDKIM_SEEN_CR;
-    else if (c == '\n')
-      {
-      ctx->flags &= ~PDKIM_SEEN_CR;
-      pdkim_bodyline_complete(ctx);
-      }
-
-    if (ctx->linebuf_offset == PDKIM_MAX_BODY_LINE_LEN-1)
-      return PDKIM_ERR_LONG_LINE;
-    }
-  else
-    {
-    /* Processing header byte */
-    if (c == '\r')
-      ctx->flags |= PDKIM_SEEN_CR;
-    else if (c == '\n')
-      {
-      if (!(ctx->flags & PDKIM_SEEN_CR))               /* emulate the CR */
-       ctx->cur_header = string_catn(ctx->cur_header, CUS "\r", 1);
-
-      if (ctx->flags & PDKIM_SEEN_LF)          /* Seen last header line */
-       {
-       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
-         return rc;
-
-       ctx->flags = (ctx->flags & ~(PDKIM_SEEN_LF|PDKIM_SEEN_CR)) | PDKIM_PAST_HDRS;
-       DEBUG(D_acl) debug_printf(
-           "DKIM >> Body data for hash, canonicalized >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-       continue;
-       }
-      else
-       ctx->flags = (ctx->flags & ~PDKIM_SEEN_CR) | PDKIM_SEEN_LF;
-      }
-    else if (ctx->flags & PDKIM_SEEN_LF)
-      {
-      if (!(c == '\t' || c == ' '))                    /* End of header */
-       if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
-         return rc;
-      ctx->flags &= ~PDKIM_SEEN_LF;
-      }
-
-    if (!ctx->cur_header || ctx->cur_header->ptr < PDKIM_MAX_HEADER_LEN)
-      ctx->cur_header = string_catn(ctx->cur_header, CUS &data[p], 1);
-    }
-  }
-return PDKIM_OK;
-}
-
-
-
-/* Extend a growing header with a continuation-linebreak */
-static gstring *
-pdkim_hdr_cont(gstring * str, int * col)
-{
-*col = 1;
-return string_catn(str, US"\r\n\t", 3);
-}
-
-
-
-/*
- * RFC 5322 specifies that header line length SHOULD be no more than 78
- *  pdkim_headcat
- *
- * Returns gstring (not nul-terminated) appending to one supplied
- *
- * col: this int holds and receives column number (octets since last '\n')
- * str: partial string to append to
- * pad: padding, split line or space after before or after eg: ";".
- *               Only the initial charater is used.
- * intro: - must join to payload eg "h=", usually the tag name
- * payload: eg base64 data - long data can be split arbitrarily.
- *
- * this code doesn't fold the header in some of the places that RFC4871
- * allows: As per RFC5322(2.2.3) it only folds before or after tag-value
- * pairs and inside long values. it also always spaces or breaks after the
- * "pad"
- *
- * No guarantees are made for output given out-of range input. like tag
- * names longer than 78, or bogus col. Input is assumed to be free of line breaks.
- */
-
-static gstring *
-pdkim_headcat(int * col, gstring * str,
-  const uschar * pad, const uschar * intro, const uschar * payload)
-{
-int len, chomp, padded = 0;
-
-/* If we can fit at least the pad at the end of current line, do it now.
-Otherwise, wrap if there is a pad. */
-
-if (pad)
-  if (*col + 1 <= 78)
-    {
-    str = string_catn(str, pad, 1);
-    (*col)++;
-    pad = NULL;
-    padded = 1;
-    }
-  else
-    str = pdkim_hdr_cont(str, col);
-
-/* Special case: if the whole addition does not fit at the end of the current
-line, but could fit on a new line, wrap to give it its full, dedicated line.  */
-
-len = (pad ? 2 : padded)
-    + (intro ? Ustrlen(intro) : 0)
-    + (payload ? Ustrlen(payload) : 0);
-if (len <= 77 && *col+len > 78)
-  {
-  str = pdkim_hdr_cont(str, col);
-  padded = 0;
-  }
-
-/* Either we already dealt with the pad or we know there is room */
-
-if (pad)
-  {
-  str = string_catn(str, pad, 1);
-  str = string_catn(str, US" ", 1);
-  *col += 2;
-  }
-else if (padded && *col < 78)
-  {
-  str = string_catn(str, US" ", 1);
-  (*col)++;
-  }
-
-/* Call recursively with intro as payload: it gets the same, special treatment
-(that is, not split if < 78).  */
-
-if (intro)
-  str = pdkim_headcat(col, str, NULL, NULL, intro);
-
-if (payload)
-  for (len = Ustrlen(payload); len; len -= chomp)
-    {
-    if (*col >= 78)
-      str = pdkim_hdr_cont(str, col);
-    chomp = *col+len > 78 ? 78 - *col : len;
-    str = string_catn(str, payload, chomp);
-    *col += chomp;
-    payload += chomp;
-    }
-
-return str;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-/* Signing: create signature header
-*/
-static uschar *
-pdkim_create_header(pdkim_signature * sig, BOOL final)
-{
-uschar * base64_bh;
-uschar * base64_b;
-int col = 0;
-gstring * hdr;
-gstring * canon_all;
-
-canon_all = string_cat (NULL, pdkim_canons[sig->canon_headers]);
-canon_all = string_catn(canon_all, US"/", 1);
-canon_all = string_cat (canon_all, pdkim_canons[sig->canon_body]);
-(void) string_from_gstring(canon_all);
-
-hdr = string_cat(NULL, US"DKIM-Signature: v="PDKIM_SIGNATURE_VERSION);
-col = hdr->ptr;
-
-/* Required and static bits */
-hdr = pdkim_headcat(&col, hdr, US";", US"a=", dkim_sig_to_a_tag(sig));
-hdr = pdkim_headcat(&col, hdr, US";", US"q=", pdkim_querymethods[sig->querymethod]);
-hdr = pdkim_headcat(&col, hdr, US";", US"c=", canon_all->s);
-hdr = pdkim_headcat(&col, hdr, US";", US"d=", sig->domain);
-hdr = pdkim_headcat(&col, hdr, US";", US"s=", sig->selector);
-
-/* list of header names can be split between items. */
-  {
-  uschar * n = string_copy(sig->headernames);
-  uschar * i = US"h=";
-  uschar * s = US";";
-
-  while (*n)
-    {
-    uschar * c = Ustrchr(n, ':');
-
-    if (c) *c ='\0';
-
-    if (!i)
-      hdr = pdkim_headcat(&col, hdr, NULL, NULL, US":");
-
-    hdr = pdkim_headcat(&col, hdr, s, i, n);
-
-    if (!c)
-      break;
-
-    n = c+1;
-    s = NULL;
-    i = NULL;
-    }
-  }
-
-base64_bh = pdkim_encode_base64(&sig->calc_body_hash->bh);
-hdr = pdkim_headcat(&col, hdr, US";", US"bh=", base64_bh);
-
-/* Optional bits */
-if (sig->identity)
-  hdr = pdkim_headcat(&col, hdr, US";", US"i=", sig->identity);
-
-if (sig->created > 0)
-  {
-  uschar minibuf[21];
-
-  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->created);
-  hdr = pdkim_headcat(&col, hdr, US";", US"t=", minibuf);
-}
-
-if (sig->expires > 0)
-  {
-  uschar minibuf[21];
-
-  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->expires);
-  hdr = pdkim_headcat(&col, hdr, US";", US"x=", minibuf);
-  }
-
-if (sig->bodylength >= 0)
-  {
-  uschar minibuf[21];
-
-  snprintf(CS minibuf, sizeof(minibuf), "%lu", sig->bodylength);
-  hdr = pdkim_headcat(&col, hdr, US";", US"l=", minibuf);
-  }
-
-/* Preliminary or final version? */
-if (final)
-  {
-  base64_b = pdkim_encode_base64(&sig->sighash);
-  hdr = pdkim_headcat(&col, hdr, US";", US"b=", base64_b);
-
-  /* add trailing semicolon: I'm not sure if this is actually needed */
-  hdr = pdkim_headcat(&col, hdr, NULL, US";", US"");
-  }
-else
-  {
-  /* To satisfy the rule "all surrounding whitespace [...] deleted"
-  ( RFC 6376 section 3.7 ) we ensure there is no whitespace here.  Otherwise
-  the headcat routine could insert a linebreak which the relaxer would reduce
-  to a single space preceding the terminating semicolon, resulting in an
-  incorrect header-hash. */
-  hdr = pdkim_headcat(&col, hdr, US";", US"b=;", US"");
-  }
-
-return string_from_gstring(hdr);
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-/* According to draft-ietf-dcrup-dkim-crypto-07 "keys are 256 bits" (referring
-to DNS, hence the pubkey).  Check for more than 32 bytes; if so assume the
-alternate possible representation (still) being discussed: a
-SubjectPublickeyInfo wrapped key - and drop all but the trailing 32-bytes (it
-should be a DER, with exactly 12 leading bytes - but we could accept a BER also,
-which could be any size).  We still rely on the crypto library for checking for
-undersize.
-
-When the RFC is published this should be re-addressed. */
-
-static void
-check_bare_ed25519_pubkey(pdkim_pubkey * p)
-{
-int excess = p->key.len - 32;
-if (excess > 0)
-  {
-  DEBUG(D_acl)
-    debug_printf("DKIM: unexpected pubkey len %lu\n", (unsigned long) p->key.len);
-  p->key.data += excess; p->key.len = 32;
-  }
-}
-
-
-static pdkim_pubkey *
-pdkim_key_from_dns(pdkim_ctx * ctx, pdkim_signature * sig, ev_ctx * vctx,
-  const uschar ** errstr)
-{
-uschar * dns_txt_name, * dns_txt_reply;
-pdkim_pubkey * p;
-
-/* Fetch public key for signing domain, from DNS */
-
-dns_txt_name = string_sprintf("%s._domainkey.%s.", sig->selector, sig->domain);
-
-if (  !(dns_txt_reply = ctx->dns_txt_callback(dns_txt_name))
-   || dns_txt_reply[0] == '\0'
-   )
-  {
-  sig->verify_status =      PDKIM_VERIFY_INVALID;
-  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE;
-  return NULL;
-  }
-
-DEBUG(D_acl)
-  {
-  debug_printf(
-    "DKIM >> Parsing public key record >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"
-    " %s\n"
-    " Raw record: %Z\n",
-    dns_txt_name,
-    CUS dns_txt_reply);
-  }
-
-if (  !(p = pdkim_parse_pubkey_record(CUS dns_txt_reply))
-   || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
-   )
-  {
-  sig->verify_status =      PDKIM_VERIFY_INVALID;
-  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD;
-
-  DEBUG(D_acl)
-    {
-    if (p)
-      debug_printf(" Invalid public key service type '%s'\n", p->srvtype);
-    else
-      debug_printf(" Error while parsing public key record\n");
-    debug_printf(
-      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    }
-  return NULL;
-  }
-
-DEBUG(D_acl) debug_printf(
-      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-
-/* Import public key */
-
-/* Normally we use the signature a= tag to tell us the pubkey format.
-When signing under debug we do a test-import of the pubkey, and at that
-time we do not have a signature so we must interpret the pubkey k= tag
-instead.  Assume writing on the sig is ok in that case. */
-
-if (sig->keytype < 0)
-  if ((sig->keytype = pdkim_keyname_to_keytype(p->keytype)) < 0)
-    {
-    DEBUG(D_acl) debug_printf("verify_init: unhandled keytype %s\n", p->keytype);
-    sig->verify_status =      PDKIM_VERIFY_INVALID;
-    sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
-    return NULL;
-    }
-
-if (sig->keytype == KEYTYPE_ED25519)
-  check_bare_ed25519_pubkey(p);
-
-if ((*errstr = exim_dkim_verify_init(&p->key,
-           sig->keytype == KEYTYPE_ED25519 ? KEYFMT_ED25519_BARE : KEYFMT_DER,
-           vctx, &sig->keybits)))
-  {
-  DEBUG(D_acl) debug_printf("verify_init: %s\n", *errstr);
-  sig->verify_status =      PDKIM_VERIFY_INVALID;
-  sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_IMPORT;
-  return NULL;
-  }
-
-vctx->keytype = sig->keytype;
-return p;
-}
-
-
-/* -------------------------------------------------------------------------- */
-/* Sort and filter the sigs developed from the message */
-
-static pdkim_signature *
-sort_sig_methods(pdkim_signature * siglist)
-{
-pdkim_signature * yield, ** ss;
-const uschar * prefs;
-uschar * ele;
-int sep;
-
-if (!siglist) return NULL;
-
-/* first select in order of hashtypes */
-DEBUG(D_acl) debug_printf("DKIM: dkim_verify_hashes   '%s'\n", dkim_verify_hashes);
-for (prefs = dkim_verify_hashes, sep = 0, yield = NULL, ss = &yield;
-     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
-  {
-  int i = pdkim_hashname_to_hashtype(CUS ele, 0);
-  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
-       s = next)
-    {
-    next = s->next;
-    if (s->hashtype == i)
-      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
-    else
-      prev = &s->next;
-    }
-  }
-
-/* then in order of keytypes */
-siglist = yield;
-DEBUG(D_acl) debug_printf("DKIM: dkim_verify_keytypes '%s'\n", dkim_verify_keytypes);
-for (prefs = dkim_verify_keytypes, sep = 0, yield = NULL, ss = &yield;
-     ele = string_nextinlist(&prefs, &sep, NULL, 0); )
-  {
-  int i = pdkim_keyname_to_keytype(CUS ele);
-  for (pdkim_signature * s = siglist, * next, ** prev = &siglist; s;
-       s = next)
-    {
-    next = s->next;
-    if (s->keytype == i)
-      { *prev = next; s->next = NULL; *ss = s; ss = &s->next; }
-    else
-      prev = &s->next;
-    }
-  }
-
-DEBUG(D_acl) for (pdkim_signature * s = yield; s; s = s->next)
-  debug_printf(" retain d=%s s=%s a=%s\n",
-    s->domain, s->selector, dkim_sig_to_a_tag(s));
-return yield;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-DLLEXPORT int
-pdkim_feed_finish(pdkim_ctx * ctx, pdkim_signature ** return_signatures,
-  const uschar ** err)
-{
-BOOL verify_pass = FALSE;
-
-/* Check if we must still flush a (partial) header. If that is the
-   case, the message has no body, and we must compute a body hash
-   out of '<CR><LF>' */
-if (ctx->cur_header && ctx->cur_header->ptr > 0)
-  {
-  blob * rnl = NULL;
-  int rc;
-
-  if ((rc = pdkim_header_complete(ctx)) != PDKIM_OK)
-    return rc;
-
-  for (pdkim_bodyhash * b = ctx->bodyhash; b; b = b->next)
-    rnl = pdkim_update_ctx_bodyhash(b, &lineending, rnl);
-  if (rnl) store_free(rnl);
-  }
-else
-  DEBUG(D_acl) debug_printf(
-      "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-
-/* Build (and/or evaluate) body hash.  Do this even if no DKIM sigs, in case we
-have a hash to do for ARC. */
-
-pdkim_finish_bodyhash(ctx);
-
-/* Sort and filter the recived signatures */
-
-if (!(ctx->flags & PDKIM_MODE_SIGN))
-  ctx->sig = sort_sig_methods(ctx->sig);
-
-if (!ctx->sig)
-  {
-  DEBUG(D_acl) debug_printf("DKIM: no signatures\n");
-  *return_signatures = NULL;
-  return PDKIM_OK;
-  }
-
-for (pdkim_signature * sig = ctx->sig; sig; sig = sig->next)
-  {
-  hctx hhash_ctx;
-  uschar * sig_hdr = US"";
-  blob hhash;
-  gstring * hdata = NULL;
-  es_ctx sctx;
-
-  if (  !(ctx->flags & PDKIM_MODE_SIGN)
-     && sig->verify_status == PDKIM_VERIFY_FAIL)
-    {
-    DEBUG(D_acl)
-       debug_printf("DKIM: [%s] abandoning this signature\n", sig->domain);
-    continue;
-    }
-
-  /*XXX The hash of the headers is needed for GCrypt (for which we can do RSA
-  signing only, as it happens) and for either GnuTLS and OpenSSL when we are
-  signing with EC (specifically, Ed25519).  The former is because the GCrypt
-  signing operation is pure (does not do its own hash) so we must hash.  The
-  latter is because we (stupidly, but this is what the IETF draft is saying)
-  must hash with the declared hash method, then pass the result to the library
-  hash-and-sign routine (because that's all the libraries are providing.  And
-  we're stuck with whatever that hidden hash method is, too).  We may as well
-  do this hash incrementally.
-  We don't need the hash we're calculating here for the GnuTLS and OpenSSL
-  cases of RSA signing, since those library routines can do hash-and-sign.
-
-  Some time in the future we could easily avoid doing the hash here for those
-  cases (which will be common for a long while.  We could also change from
-  the current copy-all-the-headers-into-one-block, then call the hash-and-sign
-  implementation  - to a proper incremental one.  Unfortunately, GnuTLS just
-  cannot do incremental - either signing or verification.  Unsure about GCrypt.
-  */
-
-  /*XXX The header hash is also used (so far) by the verify operation */
-
-  if (!exim_sha_init(&hhash_ctx, pdkim_hashes[sig->hashtype].exim_hashmethod))
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC,
-      "DKIM: hash setup error, possibly nonhandled hashtype");
-    break;
-    }
-
-  if (ctx->flags & PDKIM_MODE_SIGN)
-    DEBUG(D_acl) debug_printf(
-       "DKIM >> Headers to be signed:                            >>>>>>>>>>>>\n"
-       " %s\n",
-       sig->sign_headers);
-
-  DEBUG(D_acl) debug_printf(
-      "DKIM >> Header data for hash, canonicalized (%-7s), in sequence >>\n",
-       pdkim_canons[sig->canon_headers]);
-
-
-  /* SIGNING ---------------------------------------------------------------- */
-  /* When signing, walk through our header list and add them to the hash. As we
-     go, construct a list of the header's names to use for the h= parameter.
-     Then append to that list any remaining header names for which there was no
-     header to sign. */
-
-  if (ctx->flags & PDKIM_MODE_SIGN)
-    {
-    gstring * g = NULL;
-    const uschar * l;
-    uschar * s;
-    int sep = 0;
-
-    /* Import private key, including the keytype which we need for building
-    the signature header  */
-
-    if ((*err = exim_dkim_signing_init(CUS sig->privkey, &sctx)))
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "signing_init: %s", *err);
-      return PDKIM_ERR_RSA_PRIVKEY;
-      }
-    sig->keytype = sctx.keytype;
-
-    sig->headernames = NULL;                   /* Collected signed header names */
-    for (pdkim_stringlist * p = sig->headers; p; p = p->next)
-      {
-      uschar * rh = p->value;
-
-      if (header_name_match(rh, sig->sign_headers) == PDKIM_OK)
-       {
-       /* Collect header names (Note: colon presence is guaranteed here) */
-       g = string_append_listele_n(g, ':', rh, Ustrchr(rh, ':') - rh);
-
-       if (sig->canon_headers == PDKIM_CANON_RELAXED)
-         rh = pdkim_relax_header(rh, TRUE);    /* cook header for relaxed canon */
-
-       /* Feed header to the hash algorithm */
-       exim_sha_update_string(&hhash_ctx, CUS rh);
-
-       /* Remember headers block for signing (when the library cannot do incremental)  */
-       /*XXX we could avoid doing this for all but the GnuTLS/RSA case */
-       hdata = exim_dkim_data_append(hdata, rh);
-
-       DEBUG(D_acl) debug_printf("%Z\n", rh);
-       }
-      }
-
-    /* Any headers we wanted to sign but were not present must also be listed.
-    Ignore elements that have been ticked-off or are marked as never-oversign. */
-
-    l = sig->sign_headers;
-    while((s = string_nextinlist(&l, &sep, NULL, 0)))
-      {
-      if (*s == '+')                   /* skip oversigning marker */
-        s++;
-      if (*s != '_' && *s != '=')
-       g = string_append_listele(g, ':', s);
-      }
-    sig->headernames = string_from_gstring(g);
-
-    /* Create signature header with b= omitted */
-    sig_hdr = pdkim_create_header(sig, FALSE);
-    }
-
-  /* VERIFICATION ----------------------------------------------------------- */
-  /* When verifying, walk through the header name list in the h= parameter and
-     add the headers to the hash in that order. */
-  else
-    {
-    uschar * p = sig->headernames;
-    uschar * q;
-
-    if (p)
-      {
-      /* clear tags */
-      for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
-       hdrs->tag = 0;
-
-      p = string_copy(p);
-      while(1)
-       {
-       if ((q = Ustrchr(p, ':')))
-         *q = '\0';
-
-  /*XXX walk the list of headers in same order as received. */
-       for (pdkim_stringlist * hdrs = ctx->headers; hdrs; hdrs = hdrs->next)
-         if (  hdrs->tag == 0
-            && strncasecmp(CCS hdrs->value, CCS p, Ustrlen(p)) == 0
-            && (hdrs->value)[Ustrlen(p)] == ':'
-            )
-           {
-           /* cook header for relaxed canon, or just copy it for simple  */
-
-           uschar * rh = sig->canon_headers == PDKIM_CANON_RELAXED
-             ? pdkim_relax_header(hdrs->value, TRUE)
-             : string_copy(CUS hdrs->value);
-
-           /* Feed header to the hash algorithm */
-           exim_sha_update_string(&hhash_ctx, CUS rh);
-
-           DEBUG(D_acl) debug_printf("%Z\n", rh);
-           hdrs->tag = 1;
-           break;
-           }
-
-       if (!q) break;
-       p = q+1;
-       }
-
-      sig_hdr = string_copy(sig->rawsig_no_b_val);
-      }
-    }
-
-  DEBUG(D_acl) debug_printf(
-           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-
-  DEBUG(D_acl)
-    {
-    debug_printf(
-           "DKIM >> Signed DKIM-Signature header, pre-canonicalized >>>>>>>>>>>>>\n");
-    debug_printf("%Z\n", CUS sig_hdr);
-    debug_printf(
-           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    }
-
-  /* Relax header if necessary */
-  if (sig->canon_headers == PDKIM_CANON_RELAXED)
-    sig_hdr = pdkim_relax_header(sig_hdr, FALSE);
-
-  DEBUG(D_acl)
-    {
-    debug_printf("DKIM >> Signed DKIM-Signature header, canonicalized (%-7s) >>>>>>>\n",
-           pdkim_canons[sig->canon_headers]);
-    debug_printf("%Z\n", CUS sig_hdr);
-    debug_printf(
-           "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-    }
-
-  /* Finalize header hash */
-  exim_sha_update_string(&hhash_ctx, CUS sig_hdr);
-  exim_sha_finish(&hhash_ctx, &hhash);
-
-  DEBUG(D_acl)
-    {
-    debug_printf("DKIM [%s] Header %s computed: ",
-      sig->domain, pdkim_hashes[sig->hashtype].dkim_hashname);
-    debug_printf("%.*H\n", hhash.len, hhash.data);
-    }
-
-  /* Remember headers block for signing (when the signing library cannot do
-  incremental)  */
-  if (ctx->flags & PDKIM_MODE_SIGN)
-    hdata = exim_dkim_data_append(hdata, US sig_hdr);
-
-  /* SIGNING ---------------------------------------------------------------- */
-  if (ctx->flags & PDKIM_MODE_SIGN)
-    {
-    hashmethod hm = sig->keytype == KEYTYPE_ED25519
-#if defined(SIGN_OPENSSL)
-      ? HASH_NULL
-#else
-      ? HASH_SHA2_512
-#endif
-      : pdkim_hashes[sig->hashtype].exim_hashmethod;
-
-#ifdef SIGN_HAVE_ED25519
-    /* For GCrypt, and for EC, we pass the hash-of-headers to the signing
-    routine.  For anything else we just pass the headers. */
-
-    if (sig->keytype != KEYTYPE_ED25519)
-#endif
-      {
-      hhash.data = hdata->s;
-      hhash.len = hdata->ptr;
-      }
-
-    if ((*err = exim_dkim_sign(&sctx, hm, &hhash, &sig->sighash)))
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "signing: %s", *err);
-      return PDKIM_ERR_RSA_SIGNING;
-      }
-
-    DEBUG(D_acl)
-      {
-      debug_printf( "DKIM [%s] b computed: ", sig->domain);
-      debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
-      }
-
-    sig->signature_header = pdkim_create_header(sig, TRUE);
-    }
-
-  /* VERIFICATION ----------------------------------------------------------- */
-  else
-    {
-    ev_ctx vctx;
-    hashmethod hm;
-
-    /* Make sure we have all required signature tags */
-    if (!(  sig->domain        && *sig->domain
-        && sig->selector      && *sig->selector
-        && sig->headernames   && *sig->headernames
-        && sig->bodyhash.data
-        && sig->sighash.data
-        && sig->keytype >= 0
-        && sig->hashtype >= 0
-        && sig->version
-       ) )
-      {
-      sig->verify_status     = PDKIM_VERIFY_INVALID;
-      sig->verify_ext_status = PDKIM_VERIFY_INVALID_SIGNATURE_ERROR;
-
-      DEBUG(D_acl) debug_printf(
-         " Error in DKIM-Signature header: tags missing or invalid (%s)\n"
-         "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n",
-         !(sig->domain && *sig->domain) ? "d="
-         : !(sig->selector && *sig->selector) ? "s="
-         : !(sig->headernames && *sig->headernames) ? "h="
-         : !sig->bodyhash.data ? "bh="
-         : !sig->sighash.data ? "b="
-         : sig->keytype < 0 || sig->hashtype < 0 ? "a="
-         : "v="
-         );
-      goto NEXT_VERIFY;
-      }
-
-    /* Make sure sig uses supported DKIM version (only v1) */
-    if (sig->version != 1)
-      {
-      sig->verify_status     = PDKIM_VERIFY_INVALID;
-      sig->verify_ext_status = PDKIM_VERIFY_INVALID_DKIM_VERSION;
-
-      DEBUG(D_acl) debug_printf(
-          " Error in DKIM-Signature header: unsupported DKIM version\n"
-          "DKIM <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-      goto NEXT_VERIFY;
-      }
-
-    DEBUG(D_acl)
-      {
-      debug_printf( "DKIM [%s] b from mail: ", sig->domain);
-      debug_printf("%.*H\n", sig->sighash.len, sig->sighash.data);
-      }
-
-    if (!(sig->pubkey = pdkim_key_from_dns(ctx, sig, &vctx, err)))
-      {
-      log_write(0, LOG_MAIN, "DKIM: %s%s %s%s [failed key import]",
-       sig->domain   ? "d=" : "", sig->domain   ? sig->domain   : US"",
-       sig->selector ? "s=" : "", sig->selector ? sig->selector : US"");
-      goto NEXT_VERIFY;
-      }
-
-    /* If the pubkey limits to a list of specific hashes, ignore sigs that
-    do not have the hash part of the sig algorithm matching */
-
-    if (sig->pubkey->hashes)
-      {
-      const uschar * list = sig->pubkey->hashes, * ele;
-      int sep = ':';
-      while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
-       if (Ustrcmp(ele, pdkim_hashes[sig->hashtype].dkim_hashname) == 0) break;
-      if (!ele)
-       {
-       DEBUG(D_acl) debug_printf("pubkey h=%s vs. sig a=%s_%s\n",
-         sig->pubkey->hashes,
-         pdkim_keytypes[sig->keytype],
-         pdkim_hashes[sig->hashtype].dkim_hashname);
-       sig->verify_status =      PDKIM_VERIFY_FAIL;
-       sig->verify_ext_status =  PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH;
-       goto NEXT_VERIFY;
-       }
-      }
-
-    hm = sig->keytype == KEYTYPE_ED25519
-#if defined(SIGN_OPENSSL)
-      ? HASH_NULL
-#else
-      ? HASH_SHA2_512
-#endif
-      : pdkim_hashes[sig->hashtype].exim_hashmethod;
-
-    /* Check the signature */
-
-    if ((*err = exim_dkim_verify(&vctx, hm, &hhash, &sig->sighash)))
-      {
-      DEBUG(D_acl) debug_printf("headers verify: %s\n", *err);
-      sig->verify_status =      PDKIM_VERIFY_FAIL;
-      sig->verify_ext_status =  PDKIM_VERIFY_FAIL_MESSAGE;
-      goto NEXT_VERIFY;
-      }
-    if (*dkim_verify_min_keysizes)
-      {
-      unsigned minbits;
-      const uschar * ss = expand_getkeyed(US pdkim_keytypes[sig->keytype],
-                                   dkim_verify_min_keysizes);
-      if (ss &&  (minbits = atoi(CCS ss)) > sig->keybits)
-       {
-       DEBUG(D_acl) debug_printf("Key too short: Actual: %s %u  Minima '%s'\n",
-         pdkim_keytypes[sig->keytype], sig->keybits, dkim_verify_min_keysizes);
-       sig->verify_status =      PDKIM_VERIFY_FAIL;
-       sig->verify_ext_status =  PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE;
-       }
-      }
-
-
-    /* We have a winner! (if bodyhash was correct earlier) */
-    if (sig->verify_status == PDKIM_VERIFY_NONE)
-      {
-      sig->verify_status = PDKIM_VERIFY_PASS;
-      verify_pass = TRUE;
-      /*XXX We used to "break" here if dkim_verify_minimal, but that didn't
-      stop the ACL being called.  So move that test.  Unfortunately, we
-      need to eval all the sigs here only to possibly ignore some later,
-      because we don't know what verify options might say.
-      Could we change to a later eval of the sig?
-      Both bits are called from receive_msg().
-      Moving the test is also suboptimal for the case of no ACL (or no
-      signers to check!) so keep it for that case, but after debug output */
-      }
-
-NEXT_VERIFY:
-    DEBUG(D_acl)
-      {
-      debug_printf("DKIM [%s] %s signature status: %s",
-             sig->domain, dkim_sig_to_a_tag(sig),
-             pdkim_verify_status_str(sig->verify_status));
-      if (sig->verify_ext_status > 0)
-       debug_printf(" (%s)\n",
-               pdkim_verify_ext_status_str(sig->verify_ext_status));
-      else
-       debug_printf("\n");
-      }
-
-    if (  verify_pass && dkim_verify_minimal
-       && !(acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers))
-      break;
-    }
-  }
-
-/* If requested, set return pointer to signature(s) */
-if (return_signatures)
-  *return_signatures = ctx->sig;
-
-return ctx->flags & PDKIM_MODE_SIGN  ||  verify_pass
-  ? PDKIM_OK : PDKIM_FAIL;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-DLLEXPORT pdkim_ctx *
-pdkim_init_verify(uschar * (*dns_txt_callback)(const uschar *), BOOL dot_stuffing)
-{
-pdkim_ctx * ctx;
-
-ctx = store_get(sizeof(pdkim_ctx), GET_UNTAINTED);
-memset(ctx, 0, sizeof(pdkim_ctx));
-
-if (dot_stuffing) ctx->flags = PDKIM_DOT_TERM;
-/* The line-buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
-ctx->dns_txt_callback = dns_txt_callback;
-ctx->cur_header = string_get_tainted(36, GET_TAINTED);
-
-return ctx;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-DLLEXPORT pdkim_signature *
-pdkim_init_sign(pdkim_ctx * ctx,
-  uschar * domain, uschar * selector, uschar * privkey,
-  uschar * hashname, const uschar ** errstr)
-{
-int hashtype;
-pdkim_signature * sig;
-
-if (!domain || !selector || !privkey)
-  return NULL;
-
-/* Allocate & init one signature struct */
-
-sig = store_get(sizeof(pdkim_signature), GET_UNTAINTED);
-memset(sig, 0, sizeof(pdkim_signature));
-
-sig->bodylength = -1;
-
-sig->domain = string_copy(US domain);
-sig->selector = string_copy(US selector);
-sig->privkey = string_copy(US privkey);
-sig->keytype = -1;
-
-for (hashtype = 0; hashtype < nelem(pdkim_hashes); hashtype++)
-  if (Ustrcmp(hashname, pdkim_hashes[hashtype].dkim_hashname) == 0)
-  { sig->hashtype = hashtype; break; }
-if (hashtype >= nelem(pdkim_hashes))
-  {
-  log_write(0, LOG_MAIN|LOG_PANIC,
-    "DKIM: unrecognised hashname '%s'", hashname);
-  return NULL;
-  }
-
-DEBUG(D_acl)
-  {
-  pdkim_signature s = *sig;
-  ev_ctx vctx;
-
-  debug_printf("DKIM (checking verify key)>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
-  if (!pdkim_key_from_dns(ctx, &s, &vctx, errstr))
-    debug_printf("WARNING: bad dkim key in dns\n");
-  debug_printf("DKIM (finished checking verify key)<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
-  }
-return sig;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-DLLEXPORT void
-pdkim_set_optional(pdkim_signature * sig,
-                       char * sign_headers,
-                       char * identity,
-                       int canon_headers,
-                       int canon_body,
-                       long bodylength,
-                       unsigned long created,
-                       unsigned long expires)
-{
-if (identity)
-  sig->identity = string_copy(US identity);
-
-sig->sign_headers = string_copy(sign_headers
-       ? US sign_headers : US PDKIM_DEFAULT_SIGN_HEADERS);
-
-sig->canon_headers = canon_headers;
-sig->canon_body = canon_body;
-sig->bodylength = bodylength;
-sig->created = created;
-sig->expires = expires;
-
-return;
-}
-
-
-
-/* Set up a blob for calculating the bodyhash according to the
-given needs.  Use an existing one if possible, or create a new one.
-
-Return: hashblob pointer, or NULL on error
-*/
-pdkim_bodyhash *
-pdkim_set_bodyhash(pdkim_ctx * ctx, int hashtype, int canon_method,
-       long bodylength)
-{
-pdkim_bodyhash * b;
-
-if (hashtype == -1 || canon_method == -1) return NULL;
-
-for (b = ctx->bodyhash; b; b = b->next)
-  if (  hashtype == b->hashtype
-     && canon_method == b->canon_method
-     && bodylength == b->bodylength)
-    {
-    DEBUG(D_receive) debug_printf("DKIM: using existing bodyhash %s/%s/%ld\n",
-      pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
-    return b;
-    }
-
-DEBUG(D_receive) debug_printf("DKIM: new bodyhash %s/%s/%ld\n",
-    pdkim_hashes[hashtype].dkim_hashname, pdkim_canons[canon_method], bodylength);
-b = store_get(sizeof(pdkim_bodyhash), GET_UNTAINTED);
-b->next = ctx->bodyhash;
-b->hashtype = hashtype;
-b->canon_method = canon_method;
-b->bodylength = bodylength;
-if (!exim_sha_init(&b->body_hash_ctx,          /*XXX hash method: extend for sha512 */
-                 pdkim_hashes[hashtype].exim_hashmethod))
-  {
-  DEBUG(D_acl)
-    debug_printf("DKIM: hash init error, possibly nonhandled hashtype\n");
-  return NULL;
-  }
-b->signed_body_bytes = 0;
-b->num_buffered_blanklines = 0;
-ctx->bodyhash = b;
-return b;
-}
-
-
-/* Set up a blob for calculating the bodyhash according to the
-needs of this signature.  Use an existing one if possible, or
-create a new one.
-
-Return: hashblob pointer, or NULL on error (only used as a boolean).
-*/
-pdkim_bodyhash *
-pdkim_set_sig_bodyhash(pdkim_ctx * ctx, pdkim_signature * sig)
-{
-pdkim_bodyhash * b = pdkim_set_bodyhash(ctx,
-                       sig->hashtype, sig->canon_body, sig->bodylength);
-sig->calc_body_hash = b;
-return b;
-}
-
-
-/* -------------------------------------------------------------------------- */
-
-
-void
-pdkim_init_context(pdkim_ctx * ctx, BOOL dot_stuffed,
-  uschar * (*dns_txt_callback)(const uschar *))
-{
-memset(ctx, 0, sizeof(pdkim_ctx));
-ctx->flags = dot_stuffed ? PDKIM_MODE_SIGN | PDKIM_DOT_TERM : PDKIM_MODE_SIGN;
-/* The line buffer is for message data, hence tainted */
-ctx->linebuf = store_get(PDKIM_MAX_BODY_LINE_LEN, GET_TAINTED);
-DEBUG(D_acl) ctx->dns_txt_callback = dns_txt_callback;
-}
-
-
-void
-pdkim_init(void)
-{
-exim_dkim_init();
-}
-
-
-
-#endif /*DISABLE_DKIM*/
diff --git a/src/src/pdkim/pdkim.h b/src/src/pdkim/pdkim.h
deleted file mode 100644 (file)
index 5f91d3b..0000000
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *
- *  Copyright (C) 2009 - 2012  Tom Kistner <tom@duncanthrax.net>
- *  Copyright (c) 2016 - 2020  Jeremy Harris
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *
- *  http://duncanthrax.net/pdkim/
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License along
- *  with this program; if not, write to the Free Software Foundation, Inc.,
- *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-#ifndef PDKIM_H
-#define PDKIM_H
-
-#include "../blob.h"
-#include "../hash.h"
-
-#define PDKIM_DEFAULT_SIGN_HEADERS "From:Sender:Reply-To:Subject:Date:"\
-                             "Message-ID:To:Cc:MIME-Version:Content-Type:"\
-                             "Content-Transfer-Encoding:Content-ID:"\
-                             "Content-Description:Resent-Date:Resent-From:"\
-                             "Resent-Sender:Resent-To:Resent-Cc:"\
-                             "Resent-Message-ID:In-Reply-To:References:"\
-                             "List-Id:List-Help:List-Unsubscribe:"\
-                             "List-Subscribe:List-Post:List-Owner:List-Archive"
-
-#define PDKIM_OVERSIGN_HEADERS "+From:+Sender:+Reply-To:+Subject:+Date:"\
-                             "+Message-ID:+To:+Cc:+MIME-Version:+Content-Type:"\
-                             "+Content-Transfer-Encoding:+Content-ID:"\
-                             "+Content-Description:+Resent-Date:+Resent-From:"\
-                             "+Resent-Sender:+Resent-To:+Resent-Cc:"\
-                             "+Resent-Message-ID:+In-Reply-To:+References:"\
-                             "+List-Id:+List-Help:+List-Unsubscribe:"\
-                             "+List-Subscribe:+List-Post:+List-Owner:+List-Archive"
-
-/* -------------------------------------------------------------------------- */
-/* Length of the preallocated buffer for the "answer" from the dns/txt
-   callback function. This should match the maximum RDLENGTH from DNS. */
-#define PDKIM_DNS_TXT_MAX_RECLEN    (1 << 16)
-
-/* -------------------------------------------------------------------------- */
-/* Function success / error codes */
-#define PDKIM_OK                      0
-#define PDKIM_FAIL                   -1
-#define PDKIM_ERR_RSA_PRIVKEY      -101
-#define PDKIM_ERR_RSA_SIGNING      -102
-#define PDKIM_ERR_LONG_LINE        -103
-#define PDKIM_ERR_BUFFER_TOO_SMALL -104
-#define PDKIM_ERR_EXCESS_SIGS     -105
-#define PDKIM_SIGN_PRIVKEY_WRAP    -106
-#define PDKIM_SIGN_PRIVKEY_B64D    -107
-
-/* -------------------------------------------------------------------------- */
-/* Main/Extended verification status */
-#define PDKIM_VERIFY_NONE      0
-#define PDKIM_VERIFY_INVALID   1
-#define PDKIM_VERIFY_FAIL      2
-#define PDKIM_VERIFY_PASS      3
-#define PDKIM_VERIFY_POLICY    BIT(31)
-
-#define PDKIM_VERIFY_FAIL_BODY                    1
-#define PDKIM_VERIFY_FAIL_MESSAGE                 2
-#define PDKIM_VERIFY_FAIL_SIG_ALGO_MISMATCH      3
-#define PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE   4
-#define PDKIM_VERIFY_INVALID_BUFFER_SIZE          5
-#define PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD     6
-#define PDKIM_VERIFY_INVALID_PUBKEY_IMPORT        7
-#define PDKIM_VERIFY_INVALID_PUBKEY_KEYSIZE       8
-#define PDKIM_VERIFY_INVALID_SIGNATURE_ERROR      9
-#define PDKIM_VERIFY_INVALID_DKIM_VERSION         10
-
-/* -------------------------------------------------------------------------- */
-/* Some parameter values */
-#define PDKIM_QUERYMETHOD_DNS_TXT 0
-
-#define PDKIM_CANON_SIMPLE        0
-#define PDKIM_CANON_RELAXED       1
-
-/* -------------------------------------------------------------------------- */
-/* Some required forward declarations, please ignore */
-typedef struct pdkim_stringlist pdkim_stringlist;
-typedef struct pdkim_str pdkim_str;
-typedef struct sha1_context sha1_context;
-typedef struct sha2_context sha2_context;
-#define HAVE_SHA1_CONTEXT
-#define HAVE_SHA2_CONTEXT
-
-/* -------------------------------------------------------------------------- */
-/* Some concessions towards Redmond */
-#ifdef WINDOWS
-#define snprintf _snprintf
-#define strcasecmp _stricmp
-#define strncasecmp _strnicmp
-#define DLLEXPORT __declspec(dllexport)
-#else
-#define DLLEXPORT
-#endif
-
-
-/* -------------------------------------------------------------------------- */
-/* Public key as (usually) fetched from DNS */
-typedef struct pdkim_pubkey {
-  const uschar * version;         /* v=  */
-  const uschar *granularity;      /* g=  */
-
-  const uschar * hashes;          /* h=  */
-  const uschar * keytype;         /* k=  */
-  const uschar * srvtype;         /* s=  */
-  uschar *notes;                  /* n=  */
-
-  blob  key;                      /* p=  */
-  int   testing;                  /* t=y */
-  int   no_subdomaining;          /* t=s */
-} pdkim_pubkey;
-
-/* -------------------------------------------------------------------------- */
-/* Body-hash to be calculated */
-typedef struct pdkim_bodyhash {
-  struct pdkim_bodyhash *      next;
-  int                          hashtype;
-  int                          canon_method;
-  long                         bodylength;
-
-  hctx                                 body_hash_ctx;
-  unsigned long                        signed_body_bytes;      /* done so far */
-  int                          num_buffered_blanklines;
-
-  blob                         bh;                     /* completed hash */
-} pdkim_bodyhash;
-
-/* -------------------------------------------------------------------------- */
-/* Signature as it appears in a DKIM-Signature header */
-typedef struct pdkim_signature {
-  struct pdkim_signature * next;
-
-  /* Bits stored in a DKIM signature header --------------------------- */
-
-  /* (v=) The version, as an integer. Currently, always "1" */
-  int version;
-
-  /* (a=) The signature algorithm. */
-  int          keytype;        /* pdkim_keytypes index */
-  unsigned     keybits;        /* size of the key */
-  int          hashtype;       /* pdkim_hashes index */
-
-  /* (c=x/) Header canonicalization method. Either PDKIM_CANON_SIMPLE
-     or PDKIM_CANON_RELAXED */
-  int canon_headers;
-
-  /* (c=/x) Body canonicalization method. Either PDKIM_CANON_SIMPLE
-     or PDKIM_CANON_RELAXED */
-  int canon_body;
-
-  /* (q=) Query Method. Currently, only PDKIM_QUERYMETHOD_DNS_TXT
-     is specified */
-  int querymethod;
-
-  /* (s=) The selector string as given in the signature */
-  uschar *selector;
-
-  /* (d=) The domain as given in the signature */
-  uschar *domain;
-
-  /* (i=) The identity as given in the signature */
-  uschar *identity;
-
-  /* (t=) Timestamp of signature creation */
-  unsigned long created;
-
-  /* (x=) Timestamp of expiry of signature */
-  unsigned long expires;
-
-  /* (l=) Amount of hashed body bytes (after canonicalization). Default
-     is -1. Note: a value of 0 means that the body is unsigned! */
-  long bodylength;
-
-  /* (h=) Colon-separated list of header names that are included in the
-     signature */
-  uschar *headernames;
-
-  /* (z=) */
-  uschar *copiedheaders;
-
-  /* (b=) Raw signature data, along with its length in bytes */
-  blob sighash;
-
-  /* (bh=) Raw body hash data, along with its length in bytes */
-  blob bodyhash;
-
-  /* Folded DKIM-Signature: header. Signing only, NULL for verifying.
-     Ready for insertion into the message. Note: Folded using CRLFTB,
-     but final line terminator is NOT included. Note2: This buffer is
-     free()d when you call pdkim_free_ctx(). */
-  uschar *signature_header;
-
-  /* The main verification status. Verification only. One of:
-
-     PDKIM_VERIFY_NONE      Verification was not attempted. This status
-                            should not appear.
-
-     PDKIM_VERIFY_INVALID   There was an error while trying to verify
-                            the signature. A more precise description
-                            is available in verify_ext_status.
-
-     PDKIM_VERIFY_FAIL      Verification failed because either the body
-                            hash did not match, or the signature verification
-                            failed. This means the message was modified.
-                            Check verify_ext_status for the exact reason.
-
-     PDKIM_VERIFY_PASS      Verification succeeded.
-  */
-  int verify_status;
-
-  /* Extended verification status. Verification only. Depending on the value
-     of verify_status, it can contain:
-
-     For verify_status == PDKIM_VERIFY_INVALID:
-
-        PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE
-          Unable to retrieve a public key container.
-
-        PDKIM_VERIFY_INVALID_BUFFER_SIZE
-          Either the DNS name constructed to retrieve the public key record
-          does not fit into PDKIM_DNS_TXT_MAX_NAMELEN bytes, or the retrieved
-          record is longer than PDKIM_DNS_TXT_MAX_RECLEN bytes.
-
-        PDKIM_VERIFY_INVALID_PUBKEY_PARSING
-          (Syntax) error while parsing the retrieved public key record.
-
-
-     For verify_status == PDKIM_VERIFY_FAIL:
-
-        PDKIM_VERIFY_FAIL_BODY
-          The calculated body hash does not match the advertised body hash
-          from the bh= tag of the signature.
-
-        PDKIM_VERIFY_FAIL_MESSAGE
-          RSA verification of the signature (b= tag) failed.
-  */
-  int verify_ext_status;
-
-  /* Pointer to a public key record that was used to verify the signature.
-     See pdkim_pubkey declaration above for more information.
-     Caution: is NULL if signing or if no record was retrieved. */
-  pdkim_pubkey *pubkey;
-
-  /* Properties below this point are used internally only ------------- */
-
-  /* Per-signature helper variables ----------------------------------- */
-  pdkim_bodyhash *calc_body_hash;      /* hash to be / being calculated */
-
-  pdkim_stringlist *headers;           /* Raw headers included in the sig */
-
-  /* Signing specific ------------------------------------------------- */
-  uschar * privkey;         /* Private key                                 */
-  uschar * sign_headers;    /* To-be-signed header names                   */
-  uschar * rawsig_no_b_val; /* Original signature header w/o b= tag value. */
-} pdkim_signature;
-
-
-/* -------------------------------------------------------------------------- */
-/* Context to keep state between all operations. */
-typedef struct pdkim_ctx {
-
-#define PDKIM_MODE_SIGN   BIT(0)       /* if unset, mode==verify */
-#define PDKIM_DOT_TERM   BIT(1)        /* dot termination and unstuffing */
-#define PDKIM_SEEN_CR    BIT(2)
-#define PDKIM_SEEN_LF    BIT(3)
-#define PDKIM_PAST_HDRS          BIT(4)
-#define PDKIM_SEEN_EOD   BIT(5)
-  unsigned   flags;
-
-  /* One (signing) or several chained (verification) signatures */
-  pdkim_signature *sig;
-
-  /* One (signing) or several chained (verification) bodyhashes */
-  pdkim_bodyhash *bodyhash;
-
-  /* Callback for dns/txt query method (verification only) */
-  uschar * (*dns_txt_callback)(const uschar *);
-
-  /* Coder's little helpers */
-  gstring   *cur_header;
-  uschar    *linebuf;
-  int        linebuf_offset;
-  int        num_headers;
-  pdkim_stringlist *headers; /* Raw headers for verification         */
-} pdkim_ctx;
-
-
-/******************************************************************************/
-
-typedef struct {
-  const uschar * dkim_hashname;
-  hashmethod    exim_hashmethod;
-} pdkim_hashtype;
-extern const pdkim_hashtype pdkim_hashes[];
-
-/******************************************************************************/
-
-
-/* -------------------------------------------------------------------------- */
-/* API functions. Please see the sample code in sample/test_sign.c and
-   sample/test_verify.c for documentation.
-*/
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void      pdkim_init         (void);
-
-void      pdkim_init_context (pdkim_ctx *, BOOL, uschar * (*)(const uschar *));
-
-DLLEXPORT
-pdkim_signature *pdkim_init_sign    (pdkim_ctx *,
-                              uschar *, uschar *, uschar *, uschar *,
-                              const uschar **);
-
-DLLEXPORT
-pdkim_ctx *pdkim_init_verify  (uschar * (*)(const uschar *), BOOL);
-
-DLLEXPORT
-void       pdkim_set_optional (pdkim_signature *, char *, char *,int, int,
-                               long,
-                               unsigned long,
-                               unsigned long);
-
-int            pdkim_hashname_to_hashtype(const uschar *, unsigned);
-void           pdkim_cstring_to_canons(const uschar *, unsigned, int *, int *);
-pdkim_bodyhash *pdkim_set_bodyhash(pdkim_ctx *, int, int, long);
-pdkim_bodyhash *pdkim_set_sig_bodyhash(pdkim_ctx *, pdkim_signature *);
-
-DLLEXPORT
-int        pdkim_feed         (pdkim_ctx *, uschar *, int);
-DLLEXPORT
-int        pdkim_feed_finish  (pdkim_ctx *, pdkim_signature **, const uschar **);
-
-DLLEXPORT
-void       pdkim_free_ctx     (pdkim_ctx *);
-
-
-const uschar * pdkim_errstr(int);
-
-extern uschar *                pdkim_encode_base64(blob *);
-extern void            pdkim_decode_base64(const uschar *, blob *);
-extern pdkim_pubkey *  pdkim_parse_pubkey_record(const uschar *);
-extern uschar *                pdkim_relax_header_n(const uschar *, int, BOOL);
-extern uschar *                pdkim_relax_header(const uschar *, BOOL);
-extern uschar *                dkim_sig_to_a_tag(const pdkim_signature *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/src/src/pdkim/pdkim_hash.h b/src/src/pdkim/pdkim_hash.h
deleted file mode 100644 (file)
index d56e3ce..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *
- *  Copyright (C) 1995 - 2018  Exim maintainers
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *
- *  Hash interface functions
- */
-
-#include "../exim.h"
-
-#if !defined(HASH_H)   /* entire file */
-#define HASH_H
-
-#ifdef DISABLE_TLS
-# error Must not DISABLE_TLS, for DKIM
-#endif
-
-#include "crypt_ver.h"
-#include "../blob.h"
-#include "../hash.h"
-
-#ifdef SIGN_OPENSSL
-# include <openssl/rsa.h>
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#elif defined(SIGN_GNUTLS)
-# include <gnutls/gnutls.h>
-# include <gnutls/x509.h>
-#endif
-
-#if defined(SHA_OPENSSL)
-# include "pdkim.h"
-#elif defined(SHA_GCRYPT)
-# include "pdkim.h"
-#endif
-
-#endif
-/* End of File */
diff --git a/src/src/pdkim/signing.c b/src/src/pdkim/signing.c
deleted file mode 100644 (file)
index b564fb9..0000000
+++ /dev/null
@@ -1,904 +0,0 @@
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *  Copyright (c) The Exim Maintainers 1995 - 2024
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *
- *  signing/verification interface
- */
-
-#include "../exim.h"
-#include "crypt_ver.h"
-#include "signing.h"
-
-
-#ifdef MACRO_PREDEF
-# include "../macro_predef.h"
-
-void
-features_crypto(void)
-{
-# ifdef SIGN_HAVE_ED25519
-  builtin_macro_create(US"_CRYPTO_SIGN_ED25519");
-# endif
-# ifdef EXIM_HAVE_SHA3
-  builtin_macro_create(US"_CRYPTO_HASH_SHA3");
-# endif
-}
-#else
-
-#ifndef DISABLE_DKIM   /* rest of file */
-
-#ifdef DISABLE_TLS
-# error Must no DISABLE_TLS, for DKIM
-#endif
-
-
-/******************************************************************************/
-#ifdef SIGN_GNUTLS
-# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL 3
-
-# ifndef GNUTLS_VERIFY_ALLOW_BROKEN
-#  define GNUTLS_VERIFY_ALLOW_BROKEN 0
-# endif
-
-
-/* Logging function which can be registered with
- *   gnutls_global_set_log_function()
- *   gnutls_global_set_log_level() 0..9
- */
-#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
-static void
-exim_gnutls_logger_cb(int level, const char *message)
-{
-size_t len = strlen(message);
-if (len < 1)
-  {
-  DEBUG(D_tls) debug_printf("GnuTLS<%d> empty debug message\n", level);
-  return;
-  }
-DEBUG(D_tls) debug_printf("GnuTLS<%d>: %s%s", level, message,
-    message[len-1] == '\n' ? "" : "\n");
-}
-#endif
-
-
-
-void
-exim_dkim_init(void)
-{
-#if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0
-DEBUG(D_tls)
-  {
-  gnutls_global_set_log_function(exim_gnutls_logger_cb);
-  /* arbitrarily chosen level; bump upto 9 for more */
-  gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
-  }
-#endif
-}
-
-
-/* accumulate data (gnutls-only).  String to be appended must be nul-terminated. */
-gstring *
-exim_dkim_data_append(gstring * g, uschar * s)
-{
-return string_cat(g, s);
-}
-
-
-
-/* import private key from PEM string in memory.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
-{
-gnutls_datum_t k = { .data = (void *)privkey_pem, .size = Ustrlen(privkey_pem) };
-gnutls_x509_privkey_t x509_key;
-const uschar * where;
-int rc;
-
-if (  (where = US"internal init", rc = gnutls_x509_privkey_init(&x509_key))
-   || (rc = gnutls_privkey_init(&sign_ctx->key))
-   || (where = US"privkey PEM-block import",
-       rc = gnutls_x509_privkey_import(x509_key, &k, GNUTLS_X509_FMT_PEM))
-   || (where = US"internal privkey transfer",
-       rc = gnutls_privkey_import_x509(sign_ctx->key, x509_key, 0))
-   )
-  return string_sprintf("%s: %s", where, gnutls_strerror(rc));
-
-switch (rc = gnutls_privkey_get_pk_algorithm(sign_ctx->key, NULL))
-  {
-  case GNUTLS_PK_RSA:          sign_ctx->keytype = KEYTYPE_RSA;     break;
-#ifdef SIGN_HAVE_ED25519
-  case GNUTLS_PK_EDDSA_ED25519:        sign_ctx->keytype = KEYTYPE_ED25519; break;
-#endif
-  default: return rc < 0
-    ? CUS gnutls_strerror(rc)
-    : string_sprintf("Unhandled key type: %d '%s'", rc, gnutls_pk_get_name(rc));
-  }
-
-return NULL;
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* hash & sign data.   No way to do incremental.
-
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
-{
-gnutls_datum_t k_data = { .data = data->data, .size = data->len };
-gnutls_digest_algorithm_t dig;
-gnutls_datum_t k_sig;
-int rc;
-
-switch (hash)
-  {
-  case HASH_SHA1:      dig = GNUTLS_DIG_SHA1; break;
-  case HASH_SHA2_256:  dig = GNUTLS_DIG_SHA256; break;
-  case HASH_SHA2_512:  dig = GNUTLS_DIG_SHA512; break;
-  default:             return US"nonhandled hash type";
-  }
-
-if ((rc = gnutls_privkey_sign_data(sign_ctx->key, dig, 0, &k_data, &k_sig)))
-  return CUS gnutls_strerror(rc);
-
-/* Don't care about deinit for the key; shortlived process */
-
-sig->data = k_sig.data;
-sig->len = k_sig.size;
-return NULL;
-}
-
-
-
-/* import public key (from blob in memory)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
-  unsigned * bits)
-{
-gnutls_datum_t k;
-int rc;
-const uschar * ret = NULL;
-
-gnutls_pubkey_init(&verify_ctx->key);
-k.data = pubkey->data;
-k.size = pubkey->len;
-
-switch(fmt)
-  {
-  case KEYFMT_DER:
-    if ((rc = gnutls_pubkey_import(verify_ctx->key, &k, GNUTLS_X509_FMT_DER)))
-      ret = US gnutls_strerror(rc);
-    break;
-#ifdef SIGN_HAVE_ED25519
-  case KEYFMT_ED25519_BARE:
-    if ((rc = gnutls_pubkey_import_ecc_raw(verify_ctx->key,
-                                         GNUTLS_ECC_CURVE_ED25519, &k, NULL)))
-      ret = US gnutls_strerror(rc);
-    break;
-#endif
-  default:
-    ret = US"pubkey format not handled";
-    break;
-  }
-if (!ret && bits) gnutls_pubkey_get_pk_algorithm(verify_ctx->key, bits);
-return ret;
-}
-
-
-/* verify signature (of hash if RSA sig, of data if EC sig.  No way to do incremental)
-(given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
-{
-gnutls_datum_t k = { .data = data_hash->data, .size = data_hash->len };
-gnutls_datum_t s = { .data = sig->data,       .size = sig->len };
-int rc;
-const uschar * ret = NULL;
-
-#ifdef SIGN_HAVE_ED25519
-if (verify_ctx->keytype == KEYTYPE_ED25519)
-  {
-  if ((rc = gnutls_pubkey_verify_data2(verify_ctx->key,
-                                     GNUTLS_SIGN_EDDSA_ED25519, 0, &k, &s)) < 0)
-    ret = US gnutls_strerror(rc);
-  }
-else
-#endif
-  {
-  gnutls_sign_algorithm_t algo;
-  switch (hash)
-    {
-    case HASH_SHA1:    algo = GNUTLS_SIGN_RSA_SHA1;   break;
-    case HASH_SHA2_256:        algo = GNUTLS_SIGN_RSA_SHA256; break;
-    case HASH_SHA2_512:        algo = GNUTLS_SIGN_RSA_SHA512; break;
-    default:           return US"nonhandled hash type";
-    }
-
-  if ((rc = gnutls_pubkey_verify_hash2(verify_ctx->key, algo,
-             GNUTLS_VERIFY_ALLOW_BROKEN, &k, &s)) < 0)
-    ret = US gnutls_strerror(rc);
-  }
-
-gnutls_pubkey_deinit(verify_ctx->key);
-return ret;
-}
-
-
-
-
-#elif defined(SIGN_GCRYPT)
-/******************************************************************************/
-/* This variant is used under pre-3.0.0 GnuTLS.  Only rsa-sha1 and rsa-sha256 */
-
-
-/* Internal service routine:
-Read and move past an asn.1 header, checking class & tag,
-optionally returning the data-length */
-
-static int
-as_tag(blob * der, uschar req_cls, long req_tag, long * alen)
-{
-int rc;
-uschar tag_class;
-int taglen;
-long tag, len;
-
-debug_printf_indent("as_tag: %02x %02x %02x %02x\n",
-       der->data[0], der->data[1], der->data[2], der->data[3]);
-
-if ((rc = asn1_get_tag_der(der->data++, der->len--, &tag_class, &taglen, &tag))
-    != ASN1_SUCCESS)
-  return rc;
-
-if (tag_class != req_cls || tag != req_tag) return ASN1_ELEMENT_NOT_FOUND;
-
-if ((len = asn1_get_length_der(der->data, der->len, &taglen)) < 0)
-  return ASN1_DER_ERROR;
-if (alen) *alen = len;
-
-/* debug_printf_indent("as_tag:  tlen %d dlen %d\n", taglen, (int)len); */
-
-der->data += taglen;
-der->len -= taglen;
-return rc;
-}
-
-/* Internal service routine:
-Read and move over an asn.1 integer, setting an MPI to the value
-*/
-
-static uschar *
-as_mpi(blob * der, gcry_mpi_t * mpi)
-{
-long alen;
-int rc;
-gcry_error_t gerr;
-
-debug_printf_indent("%s\n", __FUNCTION__);
-
-/* integer; move past the header */
-if ((rc = as_tag(der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
-  return US asn1_strerror(rc);
-
-/* read to an MPI */
-if ((gerr = gcry_mpi_scan(mpi, GCRYMPI_FMT_STD, der->data, alen, NULL)))
-  return US gcry_strerror(gerr);
-
-/* move over the data */
-der->data += alen; der->len -= alen;
-return NULL;
-}
-
-
-
-void
-exim_dkim_init(void)
-{
-/* Version check should be the very first call because it
-makes sure that important subsystems are initialized. */
-if (!gcry_check_version (GCRYPT_VERSION))
-  {
-  fputs ("libgcrypt version mismatch\n", stderr);
-  exim_exit(2);
-  }
-
-/* We don't want to see any warnings, e.g. because we have not yet
-parsed program options which might be used to suppress such
-warnings. */
-gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
-
-/* ... If required, other initialization goes here.  Note that the
-process might still be running with increased privileges and that
-the secure memory has not been initialized.  */
-
-/* Allocate a pool of 16k secure memory.  This make the secure memory
-available and also drops privileges where needed.  */
-gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
-
-/* It is now okay to let Libgcrypt complain when there was/is
-a problem with the secure memory. */
-gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
-
-/* ... If required, other initialization goes here.  */
-
-/* Tell Libgcrypt that initialization has completed. */
-gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
-
-return;
-}
-
-
-
-
-/* Accumulate data (gnutls-only).
-String to be appended must be nul-terminated. */
-
-gstring *
-exim_dkim_data_append(gstring * g, uschar * s)
-{
-return g;      /*dummy*/
-}
-
-
-
-/* import private key from PEM string in memory.
-Only handles RSA keys.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
-{
-uschar * s1, * s2;
-blob der;
-long alen;
-int rc;
-
-/*XXX will need extension to _spot_ as well as handle a
-non-RSA key?  I think...
-So... this is not a PrivateKeyInfo - which would have a field
-identifying the keytype - PrivateKeyAlgorithmIdentifier -
-but a plain RSAPrivateKey (wrapped in PEM-headers.  Can we
-use those as a type tag?  What forms are there?  "BEGIN EC PRIVATE KEY" (cf. ec(1ssl))
-
-How does OpenSSL PEM_read_bio_PrivateKey() deal with it?
-gnutls_x509_privkey_import() ?
-*/
-
-/*
- *  RSAPrivateKey ::= SEQUENCE
- *      version           Version,
- *      modulus           INTEGER,  -- n
- *      publicExponent    INTEGER,  -- e
- *      privateExponent   INTEGER,  -- d
- *      prime1            INTEGER,  -- p
- *      prime2            INTEGER,  -- q
- *      exponent1         INTEGER,  -- d mod (p-1)
- *      exponent2         INTEGER,  -- d mod (q-1)
- *      coefficient       INTEGER,  -- (inverse of q) mod p
- *      otherPrimeInfos   OtherPrimeInfos OPTIONAL
-
- * ECPrivateKey ::= SEQUENCE {
- *     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
- *     privateKey     OCTET STRING,
- *     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
- *     publicKey  [1] BIT STRING OPTIONAL
- *   }
- * Hmm, only 1 useful item, and not even an integer?  Wonder how we might use it...
-
-- actually, gnutls_x509_privkey_import() appears to require a curve name parameter
-       value for that is an OID? a local-only integer (it's an enum in GnuTLS)?
-
-
-Useful cmds:
-  ssh-keygen -t ecdsa -f foo.privkey
-  ssh-keygen -t ecdsa -b384 -f foo.privkey
-  ssh-keygen -t ecdsa -b521 -f foo.privkey
-  ssh-keygen -t ed25519 -f foo.privkey
-
-  < foo openssl pkcs8 -in /dev/stdin -inform PEM -nocrypt -topk8 -outform DER | od -x
-
-  openssl asn1parse -in foo -inform PEM -dump
-  openssl asn1parse -in foo -inform PEM -dump -stroffset 24    (??)
-(not good for ed25519)
-
- */
-
-if (  !(s1 = Ustrstr(CS privkey_pem, "-----BEGIN RSA PRIVATE KEY-----"))
-   || !(s2 = Ustrstr(CS (s1+=31),    "-----END RSA PRIVATE KEY-----" ))
-   )
-  return US"Bad PEM wrapper";
-
-*s2 = '\0';
-
-if ((rc = b64decode(s1, &der.data, s1) < 0))
-  return US"Bad PEM-DER b64 decode";
-der.len = rc;
-
-/* untangle asn.1 */
-
-/* sequence; just move past the header */
-if ((rc = as_tag(&der, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
-   != ASN1_SUCCESS) goto asn_err;
-
-/* integer version; move past the header, check is zero */
-if ((rc = as_tag(&der, 0, ASN1_TAG_INTEGER, &alen)) != ASN1_SUCCESS)
-  goto asn_err;
-if (alen != 1 || *der.data != 0)
-  return US"Bad version number";
-der.data++; der.len--;
-
-if (  (s1 = as_mpi(&der, &sign_ctx->n))
-   || (s1 = as_mpi(&der, &sign_ctx->e))
-   || (s1 = as_mpi(&der, &sign_ctx->d))
-   || (s1 = as_mpi(&der, &sign_ctx->p))
-   || (s1 = as_mpi(&der, &sign_ctx->q))
-   || (s1 = as_mpi(&der, &sign_ctx->dp))
-   || (s1 = as_mpi(&der, &sign_ctx->dq))
-   || (s1 = as_mpi(&der, &sign_ctx->qp))
-   )
-  return s1;
-
-#ifdef extreme_debug
-DEBUG(D_acl) debug_printf_indent("rsa_signing_init:\n");
-  {
-  uschar * s;
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->n);
-  debug_printf_indent(" N : %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->e);
-  debug_printf_indent(" E : %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->d);
-  debug_printf_indent(" D : %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->p);
-  debug_printf_indent(" P : %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->q);
-  debug_printf_indent(" Q : %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dp);
-  debug_printf_indent(" DP: %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->dq);
-  debug_printf_indent(" DQ: %s\n", s);
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, sign_ctx->qp);
-  debug_printf_indent(" QP: %s\n", s);
-  }
-#endif
-
-sign_ctx->keytype = KEYTYPE_RSA;
-return NULL;
-
-asn_err: return US asn1_strerror(rc);
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* sign already-hashed data.
-
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
-{
-char * sexp_hash;
-gcry_sexp_t s_hash = NULL, s_key = NULL, s_sig = NULL;
-gcry_mpi_t m_sig;
-uschar * errstr;
-gcry_error_t gerr;
-
-/*XXX will need extension for hash types (though, possibly, should
-be re-specced to not rehash but take an already-hashed value? Actually
-current impl looks WRONG - it _is_ given a hash so should not be
-re-hashing.  Has this been tested?
-
-Will need extension for non-RSA sugning algos. */
-
-switch (hash)
-  {
-  case HASH_SHA1:      sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
-  case HASH_SHA2_256:  sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
-  default:             return US"nonhandled hash type";
-  }
-
-#define SIGSPACE 128
-sig->data = store_get(SIGSPACE, GET_UNTAINTED);
-
-if (gcry_mpi_cmp (sign_ctx->p, sign_ctx->q) > 0)
-  {
-  gcry_mpi_swap (sign_ctx->p, sign_ctx->q);
-  gcry_mpi_invm (sign_ctx->qp, sign_ctx->p, sign_ctx->q);
-  }
-
-if (  (gerr = gcry_sexp_build (&s_key, NULL,
-               "(private-key (rsa (n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
-               sign_ctx->n, sign_ctx->e,
-               sign_ctx->d, sign_ctx->p,
-               sign_ctx->q, sign_ctx->qp))
-   || (gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
-               (int) data->len, CS data->data))
-   ||  (gerr = gcry_pk_sign (&s_sig, s_hash, s_key))
-   )
-  return US gcry_strerror(gerr);
-
-/* gcry_sexp_dump(s_sig); */
-
-if (  !(s_sig = gcry_sexp_find_token(s_sig, "s", 0))
-   )
-  return US"no sig result";
-
-m_sig = gcry_sexp_nth_mpi(s_sig, 1, GCRYMPI_FMT_USG);
-
-#ifdef extreme_debug
-DEBUG(D_acl)
-  {
-  uschar * s;
-  gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, m_sig);
-  debug_printf_indent(" SG: %s\n", s);
-  }
-#endif
-
-gerr = gcry_mpi_print(GCRYMPI_FMT_USG, sig->data, SIGSPACE, &sig->len, m_sig);
-if (gerr)
-  {
-  debug_printf_indent("signature conversion from MPI to buffer failed\n");
-  return US gcry_strerror(gerr);
-  }
-#undef SIGSPACE
-
-return NULL;
-}
-
-
-/* import public key (from blob in memory)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
-  unsigned * bits)
-{
-/*
-in code sequence per b81207d2bfa92 rsa_parse_public_key() and asn1_get_mpi()
-*/
-uschar tag_class;
-int taglen;
-long alen;
-unsigned nbits;
-int rc;
-uschar * errstr;
-gcry_error_t gerr;
-uschar * stage = US"S1";
-
-if (fmt != KEYFMT_DER) return US"pubkey format not handled";
-
-/*
-sequence
- sequence
-  OBJECT:rsaEncryption
-  NULL
- BIT STRING:RSAPublicKey
-  sequence
-   INTEGER:Public modulus
-   INTEGER:Public exponent
-
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout -outform DER | od -t x1 | head;
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump;
-openssl rsa -in aux-fixed/dkim/dkim.private -pubout | openssl asn1parse -dump -offset 22;
-*/
-
-/* sequence; just move past the header */
-if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
-   != ASN1_SUCCESS) goto asn_err;
-
-/* sequence; skip the entire thing */
-DEBUG(D_acl) stage = US"S2";
-if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, &alen))
-   != ASN1_SUCCESS) goto asn_err;
-pubkey->data += alen; pubkey->len -= alen;
-
-
-/* bitstring: limit range to size of bitstring;
-move over header + content wrapper */
-DEBUG(D_acl) stage = US"BS";
-if ((rc = as_tag(pubkey, 0, ASN1_TAG_BIT_STRING, &alen)) != ASN1_SUCCESS)
-  goto asn_err;
-pubkey->len = alen;
-pubkey->data++; pubkey->len--;
-
-/* sequence; just move past the header */
-DEBUG(D_acl) stage = US"S3";
-if ((rc = as_tag(pubkey, ASN1_CLASS_STRUCTURED, ASN1_TAG_SEQUENCE, NULL))
-   != ASN1_SUCCESS) goto asn_err;
-
-/* read two integers */
-DEBUG(D_acl) stage = US"MPI";
-nbits = pubkey->len;
-if ((errstr = as_mpi(pubkey, &verify_ctx->n))) return errstr;
-nbits = (nbits - pubkey->len) * 8;
-if ((errstr = as_mpi(pubkey, &verify_ctx->e))) return errstr;
-
-#ifdef extreme_debug
-DEBUG(D_acl) debug_printf_indent("rsa_verify_init:\n");
-       {
-       uschar * s;
-       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->n);
-       debug_printf_indent(" N : %s\n", s);
-       gcry_mpi_aprint (GCRYMPI_FMT_HEX, &s, NULL, verify_ctx->e);
-       debug_printf_indent(" E : %s\n", s);
-       }
-
-#endif
-if (bits) *bits = nbits;
-return NULL;
-
-asn_err:
-DEBUG(D_acl) return string_sprintf("%s: %s", stage, asn1_strerror(rc));
-            return US asn1_strerror(rc);
-}
-
-
-/* verify signature (of hash)
-XXX though we appear to be doing a hash, too!
-(given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data_hash, blob * sig)
-{
-/*
-cf. libgnutls 2.8.5 _wrap_gcry_pk_verify()
-*/
-char * sexp_hash;
-gcry_mpi_t m_sig;
-gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
-gcry_error_t gerr;
-uschar * stage;
-
-/*XXX needs extension for SHA512 */
-switch (hash)
-  {
-  case HASH_SHA1:     sexp_hash = "(data(flags pkcs1)(hash sha1 %b))"; break;
-  case HASH_SHA2_256: sexp_hash = "(data(flags pkcs1)(hash sha256 %b))"; break;
-  default:           return US"nonhandled hash type";
-  }
-
-if (  (stage = US"pkey sexp build",
-       gerr = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%m)(e%m)))",
-                       verify_ctx->n, verify_ctx->e))
-   || (stage = US"data sexp build",
-       gerr = gcry_sexp_build (&s_hash, NULL, sexp_hash,
-               (int) data_hash->len, CS data_hash->data))
-   || (stage = US"sig mpi scan",
-       gerr = gcry_mpi_scan(&m_sig, GCRYMPI_FMT_USG, sig->data, sig->len, NULL))
-   || (stage = US"sig sexp build",
-       gerr = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", m_sig))
-   || (stage = US"verify",
-       gerr = gcry_pk_verify (s_sig, s_hash, s_pkey))
-   )
-  {
-  DEBUG(D_acl) debug_printf_indent("verify: error in stage '%s'\n", stage);
-  return US gcry_strerror(gerr);
-  }
-
-if (s_sig) gcry_sexp_release (s_sig);
-if (s_hash) gcry_sexp_release (s_hash);
-if (s_pkey) gcry_sexp_release (s_pkey);
-gcry_mpi_release (m_sig);
-gcry_mpi_release (verify_ctx->n);
-gcry_mpi_release (verify_ctx->e);
-
-return NULL;
-}
-
-
-
-
-#elif defined(SIGN_OPENSSL)
-/******************************************************************************/
-
-void
-exim_dkim_init(void)
-{
-ERR_load_crypto_strings();
-}
-
-
-/* accumulate data (was gnutls-only but now needed for OpenSSL non-EC too
-because now using hash-and-sign interface) */
-gstring *
-exim_dkim_data_append(gstring * g, uschar * s)
-{
-return string_cat(g, s);
-}
-
-
-/* import private key from PEM string in memory.
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_signing_init(const uschar * privkey_pem, es_ctx * sign_ctx)
-{
-BIO * bp = BIO_new_mem_buf((void *)privkey_pem, -1);
-
-if (!(sign_ctx->key = PEM_read_bio_PrivateKey(bp, NULL, NULL, NULL)))
-  return string_sprintf("privkey PEM-block import: %s",
-                       ERR_error_string(ERR_get_error(), NULL));
-
-sign_ctx->keytype =
-#ifdef SIGN_HAVE_ED25519
-       EVP_PKEY_type(EVP_PKEY_id(sign_ctx->key)) == EVP_PKEY_ED25519
-         ? KEYTYPE_ED25519 : KEYTYPE_RSA;
-#else
-       KEYTYPE_RSA;
-#endif
-return NULL;
-}
-
-
-
-/* allocate mem for signature (when signing) */
-/* hash & sign data.  Incremental not supported.
-
-Return: NULL for success with the signaature in the sig blob, or an error string */
-
-const uschar *
-exim_dkim_sign(es_ctx * sign_ctx, hashmethod hash, blob * data, blob * sig)
-{
-const EVP_MD * md;
-EVP_MD_CTX * ctx;
-size_t siglen;
-
-switch (hash)
-  {
-  case HASH_NULL:      md = NULL;         break;       /* Ed25519 signing */
-  case HASH_SHA1:      md = EVP_sha1();   break;
-  case HASH_SHA2_256:  md = EVP_sha256(); break;
-  case HASH_SHA2_512:  md = EVP_sha512(); break;
-  default:             return US"nonhandled hash type";
-  }
-
-#ifdef SIGN_HAVE_ED25519
-if (  (ctx = EVP_MD_CTX_new())
-   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
-   && EVP_DigestSign(ctx, NULL, &siglen, NULL, 0) > 0
-   && (sig->data = store_get(siglen, GET_UNTAINTED))
-
-   /* Obtain the signature (slen could change here!) */
-   && EVP_DigestSign(ctx, sig->data, &siglen, data->data, data->len) > 0
-   )
-  {
-  EVP_MD_CTX_destroy(ctx);
-  sig->len = siglen;
-  return NULL;
-  }
-#else
-/*XXX renamed to EVP_MD_CTX_new() in 1.1.0 */
-if (  (ctx = EVP_MD_CTX_create())
-   && EVP_DigestSignInit(ctx, NULL, md, NULL, sign_ctx->key) > 0
-   && EVP_DigestSignUpdate(ctx, data->data, data->len) > 0
-   && EVP_DigestSignFinal(ctx, NULL, &siglen) > 0
-   && (sig->data = store_get(siglen, GET_UNTAINTED))
-   /* Obtain the signature (slen could change here!) */
-   && EVP_DigestSignFinal(ctx, sig->data, &siglen) > 0
-   )
-  {
-  EVP_MD_CTX_destroy(ctx);
-  sig->len = siglen;
-  return NULL;
-  }
-#endif
-
-if (ctx) EVP_MD_CTX_destroy(ctx);
-return US ERR_error_string(ERR_get_error(), NULL);
-}
-
-
-
-/* import public key (from blob in memory)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify_init(blob * pubkey, keyformat fmt, ev_ctx * verify_ctx,
-  unsigned * bits)
-{
-const uschar * s = pubkey->data;
-uschar * ret = NULL;
-
-switch(fmt)
-  {
-  case KEYFMT_DER:
-    /*XXX hmm, we never free this */
-    if (!(verify_ctx->key = d2i_PUBKEY(NULL, &s, pubkey->len)))
-      ret = US ERR_error_string(ERR_get_error(), NULL);
-    break;
-#ifdef SIGN_HAVE_ED25519
-  case KEYFMT_ED25519_BARE:
-    if (!(verify_ctx->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
-                                                       s, pubkey->len)))
-      ret = US ERR_error_string(ERR_get_error(), NULL);
-    break;
-#endif
-  default:
-    ret = US"pubkey format not handled";
-    break;
-  }
-
-if (!ret && bits) *bits = EVP_PKEY_bits(verify_ctx->key);
-return ret;
-}
-
-
-
-
-/* verify signature (of hash, except Ed25519 where of-data)
-(given pubkey & alleged sig)
-Return: NULL for success, or an error string */
-
-const uschar *
-exim_dkim_verify(ev_ctx * verify_ctx, hashmethod hash, blob * data, blob * sig)
-{
-const EVP_MD * md;
-
-switch (hash)
-  {
-  case HASH_NULL:      md = NULL;         break;
-  case HASH_SHA1:      md = EVP_sha1();   break;
-  case HASH_SHA2_256:  md = EVP_sha256(); break;
-  case HASH_SHA2_512:  md = EVP_sha512(); break;
-  default:             return US"nonhandled hash type";
-  }
-
-#ifdef SIGN_HAVE_ED25519
-if (!md)
-  {
-  EVP_MD_CTX * ctx;
-
-  if ((ctx = EVP_MD_CTX_new()))
-    {
-    if (  EVP_DigestVerifyInit(ctx, NULL, md, NULL, verify_ctx->key) > 0
-       && EVP_DigestVerify(ctx, sig->data, sig->len, data->data, data->len) > 0
-       )
-      { EVP_MD_CTX_free(ctx); return NULL; }
-    EVP_MD_CTX_free(ctx);
-    }
-  }
-else
-#endif
-  {
-  EVP_PKEY_CTX * ctx;
-
-  if ((ctx = EVP_PKEY_CTX_new(verify_ctx->key, NULL)))
-    {
-    if (  EVP_PKEY_verify_init(ctx) > 0
-       && EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) > 0
-       && EVP_PKEY_CTX_set_signature_md(ctx, md) > 0
-       && EVP_PKEY_verify(ctx, sig->data, sig->len,
-                                     data->data, data->len) == 1
-       )
-      { EVP_PKEY_CTX_free(ctx); return NULL; }
-    EVP_PKEY_CTX_free(ctx);
-
-    DEBUG(D_tls)
-      if (Ustrcmp(ERR_reason_error_string(ERR_peek_error()), "wrong signature length") == 0)
-       debug_printf("sig len (from msg hdr): %d, expected (from dns pubkey) %d\n",
-        (int) sig->len, EVP_PKEY_size(verify_ctx->key));
-    }
-  }
-
-return US ERR_error_string(ERR_get_error(), NULL);
-}
-
-
-
-#endif
-/******************************************************************************/
-
-#endif /*DISABLE_DKIM*/
-#endif /*MACRO_PREDEF*/
-/* End of File */
diff --git a/src/src/pdkim/signing.h b/src/src/pdkim/signing.h
deleted file mode 100644 (file)
index 7760ce7..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- *  PDKIM - a RFC4871 (DKIM) implementation
- *
- *  Copyright (C) 1995 - 2020  Exim maintainers
- *  SPDX-License-Identifier: GPL-2.0-or-later
- *
- *  RSA signing/verification interface
- */
-
-#include "../exim.h"
-
-#ifndef DISABLE_DKIM   /* entire file */
-
-#include "crypt_ver.h"
-
-#ifdef SIGN_OPENSSL
-# include <openssl/rsa.h>
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#elif defined(SIGN_GNUTLS)
-# include <gnutls/gnutls.h>
-# include <gnutls/x509.h>
-# include <gnutls/abstract.h>
-#elif defined(SIGN_GCRYPT)
-# include <gcrypt.h>
-# include <libtasn1.h>
-#endif
-
-#include "../blob.h"
-
-typedef enum {
-  KEYTYPE_RSA,
-  KEYTYPE_ED25519
-} keytype;
-
-typedef enum {
-  KEYFMT_DER,          /* an asn.1 structure */
-  KEYFMT_ED25519_BARE  /* just the key */
-} keyformat;
-
-
-#ifdef SIGN_OPENSSL
-
-typedef struct {
-  keytype      keytype;
-  EVP_PKEY *   key;
-} es_ctx;
-
-typedef struct {
-  keytype      keytype;
-  EVP_PKEY *   key;
-} ev_ctx;
-
-#elif defined(SIGN_GNUTLS)
-
-typedef struct {
-  keytype      keytype;
-  gnutls_privkey_t key;
-} es_ctx;
-
-typedef struct {
-  keytype      keytype;
-  gnutls_pubkey_t key;
-} ev_ctx;
-
-#elif defined(SIGN_GCRYPT)
-
-typedef struct {
-  keytype      keytype;
-  gcry_mpi_t n;
-  gcry_mpi_t e;
-  gcry_mpi_t d;
-  gcry_mpi_t p;
-  gcry_mpi_t q;
-  gcry_mpi_t dp;
-  gcry_mpi_t dq;
-  gcry_mpi_t qp;
-} es_ctx;
-
-typedef struct {
-  keytype      keytype;
-  gcry_mpi_t n;
-  gcry_mpi_t e;
-} ev_ctx;
-
-#endif
-
-
-extern void exim_dkim_init(void);
-extern gstring * exim_dkim_data_append(gstring *, uschar *);
-
-extern const uschar * exim_dkim_signing_init(const uschar *, es_ctx *);
-extern const uschar * exim_dkim_sign(es_ctx *, hashmethod, blob *, blob *);
-extern const uschar * exim_dkim_verify_init(blob *, keyformat, ev_ctx *, unsigned *);
-extern const uschar * exim_dkim_verify(ev_ctx *, hashmethod, blob *, blob *);
-
-#endif /*DISABLE_DKIM*/
-/* End of File */
index ae7073229a63a36b3d9605253f313f2f06d4917e..5aabd019456eae1a9232c1a1981cb23ea94c4938 100644 (file)
@@ -48,7 +48,7 @@ static optionlist optionlist_config[] = {
   { "acl_smtp_data_prdr",       opt_stringptr,   {&acl_smtp_data_prdr} },
 #endif
 #ifndef DISABLE_DKIM
-  { "acl_smtp_dkim",            opt_stringptr,   {&acl_smtp_dkim} },
+  { "acl_smtp_dkim",            opt_module,     {US"dkim"} },
 #endif
   { "acl_smtp_etrn",            opt_stringptr,   {&acl_smtp_etrn} },
   { "acl_smtp_expn",            opt_stringptr,   {&acl_smtp_expn} },
@@ -122,11 +122,11 @@ static optionlist optionlist_config[] = {
 #endif
   { "disable_ipv6",             opt_bool,        {&disable_ipv6} },
 #ifndef DISABLE_DKIM
-  { "dkim_verify_hashes",       opt_stringptr,   {&dkim_verify_hashes} },
-  { "dkim_verify_keytypes",     opt_stringptr,   {&dkim_verify_keytypes} },
-  { "dkim_verify_min_keysizes", opt_stringptr,   {&dkim_verify_min_keysizes} },
-  { "dkim_verify_minimal",      opt_bool,        {&dkim_verify_minimal} },
-  { "dkim_verify_signers",      opt_stringptr,   {&dkim_verify_signers} },
+  { "dkim_verify_hashes",       opt_module,     {US"dkim"} },
+  { "dkim_verify_keytypes",     opt_module,     {US"dkim"} },
+  { "dkim_verify_min_keysizes", opt_module,     {US"dkim"} },
+  { "dkim_verify_minimal",      opt_module,     {US"dkim"} },
+  { "dkim_verify_signers",      opt_module,     {US"dkim"} },
 #endif
 #ifdef SUPPORT_DMARC
   { "dmarc_forensic_sender",    opt_module,     {US"dmarc"} },
index a6b7722bfc9d0b0958207be7ad0e70b57fca1125..541e9320daab2b7fa1024e1d78a5d377acfe341b 100644 (file)
@@ -1828,13 +1828,6 @@ mime_is_rfc822         = 0;
 mime_part_count        = -1;
 #endif
 
-#ifndef DISABLE_DKIM
-/* Call into DKIM to set up the context.  In CHUNKING mode
-we clear the dot-stuffing flag */
-if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
-  dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
-#endif
-
 if (misc_mod_msg_init() != OK)
   goto TIDYUP;
 
@@ -3517,100 +3510,47 @@ else
 #ifndef DISABLE_DKIM
     if (!f.dkim_disable_verify)
       {
-      /* Finish off the body hashes, calculate sigs and do compares */
-      dkim_exim_verify_finish();
+      misc_module_info * mi = misc_mod_findonly(US"dkim");
+      if (mi)
+       {
+       typedef void (*vfin_fn_t)(void);
+       typedef int  (*vacl_fn_t)(uschar **, uschar**);
+       typedef void (*vlog_fn_t)(void);
 
-      /* Check if we must run the DKIM ACL */
-      GET_OPTION("acl_smtp_dkim");
-      if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
-        {
-        uschar * dkim_verify_signers_expanded =
-          expand_string(dkim_verify_signers);
-       gstring * results = NULL, * seen_items = NULL;
-       int signer_sep = 0, old_pool = store_pool;
-       const uschar * ptr;
-       uschar * item;
-
-       store_pool = POOL_PERM;   /* Allow created variables to live to data ACL */
-
-        if (!(ptr = dkim_verify_signers_expanded))
-          log_write(0, LOG_MAIN|LOG_PANIC,
-            "expansion of dkim_verify_signers option failed: %s",
-            expand_string_message);
-
-       /* Loop over signers we want to verify, calling ACL.  Default to OK
-       when no signers are present.  Each call from here expands to a n ACL
-       call per matching sig in the message. */
-
-       rc = OK;
-       while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
-         {
-         /* Prevent running ACL for an empty item */
-         if (!item || !*item) continue;
+       /* Finish off the body hashes, calculate sigs and do compares */
 
-         /* Only run ACL once for each domain or identity,
-         no matter how often it appears in the expanded list. */
-         if (seen_items)
-           {
-           uschar * seen_item;
-           const uschar * seen_items_list = string_from_gstring(seen_items);
-           int seen_sep = ':';
-           BOOL seen_this_item = FALSE;
-
-           while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
-                                                 NULL, 0)))
-             if (Ustrcmp(seen_item,item) == 0)
-               {
-               seen_this_item = TRUE;
-               break;
-               }
-
-           if (seen_this_item)
-             {
-             DEBUG(D_receive)
-               debug_printf("acl_smtp_dkim: skipping signer %s, "
-                 "already seen\n", item);
-             continue;
-             }
+       (((vfin_fn_t *) mi->functions)[DKIM_VERIFY_FINISH]) ();
 
-           seen_items = string_catn(seen_items, US":", 1);
-           }
-         seen_items = string_cat(seen_items, item);
+       /* Check if we must run the DKIM ACL */
+
+       GET_OPTION("acl_smtp_dkim");
+       if (acl_smtp_dkim)
+         {
+         rc = (((vacl_fn_t *) mi->functions)[DKIM_ACL_ENTRY])
+                                                   (&user_msg, &log_msg);
+         add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
 
-         rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
          if (rc != OK)
            {
-           DEBUG(D_receive)
-             debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
-               "skipping remaining items\n", rc, item);
            cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
-           break;
+
+           if (rc != DISCARD)
+             {
+             Uunlink(spool_name);
+             if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+               smtp_yield = FALSE;     /* No more msgs after dropped conn */
+             smtp_reply = US"";        /* Indicate reply already sent */
+             goto NOT_ACCEPTED;        /* Skip to end of function */
+             }
+           recipients_count = 0;
+           blackholed_by = US"DKIM ACL";
+           if (log_msg)
+             blackhole_log_msg = string_sprintf(": %s", log_msg);
            }
-         else
-           if (dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
-             break;
-         }
-       dkim_verify_status = string_from_gstring(results);
-       store_pool = old_pool;
-       add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
-       if (rc == DISCARD)
-         {
-         recipients_count = 0;
-         blackholed_by = US"DKIM ACL";
-         if (log_msg)
-           blackhole_log_msg = string_sprintf(": %s", log_msg);
-         }
-       else if (rc != OK)
-         {
-         Uunlink(spool_name);
-         if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
-           smtp_yield = FALSE;    /* No more messages after dropped connection */
-         smtp_reply = US"";       /* Indicate reply already sent */
-         goto NOT_ACCEPTED;                    /* Skip to end of function */
          }
-        }
-      else                             /* No acl or no wanted signers */
-       dkim_exim_verify_log_all();
+       else    /* No ACL; just log */
+         (((vlog_fn_t *) mi->functions)[DKIM_VERIFY_LOG_ALL]) ();
+       }
       }
 #endif /* DISABLE_DKIM */
 
@@ -4189,8 +4129,13 @@ if (LOGGING(8bitmime))
   g = string_fmt_append(g, " M8S=%d", body_8bitmime);
 
 #ifndef DISABLE_DKIM
-if (LOGGING(dkim) && dkim_verify_overall)
-  g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+if (LOGGING(dkim))
+  {
+  misc_module_info * mi = misc_mod_findonly(US"dkim");
+  typedef gstring * (*fn_t)(gstring *);
+  if (mi)
+    g = (((fn_t *) mi->functions)[DKIM_VDOM_FIRSTPASS]) (g);
+  }
 # ifdef EXPERIMENTAL_ARC
 if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
   g = string_catn(g, US" ARC", 4);
index adf6c59cb966169b673ca09f01b8578fd2f1e939..e7589485060becd6803e66a9ffd873aff888f6dd 100644 (file)
@@ -464,6 +464,23 @@ smtp_had_eof = smtp_had_error = 0;
 
 
 
+#ifndef DISABLE_DKIM
+/* Feed received message data to the dkim module */
+/*XXX maybe a global dkim_info? */
+void
+smtp_verify_feed(const uschar * s, unsigned n)
+{
+static misc_module_info * dkim_mi = NULL;
+typedef void (*fn_t)(const uschar *, int);
+
+if (!dkim_mi && !(dkim_mi = misc_mod_findonly(US"dkim")))
+  return;
+
+(((fn_t *) dkim_mi->functions)[DKIM_VERIFY_FEED]) (s, n);
+}
+#endif
+
+
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
@@ -507,7 +524,7 @@ if (rc <= 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
-dkim_exim_verify_feed(smtp_inbuffer, rc);
+smtp_verify_feed(smtp_inbuffer, rc);
 #endif
 smtp_inend = smtp_inbuffer + rc;
 smtp_inptr = smtp_inbuffer;
@@ -570,7 +587,7 @@ int n = smtp_inend - smtp_inptr;
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(smtp_inptr, n);
+  smtp_verify_feed(smtp_inptr, n);
 #endif
 }
 
@@ -726,19 +743,24 @@ bdat_getc(unsigned lim)
 uschar * user_msg = NULL;
 uschar * log_msg;
 
-for(;;)
-  {
 #ifndef DISABLE_DKIM
-  unsigned dkim_save;
+misc_module_info * dkim_info = misc_mod_findonly(US"dkim");
+typedef void (*dkim_pause_t)(BOOL);
+dkim_pause_t dkim_pause;
+
+dkim_pause = dkim_info
+  ? ((dkim_pause_t *) dkim_info->functions)[DKIM_VERIFY_PAUSE] : NULL;
 #endif
 
+for(;;)
+  {
+
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
-  dkim_save = dkim_collect_input;
-  dkim_collect_input = 0;
+  if (dkim_pause) dkim_pause(TRUE);
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
@@ -767,9 +789,7 @@ for(;;)
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
-    dkim_collect_input = dkim_save;
-    dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
-    dkim_collect_input = 0;
+    smtp_verify_feed(NULL, 0); /* notify EOD */
 #endif
     return EOD;
     }
@@ -843,7 +863,7 @@ next_cmd:
 
       bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
-      dkim_collect_input = dkim_save;
+      if (dkim_pause) dkim_pause(FALSE);
 #endif
       break;   /* to top of main loop */
       }
@@ -1681,17 +1701,6 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
-#ifndef DISABLE_DKIM
-dkim_cur_signer = dkim_signers =
-dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
-f.dkim_disable_verify = FALSE;
-dkim_collect_input = 0;
-dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
-dkim_key_length = 0;
-#endif
-#ifdef SUPPORT_DMARC
-f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
-#endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
 arc_received_instance = 0;
index bb54571beb96025601df67aedfbf8029b80c45b2..43b30986d9225ed761496aec9b44fade4e8bc796 100644 (file)
@@ -269,9 +269,18 @@ bmi_verdicts = NULL;
 #endif
 
 #ifndef DISABLE_DKIM
-dkim_signers = NULL;
 f.dkim_disable_verify = FALSE;
+# ifdef COMPILE_UTILITY
+dkim_signers = NULL;
 dkim_collect_input = 0;
+#else
+  {
+  misc_module_info * mi = misc_mod_findonly(US"dkim");
+  /* We used to clear only dkim_signers, dkim_collect_input. This does more
+  but I think it is safe. */
+  if (mi) mi->smtp_reset();
+  }
+# endif
 #endif
 
 #ifndef DISABLE_TLS
index 2b62233d825a1428f7c110de15316b43a2c9e1d6..dfda2d405381821b62efffeccae852398e4835f0 100644 (file)
@@ -1688,7 +1688,7 @@ while (*fp)
            case '}' : zg = string_catn(zg, US"{BC}", 4); break;
            default:
              {
-             unsigned char u = *s;
+             uschar u = *s;
              if ( (u < 32) || (u > 127) )
                zg = string_fmt_append(zg, "{%02x}", u);
              else
index 46abac7289aee7957d14ace78bb1a44a23725a9c..ef311b6777852e60537c2def16d9fd71bfe0002f 100644 (file)
@@ -1026,6 +1026,7 @@ typedef struct misc_module_info {
   int          (*conn_init)(const uschar *, const uschar *);
   void         (*smtp_reset)(void);
   int          (*msg_init)(void);
+  gstring *    (*authres)(gstring *);
 
   void *       options;
   unsigned     options_count;
index 06cd4a5f85c9e4d05965c4575c91804ee5f72eb9..7963e2c97bb5664824c990bd5e1ce3df1508dba3 100644 (file)
@@ -3904,7 +3904,7 @@ else if (inbytes < 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
-dkim_exim_verify_feed(state->xfer_buffer, inbytes);
+smtp_verify_feed(state->xfer_buffer, inbytes);
 #endif
 state->xfer_buffer_hwm = (int) inbytes;
 state->xfer_buffer_lwm = 0;
@@ -3980,7 +3980,7 @@ int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm;
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
+  smtp_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n);
 #endif
 }
 
index 033bd0e1061c8ad85df9a4344aa970f8f165a570..302404b6c9029f1d8321f58cf44916fa1045c4f3 100644 (file)
@@ -4545,7 +4545,7 @@ switch(error)
   }
 
 #ifndef DISABLE_DKIM
-dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+smtp_verify_feed(ssl_xfer_buffer, inbytes);
 #endif
 ssl_xfer_buffer_hwm = inbytes;
 ssl_xfer_buffer_lwm = 0;
@@ -4615,7 +4615,7 @@ int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
+  smtp_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
 #endif
 }
 
index d25a2b1f64e292234040d609689cb6fb123cbca0..cdd2a404f795da3ce930e53cb3af33593a3ac419 100644 (file)
@@ -46,6 +46,7 @@ optionlist smtp_transport_options[] = {
   { "data_timeout",         opt_time,     LOFF(data_timeout) },
   { "delay_after_cutoff",   opt_bool,     LOFF(delay_after_cutoff) },
 #ifndef DISABLE_DKIM
+  /*XXX dkim module */
   { "dkim_canon", opt_stringptr,          LOFF(dkim.dkim_canon) },
   { "dkim_domain", opt_stringptr,         LOFF(dkim.dkim_domain) },
   { "dkim_hash", opt_stringptr,                   LOFF(dkim.dkim_hash) },
@@ -4103,13 +4104,18 @@ else
 
 #ifndef DISABLE_DKIM
   {
+  typedef void (*fn_t)(void);
+  misc_module_info * mi;
 # ifdef MEASURE_TIMING
   struct timeval t0;
   gettimeofday(&t0, NULL);
 # endif
-  dkim_exim_sign_init();
-# ifdef EXPERIMENTAL_ARC
+
+  if ((mi = misc_mod_find(US"dkim", NULL)))
     {
+    (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();
+
+# ifdef EXPERIMENTAL_ARC
     uschar * s = ob->arc_sign;
     if (s)
       {
@@ -4129,8 +4135,8 @@ else
        ob->dkim.force_bodyhash = TRUE;
        }
       }
+# endif        /*ARC*/
     }
-# endif
 # ifdef MEASURE_TIMING
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
@@ -4175,7 +4181,15 @@ else
     }
 
 #ifndef DISABLE_DKIM
-  sx->ok = dkim_transport_write_message(&tctx, &ob->dkim, CUSS &message);
+    {
+    misc_module_info * mi = misc_mod_find(US"dkim", NULL);
+    typedef BOOL (*fn_t)(transport_ctx *, struct ob_dkim *, const uschar **);
+
+    sx->ok = mi
+      ? (((fn_t *) mi->functions)[DKIM_TRANSPORT_WRITE])
+                                     (&tctx, &ob->dkim, CUSS &message)
+      : transport_write_message(&tctx, 0);
+    }
 #else
   sx->ok = transport_write_message(&tctx, 0);
 #endif
index 70499312d368d23a810c4371b87c9381055f0517..83659ea192db95041458e639069bee1551cc454e 100755 (executable)
@@ -1560,8 +1560,8 @@ RESET_AFTER_EXTRA_LINE_READ:
     # Not all platforms build with SPF enabled
     next if /(^$time_pid?spf_conn_init|spf_compile\.c)/;
     next if /try option spf_smtp_comment_template$/;
-    next if /loading module '(?:dmarc|spf)'$/;
-    next if /^$time_pid?Loaded "(?:dmarc|spf)"$/;
+    next if /loading module '(?:dkim|dmarc|spf)'$/;
+    next if /^$time_pid?Loaded "(?:dkim|dmarc|spf)"$/;
 
     # Not all platforms have sendfile support
     next if /^cannot use sendfile for body: no support$/;
@@ -4147,7 +4147,6 @@ system("sudo cp eximdir/exim eximdir/exim_exim;" .
        "sudo chmod 06755 eximdir/exim_exim");
 
 # Copy any libraries that were built for dynamic load
-# Currently this is only for lookup methods
 
 ($parm_exim_dir) = $parm_exim =~ m?^(.*)/exim?;