X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Farc.c;h=ddf73959c31eb69fec0326fd3da3b2cb06a1be65;hb=69a82da3e9018b082cee870030ecc557497301df;hp=a0e2184bddc513bec500b644ce11ba51b6f2a49c;hpb=d64f251592db09252ac0584adbe72a2eeb9ca122;p=exim.git diff --git a/src/src/arc.c b/src/src/arc.c index a0e2184bd..ddf73959c 100644 --- a/src/src/arc.c +++ b/src/src/arc.c @@ -21,6 +21,11 @@ extern pdkim_ctx * dkim_verify_ctx; extern pdkim_ctx dkim_sign_ctx; +#define ARC_SIGN_OPT_TSTAMP BIT(0) +#define ARC_SIGN_OPT_EXPIRE BIT(1) + +#define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */ + /******************************************************************************/ typedef struct hdr_rlist { @@ -84,6 +89,8 @@ typedef struct arc_ctx { #define HDR_AR US"Authentication-Results:" #define HDRLEN_AR 23 +static time_t now; +static time_t expire; static hdr_rlist * headers_rlist; static arc_ctx arc_sign_ctx = { NULL }; @@ -631,6 +638,7 @@ arc_ams_setup_vfy_bodyhash(arc_line * ams) int canon_head, canon_body; 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; @@ -759,7 +767,6 @@ arc_headers_check(arc_ctx * ctx) arc_set * as; int inst; BOOL ams_fail_found = FALSE; -uschar * ret = NULL; if (!(as = ctx->arcset_chain)) return US"none"; @@ -771,10 +778,10 @@ for(inst = 0; as; as = as->next) || arc_cv_match(as->hdr_as, US"fail") ) { - arc_state_reason = string_sprintf("i=%d fail" + arc_state_reason = string_sprintf("i=%d" " (cv, sequence or missing header)", as->instance); - DEBUG(D_acl) debug_printf("ARC %s\n", arc_state_reason); - ret = US"fail"; + DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason); + return US"fail"; } /* Evaluate the oldest-pass AMS validation while we're here. @@ -791,20 +798,20 @@ for(inst = 0; as; as = as->next) arc_received = ctx->arcset_chain_last; arc_received_instance = inst; -if (ret) - return ret; /* We can skip the latest-AMS validation, if we already did it. */ as = ctx->arcset_chain_last; -if (as->ams_verify_done && !as->ams_verify_passed) +if (!as->ams_verify_passed) { - arc_state_reason = as->ams_verify_done; - return US"fail"; + if (as->ams_verify_done) + { + arc_state_reason = as->ams_verify_done; + return US"fail"; + } + if (!!arc_ams_verify(ctx, as)) + return US"fail"; } -if (!!arc_ams_verify(ctx, as)) - return US"fail"; - return NULL; } @@ -993,6 +1000,12 @@ acl_verify_arc(void) arc_ctx ctx = { NULL }; const uschar * res; +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 */ @@ -1096,8 +1109,7 @@ return r; /* Walk the given headers strings identifying each header, and construct -a reverse-order list. Also parse ARC-chain headers and build the chain -struct, retaining pointers into the string. +a reverse-order list. */ static hdr_rlist * @@ -1233,7 +1245,7 @@ else if ( (errstr = exim_dkim_signing_init(privkey, &sctx)) || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig))) { - log_write(0, LOG_MAIN|LOG_PANIC, "ARC: %s signing: %s\n", why, errstr); + log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr); return FALSE; } return TRUE; @@ -1267,7 +1279,7 @@ return g; static gstring * arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance, const uschar * identity, const uschar * selector, blob * bodyhash, - hdr_rlist * rheaders, const uschar * privkey) + hdr_rlist * rheaders, const uschar * privkey, unsigned options) { uschar * s; gstring * hdata = NULL; @@ -1283,11 +1295,18 @@ header_line * h = (header_line *)(al+1); /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */ ams_off = g->ptr; -g = string_append(g, 10, +g = string_append(g, 7, ARC_HDR_AMS, US" i=", string_sprintf("%d", instance), US"; a=rsa-sha256; c=relaxed; d=", identity, /*XXX hardwired */ - US"; s=", selector, + US"; s=", selector); +if (options & ARC_SIGN_OPT_TSTAMP) + g = string_append(g, 2, + US"; t=", string_sprintf("%lu", (u_long)now)); +if (options & ARC_SIGN_OPT_EXPIRE) + g = string_append(g, 2, + US"; x=", string_sprintf("%lu", (u_long)expire)); +g = string_append(g, 3, US";\r\n\tbh=", pdkim_encode_base64(bodyhash), US";\r\n\th="); @@ -1375,7 +1394,7 @@ while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0))) (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++; return string_copyn(methodspec, s - methodspec); } -return NULL; +return US"none"; } @@ -1385,7 +1404,7 @@ return NULL; static gstring * arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx, int instance, const uschar * identity, const uschar * selector, blob * ar, - const uschar * privkey) + const uschar * privkey, unsigned options) { gstring * arcset; arc_set * as; @@ -1412,12 +1431,16 @@ blob sig; /* Construct the AS except for the signature */ -arcset = string_append(NULL, 10, +arcset = string_append(NULL, 9, ARC_HDR_AS, US" i=", string_sprintf("%d", instance), US"; cv=", status, US"; a=rsa-sha256; d=", identity, /*XXX hardwired */ - US"; s=", selector, /*XXX same as AMS */ + US"; s=", selector); /*XXX same as AMS */ +if (options & ARC_SIGN_OPT_TSTAMP) + arcset = string_append(arcset, 2, + US"; t=", string_sprintf("%lu", (u_long)now)); +arcset = string_cat(arcset, US";\r\n\t b=;"); h->slen = arcset->ptr; @@ -1480,6 +1503,14 @@ return pdkim_set_bodyhash(&dkim_sign_ctx, +void +arc_sign_init(void) +{ +memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); +} + + + /* A "normal" header line, identified by DKIM processing. These arrive before the call to arc_sign(), which carries any newly-created DKIM headers - and those go textually before the normal ones in the message. @@ -1507,7 +1538,8 @@ 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. Arguments: - signspec Three-element colon-sep list: identity, selector, privkey + 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 @@ -1520,7 +1552,8 @@ Return value gstring * arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr) { -const uschar * identity, * selector, * privkey; +const uschar * identity, * selector, * privkey, * opts, * s; +unsigned options = 0; int sep = 0; header_line * headers; hdr_rlist * rheaders; @@ -1529,55 +1562,95 @@ int instance; gstring * g = NULL; pdkim_bodyhash * b; +expire = now = 0; + /* Parse the signing specification */ identity = string_nextinlist(&signspec, &sep, NULL, 0); selector = string_nextinlist(&signspec, &sep, NULL, 0); -if ( !*identity | !*selector +if ( !*identity || !*selector || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) { - log_write(0, LOG_MAIN|LOG_PANIC, "ARC: bad signing-specification"); - return NULL; + log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", + !*identity ? "identity" : !*selector ? "selector" : "private-key"); + return sigheaders ? sigheaders : string_get(0); } if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey))) - return NULL; + return sigheaders ? sigheaders : string_get(0); -DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity); +if ((opts = string_nextinlist(&signspec, &sep, NULL, 0))) + { + int osep = ','; + while ((s = string_nextinlist(&opts, &osep, NULL, 0))) + if (Ustrcmp(s, "timestamps") == 0) + { + options |= ARC_SIGN_OPT_TSTAMP; + if (!now) now = time(NULL); + } + else if (Ustrncmp(s, "expire", 6) == 0) + { + options |= ARC_SIGN_OPT_EXPIRE; + if (*(s += 6) == '=') + if (*++s == '+') + { + if (!(expire = (time_t)atoi(++s))) + expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA; + if (!now) now = time(NULL); + expire += now; + } + else + expire = (time_t)atol(s); + else + { + if (!now) now = time(NULL); + expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA; + } + } + } -/* -- scan headers for existing ARC chain & A-R (with matching system-identfier) - - paniclog & skip on problems (no A-R) -*/ +DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity); -/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it */ +/* Make an rlist of any new DKIM headers, then add the "normals" rlist to it. +Then scan the list for an A-R header. */ string_from_gstring(sigheaders); if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders))) { hdr_rlist ** rp; - for (rp = &rheaders; *rp; ) rp = &(*rp)->prev; - *rp = headers_rlist; - headers_rlist = rheaders; + for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev; + *rp = rheaders; } -else - rheaders = headers_rlist; + /* Finally, build a normal-order headers list */ /*XXX only needed for hunt-the-AR? */ -{ -header_line * hnext = NULL; -for (; rheaders; hnext = rheaders->h, rheaders = rheaders->prev) - rheaders->h->next = hnext; -headers = hnext; -} - -instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1; +/*XXX also, we really should be accepting any number of ADMD-matching ARs */ + { + header_line * hnext = NULL; + for (rheaders = headers_rlist; rheaders; + hnext = rheaders->h, rheaders = rheaders->prev) + rheaders->h->next = hnext; + headers = hnext; + } if (!(arc_sign_find_ar(headers, identity, &ar))) { - log_write(0, LOG_MAIN|LOG_PANIC, "ARC: no Authentication-Results header for signing"); + log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing"); return sigheaders ? sigheaders : string_get(0); } +/* We previously built the data-struct for the existing ARC chain, if any, using a headers +feed from the DKIM module. Use that to give the instance number for the ARC set we are +about to build. */ + +DEBUG(D_transport) + if (arc_sign_ctx.arcset_chain_last) + debug_printf("ARC: existing chain highest instance: %d\n", + arc_sign_ctx.arcset_chain_last->instance); + else + debug_printf("ARC: no existing chain\n"); + +instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1; + /* - Generate AAR - copy the A-R; prepend i= & identity @@ -1596,7 +1669,7 @@ g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar); b = arc_ams_setup_sign_bodyhash(); g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector, - &b->bh, headers_rlist, privkey); + &b->bh, headers_rlist, privkey, options); /* - Generate AS @@ -1611,7 +1684,8 @@ g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector, including self (but with an empty b= in self) */ -g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, privkey); +g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, + privkey, options); /* Finally, append the dkim headers and return the lot. */ @@ -1725,6 +1799,8 @@ if (arc_state) if (sender_host_address) g = string_append(g, 2, US" smtp.client-ip=", sender_host_address); } + else if (arc_state_reason) + g = string_append(g, 3, US" (", arc_state_reason, US")"); DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3); }