From 9b604221c5e94f8146f48e47a76865c11eedb7a1 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Fri, 6 Sep 2024 12:29:23 +0100 Subject: [PATCH] arc dynamic module --- doc/doc-txt/NewStuff | 6 +- doc/doc-txt/experimental-spec.txt | 2 + src/OS/Makefile-Base | 6 +- src/scripts/Configure-Makefile | 2 +- src/scripts/MakeLinks | 3 +- src/src/acl.c | 67 ++++++--- src/src/config.h.defaults | 1 + src/src/drtables.c | 13 +- src/src/exim.c | 3 - src/src/exim.h | 3 + src/src/expand.c | 11 +- src/src/functions.h | 16 -- src/src/globals.c | 8 - src/src/globals.h | 7 - src/src/miscmods/Makefile | 1 + src/src/{ => miscmods}/arc.c | 237 ++++++++++++++++++------------ src/src/miscmods/arc_api.h | 19 +++ src/src/miscmods/dkim_transport.c | 40 +++-- src/src/miscmods/dmarc.c | 27 +++- src/src/miscmods/pdkim/pdkim.c | 32 ++-- src/src/miscmods/spf_api.h | 8 +- src/src/receive.c | 11 +- src/src/smtp_in.c | 5 +- src/src/transports/smtp.c | 37 ++--- 24 files changed, 345 insertions(+), 220 deletions(-) rename src/src/{ => miscmods}/arc.c (92%) create mode 100644 src/src/miscmods/arc_api.h diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 1189ce3f3..5220408e8 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -14,9 +14,9 @@ Version 4.98 3. Events smtp:fail:protocol and smtp:fail:syntax - 4. JSON and LDAP lookup support, 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 + 4. JSON and LDAP lookup support, SPF, DKIM, DMARC and ARC support, all the + router and authenticator drivers, and all the transport drivers except + smtp, can now be built as loadable modules Version 4.98 ------------ diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 56ee10f82..a73007700 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -498,6 +498,8 @@ Enable using EXPERIMENTAL_ARC=yes in your Local/Makefile. You must also have DKIM present (not disabled), and you very likely want to have SPF enabled. +It is possible to build as a dynamic-load module: set also SUPPORT_ARC=2. + Verification -- diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index 12319967e..857c44776 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -495,8 +495,7 @@ transport-filter.pl: config ../src/transport-filter.src # are thrown away by the linker. OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o -OBJ_EXPERIMENTAL = arc.o \ - bmi_spam.o \ +OBJ_EXPERIMENTAL = bmi_spam.o \ dane.o \ dcc.o \ imap_utf7.o \ @@ -685,6 +684,7 @@ HDRS = blob.h \ hintsdb/hints_tdb.h \ local_scan.h \ macros.h \ + miscmods/arc_api.h \ miscmods/dkim_api.h \ miscmods/dmarc_api.h \ miscmods/spf_api.h \ @@ -707,6 +707,7 @@ PHDRS = ../config.h \ ../hintsdb/hints_tdb.h \ ../local_scan.h \ ../macros.h \ + ../miscmods/arc_api.h \ ../miscmods/dkim_api.h \ ../miscmods/dmarc_api.h \ ../miscmods/spf_api.h \ @@ -900,7 +901,6 @@ spool_mbox.o: $(HDRS) spool_mbox.c # Dependencies for EXPERIMENTAL_* modules -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 diff --git a/src/scripts/Configure-Makefile b/src/scripts/Configure-Makefile index 12f0ddd9c..1eb79a291 100755 --- a/src/scripts/Configure-Makefile +++ b/src/scripts/Configure-Makefile @@ -311,7 +311,7 @@ done <<-END routers ROUTER ACCEPT DNSLOOKUP IPLITERAL IPLOOKUP MANUALROUTE QUERYPROGRAM REDIRECT transports TRANSPORT APPENDFILE AUTOREPLY LMTP PIPE QUEUEFILE SMTP auths AUTH CRAM_MD5 CYRUS_SASL DOVECOT EXTERNAL GSASL HEIMDAL_GSSAPI PLAINTEXT SPA TLS - miscmods SUPPORT _DKIM DMARC SPF + miscmods SUPPORT ARC _DKIM DMARC SPF END # See if there is a definition of EXIM_PERL in what we have built so far. diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index a6521a95e..481f36fe3 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -97,6 +97,7 @@ mkdir $d cd $d # Makefile is generated for f in dummy.c \ + arc.c arc_api.h \ 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 \ @@ -149,7 +150,7 @@ do done # EXPERIMENTAL_* -for f in arc.c bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ +for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ danessl.h imap_utf7.c utf8.c xclient.c do ln -s ../src/$f $f diff --git a/src/src/acl.c b/src/src/acl.c index 878278313..18d892ec5 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -207,9 +207,17 @@ static condition_def conditions[] = { [ACLC_DELAY] = { US"delay", ACD_EXP | ACD_MOD, FORBIDDEN(ACL_BIT_NOTQUIT) }, #ifndef DISABLE_DKIM - [ACLC_DKIM_SIGNER] = { US"dkim_signers", ACD_EXP, + [ACLC_DKIM_SIGNER] = { US"dkim_signers", +# if SUPPORT_DKIM==2 + ACD_LOAD | +# endif + ACD_EXP, PERMITTED(ACL_BIT_DKIM) }, - [ACLC_DKIM_STATUS] = { US"dkim_status", ACD_EXP, + [ACLC_DKIM_STATUS] = { US"dkim_status", +# if SUPPORT_DKIM==2 + ACD_LOAD | +# endif + ACD_EXP, PERMITTED(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME # ifndef DISABLE_PRDR | ACL_BIT_PRDR @@ -394,6 +402,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) } #endif +/******************************************************************************/ #ifndef MACRO_PREDEF @@ -410,20 +419,31 @@ typedef struct condition_module { # if SUPPORT_SPF==2 static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 }; # endif +# if SUPPORT_DKIM==2 +static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 }; +# endif # if SUPPORT_DMARC==2 static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 }; # endif +/* These are modules which can be loaded on seeing an ACL condition +during readconf, The "arc" module is handled by custom coding. */ + static condition_module condition_modules[] = { # if SUPPORT_SPF==2 {.mod_name = US"spf", .conditions = spf_condx}, # endif -# if SUPPORT_SPF==2 +# if SUPPORT_DKIM==2 + {.mod_name = US"dkim", .conditions = dkim_condx}, +# endif +# if SUPPORT_DMARC==2 {.mod_name = US"dmarc", .conditions = dmarc_condx}, # endif }; -# endif +# endif /*LOOKUP_MODULE_DIR*/ + +/****************************/ /* Return values from decode_control() */ @@ -933,7 +953,7 @@ while ((s = (*func)())) if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0) { - if (!this) + if (!this) /* not handling a verb right now */ { *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name, saveline); @@ -1002,6 +1022,9 @@ while ((s = (*func)())) condition_module * cm; uschar * s = NULL; + /* Over the list of modules we support, check the list of ACL conditions + each supports. This assumes no duplicates. */ + for (cm = condition_modules; cm < condition_modules + nelem(condition_modules); cm++) for (const int * cond = cm->conditions; *cond != -1; cond++) @@ -1022,7 +1045,21 @@ while ((s = (*func)())) return NULL; } } -#endif +# ifdef EXPERIMENTAL_ARC + else if (c == ACLC_VERIFY) /* Special handling for verify=arc; */ + { /* not invented a more general method yet- flag in verify_type_list? */ + const uschar * t = s; + uschar * e; + if ( *t++ == '=' && Uskip_whitespace(&t) && Ustrncmp(t, "arc", 3) == 0 + && !misc_mod_find(US"arc", &e)) + { + *error = string_sprintf("ACL error: failed to find module for '%s': %s", + conditions[c].name, e); + return NULL; + } + } +# endif +#endif /*LOOKUP_MODULE_DIR*/ cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); cond->next = NULL; @@ -1876,19 +1913,11 @@ switch(vp->value) #ifdef EXPERIMENTAL_ARC case VERIFY_ARC: - { /* Do Authenticated Received Chain checks in a separate function. */ - const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0); - int csep = 0; - uschar * cond; - - if (!(arc_state = acl_verify_arc())) return DEFER; - DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state, - arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":""); - - if (!condlist) condlist = US"none:pass"; - while ((cond = string_nextinlist(&condlist, &csep, NULL, 0))) - if (Ustrcmp(arc_state, cond) == 0) return OK; - return FAIL; + { + const misc_module_info * mi = misc_mod_findonly(US"arc"); + typedef int (*fn_t)(const uschar *); + if (mi) return (((fn_t *) mi->functions)[ARC_VERIFY]) + (CUS string_nextinlist(&list, &sep, NULL, 0)); } #endif diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index d602886a0..20a288d66 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -169,6 +169,7 @@ Do not put spaces between # and the 'define'. /* Required to support dynamic-module build */ #define SUPPORT_DKIM +#define SUPPORT_ARC #define SYSLOG_LOG_PID #define SYSLOG_LONG_LINES diff --git a/src/src/drtables.c b/src/src/drtables.c index 61ced3e6a..32765aedc 100644 --- a/src/src/drtables.c +++ b/src/src/drtables.c @@ -441,6 +441,7 @@ if (mi->init && mi->init(mi)) } else DEBUG(D_any) debug_printf_indent("module init call failed for %s\n", mi->name); +/* fprintf(stderr,"misc_mod_add: added %s\n", mi->name); */ } @@ -746,6 +747,9 @@ extern misc_module_info dmarc_module_info; #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2 extern misc_module_info spf_module_info; #endif +#if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2) +extern misc_module_info arc_module_info; +#endif void init_misc_mod_list(void) @@ -755,14 +759,17 @@ if (onetime) return; onetime = TRUE; #if !defined(DISABLE_DKIM) && (!defined(SUPPORT_DKIM) || SUPPORT_DKIM!=2) -misc_mod_add(&dkim_module_info); + misc_mod_add(&dkim_module_info); #endif #if defined(SUPPORT_SPF) && SUPPORT_SPF!=2 -misc_mod_add(&spf_module_info); + 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); + misc_mod_add(&dmarc_module_info); +#endif +#if defined(EXPERIMENTAL_ARC) && (!defined(SUPPORT_ARC) || SUPPORT_ARC!=2) + misc_mod_add(&arc_module_info); #endif } diff --git a/src/src/exim.c b/src/src/exim.c index ca98e25de..2349260df 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -4211,9 +4211,6 @@ is equivalent to the ability to modify a setuid binary! This needs to happen before we read the main configuration. */ init_lookup_list(); init_misc_mod_list(); -#ifdef EXPERIMENTAL_ARC -arc_init(); /*XXX temporary, until we do an arc module */ -#endif /*XXX this excrescence could move to the testsuite standard config setup file */ #ifdef SUPPORT_I18N diff --git a/src/src/exim.h b/src/src/exim.h index 8260dc75f..f5043aea9 100644 --- a/src/src/exim.h +++ b/src/src/exim.h @@ -555,6 +555,9 @@ config.h, mytypes.h, and store.h, so we don't need to mention them explicitly. # include "miscmods/dmarc_api.h" # include #endif +#ifdef EXPERIMENTAL_ARC +# include "miscmods/arc_api.h" +#endif /* The following stuff must follow the inclusion of config.h because it requires various things that are set therein. */ diff --git a/src/src/expand.c b/src/src/expand.c index 02680771f..a41ba98cf 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -454,10 +454,10 @@ static var_entry var_table[] = { { "address_file", vtype_stringptr, &address_file }, { "address_pipe", vtype_stringptr, &address_pipe }, #ifdef EXPERIMENTAL_ARC - { "arc_domains", vtype_string_func, (void *) &fn_arc_domains }, - { "arc_oldest_pass", vtype_int, &arc_oldest_pass }, - { "arc_state", vtype_stringptr, &arc_state }, - { "arc_state_reason", vtype_stringptr, &arc_state_reason }, + { "arc_domains", vtype_module, US"arc" }, + { "arc_oldest_pass", vtype_module, US"arc" }, + { "arc_state", vtype_module, US"arc" }, + { "arc_state_reason", vtype_module, US"arc" }, #endif { "authenticated_fail_id",vtype_stringptr, &authenticated_fail_id }, { "authenticated_id", vtype_stringptr, &authenticated_id }, @@ -4890,9 +4890,6 @@ while (*s) yield = authres_iprev(yield); yield = authres_smtpauth(yield); yield = misc_mod_authres(yield); -#ifdef EXPERIMENTAL_ARC - yield = authres_arc(yield); -#endif break; } diff --git a/src/src/functions.h b/src/src/functions.h index 4f4e615ca..deec54590 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -1,4 +1,3 @@ -extern BOOL arc_init(void); /************************************************* * Exim - an Internet mail transport agent * *************************************************/ @@ -111,18 +110,6 @@ extern void acl_var_write(uschar *, uschar *, void *); extern void add_driver_info(driver_info **, const driver_info *, size_t); -#ifdef EXPERIMENTAL_ARC -# ifdef SUPPORT_DMARC -extern gstring *arc_dmarc_hist_append(gstring *); -# endif -extern void *arc_ams_setup_sign_bodyhash(void); -extern const uschar *arc_header_feed(gstring *, BOOL); -extern gstring *arc_sign(const uschar *, gstring *, uschar **); -extern void arc_sign_init(void); -extern const uschar *acl_verify_arc(void); -extern uschar * fn_arc_domains(void); -#endif - extern void assert_no_variables(void *, int, const char *, int); extern int auth_call_pam(const uschar *, uschar **); extern int auth_call_pwcheck(uschar *, uschar **); @@ -142,9 +129,6 @@ extern int auth_read_input(const uschar *); extern gstring * auth_show_supported(gstring *); extern uschar *authenticator_current_name(void); -#ifdef EXPERIMENTAL_ARC -extern gstring *authres_arc(gstring *); -#endif extern gstring *authres_smtpauth(gstring *); extern uschar *b64encode(const uschar *, int); diff --git a/src/src/globals.c b/src/src/globals.c index 6fae1582f..c65ddf413 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -615,14 +615,6 @@ tree_node *addresslist_anchor = NULL; int addresslist_count = 0; gid_t *admin_groups = NULL; -#ifdef EXPERIMENTAL_ARC -struct arc_set *arc_received = NULL; -int arc_received_instance = 0; -int arc_oldest_pass = 0; -const uschar *arc_state = NULL; -const uschar *arc_state_reason = NULL; -#endif - uschar *authenticated_fail_id = NULL; uschar *authenticated_id = NULL; uschar *authenticated_sender = NULL; diff --git a/src/src/globals.h b/src/src/globals.h index 1f03cefee..2f2f023e3 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -360,13 +360,6 @@ extern int addresslist_count; /* Number defined */ extern gid_t *admin_groups; /* List of admin groups */ extern BOOL allow_domain_literals; /* As it says */ extern BOOL allow_mx_to_ip; /* Allow MX records to -> ip address */ -#ifdef EXPERIMENTAL_ARC -extern struct arc_set *arc_received; /* highest ARC instance evaluation struct */ -extern int arc_received_instance; /* highest ARC instance number in headers */ -extern int arc_oldest_pass; /* lowest passing instance number in headers */ -extern const uschar *arc_state; /* verification state */ -extern const uschar *arc_state_reason; -#endif extern BOOL allow_utf8_domains; /* For experimenting */ extern uschar *authenticated_fail_id; /* ID that failed authentication */ extern uschar *authenticated_id; /* ID that was authenticated */ diff --git a/src/src/miscmods/Makefile b/src/src/miscmods/Makefile index 3013a88da..64a66276f 100644 --- a/src/src/miscmods/Makefile +++ b/src/src/miscmods/Makefile @@ -31,6 +31,7 @@ miscmods.a: $(OBJ) # Note that the sources from pdkim/ are linked into the build.../miscmods/ dir # by scripts/Makelinks. +arc.o arc.so: $(HDRS) pdkim.h arc.c 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 diff --git a/src/src/arc.c b/src/src/miscmods/arc.c similarity index 92% rename from src/src/arc.c rename to src/src/miscmods/arc.c index a065ca8e3..db546e1ab 100644 --- a/src/src/arc.c +++ b/src/src/miscmods/arc.c @@ -8,20 +8,25 @@ SPDX-License-Identifier: GPL-2.0-or-later */ -#include "exim.h" +#include "../exim.h" #if defined EXPERIMENTAL_ARC # if defined DISABLE_DKIM # error DKIM must also be enabled for ARC # else -# include "functions.h" -# include "miscmods/pdkim.h" -# include "miscmods/signing.h" +# include "../functions.h" +# include "pdkim.h" +# include "signing.h" -# ifdef SUPPORT_DMARC -# include "miscmods/dmarc.h" -# endif +/* Globals */ +struct arc_set *arc_received = NULL; /* highest ARC instance eval struct */ +int arc_received_instance = 0; /* highest ARC instance num in hdrs */ +int arc_oldest_pass = 0; /* lowest passing inst num in hdrs */ +const uschar *arc_state = NULL; /* verification state */ +const uschar *arc_state_reason = NULL; + +/******************************************************************************/ #define ARC_SIGN_OPT_TSTAMP BIT(0) #define ARC_SIGN_OPT_EXPIRE BIT(1) @@ -134,20 +139,26 @@ arc_parse_line() gathering only the 'i' tag (instance) information. /******************************************************************************/ /* We need a module init function, to check on the dkim module being present -(and we may as well stack it's modinfo ptr) - -For now (until we do an arc module), called from exim.c main(). +(and we may as well stash it's modinfo ptr) */ -BOOL -arc_init(void) + +static BOOL +arc_init(void * dummy) { 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); +log_write(0, LOG_MAIN, "arc: %s", errstr); return FALSE; } +static void +arc_smtp_reset(void) +{ +arc_state = arc_state_reason = NULL; +arc_received_instance = 0; +} + /******************************************************************************/ @@ -708,11 +719,7 @@ 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? */ + (string_sprintf("%b._domainkey.%b", &al->s, &al->d), &pubkey, &hashes); if (!srvtype) { *errstr = US"pubkey dns lookup fail"; return NULL; } @@ -734,8 +741,7 @@ if (hashes) if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break; if (!ele) { - DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n", - hashes, (int)al->a.len, al->a.data); + DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%b\n", hashes, &al->a); *errstr = US"no usable sig for this pubkey hash list"; return NULL; } @@ -886,10 +892,9 @@ if (!(b = arc_ams_setup_vfy_bodyhash(ams))) DEBUG(D_acl) { debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n" - " Body %.*s computed: ", + " Body %b computed: %.*H\n", as->instance, b->signed_body_bytes, - (int)ams->a_hash.len, ams->a_hash.data); - debug_printf("%.*H\n", b->bh.len, b->bh.data); + &ams->a_hash, b->bh.len, b->bh.data); } /* We know the bh-tag blob is of a nul-term string, so safe as a string */ @@ -1072,7 +1077,6 @@ for (as2 = ctx->arcset_chain; al = as2->hdr_aar; if (!(s = al->relaxed)) - /*XXX dkim module */ al->relaxed = s = arc_relax_header_n(al->complete->text, al->complete->slen, TRUE); len = Ustrlen(s); @@ -1081,7 +1085,6 @@ for (as2 = ctx->arcset_chain; al = as2->hdr_ams; if (!(s = al->relaxed)) - /*XXX dkim module */ al->relaxed = s = arc_relax_header_n(al->complete->text, al->complete->slen, TRUE); len = Ustrlen(s); @@ -1090,11 +1093,9 @@ for (as2 = ctx->arcset_chain; al = as2->hdr_as; if (as2->instance == as->instance) - /*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)) - /*XXX dkim module */ al->relaxed = s = arc_relax_header_n(al->complete->text, al->complete->slen, TRUE); len = Ustrlen(s); @@ -1109,8 +1110,8 @@ for (as2 = ctx->arcset_chain; exim_sha_finish(&hhash_ctx, &hhash_computed); DEBUG(D_acl) { - debug_printf("ARC i=%d AS Header %.*s computed: ", - as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data); + debug_printf("ARC i=%d AS Header %b computed: ", + as->instance, &hdr_as->a_hash); debug_printf("%.*H\n", hhash_computed.len, hhash_computed.data); } @@ -1161,13 +1162,17 @@ return NULL; /******************************************************************************/ /* Do ARC verification. Called from DATA ACL, on a verify = arc -condition. No arguments; we are checking globals. +condition. Set arc_state, and compare with given list of acceptable states. + +Arguments: + condlist list of resulta to test for OK/FAIL return; + NULL for default list -Return: The ARC state, or NULL on error. +Return: OK/FAIL, or DEFER on error */ -const uschar * -acl_verify_arc(void) +static int +acl_verify_arc(const uschar * condlist) { const uschar * res; @@ -1246,7 +1251,27 @@ if ((res = arc_verify_seals(&arc_verify_ctx))) res = US"pass"; out: - return res; + { + int csep = 0; + uschar * cond; + + if (!(arc_state = res)) + return DEFER; + + DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state, + arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":""); + + if (!condlist) condlist = US"none:pass"; + while ((cond = string_nextinlist(&condlist, &csep, NULL, 0))) + if (Ustrcmp(res, cond) == 0) return OK; + return FAIL; + } +} + +static BOOL +arc_is_pass(void) +{ +return arc_state && Ustrcmp(arc_state, "pass") == 0; } /******************************************************************************/ @@ -1348,9 +1373,8 @@ arc_line * al = (arc_line *)(as+1); header_line * h = (header_line *)(al+1); g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR); -g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t", - instance, identity, sender_host_address); -g = string_catn(g, US ar->data, ar->len); +g = string_fmt_append(g, " i=%d; %s; smtp.remote-ip=%s;\r\n\t%b", + instance, identity, sender_host_address, ar); h->slen = g->ptr - aar_off; h->text = g->s + aar_off; @@ -1503,7 +1527,6 @@ for(col = 3; rheaders; rheaders = rheaders->prev) /* Accumulate header for hashing/signing */ hdata = string_cat(hdata, - /*XXX dkim module */ arc_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */ break; } @@ -1518,7 +1541,6 @@ g = string_catn(g, US";\r\n\tb=;", 7); /* Include the pseudo-header in the accumulation */ -/*XXX dkim module */ s = arc_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE); hdata = string_cat(hdata, s); @@ -1633,17 +1655,14 @@ for (arc_set * as = Ustrcmp(status, US"fail") == 0 badline_str = US"aar"; if (!(l = as->hdr_aar)) goto badline; h = l->complete; - /*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; - /*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; - /*XXX dkim module */ hdata = string_cat(hdata, arc_relax_header_n(h->text, h->slen, !!as->next)); } @@ -1670,11 +1689,7 @@ badline: /**************************************/ -/*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 * +static pdkim_bodyhash * arc_ams_setup_sign_bodyhash(void) { blob canon = {.data = US"relaxed", .len = 7}; /*XXX hardwired */ @@ -1687,11 +1702,18 @@ return arc_set_bodyhash(TRUE, &canon, &hash, -1); -void +/* Module API: initilise, and set up a bodyhash for AMS */ + +static void arc_sign_init(void) { +blob canon = {.data = US"relaxed", .len = 7}; /*XXX hardwired */ +blob hash = {.data = US"sha256", .len = 6}; /*XXX hardwired */ + memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); headers_rlist = NULL; + +(void) arc_ams_setup_sign_bodyhash(); } @@ -1735,7 +1757,9 @@ return TRUE; -/* ARC signing. Called from the smtp transport, if the arc_sign option is set. +/* Module API: ARC signing. + +Called from the smtp transport, if the arc_sign option is set. The dkim_exim_sign() function has already been called, so will have hashed the message body for us so long as we requested a hash previously. @@ -1751,7 +1775,7 @@ Return value but not the plainheaders. */ -gstring * +static gstring * arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr) { const uschar * identity, * selector, * privkey, * opts, * s; @@ -1868,10 +1892,7 @@ g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar); - ? oversigning? - Covers the data - we must have requested a suitable bodyhash previously -XXX so where was that done? I don't see it! -XXX ah, ok - the smtp tpt calls arc_ams_setup_sign_bodyhash() directly, early - -> should pref use a better named call to make the point, but that - can wait until arc becomes a module + [done in arc_sign_init()] */ b = arc_ams_setup_sign_bodyhash(); @@ -1975,7 +1996,8 @@ badline: -/* A header line has been identified by DKIM processing. +/* Module API: A header line has been identified by DKIM processing; +feed it to ARC processing. Arguments: g Header line @@ -1985,7 +2007,7 @@ Return: NULL for success, or an error string (probably unused) */ -const uschar * +static const uschar * arc_header_feed(gstring * g, BOOL is_vfy) { return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g); @@ -1997,7 +2019,7 @@ return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g); /* Construct the list of domains from the ARC chain after validation */ -uschar * +const uschar * fn_arc_domains(void) { arc_set * as; @@ -2033,7 +2055,6 @@ authres_arc(gstring * g) { if (arc_state) { - arc_line * highest_ams; int start = 0; /* Compiler quietening */ DEBUG(D_acl) start = gstring_length(g); @@ -2043,11 +2064,10 @@ if (arc_state) g = string_fmt_append(g, " (i=%d)", arc_received_instance); if (arc_state_reason) g = string_append(g, 3, US"(", arc_state_reason, US")"); - g = string_catn(g, US" header.s=", 10); - highest_ams = arc_received->hdr_ams; - g = string_catn(g, highest_ams->s.data, highest_ams->s.len); - g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass); + g = string_fmt_append(g, " header.s=%b arc.oldest-pass=%d", + &arc_received->hdr_ams->s, + arc_oldest_pass); if (sender_host_address) g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address); @@ -2064,62 +2084,87 @@ return g; # ifdef SUPPORT_DMARC -/* Append a DMARC history record pair for ARC, to the given history set */ -gstring * -arc_dmarc_hist_append(gstring * g) +/* Module API: obtain ARC info for DMARC history. +Arguments: + gp pointer for return of arcset info string +Return: + status string, or NULL if none +*/ + +static const uschar * +arc_arcset_string(gstring ** gp) { if (arc_state) { - BOOL first = TRUE; - int i = Ustrcmp(arc_state, "pass") == 0 ? ARES_RESULT_PASS - : Ustrcmp(arc_state, "fail") == 0 ? ARES_RESULT_FAIL - : ARES_RESULT_UNKNOWN; - g = string_fmt_append(g, "arc %d\n", i); - g = string_fmt_append(g, "arc_policy %d json[", - i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS - : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL - : DMARC_ARC_POLICY_RESULT_UNUSED); + gstring * g = NULL; + /*XXX would we prefer this backwards? */ - for (arc_set * as = arc_verify_ctx.arcset_chain; as; - as = as->next, first = FALSE) + for (arc_set * as = arc_verify_ctx.arcset_chain; as; as = as->next) { arc_line * line = as->hdr_as; if (line) { - blob * d = &line->d; - blob * s = &line->s; - - if (!first) - g = string_catn(g, US",", 1); - - g = string_fmt_append(g, " (\"i\":%u," /*)*/ - " \"d\":\"%.*s\"," - " \"s\":\"%.*s\"", - as->instance, - d->data ? (int)d->len : 0, d->data && d->len ? d->data : US"", - s->data ? (int)s->len : 0, s->data && s->len ? s->data : US"" - ); + g = string_append_listele_fmt(g, ',', " (\"i\":%u" /*)*/ + ", \"d\":\"%#b\"" + ", \"s\":\"%#b\"", + as->instance, &line->d, &line->s); + if ((line = as->hdr_aar)) { blob * ip = &line->ip; if (ip->data && ip->len) - g = string_fmt_append(g, ", \"ip\":\"%.*s\"", (int)ip->len, ip->data); + g = string_fmt_append(g, ", \"ip\":\"%#b\"", ip); } - + /*(*/ g = string_catn(g, US")", 1); } } - g = string_catn(g, US" ]\n", 3); + *gp = g; } -else - g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", - ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); -return g; +return arc_state; } # endif +/******************************************************************************/ +/* Module API */ + +static void * arc_functions[] = { + [ARC_VERIFY] = acl_verify_arc, + [ARC_HEADER_FEED] = arc_header_feed, + [ARC_STATE_IS_PASS] = arc_is_pass, + [ARC_SIGN_INIT] = arc_sign_init, + [ARC_SIGN] = arc_sign, +# ifdef SUPPORT_DMARC + [ARC_ARCSET_INFO] = arc_arcset_string, +# endif +}; + +static var_entry arc_variables[] = { + { "arc_domains", vtype_string_func, (void *) &fn_arc_domains }, + { "arc_oldest_pass", vtype_int, &arc_oldest_pass }, + { "arc_state", vtype_stringptr, &arc_state }, + { "arc_state_reason", vtype_stringptr, &arc_state_reason }, +}; + +misc_module_info arc_module_info = +{ + .name = US"arc", +# if SUPPORT_SPF==2 + .dyn_magic = MISC_MODULE_MAGIC, +# endif + .init = arc_init, + .smtp_reset = arc_smtp_reset, + .authres = authres_arc, + + .functions = arc_functions, + .functions_count = nelem(arc_functions), + + .variables = arc_variables, + .variables_count = nelem(arc_variables), +}; + # endif /* DISABLE_DKIM */ #endif /* EXPERIMENTAL_ARC */ /* vi: aw ai sw=2 diff --git a/src/src/miscmods/arc_api.h b/src/src/miscmods/arc_api.h new file mode 100644 index 000000000..cf24a4cb3 --- /dev/null +++ b/src/src/miscmods/arc_api.h @@ -0,0 +1,19 @@ +/************************************************* +* 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 arcmodule */ + + +/* Function table entry numbers */ + +#define ARC_VERIFY 0 +#define ARC_HEADER_FEED 1 +#define ARC_STATE_IS_PASS 2 +#define ARC_SIGN_INIT 3 +#define ARC_SIGN 4 +#define ARC_ARCSET_INFO 5 diff --git a/src/src/miscmods/dkim_transport.c b/src/src/miscmods/dkim_transport.c index 0500da2be..e2d1705e3 100644 --- a/src/src/miscmods/dkim_transport.c +++ b/src/src/miscmods/dkim_transport.c @@ -109,6 +109,32 @@ return TRUE; +/* Prepend ARC-signing headers to given set of headers + +Arguments: + signspec Three-element colon-sep list: identity, selector, privkey. + Optional fourth element: comma-sep list of options. + Already expanded + sigheaders Any signature headers already generated, eg. by DKIM, or NULL + errstr Error string + +Return value + Set of headers to prepend to the message, including the supplied sigheaders + but not the plainheaders. +*/ + +static gstring * +dkt_arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr_p) +{ +const misc_module_info * mi = misc_mod_findonly(US"arc"); +typedef gstring * (*fn_t)(const uschar *, gstring *, uschar **); +if (mi) + return (((fn_t *) mi->functions)[ARC_SIGN]) (signspec, sigheaders, errstr_p); +*errstr_p = US"failed to find arc module"; +return NULL; +} + + /* This function is a wrapper around transport_write_message(). It is only called from the smtp transport if DKIM or Domainkeys support @@ -153,11 +179,6 @@ 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. */ @@ -174,7 +195,7 @@ if (!(dkim_signature = dkim_exim_sign(deliver_datafile, if (dkim->arc_signspec) /* Prepend ARC headers */ { uschar * e = NULL; - if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e))) + if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, &e))) { *err = e; return FALSE; @@ -275,10 +296,6 @@ if (!rc) 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. */ @@ -299,7 +316,8 @@ else #ifdef EXPERIMENTAL_ARC if (dkim->arc_signspec) /* Prepend ARC headers */ { - if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err))) + if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, + USS err))) goto CLEANUP; dlen = dkim_signature->ptr; } diff --git a/src/src/miscmods/dmarc.c b/src/src/miscmods/dmarc.c index d977d29fe..e192bda1b 100644 --- a/src/src/miscmods/dmarc.c +++ b/src/src/miscmods/dmarc.c @@ -360,7 +360,32 @@ g = string_fmt_append(g, "align_dkim %d\nalign_spf %d\naction %d\n", #if DMARC_API >= 100400 # ifdef EXPERIMENTAL_ARC -g = arc_dmarc_hist_append(g); + { + const misc_module_info * mi = misc_mod_findonly(US"arc"); + const uschar * s; + gstring * g2 = NULL; + typedef const uschar * (*fn_t)(gstring **); + + if (mi && (s = (((fn_t *) mi->functions)[ARC_ARCSET_INFO]) (&g2))) + { + int i = Ustrcmp(s, "pass") == 0 ? ARES_RESULT_PASS + : Ustrcmp(s, "fail") == 0 ? ARES_RESULT_FAIL + : ARES_RESULT_UNKNOWN; + + g = string_fmt_append(g, "arc %d\n" + "arc_policy %d json[%#Y ]\n", + i, + i == ARES_RESULT_PASS ? DMARC_ARC_POLICY_RESULT_PASS + : i == ARES_RESULT_FAIL ? DMARC_ARC_POLICY_RESULT_FAIL + : DMARC_ARC_POLICY_RESULT_UNUSED, + g2 + ); + } + else + string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", + ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); + } + # else g = string_fmt_append(g, "arc %d\narc_policy %d json:[]\n", ARES_RESULT_UNKNOWN, DMARC_ARC_POLICY_RESULT_UNUSED); diff --git a/src/src/miscmods/pdkim/pdkim.c b/src/src/miscmods/pdkim/pdkim.c index cdbdfc5e0..7c2f34217 100644 --- a/src/src/miscmods/pdkim/pdkim.c +++ b/src/src/miscmods/pdkim/pdkim.c @@ -935,13 +935,19 @@ return; 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); +gstring * g = ctx->cur_header; +const misc_module_info * mi; +typedef const uschar * (*fn_t)(gstring *, BOOL); + +if (gstring_length(g) > 1) + gstring_trim_trailing(g, '\r'); +(void) string_from_gstring(g); #ifdef EXPERIMENTAL_ARC -/* Feed the header line to ARC processing */ -(void) arc_header_feed(ctx->cur_header, !(ctx->flags & PDKIM_MODE_SIGN)); +/* Feed the header line also to ARC processing */ +if ((mi = misc_mod_findonly(US"arc"))) + (((fn_t *) mi->functions)[ARC_HEADER_FEED]) + (g, !(ctx->flags & PDKIM_MODE_SIGN)); #endif if (++ctx->num_headers > PDKIM_MAX_HEADERS) goto BAIL; @@ -951,7 +957,7 @@ 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); + sig->headers = pdkim_prepend_stringlist(sig->headers, g->s); /* VERIFICATION ----------------------------------------------------------- */ /* DKIM-Signature: headers are added to the verification list */ @@ -959,9 +965,9 @@ else { #ifdef notdef DEBUG(D_acl) debug_printf("DKIM >> raw hdr: %.*Z\n", - ctx->cur_head->ptr, CUS ctx->cur_header->s); + ctx->cur_head->ptr, CUS g->s); #endif - if (strncasecmp(CCS ctx->cur_header->s, + if (strncasecmp(CCS g->s, DKIM_SIGNATURE_HEADERNAME, Ustrlen(DKIM_SIGNATURE_HEADERNAME)) == 0) { @@ -973,7 +979,7 @@ else DEBUG(D_acl) debug_printf( "DKIM >> Found sig, trying to parse >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); - sig = pdkim_parse_sig_header(ctx, ctx->cur_header->s); + sig = pdkim_parse_sig_header(ctx, g->s); if (!(last_sig = ctx->sig)) ctx->sig = sig; @@ -985,18 +991,18 @@ else 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'; + ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s); + g->s[g->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); + ctx->headers = pdkim_prepend_stringlist(ctx->headers, g->s); } BAIL: -ctx->cur_header->s[ctx->cur_header->ptr = 0] = '\0'; /* leave buffer for reuse */ +g->s[g->ptr = 0] = '\0'; /* leave buffer for reuse */ return PDKIM_OK; } diff --git a/src/src/miscmods/spf_api.h b/src/src/miscmods/spf_api.h index 117a7ae6a..0e1907d9a 100644 --- a/src/src/miscmods/spf_api.h +++ b/src/src/miscmods/spf_api.h @@ -12,7 +12,7 @@ /* Function table entry numbers */ #define SPF_PROCESS 0 -#define SPF_GET_RESPONSE 2 -#define SPF_OPEN 3 -#define SPF_CLOSE 4 -#define SPF_FIND 5 +#define SPF_GET_RESPONSE 1 +#define SPF_OPEN 2 +#define SPF_CLOSE 3 +#define SPF_FIND 4 diff --git a/src/src/receive.c b/src/src/receive.c index 541e9320d..ae4e1ff7e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -4135,11 +4135,16 @@ if (LOGGING(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); + { + mi = misc_mod_findonly(US"arc"); + typedef BOOL (*fn_t)(void); + if (mi && (((fn_t *) mi->functions)[ARC_STATE_IS_PASS]) ()) + g = string_catn(g, US" ARC", 4); + } # endif + } #endif if (LOGGING(receive_time)) diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index e75894850..e76790fea 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1701,10 +1701,7 @@ bmi_run = 0; bmi_verdicts = NULL; #endif dnslist_domain = dnslist_matched = NULL; -#ifdef EXPERIMENTAL_ARC -arc_state = arc_state_reason = NULL; -arc_received_instance = 0; -#endif + dsn_ret = 0; dsn_envid = NULL; deliver_host = deliver_host_address = NULL; /* Can be set by ACL */ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 36b5e61fc..594b42e1f 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -32,7 +32,7 @@ optionlist smtp_transport_options[] = { LOFF(address_retry_include_sender) }, { "allow_localhost", opt_bool, LOFF(allow_localhost) }, #ifdef EXPERIMENTAL_ARC - { "arc_sign", opt_stringptr, LOFF(arc_sign) }, + { "arc_sign", opt_stringptr, LOFF(arc_sign) }, #endif { "authenticated_sender", opt_stringptr, LOFF(authenticated_sender) }, { "authenticated_sender_force", opt_bool, LOFF(authenticated_sender_force) }, @@ -4104,8 +4104,8 @@ else #ifndef DISABLE_DKIM { - typedef void (*fn_t)(void); misc_module_info * mi; + # ifdef MEASURE_TIMING struct timeval t0; gettimeofday(&t0, NULL); @@ -4113,27 +4113,30 @@ else if ((mi = misc_mod_find(US"dkim", NULL))) { + typedef void (*fn_t)(void); (((fn_t *) mi->functions)[DKIM_TRANSPORT_INIT]) (); # ifdef EXPERIMENTAL_ARC - uschar * s = ob->arc_sign; - if (s) { - if (!(ob->dkim.arc_signspec = s = expand_string(s))) - { - if (!f.expand_string_forcedfail) + uschar * s = ob->arc_sign; + if (s) + if (!(ob->dkim.arc_signspec = s = expand_string(s))) { - message = US"failed to expand arc_sign"; - sx->ok = FALSE; - goto SEND_FAILED; + if (!f.expand_string_forcedfail) + { + message = US"failed to expand arc_sign"; + sx->ok = FALSE; + goto SEND_FAILED; + } + } + else if (*s && (mi = misc_mod_find(US"arc", NULL))) + { + typedef void (*fn_t)(void); + (((fn_t *) mi->functions)[ARC_SIGN_INIT]) (); + + /* Ask dkim code to hash the body for ARC */ + ob->dkim.force_bodyhash = TRUE; } - } - else if (*s) - { - /* Ask dkim code to hash the body for ARC */ - (void) arc_ams_setup_sign_bodyhash(); - ob->dkim.force_bodyhash = TRUE; - } } # endif /*ARC*/ } -- 2.30.2