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
 
 
  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
 ------------
 
 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-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-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 $@"
 
 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 \
 # 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
 
 
         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 \
 
 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 \
         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)
 
         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 \
         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)
          $(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 \
        hintsdb/hints_tdb.h \
        local_scan.h \
        macros.h \
+       miscmods/dkim_api.h \
        miscmods/dmarc_api.h \
        miscmods/spf_api.h \
        mytypes.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 \
        ../hintsdb/hints_tdb.h \
        ../local_scan.h \
        ../macros.h \
+       ../miscmods/dkim_api.h \
        ../miscmods/dmarc_api.h \
        ../miscmods/spf_api.h \
        ../mytypes.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
 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
 
 
 # Dependencies for WITH_CONTENT_SCAN modules
 
@@ -900,7 +900,7 @@ spool_mbox.o:    $(HDRS) spool_mbox.c
 
 # Dependencies for EXPERIMENTAL_* modules
 
 
 # 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
 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 " "
 
           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)" \
 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
  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.
 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
 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
 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
 do
-  ln -s ../../src/$d/$f $f
+  ln -s ../../src/$d/$f `basename $f`
 done
 cd ..
 
 done
 cd ..
 
@@ -110,17 +117,6 @@ do
 done
 cd ..
 
 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.
 # 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 \
   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
   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 \
 
 # 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
 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() {
 # 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"
   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"
 }
 
   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]*."
 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
       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/^.*= *//')
     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"
     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
     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
 }
     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"
 # 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
 
 
 # DISABLE_DKIM=yes
 
index 023ac2ff6ee1d8afa08a02ae1722e9873702015b..878278313931c2d4024e2861fe947616e20adbd8 100644 (file)
@@ -3934,23 +3934,19 @@ for (; cb; cb = cb->next)
 
 #ifndef DISABLE_DKIM
     case ACLC_DKIM_SIGNER:
 
 #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:
     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;
       break;
+      }
 #endif
 
 #ifdef SUPPORT_DMARC
 #endif
 
 #ifdef SUPPORT_DMARC
@@ -4183,11 +4179,19 @@ for (; cb; cb = cb->next)
 #endif
         )
         store_pool = POOL_PERM;
 #endif
         )
         store_pool = POOL_PERM;
+
 #ifndef DISABLE_DKIM   /* Overwriteable dkim result variables */
 #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);
       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
     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
       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"
 # 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
 
 
 #  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)
 
 #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;
 
   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;
 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.
 
 /* 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 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;
 
 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");
   {
   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
        )
       {
        && 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);
 
       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)
 
 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);
 
 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? */
 
   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)))
   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",
   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 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)
 {
 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.
 */
 /* 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;
 {
 arc_line * ams = as->hdr_ams;
 pdkim_bodyhash * b;
-pdkim_pubkey * p;
 blob sighash;
 blob sighash;
-blob hhash;
-ev_ctx vctx;
-int hashtype;
+blob hhash_computed;
+hashmethod hm;
 const uschar * errstr;
 const uschar * errstr;
+int rc;
 
 as->ams_verify_done = US"in-progress";
 
 
 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
 /* 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
    )
   {
    || 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);
 
 
 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 */
 /* 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";
   }
 
   {
   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;
 
 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;
 {
 arc_line * hdr_as = as->hdr_as;
 arc_set * as2;
-int hashtype;
+hashmethod hm;
 hctx hhash_ctx;
 blob hhash_computed;
 blob sighash;
 hctx hhash_ctx;
 blob hhash_computed;
 blob sighash;
-ev_ctx vctx;
-pdkim_pubkey * p;
 const uschar * errstr;
 const uschar * errstr;
+int rc;
 
 DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
 /*
 
 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.
 */
 
            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");
   {
   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 = 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);
                                            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 = 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);
                                            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)
 
   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->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);
                                            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.
 /*
        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
        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 */
 */
 
 /* 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";
   }
 
   return US"fail";
   }
 
@@ -1076,12 +1173,6 @@ const uschar * res;
 
 memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx));
 
 
 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
 */
 /* 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
   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;
 blob hhash;
-es_ctx sctx;
 const uschar * errstr;
 const uschar * errstr;
+typedef const uschar * (*fn_t)
+                         (const blob *, hashmethod, const uschar *, blob *);
 
 DEBUG(D_transport)
   {
 
 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);
 
   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);
   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;
 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);
   }
   exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr);
   exim_sha_finish(&hhash_ctx, &hhash);
   }
@@ -1315,8 +1409,9 @@ else
   hhash.len = hdata->ptr;
   }
 
   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)
   {
   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;
   }
       privkey);
   return FALSE;
   }
+
 return TRUE;
 }
 
 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);*/
 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 (;;)
   {
 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;
 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);
 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=",
 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)
   {
 
 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,
       /* 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;
       }
     }
       break;
       }
     }
@@ -1420,7 +1518,8 @@ g = string_catn(g, US";\r\n\tb=;", 7);
 
 /* Include the pseudo-header in the accumulation */
 
 
 /* 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 */
 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;
 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;
 
 /*
 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;
   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;
   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;
   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 */
   }
 
 /* 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)
 {
 
 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");
 
 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
     - ? 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();
 */
 
 b = arc_ams_setup_sign_bodyhash();
