X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b4dd15a736795e6d06a79febbae76a926964bbb0..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/arc.c diff --git a/src/src/arc.c b/src/src/arc.c index e7ebb9719..e3a2b3dad 100644 --- a/src/src/arc.c +++ b/src/src/arc.c @@ -2,15 +2,15 @@ * Exim - an Internet mail transport agent * *************************************************/ /* Experimental ARC support for Exim - Copyright (c) Jeremy Harris 2018 + Copyright (c) Jeremy Harris 2018 - 2020 + Copyright (c) The Exim Maintainers 2021 - 2022 License: GPL + SPDX-License-Identifier: GPL-2.0-only */ #include "exim.h" -#ifdef EXPERIMENTAL_ARC -# if !defined SUPPORT_SPF -# error SPF must also be enabled for ARC -# elif defined DISABLE_DKIM +#if defined EXPERIMENTAL_ARC +# if defined DISABLE_DKIM # error DKIM must also be enabled for ARC # else @@ -143,7 +143,7 @@ for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain; } DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i); -*pas = as = store_get(sizeof(arc_set)); +*pas = as = store_get(sizeof(arc_set), GET_UNTAINTED); memset(as, 0, sizeof(arc_set)); as->next = next; as->prev = prev; @@ -201,7 +201,7 @@ al->complete = h; if (!instance_only) { - al->rawsig_no_b_val.data = store_get(h->slen + 1); + al->rawsig_no_b_val.data = store_get(h->slen + 1, GET_TAINTED); memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */ r = al->rawsig_no_b_val.data + off; al->rawsig_no_b_val.len = off; @@ -263,7 +263,7 @@ while ((c = *s)) if (!g) return US"no b= value"; al->b.data = string_from_gstring(g); al->b.len = g->ptr; - gstring_reset_unused(g); + gstring_release_unused(g); bend = s; break; case 'h': /* bh= AMS body hash */ @@ -280,7 +280,7 @@ while ((c = *s)) if (!g) return US"no bh= value"; al->bh.data = string_from_gstring(g); al->bh.len = g->ptr; - gstring_reset_unused(g); + gstring_release_unused(g); break; default: return US"b? tag"; @@ -383,11 +383,11 @@ adding instances as needed and checking for duplicate lines. static uschar * arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff, - BOOL instance_only) + BOOL instance_only, arc_line ** alp_ret) { unsigned i; arc_set * as; -arc_line * al = store_get(sizeof(arc_line)), ** alp; +arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp; uschar * e; memset(al, 0, sizeof(arc_line)); @@ -403,6 +403,7 @@ if (!(as = arc_find_set(ctx, i))) return US"set find"; if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr"; *alp = al; +if (alp_ret) *alp_ret = al; return NULL; } @@ -426,7 +427,7 @@ if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0) debug_printf("ARC: found AAR: %.*s\n", len, h->text); } if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar), - TRUE))) + TRUE, NULL))) { DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e); return US"inserting AAR"; @@ -445,15 +446,13 @@ else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0) debug_printf("ARC: found AMS: %.*s\n", len, h->text); } if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams), - instance_only))) + instance_only, &ams))) { DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e); return US"inserting AMS"; } /* defaults */ - /*XXX dubious selection of ams here */ - ams = ctx->arcset_chain->hdr_ams; if (!ams->c.data) { ams->c_head.data = US"simple"; ams->c_head.len = 6; @@ -471,7 +470,7 @@ else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0) debug_printf("ARC: found AS: %.*s\n", len, h->text); } if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as), - instance_only))) + instance_only, NULL))) { DEBUG(D_acl) debug_printf("inserting AS: %s\n", e); return US"inserting AS"; @@ -499,7 +498,7 @@ const uschar * e; DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n"); for (h = header_list; h; h = h->next) { - r = store_get(sizeof(hdr_rlist)); + r = store_get(sizeof(hdr_rlist), GET_UNTAINTED); r->prev = rprev; r->used = FALSE; r->h = h; @@ -544,7 +543,8 @@ hctx hhash_ctx; const uschar * s; int len; -if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) +if ( hashtype == -1 + || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) { DEBUG(D_acl) debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); @@ -570,7 +570,7 @@ while ((hn = string_nextinlist(&headernames, &sep, NULL, 0))) len = Ustrlen(s); DEBUG(D_acl) pdkim_quoteprint(s, len); - exim_sha_update(&hhash_ctx, s, Ustrlen(s)); + exim_sha_update_string(&hhash_ctx, s); r->used = TRUE; break; } @@ -599,7 +599,7 @@ uschar * dns_txt; pdkim_pubkey * p; if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s", - al->s.len, al->s.data, al->d.len, al->d.data)))) + (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; @@ -639,7 +639,7 @@ return p; static pdkim_bodyhash * arc_ams_setup_vfy_bodyhash(arc_line * ams) { -int canon_head, canon_body; +int canon_head = -1, canon_body = -1; long bodylen; if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */ @@ -737,7 +737,7 @@ arc_get_verify_hhash(ctx, ams, &hhash); /* Setup the interface to the signing library */ -if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) +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"; @@ -745,6 +745,11 @@ if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) } hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len); +if (hashtype == -1) + { + 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))) @@ -871,7 +876,8 @@ if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none") hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len); -if (!exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) +if ( hashtype == -1 + || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) { DEBUG(D_acl) debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); @@ -960,14 +966,12 @@ if (!(p = arc_line_to_pubkey(hdr_as))) /* We know the b-tag blob is of a nul-term string, so safe as a string */ pdkim_decode_base64(hdr_as->b.data, &sighash); -if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) +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"; } -hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len); - if ((errstr = exim_dkim_verify(&vctx, pdkim_hashes[hashtype].exim_hashmethod, &hhash_computed, &sighash))) @@ -1100,7 +1104,7 @@ out: static hdr_rlist * arc_rlist_entry(hdr_rlist * list, const uschar * s, int len) { -hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line)); +hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), GET_UNTAINTED); header_line * h = r->h = (header_line *)(r+1); r->prev = list; @@ -1110,11 +1114,6 @@ h->type = 0; h->slen = len; h->text = US s; -/* This works for either NL or CRLF lines; also nul-termination */ -while (*++s) - if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break; -s++; /* move past end of line */ - return r; } @@ -1190,8 +1189,9 @@ static gstring * arc_sign_append_aar(gstring * g, arc_ctx * ctx, const uschar * identity, int instance, blob * ar) { -int aar_off = g ? g->ptr : 0; -arc_set * as = store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line)); +int aar_off = gstring_length(g); +arc_set * as = + store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), GET_UNTAINTED); arc_line * al = (arc_line *)(as+1); header_line * h = (header_line *)(al+1); @@ -1282,7 +1282,7 @@ for (;;) g = string_catn(g, US"\r\n\t ", 5); } g = string_catn(g, US";\r\n", 3); -gstring_reset_unused(g); +gstring_release_unused(g); string_from_gstring(g); return g; } @@ -1301,7 +1301,7 @@ int col; int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ blob sig; int ams_off; -arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line)); +arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED); header_line * h = (header_line *)(al+1); /* debug_printf("%s\n", __FUNCTION__); */ @@ -1415,10 +1415,10 @@ arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx, const uschar * privkey, unsigned options) { gstring * arcset; -arc_set * as; uschar * status = arc_ar_cv_status(ar); -arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line)); +arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), GET_UNTAINTED); header_line * h = (header_line *)(al+1); +uschar * badline_str; gstring * hdata = NULL; int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ @@ -1436,6 +1436,7 @@ blob sig; - all ARC set headers, set-number order, aar then ams then as, including self (but with an empty b= in self) */ +DEBUG(D_transport) debug_printf("ARC: building AS for status '%s'\n", status); /* Construct the AS except for the signature */ @@ -1459,18 +1460,25 @@ ctx->arcset_chain_last->hdr_as = al; /* For any but "fail" chain-verify status, walk the entire chain in order by instance. For fail, only the new arc-set. Accumulate the elements walked. */ -for (as = Ustrcmp(status, US"fail") == 0 +for (arc_set * as = Ustrcmp(status, US"fail") == 0 ? ctx->arcset_chain_last : ctx->arcset_chain; as; as = as->next) { + arc_line * l; /* Accumulate AAR then AMS then AS. Relaxed canonicalisation is required per standard. */ - h = as->hdr_aar->complete; + badline_str = US"aar"; + if (!(l = as->hdr_aar)) goto badline; + h = l->complete; hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); - h = as->hdr_ams->complete; + badline_str = US"ams"; + if (!(l = as->hdr_ams)) goto badline; + h = l->complete; hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); - h = as->hdr_as->complete; + badline_str = US"as"; + if (!(l = as->hdr_as)) goto badline; + h = l->complete; hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next)); } @@ -1487,6 +1495,11 @@ DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s) /* Finally, append the AMS and AAR to the new AS */ return string_catn(arcset, arcset_interim->s, arcset_interim->ptr); + +badline: + DEBUG(D_transport) + debug_printf("ARC: while building AS, missing %s in chain\n", badline_str); + return NULL; } @@ -1515,6 +1528,7 @@ void arc_sign_init(void) { memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); +headers_rlist = NULL; } @@ -1541,6 +1555,23 @@ return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE); +/* Per RFCs 6376, 7489 the only allowed chars in either an ADMD id +or a selector are ALPHA/DIGGIT/'-'/'.' + +Check, to help catch misconfigurations such as a missing selector +element in the arc_sign list. +*/ + +static BOOL +arc_valid_id(const uschar * s) +{ +for (uschar c; c = *s++; ) + if (!isalnum(c) && c != '-' && c != '.') return FALSE; +return TRUE; +} + + + /* 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. @@ -1574,17 +1605,18 @@ expire = now = 0; /* Parse the signing specification */ -identity = string_nextinlist(&signspec, &sep, NULL, 0); -selector = string_nextinlist(&signspec, &sep, NULL, 0); -if ( !*identity || !*selector - || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) - { - log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", - !*identity ? "identity" : !*selector ? "selector" : "private-key"); - return sigheaders ? sigheaders : string_get(0); - } +if (!(identity = string_nextinlist(&signspec, &sep, NULL, 0)) || !*identity) + { s = US"identity"; goto bad_arg_ret; } +if (!(selector = string_nextinlist(&signspec, &sep, NULL, 0)) || !*selector) + { s = US"selector"; goto bad_arg_ret; } +if (!(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) + { s = US"privkey"; goto bad_arg_ret; } +if (!arc_valid_id(identity)) + { s = US"identity"; goto bad_arg_ret; } +if (!arc_valid_id(selector)) + { s = US"selector"; goto bad_arg_ret; } if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey))) - return sigheaders ? sigheaders : string_get(0); + goto ret_sigheaders; if ((opts = string_nextinlist(&signspec, &sep, NULL, 0))) { @@ -1643,7 +1675,7 @@ if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders))) if (!(arc_sign_find_ar(headers, identity, &ar))) { log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing"); - return sigheaders ? sigheaders : string_get(0); + goto ret_sigheaders; } /* We previously built the data-struct for the existing ARC chain, if any, using a headers @@ -1699,9 +1731,19 @@ if (g) /* Finally, append the dkim headers and return the lot. */ if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr); -(void) string_from_gstring(g); -gstring_reset_unused(g); -return g; + +out: + if (!g) return string_get(1); + (void) string_from_gstring(g); + gstring_release_unused(g); + return g; + + +bad_arg_ret: + log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", s); +ret_sigheaders: + g = sigheaders; + goto out; } @@ -1737,7 +1779,13 @@ memset(&al, 0, sizeof(arc_line)); if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE))) { DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr); - return US"line parsing error"; + goto badline; + } + +if (!al.a_hash.data) + { + DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text); + goto badline; } /* defaults */ @@ -1756,6 +1804,9 @@ if (!(b = arc_ams_setup_vfy_bodyhash(&al))) should have been created here. */ return NULL; + +badline: + return US"line parsing error"; } @@ -1847,7 +1898,7 @@ return g; } -# endif /* SUPPORT_SPF */ +# endif /* DISABLE_DKIM */ #endif /* EXPERIMENTAL_ARC */ /* vi: aw ai sw=2 */