X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/7d4064fb2ca678359a6fa29a30a00c08fa75e70a..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/arc.c diff --git a/src/src/arc.c b/src/src/arc.c index 8a94015b3..e3a2b3dad 100644 --- a/src/src/arc.c +++ b/src/src/arc.c @@ -2,8 +2,10 @@ * 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" @@ -141,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), FALSE); +*pas = as = store_get(sizeof(arc_set), GET_UNTAINTED); memset(as, 0, sizeof(arc_set)); as->next = next; as->prev = prev; @@ -199,7 +201,7 @@ al->complete = h; if (!instance_only) { - al->rawsig_no_b_val.data = store_get(h->slen + 1, TRUE); /* tainted */ + 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; @@ -381,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), FALSE), ** alp; +arc_line * al = store_get(sizeof(arc_line), GET_UNTAINTED), ** alp; uschar * e; memset(al, 0, sizeof(arc_line)); @@ -401,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; } @@ -424,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"; @@ -443,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; @@ -469,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"; @@ -497,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), FALSE); + r = store_get(sizeof(hdr_rlist), GET_UNTAINTED); r->prev = rprev; r->used = FALSE; r->h = h; @@ -569,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; } @@ -736,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"; @@ -965,7 +966,7 @@ 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"; @@ -1103,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), FALSE); +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; @@ -1113,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; } @@ -1193,9 +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; +int aar_off = gstring_length(g); arc_set * as = - store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), FALSE); + 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); @@ -1305,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), FALSE); +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__); */ @@ -1419,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), FALSE); +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 */ @@ -1440,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 */ @@ -1463,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)); } @@ -1491,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; } @@ -1519,6 +1528,7 @@ void arc_sign_init(void) { memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); +headers_rlist = NULL; } @@ -1545,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. @@ -1578,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))) { @@ -1647,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 @@ -1703,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_release_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; }