@@ -1827,8 +1932,6 @@ arc_line al;
 pdkim_bodyhash * b;
 uschar * errstr;
 
 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");
 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
 
 #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
 
 #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 */
 
 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
 #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)
 {
 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("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;
   return NULL;
+  }
 
 mi = (struct misc_module_info *) dlsym(dl,
                                    CS string_sprintf("%s_module_info", name));
 
 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;
 }
 
 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
 #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;
 {
 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
 #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
-/* dmarc depends on spf so this add must go first, for the dmarc-static case */
 misc_mod_add(&spf_module_info);
 #endif
 #if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
 misc_mod_add(&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
 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();
 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
 
 /*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;
 
       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;
       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 "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"
 #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
   { "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" },
 #endif
 #ifdef SUPPORT_DMARC
   { "dmarc_domain_policy", vtype_module,       US"dmarc" },
@@ -2131,7 +2132,13 @@ switch (vp->type)
 
 #ifndef DISABLE_DKIM
   case vtype_dkim:
 
 #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:
 #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);
       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
 #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    *
 *************************************************/
 /*************************************************
 *     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
 #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);
 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);
 
 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 *);
 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 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);
 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 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
 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)
 {
 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;
 
 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;
 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 */
 
 #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) */
 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 */
 #elif defined(SHA_NATIVE)
 /******************************************************************************/
 /* Only sha-1 supported */
index 788c9f0ad6571cb9700bcc4b905c744119362a44..9ad837b62958f0abc0f2d92d865b8a364e3cd1be 100644 (file)
 # include <gnutls/crypto.h>
 #elif defined(SHA_GCRYPT)
 # include <gcrypt.h>
 # 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
 
 
 #endif
 
 
@@ -63,12 +59,6 @@ typedef struct {
 #elif defined(SHA_GCRYPT)
   gcry_md_hd_t sha;          /* Either SHA1 or SHA256 block               */
 
 #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
 #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) -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
 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
 
 # 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.
 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
 
 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.
 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
 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 "../functions.h"
 #  include "dmarc.h"
-#  include "../pdkim/pdkim.h"
+#  include "pdkim.h"
 
 OPENDMARC_LIB_T     dmarc_ctx;
 DMARC_POLICY_T     *dmarc_pctx = NULL;
 
 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;
 
 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;
 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;
 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;
 }
 
 return TRUE;
 }
 
@@ -188,6 +199,8 @@ return OK;
 static void
 dmarc_smtp_reset(void)
 {
 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;
 }
 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;
 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 */
 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;
   {
   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 */
 
 
   /* 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);
 
     {
     typedef SPF_response_t * (*fn_t)(void);
-    if (spf_mod_info)
+    if (dmarc_spf_mod_info)
       /*XXX ugly use of a pointer */
       /*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)
     }
 
   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. */
 
   /* 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;
 
     {
     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,
 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,
 };
 
   [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,
   .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),
 
   .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_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,
 
 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,
   [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,
   .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),
 
   .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 */
 
 /* 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
 
 
 /* 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
 #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_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} },
 #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
 #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"} },
 #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
 
 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;
 
 if (misc_mod_msg_init() != OK)
   goto TIDYUP;
 
@@ -3517,100 +3510,47 @@ else
 #ifndef DISABLE_DKIM
     if (!f.dkim_disable_verify)
       {
 #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)
            {
          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");
            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 */
 
       }
 #endif /* DISABLE_DKIM */
 
@@ -4189,8 +4129,13 @@ if (LOGGING(8bitmime))
   g = string_fmt_append(g, " M8S=%d", body_8bitmime);
 
 #ifndef DISABLE_DKIM
   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);
 # 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.
 */
 /* 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
   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;
 #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)
 if (n > lim)
   n = lim;
 if (n > 0)
-  dkim_exim_verify_feed(smtp_inptr, n);
+  smtp_verify_feed(smtp_inptr, n);
 #endif
 }
 
 #endif
 }
 
@@ -726,19 +743,24 @@ bdat_getc(unsigned lim)
 uschar * user_msg = NULL;
 uschar * log_msg;
 
 uschar * user_msg = NULL;
 uschar * log_msg;
 
-for(;;)
-  {
 #ifndef DISABLE_DKIM
 #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
 
 #endif
 
+for(;;)
+  {
+
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
   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
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
@@ -767,9 +789,7 @@ for(;;)
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
   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;
     }
 #endif
     return EOD;
     }
@@ -843,7 +863,7 @@ next_cmd:
 
       bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
 
       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 */
       }
 #endif
       break;   /* to top of main loop */
       }
