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
------------
macro-authtls.o: auths/tls.c
@echo "$(CC) -DMACRO_PREDEF auths/tls.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ auths/tls.c
-macro-dkim.o: dkim.c
- @echo "$(CC) -DMACRO_PREDEF dkim.c"
- $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ dkim.c
+macro-dkim.o: miscmods/dkim.c
+ @echo "$(CC) -DMACRO_PREDEF miscmods/dkim.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/dkim.c
macro-malware.o: malware.c
@echo "$(CC) -DMACRO_PREDEF malware.c"
$(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ malware.c
-macro-signing.o: pdkim/signing.c
- @echo "$(CC) -DMACRO_PREDEF pdkim/signing.c"
- $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ pdkim/signing.c
+macro-signing.o: miscmods/signing.c
+ @echo "$(CC) -DMACRO_PREDEF miscmods/signing.c"
+ $(FE)$(CC) -c $(CFLAGS) -DMACRO_PREDEF $(INCLUDE) -o $@ miscmods/signing.c
macro_predef: $(OBJ_MACRO)
@echo "$(LNCC) -o $@"
# 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
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 \
local_scan.o $(EXIM_PERL) $(OBJ_WITH_CONTENT_SCAN) \
$(OBJ_EXPERIMENTAL)
-exim: buildlookups buildauths pdkim/pdkim.a \
+exim: buildlookups buildauths \
buildrouters buildtransports buildmisc \
$(OBJ_EXIM) version.o
@echo "$(LNCC) -o exim"
$(FE)$(PURIFY) $(LNCC) -o exim $(LFLAGS) $(OBJ_EXIM) version.o \
routers/routers.a transports/transports.a lookups/lookups.a \
- auths/auths.a pdkim/pdkim.a miscmods/miscmods.a \
+ auths/auths.a miscmods/miscmods.a \
$(LIBRESOLV) $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) \
$(EXTRALIBS_EXIM) $(DBMLIB) $(LOOKUP_LIBS) $(AUTH_LIBS) \
$(PERL_LIBS) $(TLS_LIBS) $(PCRE_LIBS) $(LDFLAGS)
hintsdb/hints_tdb.h \
local_scan.h \
macros.h \
+ miscmods/dkim_api.h \
miscmods/dmarc_api.h \
miscmods/spf_api.h \
mytypes.h \
../hintsdb/hints_tdb.h \
../local_scan.h \
../macros.h \
+ ../miscmods/dkim_api.h \
../miscmods/dmarc_api.h \
../miscmods/spf_api.h \
../mytypes.h \
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 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
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)" \
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.
cd ..
# miscellaneous modules
+# Note that the file in the miscmods/pdkim/ source subdir get linked to the
+# destination miscmods/ dir
d="miscmods"
mkdir $d
cd $d
# Makefile is generated
-for f in dmarc.c dmarc.h dmarc_api.h dummy.c spf.c spf.h spf_api.h
+for f in dummy.c \
+ dkim.c dkim_transport.c dkim.h dkim_api.h \
+ pdkim/crypt_ver.h pdkim/pdkim.c pdkim/pdkim.h \
+ pdkim/pdkim_hash.h pdkim/signing.c pdkim/signing.h \
+ dmarc.c dmarc.h dmarc_api.h \
+ spf.c spf.h spf_api.h
do
- ln -s ../../src/$d/$f $f
+ ln -s ../../src/$d/$f `basename $f`
done
cd ..
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.
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
# 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
# command-line, not just check the Makefile.
want_dynamic() {
- local dyn_name="$1"
+ local dyn_name="${1#_}"
local re="(${classdef}|EXPERIMENTAL)_${dyn_name}[ $tab]*=[ $tab]*2"
+ #XXX Solaris does not support -E on grep. Must use egrep.
env | grep -E -q "^$re"
if [ $? -eq 0 ]; then return 0; fi
grep -E -q "^[ $tab]*$re" "$defs_source"
}
+want_not_disabled() {
+ local want_name="${1#_}"
+ [ "$local_want_name" = "$1" ] && return 0;
+ local re="DISABLED_${want_name}[ $tab]*=[ $tab]*."
+ env | grep -E -q "^$re"
+ [ $? -ne 0 ] && return 0
+ grep -E -q "^[ $tab]*$re" "$defs_source"
+ [ $? -ne 0 ] && return 0
+ return 1
+}
+
want_at_all() {
local want_name="$1"
local re="(${classdef}|EXPERIMENTAL)_${want_name}[ $tab]*=[ $tab]*."
echo >&2 "Missing CFLAGS_DYNAMIC prevents building dynamic $name"
exit 1
fi
- MODS="${MODS} ${mod_name}.so"
+ MODS="${MODS} ${mod_name#_}.so"
grep "^${classdef}_${name}_PC" "$defs_source" 1>&2
pkgconf=$(grep "^${classdef}_${name}_PC" "$defs_source")
if [ $? -eq 0 ]; then
pkgconf=$(echo $pkgconf | sed 's/^.*= *//')
- echo "${classdef}_${mod_name}_INCLUDE = $(pkg-config --cflags $pkgconf)"
- echo "${classdef}_${mod_name}_LIBS = $(pkg-config --libs $pkgconf)"
+ echo "${classdef}_${mod_name#_}_INCLUDE = $(pkg-config --cflags $pkgconf)"
+ echo "${classdef}_${mod_name#_}_LIBS = $(pkg-config --libs $pkgconf)"
else
grep "^${classdef}_${name}_" "$defs_source"
- echo "${classdef}_${mod_name}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
- echo "${classdef}_${mod_name}_LIBS = \$(${classdef}_${name}_LIBS)"
+ echo "${classdef}_${mod_name#_}_INCLUDE = \$(${classdef}_${name}_INCLUDE)"
+ echo "${classdef}_${mod_name#_}_LIBS = \$(${classdef}_${name}_LIBS)"
fi
- elif want_at_all "$name"
- then
+ elif want_not_disabled "$name"; then
+ OBJ="${OBJ} ${mod_name#_}.o"
+ elif want_at_all "$name"; then
OBJ="${OBJ} ${mod_name}.o"
fi
}
# 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
#ifndef DISABLE_DKIM
case ACLC_DKIM_SIGNER:
- if (dkim_cur_signer)
- rc = match_isinlist(dkim_cur_signer,
- &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
- else
- rc = FAIL;
- break;
-
case ACLC_DKIM_STATUS:
- { /* return good for any match */
- const uschar * s = dkim_verify_status ? dkim_verify_status : US"none";
- int sep = 0;
- for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); )
- if ( (rc = match_isinlist(ss, &arg,
- 0, NULL, NULL, MCL_STRING, TRUE, NULL))
- == OK) break;
- }
+ /* See comment on ACLC_SPF wrt. coding issues */
+ {
+ misc_module_info * mi = misc_mod_find(US"dkim", &log_message);
+ typedef int (*fn_t)(const uschar *);
+ rc = mi
+ ? (((fn_t *) mi->functions)
+ [cb->type == ACLC_DKIM_SIGNER
+ ? DKIM_SIGNER_ISINLIST
+ : DKIM_STATUS_LISTMATCH]) (arg)
+ : DEFER;
break;
+ }
#endif
#ifdef SUPPORT_DMARC
#endif
)
store_pool = POOL_PERM;
+
#ifndef DISABLE_DKIM /* Overwriteable dkim result variables */
- if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0)
- dkim_verify_status = string_copy(arg);
- else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0)
- dkim_verify_reason = string_copy(arg);
+ if ( Ustrcmp(cb->u.varname, "dkim_verify_status") == 0
+ || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0
+ )
+ {
+ misc_module_info * mi = misc_mod_findonly(US"dkim");
+ typedef void (*fn_t)(const uschar *, void *);
+
+ if (mi)
+ (((fn_t *) mi->functions)[DKIM_SETVAR])
+ (cb->u.varname, string_copy(arg));
+ }
else
#endif
acl_var_create(cb->u.varname)->data.ptr = string_copy(arg);
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
# else
# include "functions.h"
-# include "pdkim/pdkim.h"
-# include "pdkim/signing.h"
+# include "miscmods/pdkim.h"
+# include "miscmods/signing.h"
# ifdef SUPPORT_DMARC
# include "miscmods/dmarc.h"
# endif
-extern pdkim_ctx * dkim_verify_ctx;
-extern pdkim_ctx dkim_sign_ctx;
-
#define ARC_SIGN_OPT_TSTAMP BIT(0)
#define ARC_SIGN_OPT_EXPIRE BIT(1)
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;
*/
+/******************************************************************************/
+
+/* 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;
+}
+
/******************************************************************************/
}
/******************************************************************************/
+/* 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.
int sep = ':';
hdr_rlist * r;
BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0;
-int hashtype = pdkim_hashname_to_hashtype(
- ams->a_hash.data, ams->a_hash.len);
+hashmethod hm = arc_dkim_hashname_blob_to_method(&ams->a_hash);
hctx hhash_ctx;
const uschar * s;
int len;
-if ( hashtype == -1
- || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod))
+if (hm < 0 || !exim_sha_init(&hhash_ctx, hm))
{
DEBUG(D_acl)
debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n");
&& 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);
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);
-static pdkim_pubkey *
-arc_line_to_pubkey(arc_line * al)
+static blob *
+arc_line_to_pubkey(arc_line * al, const uschar ** errstr)
{
-uschar * dns_txt;
-pdkim_pubkey * p;
-
-if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s",
- (int)al->s.len, al->s.data, (int)al->d.len, al->d.data))))
- {
- DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n");
- return NULL;
- }
-
-if ( !(p = pdkim_parse_pubkey_record(dns_txt))
- || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0)
- )
+typedef const uschar * (*fn_t)(const uschar *, blob **, const uschar **);
+blob * pubkey;
+const uschar * hashes;
+const uschar * srvtype =
+ (((fn_t *) arc_dkim_mod_info->functions)[DKIM_DNS_PUBKEY])
+ (string_sprintf("%.*s._domainkey.%.*s",
+ (int)al->s.len, al->s.data, (int)al->d.len, al->d.data),
+ &pubkey, &hashes);
+
+/*XXX do we need a blob-string printf %handler? Other types of blob? */
+
+if (!srvtype)
+ { *errstr = US"pubkey dns lookup fail"; return NULL; }
+if ((Ustrcmp(srvtype, "*") != 0 && Ustrcmp(srvtype, "email") != 0))
{
- DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n");
+ *errstr = string_sprintf("pubkey format error: srvtype '%s'", srvtype);
return NULL;
}
/* If the pubkey limits use to specified hashes, reject unusable
signatures. XXX should we have looked for multiple dns records? */
-if (p->hashes)
+if (hashes)
{
- const uschar * list = p->hashes, * ele;
+ const uschar * list = hashes, * ele;
int sep = ':';
while ((ele = string_nextinlist(&list, &sep, NULL, 0)))
if (!ele)
{
DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n",
- p->hashes, (int)al->a.len, al->a.data);
+ hashes, (int)al->a.len, al->a.data);
+ *errstr = US"no usable sig for this pubkey hash list";
return NULL;
}
}
-return p;
+return pubkey;
+}
+
+
+
+
+/* Set up a body hashing method on the given signature-context
+(creates a new one if needed, or uses an already-present one).
+
+Arguments:
+ signing TRUE for signing, FALSE for verification
+ c canonicalization spec, text form
+ ah hash, text form
+ bodylen byte count for message body
+
+Return: pointer to hashing method struct
+*/
+
+static pdkim_bodyhash *
+arc_set_bodyhash(BOOL signing,
+ const blob * c, const blob * ah, long bodylen)
+{
+typedef pdkim_bodyhash * (*fn_t)(BOOL,
+ const blob * canon, const blob * hash, long bodylen);
+
+return (((fn_t *) arc_dkim_mod_info->functions)[DKIM_SET_BODYHASH])
+ (signing, c, ah, bodylen);
}
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.
*/
{
arc_line * ams = as->hdr_ams;
pdkim_bodyhash * b;
-pdkim_pubkey * p;
blob sighash;
-blob hhash;
-ev_ctx vctx;
-int hashtype;
+blob hhash_computed;
+hashmethod hm;
const uschar * errstr;
+int rc;
as->ams_verify_done = US"in-progress";
/* 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
)
{
DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance);
-/* Get the public key from DNS */
-
-if (!(p = arc_line_to_pubkey(ams)))
- return as->ams_verify_done = arc_state_reason = US"pubkey problem";
-
/* We know the b-tag blob is of a nul-term string, so safe as a string */
-pdkim_decode_base64(ams->b.data, &sighash);
+arc_decode_base64(ams->b.data, &sighash);
-arc_get_verify_hhash(ctx, ams, &hhash);
+arc_get_verify_hhash(ctx, ams, &hhash_computed);
-/* Setup the interface to the signing library */
-
-if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx, NULL)))
- {
- DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr);
- as->ams_verify_done = arc_state_reason = US"internal sigverify init error";
- return US"fail";
- }
-
-hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len);
-if (hashtype == -1)
+if ((hm = arc_dkim_hashname_blob_to_method(&ams->a_hash)) < 0)
{
DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance);
return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
}
-if ((errstr = exim_dkim_verify(&vctx,
- pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash)))
- {
- DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr);
- return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify";
- }
+rc = arc_sig_verify(as, ams, hm, &hhash_computed, &sighash, US"AMS", &errstr);
+if (rc != OK)
+ return as->ams_verify_done = arc_state_reason =
+ rc == FAIL ? US"AMS sig nonverify" : errstr;
DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance);
as->ams_verify_passed = TRUE;
{
arc_line * hdr_as = as->hdr_as;
arc_set * as2;
-int hashtype;
+hashmethod hm;
hctx hhash_ctx;
blob hhash_computed;
blob sighash;
-ev_ctx vctx;
-pdkim_pubkey * p;
const uschar * errstr;
+int rc;
DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance);
/*
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");
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 = 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 = as2->hdr_as;
if (as2->instance == as->instance)
- s = pdkim_relax_header_n(al->rawsig_no_b_val.data,
+ /*XXX dkim module */
+ s = arc_relax_header_n(al->rawsig_no_b_val.data,
al->rawsig_no_b_val.len, FALSE);
else if (!(s = al->relaxed))
- al->relaxed = s = pdkim_relax_header_n(al->complete->text,
+ /*XXX dkim module */
+ al->relaxed = s = arc_relax_header_n(al->complete->text,
al->complete->slen, TRUE);
len = Ustrlen(s);
DEBUG(D_acl) debug_printf("%Z\n", s);
/*
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
*/
/* 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";
}
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
*/
blob * sig, const uschar * why)
{
hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE
- ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod;
+ ? HASH_SHA2_512
+ : arc_dkim_hashtype_to_method(hashtype);
+
blob hhash;
-es_ctx sctx;
const uschar * errstr;
+typedef const uschar * (*fn_t)
+ (const blob *, hashmethod, const uschar *, blob *);
DEBUG(D_transport)
{
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);
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);
}
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)
privkey);
return FALSE;
}
+
return TRUE;
}
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 (;;)
{
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);
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)
{
/* 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;
}
}
/* 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 */
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;
/*
badline_str = US"aar";
if (!(l = as->hdr_aar)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
badline_str = US"ams";
if (!(l = as->hdr_ams)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, TRUE));
badline_str = US"as";
if (!(l = as->hdr_as)) goto badline;
h = l->complete;
- hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next));
+ /*XXX dkim module */
+ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next));
}
/* Calculate the signature from the accumulation */
/**************************************/
-/* Return pointer to pdkim_bodyhash for given hash method, creating new
-method if needed.
-*/
+/*XXX not static currently as the smtp tpt calls us */
+/* Really returns pdkim_bodyhash* - but there's an ordering
+problem for functions.h so call it void* */
void *
arc_ams_setup_sign_bodyhash(void)
{
-int canon_head, canon_body;
+blob canon = {.data = US"relaxed", .len = 7}; /*XXX hardwired */
+blob hash = {.data = US"sha256", .len = 6}; /*XXX hardwired */
DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n");
-pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */
-return pdkim_set_bodyhash(&dkim_sign_ctx,
- pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */
- canon_body,
- -1);
+
+return arc_set_bodyhash(TRUE, &canon, &hash, -1);
}
- ? 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();
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");
#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
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
+++ /dev/null
-/*************************************************
-* 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*/
+++ /dev/null
-/*************************************************
-* 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
+++ /dev/null
-/*************************************************
-* 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 */
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);
}
DEBUG(D_any) debug_printf_indent("loading module '%s'\n", name);
if (!(dl = mod_open(name, US"miscmod", errstr)))
+ {
+ DEBUG(D_any) debug_printf_indent(" mod_open: %s\n", *errstr);
return NULL;
+ }
mi = (struct misc_module_info *) dlsym(dl,
CS string_sprintf("%s_module_info", name));
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);
+}
+
+
}
+#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
{
static BOOL onetime = FALSE;
if (onetime) return;
+onetime = TRUE;
+#if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2)
+misc_mod_add(&dkim_module_info);
+#endif
#if defined(SUPPORT_SPF) && SUPPORT_SPF!=2
-/* dmarc depends on spf so this add must go first, for the dmarc-static case */
misc_mod_add(&spf_module_info);
#endif
#if defined(SUPPORT_DMARC) && SUPPORT_DMARC!=2
+/* dmarc depends on spf so this add must go after, for the both-static case */
misc_mod_add(&dmarc_module_info);
#endif
-
-onetime = TRUE;
}
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
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;
# 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"
{ "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" },
#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:
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
+extern BOOL arc_init(void);
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
#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 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 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 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
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;
}
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;
#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) */
-#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 */
# 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
#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
$(FE)$(CC) -c $(CFLAGS) $(INCLUDE) $*.c
.c.so:; @echo "$(CC) -shared $*.c"
- $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) $(DLFLAGS) $*.c -o $@
-
-dmarc.o dmarc.so: $(HDRS) ../pdkim/pdkim.h dmarc.h dmarc.c
+ $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+ -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+ $(DLFLAGS) $*.c -o $@
+
+# Note that the sources from pdkim/ are linked into the build.../miscmods/ dir
+# by scripts/Makelinks.
+dkim.o dkim.so: $(HDRS) dkim.h dkim.c dkim_transport.c \
+ crypt_ver.h pdkim.h pdkim_hash.h pdkim.c \
+ signing.h signing.c
+dmarc.o dmarc.so: $(HDRS) pdkim.h dmarc.h dmarc.c
dummy.o: dummy.c
-spf.o spf.so: $(HDRS) spf.h spf.c
+spf.o spf.so: $(HDRS) spf.h spf.c
+
+dkim.o:
+ @echo "$(CC) dkim.c dkim_transport.c pdkim.c signing.c"
+ $(FE)$(CC) -r $(CFLAGS) $(INCLUDE) \
+ dkim.c dkim_transport.c pdkim.c signing.c -o $@
+
+dkim.so:
+ @echo "$(CC) -shared dkim.c dkim_transport.c pdkim.c signing.c"
+ $(FE)$(CC) $(SUPPORT_$*_INCLUDE) $(SUPPORT_$*_LIBS) \
+ -DDYNLOOKUP $(CFLAGS_DYNAMIC) $(CFLAGS) $(INCLUDE) \
+ $(DLFLAGS) dkim.c dkim_transport.c pdkim.c signing.c -o $@
# End
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
and add it to HDRS and PHDRS in OS/Makefile-Base.
Add a SUPPORT_<foo> line to Local/Makefile, and (if dynamic) any
SUPPORT_<foo>_INCLUDE or SUPPORT_<foo>_LIBS required.
-Add the capitalised module name <foo> to the "miscmods" like in
+Add the capitalised module name <foo> to the "miscmods" line in
scripts/Configure-Makefile.
Add all the filenames to the "miscmods" list in scripts/Makelinks
+
+For statically-linked modules the SUPPORT_<foo> line should say "yes",
+for dynamic: "2".
+
+If include-by-default is wanted for the module, use DISABLE_<foo> instead
+of SUPPORT_<foo> (and leave it commented out as appropriate), and prefix
+the name in the "miscmods" line with an underbar ("_").
+For dynamic builds, a SUPPORT_ line is still needed.
--- /dev/null
+/*************************************************
+* 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*/
--- /dev/null
+/*************************************************
+* 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 */
+
--- /dev/null
+/*************************************************
+* 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
--- /dev/null
+/*************************************************
+* 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 */
# include "../functions.h"
# include "dmarc.h"
-# include "../pdkim/pdkim.h"
+# include "pdkim.h"
OPENDMARC_LIB_T dmarc_ctx;
DMARC_POLICY_T *dmarc_pctx = 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;
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;
}
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;
}
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 * 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 */
{
typedef SPF_response_t * (*fn_t)(void);
- if (spf_mod_info)
+ if (dmarc_spf_mod_info)
/*XXX ugly use of a pointer */
- spf_response_p = ((fn_t *) spf_mod_info->functions)[SPF_GET_RESPONSE]();
+ spf_response_p = ((fn_t *) dmarc_spf_mod_info->functions)[SPF_GET_RESPONSE]();
}
if (!spf_response_p)
/* 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;
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,
};
.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),
#define DMARC_PROCESS 0
#define DMARC_EXPAND_QUERY 1
-#define DMARC_AUTHRES 2
-#define DMARC_STORE_DATA 3
+#define DMARC_STORE_DATA 2
--- /dev/null
+# 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
--- /dev/null
+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/.
--- /dev/null
+/*************************************************
+* 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
+
--- /dev/null
+/*
+ * 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*/
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
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,
.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),
/* See the file NOTICE for conditions of use and distribution. */
/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* API definitions for the spfmodule */
+/* API definitions for the spf module */
/* Function table entry numbers */
#define SPF_PROCESS 0
-#define SPF_AUTHRES 1
#define SPF_GET_RESPONSE 2
#define SPF_OPEN 3
#define SPF_CLOSE 4
+++ /dev/null
-# 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
+++ /dev/null
-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/.
+++ /dev/null
-#define POLARSSL_BASE64_C
-
-
-
+++ /dev/null
-/*************************************************
-* 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
-
+++ /dev/null
-/*
- * 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*/
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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 */
+++ /dev/null
-/*
- * 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 */
{ "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
{ "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"} },
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;
#ifndef DISABLE_DKIM
if (!f.dkim_disable_verify)
{
- /* Finish off the body hashes, calculate sigs and do compares */
- dkim_exim_verify_finish();
+ misc_module_info * mi = misc_mod_findonly(US"dkim");
+ if (mi)
+ {
+ typedef void (*vfin_fn_t)(void);
+ typedef int (*vacl_fn_t)(uschar **, uschar**);
+ typedef void (*vlog_fn_t)(void);
- /* Check if we must run the DKIM ACL */
- GET_OPTION("acl_smtp_dkim");
- if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers)
- {
- uschar * dkim_verify_signers_expanded =
- expand_string(dkim_verify_signers);
- gstring * results = NULL, * seen_items = NULL;
- int signer_sep = 0, old_pool = store_pool;
- const uschar * ptr;
- uschar * item;
-
- store_pool = POOL_PERM; /* Allow created variables to live to data ACL */
-
- if (!(ptr = dkim_verify_signers_expanded))
- log_write(0, LOG_MAIN|LOG_PANIC,
- "expansion of dkim_verify_signers option failed: %s",
- expand_string_message);
-
- /* Loop over signers we want to verify, calling ACL. Default to OK
- when no signers are present. Each call from here expands to a n ACL
- call per matching sig in the message. */
-
- rc = OK;
- while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0)))
- {
- /* Prevent running ACL for an empty item */
- if (!item || !*item) continue;
+ /* Finish off the body hashes, calculate sigs and do compares */
- /* Only run ACL once for each domain or identity,
- no matter how often it appears in the expanded list. */
- if (seen_items)
- {
- uschar * seen_item;
- const uschar * seen_items_list = string_from_gstring(seen_items);
- int seen_sep = ':';
- BOOL seen_this_item = FALSE;
-
- while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep,
- NULL, 0)))
- if (Ustrcmp(seen_item,item) == 0)
- {
- seen_this_item = TRUE;
- break;
- }
-
- if (seen_this_item)
- {
- DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: skipping signer %s, "
- "already seen\n", item);
- continue;
- }
+ (((vfin_fn_t *) mi->functions)[DKIM_VERIFY_FINISH]) ();
- seen_items = string_catn(seen_items, US":", 1);
- }
- seen_items = string_cat(seen_items, item);
+ /* Check if we must run the DKIM ACL */
+
+ GET_OPTION("acl_smtp_dkim");
+ if (acl_smtp_dkim)
+ {
+ rc = (((vacl_fn_t *) mi->functions)[DKIM_ACL_ENTRY])
+ (&user_msg, &log_msg);
+ add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
- rc = dkim_exim_acl_run(item, &results, &user_msg, &log_msg);
if (rc != OK)
{
- DEBUG(D_receive)
- debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
- "skipping remaining items\n", rc, item);
cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
- break;
+
+ if (rc != DISCARD)
+ {
+ Uunlink(spool_name);
+ if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
+ smtp_yield = FALSE; /* No more msgs after dropped conn */
+ smtp_reply = US""; /* Indicate reply already sent */
+ goto NOT_ACCEPTED; /* Skip to end of function */
+ }
+ recipients_count = 0;
+ blackholed_by = US"DKIM ACL";
+ if (log_msg)
+ blackhole_log_msg = string_sprintf(": %s", log_msg);
}
- else
- if (dkim_verify_minimal && Ustrcmp(dkim_verify_status, "pass") == 0)
- break;
- }
- dkim_verify_status = string_from_gstring(results);
- store_pool = old_pool;
- add_acl_headers(ACL_WHERE_DKIM, US"DKIM");
- if (rc == DISCARD)
- {
- recipients_count = 0;
- blackholed_by = US"DKIM ACL";
- if (log_msg)
- blackhole_log_msg = string_sprintf(": %s", log_msg);
- }
- else if (rc != OK)
- {
- Uunlink(spool_name);
- if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0)
- smtp_yield = FALSE; /* No more messages after dropped connection */
- smtp_reply = US""; /* Indicate reply already sent */
- goto NOT_ACCEPTED; /* Skip to end of function */
}
- }
- else /* No acl or no wanted signers */
- dkim_exim_verify_log_all();
+ else /* No ACL; just log */
+ (((vlog_fn_t *) mi->functions)[DKIM_VERIFY_LOG_ALL]) ();
+ }
}
#endif /* DISABLE_DKIM */
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);
+#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.
*/
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;
if (n > lim)
n = lim;
if (n > 0)
- dkim_exim_verify_feed(smtp_inptr, n);
+ smtp_verify_feed(smtp_inptr, n);
#endif
}
uschar * user_msg = NULL;
uschar * log_msg;
-for(;;)
- {
#ifndef DISABLE_DKIM
- unsigned dkim_save;
+misc_module_info * dkim_info = misc_mod_findonly(US"dkim");
+typedef void (*dkim_pause_t)(BOOL);
+dkim_pause_t dkim_pause;
+
+dkim_pause = dkim_info
+ ? ((dkim_pause_t *) dkim_info->functions)[DKIM_VERIFY_PAUSE] : NULL;
#endif
+for(;;)
+ {
+
if (chunking_data_left > 0)
return lwr_receive_getc(chunking_data_left--);
bdat_pop_receive_functions();
#ifndef DISABLE_DKIM
- dkim_save = dkim_collect_input;
- dkim_collect_input = 0;
+ if (dkim_pause) dkim_pause(TRUE);
#endif
/* Unless PIPELINING was offered, there should be no next command
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;
}
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 */
}
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;
#endif
#ifndef DISABLE_DKIM
-dkim_signers = NULL;
f.dkim_disable_verify = FALSE;
+# ifdef COMPILE_UTILITY
+dkim_signers = NULL;
dkim_collect_input = 0;
+#else
+ {
+ misc_module_info * mi = misc_mod_findonly(US"dkim");
+ /* We used to clear only dkim_signers, dkim_collect_input. This does more
+ but I think it is safe. */
+ if (mi) mi->smtp_reset();
+ }
+# endif
#endif
#ifndef DISABLE_TLS
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
int (*conn_init)(const uschar *, const uschar *);
void (*smtp_reset)(void);
int (*msg_init)(void);
+ gstring * (*authres)(gstring *);
void * options;
unsigned options_count;
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;
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
}
}
#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;
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
}
{ "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) },
#ifndef DISABLE_DKIM
{
+ typedef void (*fn_t)(void);
+ misc_module_info * mi;
# ifdef MEASURE_TIMING
struct timeval t0;
gettimeofday(&t0, NULL);
# endif
- dkim_exim_sign_init();
-# ifdef EXPERIMENTAL_ARC
+
+ if ((mi = misc_mod_find(US"dkim", NULL)))
{
+ (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) ();
+
+# ifdef EXPERIMENTAL_ARC
uschar * s = ob->arc_sign;
if (s)
{
ob->dkim.force_bodyhash = TRUE;
}
}
+# endif /*ARC*/
}
-# endif
# ifdef MEASURE_TIMING
report_time_since(&t0, US"dkim_exim_sign_init (delta)");
# endif
}
#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
# 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$/;
"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?;