@@ -1681,17 +1701,6 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
 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;
 #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
 #endif
 
 #ifndef DISABLE_DKIM
-dkim_signers = NULL;
 f.dkim_disable_verify = FALSE;
 f.dkim_disable_verify = FALSE;
+# ifdef COMPILE_UTILITY
+dkim_signers = NULL;
 dkim_collect_input = 0;
 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
 #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:
              {
            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
              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);
   int          (*conn_init)(const uschar *, const uschar *);
   void         (*smtp_reset)(void);
   int          (*msg_init)(void);
+  gstring *    (*authres)(gstring *);
 
   void *       options;
   unsigned     options_count;
 
   void *       options;
   unsigned     options_count;
index 06cd4a5f85c9e4d05965c4575c91804ee5f72eb9..7963e2c97bb5664824c990bd5e1ce3df1508dba3 100644 (file)
@@ -3904,7 +3904,7 @@ else if (inbytes < 0)
   return FALSE;
   }
 #ifndef DISABLE_DKIM
   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;
 #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)
 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
 }
 
 #endif
 }
 
index 033bd0e1061c8ad85df9a4344aa970f8f165a570..302404b6c9029f1d8321f58cf44916fa1045c4f3 100644 (file)
@@ -4545,7 +4545,7 @@ switch(error)
   }
 
 #ifndef DISABLE_DKIM
   }
 
 #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;
 #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)
 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
 }
 
 #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
   { "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) },
   { "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
   {
 
 #ifndef DISABLE_DKIM
   {
+  typedef void (*fn_t)(void);
+  misc_module_info * mi;
 # ifdef MEASURE_TIMING
   struct timeval t0;
   gettimeofday(&t0, NULL);
 # endif
 # 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)
       {
     uschar * s = ob->arc_sign;
     if (s)
       {
@@ -4129,8 +4135,8 @@ else
        ob->dkim.force_bodyhash = TRUE;
        }
       }
        ob->dkim.force_bodyhash = TRUE;
        }
       }
+# endif        /*ARC*/
     }
     }
-# endif
 # ifdef MEASURE_TIMING
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
 # ifdef MEASURE_TIMING
   report_time_since(&t0, US"dkim_exim_sign_init (delta)");
 # endif
@@ -4175,7 +4181,15 @@ else
     }
 
 #ifndef DISABLE_DKIM
     }
 
 #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
 #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$/;
     # 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$/;
 
     # 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
        "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?;
 
 
 ($parm_exim_dir) = $parm_exim =~ m?^(.*)/exim?;