| 1 | /************************************************* |
| 2 | * Exim - an Internet mail transport agent * |
| 3 | *************************************************/ |
| 4 | /* Experimental ARC support for Exim |
| 5 | Copyright (c) Jeremy Harris 2018 |
| 6 | License: GPL |
| 7 | */ |
| 8 | |
| 9 | #include "exim.h" |
| 10 | #ifdef EXPERIMENTAL_ARC |
| 11 | # if !defined SUPPORT_SPF |
| 12 | # error SPF must also be enabled for ARC |
| 13 | # elif defined DISABLE_DKIM |
| 14 | # error DKIM must also be enabled for ARC |
| 15 | # else |
| 16 | |
| 17 | # include "functions.h" |
| 18 | # include "pdkim/pdkim.h" |
| 19 | # include "pdkim/signing.h" |
| 20 | |
| 21 | extern pdkim_ctx * dkim_verify_ctx; |
| 22 | extern pdkim_ctx dkim_sign_ctx; |
| 23 | |
| 24 | #define ARC_SIGN_OPT_TSTAMP BIT(0) |
| 25 | #define ARC_SIGN_OPT_EXPIRE BIT(1) |
| 26 | |
| 27 | #define ARC_SIGN_DEFAULT_EXPIRE_DELTA (60 * 60 * 24 * 30) /* one month */ |
| 28 | |
| 29 | /******************************************************************************/ |
| 30 | |
| 31 | typedef struct hdr_rlist { |
| 32 | struct hdr_rlist * prev; |
| 33 | BOOL used; |
| 34 | header_line * h; |
| 35 | } hdr_rlist; |
| 36 | |
| 37 | typedef struct arc_line { |
| 38 | header_line * complete; /* including the header name; nul-term */ |
| 39 | uschar * relaxed; |
| 40 | |
| 41 | /* identified tag contents */ |
| 42 | /*XXX t= for AS? */ |
| 43 | blob i; |
| 44 | blob cv; |
| 45 | blob a; |
| 46 | blob b; |
| 47 | blob bh; |
| 48 | blob d; |
| 49 | blob h; |
| 50 | blob s; |
| 51 | blob c; |
| 52 | blob l; |
| 53 | |
| 54 | /* tag content sub-portions */ |
| 55 | blob a_algo; |
| 56 | blob a_hash; |
| 57 | |
| 58 | blob c_head; |
| 59 | blob c_body; |
| 60 | |
| 61 | /* modified copy of b= field in line */ |
| 62 | blob rawsig_no_b_val; |
| 63 | } arc_line; |
| 64 | |
| 65 | typedef struct arc_set { |
| 66 | struct arc_set * next; |
| 67 | struct arc_set * prev; |
| 68 | |
| 69 | unsigned instance; |
| 70 | arc_line * hdr_aar; |
| 71 | arc_line * hdr_ams; |
| 72 | arc_line * hdr_as; |
| 73 | |
| 74 | const uschar * ams_verify_done; |
| 75 | BOOL ams_verify_passed; |
| 76 | } arc_set; |
| 77 | |
| 78 | typedef struct arc_ctx { |
| 79 | arc_set * arcset_chain; |
| 80 | arc_set * arcset_chain_last; |
| 81 | } arc_ctx; |
| 82 | |
| 83 | #define ARC_HDR_AAR US"ARC-Authentication-Results:" |
| 84 | #define ARC_HDRLEN_AAR 27 |
| 85 | #define ARC_HDR_AMS US"ARC-Message-Signature:" |
| 86 | #define ARC_HDRLEN_AMS 22 |
| 87 | #define ARC_HDR_AS US"ARC-Seal:" |
| 88 | #define ARC_HDRLEN_AS 9 |
| 89 | #define HDR_AR US"Authentication-Results:" |
| 90 | #define HDRLEN_AR 23 |
| 91 | |
| 92 | static time_t now; |
| 93 | static time_t expire; |
| 94 | static hdr_rlist * headers_rlist; |
| 95 | static arc_ctx arc_sign_ctx = { NULL }; |
| 96 | static arc_ctx arc_verify_ctx = { NULL }; |
| 97 | |
| 98 | |
| 99 | /******************************************************************************/ |
| 100 | |
| 101 | |
| 102 | /* Get the instance number from the header. |
| 103 | Return 0 on error */ |
| 104 | static unsigned |
| 105 | arc_instance_from_hdr(const arc_line * al) |
| 106 | { |
| 107 | const uschar * s = al->i.data; |
| 108 | if (!s || !al->i.len) return 0; |
| 109 | return (unsigned) atoi(CCS s); |
| 110 | } |
| 111 | |
| 112 | |
| 113 | static uschar * |
| 114 | skip_fws(uschar * s) |
| 115 | { |
| 116 | uschar c = *s; |
| 117 | while (c && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) c = *++s; |
| 118 | return s; |
| 119 | } |
| 120 | |
| 121 | |
| 122 | /* Locate instance struct on chain, inserting a new one if |
| 123 | needed. The chain is in increasing-instance-number order |
| 124 | by the "next" link, and we have a "prev" link also. |
| 125 | */ |
| 126 | |
| 127 | static arc_set * |
| 128 | arc_find_set(arc_ctx * ctx, unsigned i) |
| 129 | { |
| 130 | arc_set ** pas, * as, * next, * prev; |
| 131 | |
| 132 | for (pas = &ctx->arcset_chain, prev = NULL, next = ctx->arcset_chain; |
| 133 | as = *pas; pas = &as->next) |
| 134 | { |
| 135 | if (as->instance > i) break; |
| 136 | if (as->instance == i) |
| 137 | { |
| 138 | DEBUG(D_acl) debug_printf("ARC: existing instance %u\n", i); |
| 139 | return as; |
| 140 | } |
| 141 | next = as->next; |
| 142 | prev = as; |
| 143 | } |
| 144 | |
| 145 | DEBUG(D_acl) debug_printf("ARC: new instance %u\n", i); |
| 146 | *pas = as = store_get(sizeof(arc_set), FALSE); |
| 147 | memset(as, 0, sizeof(arc_set)); |
| 148 | as->next = next; |
| 149 | as->prev = prev; |
| 150 | as->instance = i; |
| 151 | if (next) |
| 152 | next->prev = as; |
| 153 | else |
| 154 | ctx->arcset_chain_last = as; |
| 155 | return as; |
| 156 | } |
| 157 | |
| 158 | |
| 159 | |
| 160 | /* Insert a tag content into the line structure. |
| 161 | Note this is a reference to existing data, not a copy. |
| 162 | Check for already-seen tag. |
| 163 | The string-pointer is on the '=' for entry. Update it past the |
| 164 | content (to the ;) on return; |
| 165 | */ |
| 166 | |
| 167 | static uschar * |
| 168 | arc_insert_tagvalue(arc_line * al, unsigned loff, uschar ** ss) |
| 169 | { |
| 170 | uschar * s = *ss; |
| 171 | uschar c = *++s; |
| 172 | blob * b = (blob *)(US al + loff); |
| 173 | size_t len = 0; |
| 174 | |
| 175 | /* [FWS] tag-value [FWS] */ |
| 176 | |
| 177 | if (b->data) return US"fail"; |
| 178 | s = skip_fws(s); /* FWS */ |
| 179 | |
| 180 | b->data = s; |
| 181 | while ((c = *s) && c != ';') { len++; s++; } |
| 182 | *ss = s; |
| 183 | while (len && ((c = s[-1]) == ' ' || c == '\t' || c == '\n' || c == '\r')) |
| 184 | { s--; len--; } /* FWS */ |
| 185 | b->len = len; |
| 186 | return NULL; |
| 187 | } |
| 188 | |
| 189 | |
| 190 | /* Inspect a header line, noting known tag fields. |
| 191 | Check for duplicates. */ |
| 192 | |
| 193 | static uschar * |
| 194 | arc_parse_line(arc_line * al, header_line * h, unsigned off, BOOL instance_only) |
| 195 | { |
| 196 | uschar * s = h->text + off; |
| 197 | uschar * r = NULL; /* compiler-quietening */ |
| 198 | uschar c; |
| 199 | |
| 200 | al->complete = h; |
| 201 | |
| 202 | if (!instance_only) |
| 203 | { |
| 204 | al->rawsig_no_b_val.data = store_get(h->slen + 1, TRUE); /* tainted */ |
| 205 | memcpy(al->rawsig_no_b_val.data, h->text, off); /* copy the header name blind */ |
| 206 | r = al->rawsig_no_b_val.data + off; |
| 207 | al->rawsig_no_b_val.len = off; |
| 208 | } |
| 209 | |
| 210 | /* tag-list = tag-spec *( ";" tag-spec ) [ ";" ] */ |
| 211 | |
| 212 | while ((c = *s)) |
| 213 | { |
| 214 | char tagchar; |
| 215 | uschar * t; |
| 216 | unsigned i = 0; |
| 217 | uschar * fieldstart = s; |
| 218 | uschar * bstart = NULL, * bend; |
| 219 | |
| 220 | /* tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] */ |
| 221 | |
| 222 | s = skip_fws(s); /* FWS */ |
| 223 | if (!*s) break; |
| 224 | /* debug_printf("%s: consider '%s'\n", __FUNCTION__, s); */ |
| 225 | tagchar = *s++; |
| 226 | s = skip_fws(s); /* FWS */ |
| 227 | if (!*s) break; |
| 228 | |
| 229 | if (!instance_only || tagchar == 'i') switch (tagchar) |
| 230 | { |
| 231 | case 'a': /* a= AMS algorithm */ |
| 232 | { |
| 233 | if (*s != '=') return US"no 'a' value"; |
| 234 | if (arc_insert_tagvalue(al, offsetof(arc_line, a), &s)) return US"a tag dup"; |
| 235 | |
| 236 | /* substructure: algo-hash (eg. rsa-sha256) */ |
| 237 | |
| 238 | t = al->a_algo.data = al->a.data; |
| 239 | while (*t != '-') |
| 240 | if (!*t++ || ++i > al->a.len) return US"no '-' in 'a' value"; |
| 241 | al->a_algo.len = i; |
| 242 | if (*t++ != '-') return US"no '-' in 'a' value"; |
| 243 | al->a_hash.data = t; |
| 244 | al->a_hash.len = al->a.len - i - 1; |
| 245 | } |
| 246 | break; |
| 247 | case 'b': |
| 248 | { |
| 249 | gstring * g = NULL; |
| 250 | |
| 251 | switch (*s) |
| 252 | { |
| 253 | case '=': /* b= AMS signature */ |
| 254 | if (al->b.data) return US"already b data"; |
| 255 | bstart = s+1; |
| 256 | |
| 257 | /* The signature can have FWS inserted in the content; |
| 258 | make a stripped copy */ |
| 259 | |
| 260 | while ((c = *++s) && c != ';') |
| 261 | if (c != ' ' && c != '\t' && c != '\n' && c != '\r') |
| 262 | g = string_catn(g, s, 1); |
| 263 | if (!g) return US"no b= value"; |
| 264 | al->b.data = string_from_gstring(g); |
| 265 | al->b.len = g->ptr; |
| 266 | gstring_release_unused(g); |
| 267 | bend = s; |
| 268 | break; |
| 269 | case 'h': /* bh= AMS body hash */ |
| 270 | s = skip_fws(++s); /* FWS */ |
| 271 | if (*s != '=') return US"no bh value"; |
| 272 | if (al->bh.data) return US"already bh data"; |
| 273 | |
| 274 | /* The bodyhash can have FWS inserted in the content; |
| 275 | make a stripped copy */ |
| 276 | |
| 277 | while ((c = *++s) && c != ';') |
| 278 | if (c != ' ' && c != '\t' && c != '\n' && c != '\r') |
| 279 | g = string_catn(g, s, 1); |
| 280 | if (!g) return US"no bh= value"; |
| 281 | al->bh.data = string_from_gstring(g); |
| 282 | al->bh.len = g->ptr; |
| 283 | gstring_release_unused(g); |
| 284 | break; |
| 285 | default: |
| 286 | return US"b? tag"; |
| 287 | } |
| 288 | } |
| 289 | break; |
| 290 | case 'c': |
| 291 | switch (*s) |
| 292 | { |
| 293 | case '=': /* c= AMS canonicalisation */ |
| 294 | if (arc_insert_tagvalue(al, offsetof(arc_line, c), &s)) return US"c tag dup"; |
| 295 | |
| 296 | /* substructure: head/body (eg. relaxed/simple)) */ |
| 297 | |
| 298 | t = al->c_head.data = al->c.data; |
| 299 | while (isalpha(*t)) |
| 300 | if (!*t++ || ++i > al->a.len) break; |
| 301 | al->c_head.len = i; |
| 302 | if (*t++ == '/') /* /body is optional */ |
| 303 | { |
| 304 | al->c_body.data = t; |
| 305 | al->c_body.len = al->c.len - i - 1; |
| 306 | } |
| 307 | else |
| 308 | { |
| 309 | al->c_body.data = US"simple"; |
| 310 | al->c_body.len = 6; |
| 311 | } |
| 312 | break; |
| 313 | case 'v': /* cv= AS validity */ |
| 314 | if (*++s != '=') return US"cv tag val"; |
| 315 | if (arc_insert_tagvalue(al, offsetof(arc_line, cv), &s)) return US"cv tag dup"; |
| 316 | break; |
| 317 | default: |
| 318 | return US"c? tag"; |
| 319 | } |
| 320 | break; |
| 321 | case 'd': /* d= AMS domain */ |
| 322 | if (*s != '=') return US"d tag val"; |
| 323 | if (arc_insert_tagvalue(al, offsetof(arc_line, d), &s)) return US"d tag dup"; |
| 324 | break; |
| 325 | case 'h': /* h= AMS headers */ |
| 326 | if (*s != '=') return US"h tag val"; |
| 327 | if (arc_insert_tagvalue(al, offsetof(arc_line, h), &s)) return US"h tag dup"; |
| 328 | break; |
| 329 | case 'i': /* i= ARC set instance */ |
| 330 | if (*s != '=') return US"i tag val"; |
| 331 | if (arc_insert_tagvalue(al, offsetof(arc_line, i), &s)) return US"i tag dup"; |
| 332 | if (instance_only) goto done; |
| 333 | break; |
| 334 | case 'l': /* l= bodylength */ |
| 335 | if (*s != '=') return US"l tag val"; |
| 336 | if (arc_insert_tagvalue(al, offsetof(arc_line, l), &s)) return US"l tag dup"; |
| 337 | break; |
| 338 | case 's': /* s= AMS selector */ |
| 339 | if (*s != '=') return US"s tag val"; |
| 340 | if (arc_insert_tagvalue(al, offsetof(arc_line, s), &s)) return US"s tag dup"; |
| 341 | break; |
| 342 | } |
| 343 | |
| 344 | while ((c = *s) && c != ';') s++; |
| 345 | if (c) s++; /* ; after tag-spec */ |
| 346 | |
| 347 | /* for all but the b= tag, copy the field including FWS. For the b=, |
| 348 | drop the tag content. */ |
| 349 | |
| 350 | if (!instance_only) |
| 351 | if (bstart) |
| 352 | { |
| 353 | size_t n = bstart - fieldstart; |
| 354 | memcpy(r, fieldstart, n); /* FWS "b=" */ |
| 355 | r += n; |
| 356 | al->rawsig_no_b_val.len += n; |
| 357 | n = s - bend; |
| 358 | memcpy(r, bend, n); /* FWS ";" */ |
| 359 | r += n; |
| 360 | al->rawsig_no_b_val.len += n; |
| 361 | } |
| 362 | else |
| 363 | { |
| 364 | size_t n = s - fieldstart; |
| 365 | memcpy(r, fieldstart, n); |
| 366 | r += n; |
| 367 | al->rawsig_no_b_val.len += n; |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | if (!instance_only) |
| 372 | *r = '\0'; |
| 373 | |
| 374 | done: |
| 375 | /* debug_printf("%s: finshed\n", __FUNCTION__); */ |
| 376 | return NULL; |
| 377 | } |
| 378 | |
| 379 | |
| 380 | /* Insert one header line in the correct set of the chain, |
| 381 | adding instances as needed and checking for duplicate lines. |
| 382 | */ |
| 383 | |
| 384 | static uschar * |
| 385 | arc_insert_hdr(arc_ctx * ctx, header_line * h, unsigned off, unsigned hoff, |
| 386 | BOOL instance_only) |
| 387 | { |
| 388 | unsigned i; |
| 389 | arc_set * as; |
| 390 | arc_line * al = store_get(sizeof(arc_line), FALSE), ** alp; |
| 391 | uschar * e; |
| 392 | |
| 393 | memset(al, 0, sizeof(arc_line)); |
| 394 | |
| 395 | if ((e = arc_parse_line(al, h, off, instance_only))) |
| 396 | { |
| 397 | DEBUG(D_acl) if (e) debug_printf("ARC: %s\n", e); |
| 398 | return US"line parse"; |
| 399 | } |
| 400 | if (!(i = arc_instance_from_hdr(al))) return US"instance find"; |
| 401 | if (i > 50) return US"overlarge instance number"; |
| 402 | if (!(as = arc_find_set(ctx, i))) return US"set find"; |
| 403 | if (*(alp = (arc_line **)(US as + hoff))) return US"dup hdr"; |
| 404 | |
| 405 | *alp = al; |
| 406 | return NULL; |
| 407 | } |
| 408 | |
| 409 | |
| 410 | |
| 411 | |
| 412 | static const uschar * |
| 413 | arc_try_header(arc_ctx * ctx, header_line * h, BOOL instance_only) |
| 414 | { |
| 415 | const uschar * e; |
| 416 | |
| 417 | /*debug_printf("consider hdr '%s'\n", h->text);*/ |
| 418 | if (strncmpic(ARC_HDR_AAR, h->text, ARC_HDRLEN_AAR) == 0) |
| 419 | { |
| 420 | DEBUG(D_acl) |
| 421 | { |
| 422 | int len = h->slen; |
| 423 | uschar * s; |
| 424 | for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) |
| 425 | s--, len--; |
| 426 | debug_printf("ARC: found AAR: %.*s\n", len, h->text); |
| 427 | } |
| 428 | if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AAR, offsetof(arc_set, hdr_aar), |
| 429 | TRUE))) |
| 430 | { |
| 431 | DEBUG(D_acl) debug_printf("inserting AAR: %s\n", e); |
| 432 | return US"inserting AAR"; |
| 433 | } |
| 434 | } |
| 435 | else if (strncmpic(ARC_HDR_AMS, h->text, ARC_HDRLEN_AMS) == 0) |
| 436 | { |
| 437 | arc_line * ams; |
| 438 | |
| 439 | DEBUG(D_acl) |
| 440 | { |
| 441 | int len = h->slen; |
| 442 | uschar * s; |
| 443 | for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) |
| 444 | s--, len--; |
| 445 | debug_printf("ARC: found AMS: %.*s\n", len, h->text); |
| 446 | } |
| 447 | if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AMS, offsetof(arc_set, hdr_ams), |
| 448 | instance_only))) |
| 449 | { |
| 450 | DEBUG(D_acl) debug_printf("inserting AMS: %s\n", e); |
| 451 | return US"inserting AMS"; |
| 452 | } |
| 453 | |
| 454 | /* defaults */ |
| 455 | /*XXX dubious selection of ams here */ |
| 456 | ams = ctx->arcset_chain->hdr_ams; |
| 457 | if (!ams->c.data) |
| 458 | { |
| 459 | ams->c_head.data = US"simple"; ams->c_head.len = 6; |
| 460 | ams->c_body = ams->c_head; |
| 461 | } |
| 462 | } |
| 463 | else if (strncmpic(ARC_HDR_AS, h->text, ARC_HDRLEN_AS) == 0) |
| 464 | { |
| 465 | DEBUG(D_acl) |
| 466 | { |
| 467 | int len = h->slen; |
| 468 | uschar * s; |
| 469 | for (s = h->text + h->slen; s[-1] == '\r' || s[-1] == '\n'; ) |
| 470 | s--, len--; |
| 471 | debug_printf("ARC: found AS: %.*s\n", len, h->text); |
| 472 | } |
| 473 | if ((e = arc_insert_hdr(ctx, h, ARC_HDRLEN_AS, offsetof(arc_set, hdr_as), |
| 474 | instance_only))) |
| 475 | { |
| 476 | DEBUG(D_acl) debug_printf("inserting AS: %s\n", e); |
| 477 | return US"inserting AS"; |
| 478 | } |
| 479 | } |
| 480 | return NULL; |
| 481 | } |
| 482 | |
| 483 | |
| 484 | |
| 485 | /* Gather the chain of arc sets from the headers. |
| 486 | Check for duplicates while that is done. Also build the |
| 487 | reverse-order headers list; |
| 488 | |
| 489 | Return: ARC state if determined, eg. by lack of any ARC chain. |
| 490 | */ |
| 491 | |
| 492 | static const uschar * |
| 493 | arc_vfy_collect_hdrs(arc_ctx * ctx) |
| 494 | { |
| 495 | header_line * h; |
| 496 | hdr_rlist * r = NULL, * rprev = NULL; |
| 497 | const uschar * e; |
| 498 | |
| 499 | DEBUG(D_acl) debug_printf("ARC: collecting arc sets\n"); |
| 500 | for (h = header_list; h; h = h->next) |
| 501 | { |
| 502 | r = store_get(sizeof(hdr_rlist), FALSE); |
| 503 | r->prev = rprev; |
| 504 | r->used = FALSE; |
| 505 | r->h = h; |
| 506 | rprev = r; |
| 507 | |
| 508 | if ((e = arc_try_header(ctx, h, FALSE))) |
| 509 | { |
| 510 | arc_state_reason = string_sprintf("collecting headers: %s", e); |
| 511 | return US"fail"; |
| 512 | } |
| 513 | } |
| 514 | headers_rlist = r; |
| 515 | |
| 516 | if (!ctx->arcset_chain) return US"none"; |
| 517 | return NULL; |
| 518 | } |
| 519 | |
| 520 | |
| 521 | static BOOL |
| 522 | arc_cv_match(arc_line * al, const uschar * s) |
| 523 | { |
| 524 | return Ustrncmp(s, al->cv.data, al->cv.len) == 0; |
| 525 | } |
| 526 | |
| 527 | /******************************************************************************/ |
| 528 | |
| 529 | /* Return the hash of headers from the message that the AMS claims it |
| 530 | signed. |
| 531 | */ |
| 532 | |
| 533 | static void |
| 534 | arc_get_verify_hhash(arc_ctx * ctx, arc_line * ams, blob * hhash) |
| 535 | { |
| 536 | const uschar * headernames = string_copyn(ams->h.data, ams->h.len); |
| 537 | const uschar * hn; |
| 538 | int sep = ':'; |
| 539 | hdr_rlist * r; |
| 540 | BOOL relaxed = Ustrncmp(US"relaxed", ams->c_head.data, ams->c_head.len) == 0; |
| 541 | int hashtype = pdkim_hashname_to_hashtype( |
| 542 | ams->a_hash.data, ams->a_hash.len); |
| 543 | hctx hhash_ctx; |
| 544 | const uschar * s; |
| 545 | int len; |
| 546 | |
| 547 | if ( hashtype == -1 |
| 548 | || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) |
| 549 | { |
| 550 | DEBUG(D_acl) |
| 551 | debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); |
| 552 | return; |
| 553 | } |
| 554 | |
| 555 | /* For each headername in the list from the AMS (walking in order) |
| 556 | walk the message headers in reverse order, adding to the hash any |
| 557 | found for the first time. For that last point, maintain used-marks |
| 558 | on the list of message headers. */ |
| 559 | |
| 560 | DEBUG(D_acl) debug_printf("ARC: AMS header data for verification:\n"); |
| 561 | |
| 562 | for (r = headers_rlist; r; r = r->prev) |
| 563 | r->used = FALSE; |
| 564 | while ((hn = string_nextinlist(&headernames, &sep, NULL, 0))) |
| 565 | for (r = headers_rlist; r; r = r->prev) |
| 566 | if ( !r->used |
| 567 | && strncasecmp(CCS (s = r->h->text), CCS hn, Ustrlen(hn)) == 0 |
| 568 | ) |
| 569 | { |
| 570 | if (relaxed) s = pdkim_relax_header_n(s, r->h->slen, TRUE); |
| 571 | |
| 572 | len = Ustrlen(s); |
| 573 | DEBUG(D_acl) pdkim_quoteprint(s, len); |
| 574 | exim_sha_update(&hhash_ctx, s, Ustrlen(s)); |
| 575 | r->used = TRUE; |
| 576 | break; |
| 577 | } |
| 578 | |
| 579 | /* Finally add in the signature header (with the b= tag stripped); no CRLF */ |
| 580 | |
| 581 | s = ams->rawsig_no_b_val.data, len = ams->rawsig_no_b_val.len; |
| 582 | if (relaxed) |
| 583 | len = Ustrlen(s = pdkim_relax_header_n(s, len, FALSE)); |
| 584 | DEBUG(D_acl) pdkim_quoteprint(s, len); |
| 585 | exim_sha_update(&hhash_ctx, s, len); |
| 586 | |
| 587 | exim_sha_finish(&hhash_ctx, hhash); |
| 588 | DEBUG(D_acl) |
| 589 | { debug_printf("ARC: header hash: "); pdkim_hexprint(hhash->data, hhash->len); } |
| 590 | return; |
| 591 | } |
| 592 | |
| 593 | |
| 594 | |
| 595 | |
| 596 | static pdkim_pubkey * |
| 597 | arc_line_to_pubkey(arc_line * al) |
| 598 | { |
| 599 | uschar * dns_txt; |
| 600 | pdkim_pubkey * p; |
| 601 | |
| 602 | if (!(dns_txt = dkim_exim_query_dns_txt(string_sprintf("%.*s._domainkey.%.*s", |
| 603 | (int)al->s.len, al->s.data, (int)al->d.len, al->d.data)))) |
| 604 | { |
| 605 | DEBUG(D_acl) debug_printf("pubkey dns lookup fail\n"); |
| 606 | return NULL; |
| 607 | } |
| 608 | |
| 609 | if ( !(p = pdkim_parse_pubkey_record(dns_txt)) |
| 610 | || (Ustrcmp(p->srvtype, "*") != 0 && Ustrcmp(p->srvtype, "email") != 0) |
| 611 | ) |
| 612 | { |
| 613 | DEBUG(D_acl) debug_printf("pubkey dns lookup format error\n"); |
| 614 | return NULL; |
| 615 | } |
| 616 | |
| 617 | /* If the pubkey limits use to specified hashes, reject unusable |
| 618 | signatures. XXX should we have looked for multiple dns records? */ |
| 619 | |
| 620 | if (p->hashes) |
| 621 | { |
| 622 | const uschar * list = p->hashes, * ele; |
| 623 | int sep = ':'; |
| 624 | |
| 625 | while ((ele = string_nextinlist(&list, &sep, NULL, 0))) |
| 626 | if (Ustrncmp(ele, al->a_hash.data, al->a_hash.len) == 0) break; |
| 627 | if (!ele) |
| 628 | { |
| 629 | DEBUG(D_acl) debug_printf("pubkey h=%s vs sig a=%.*s\n", |
| 630 | p->hashes, (int)al->a.len, al->a.data); |
| 631 | return NULL; |
| 632 | } |
| 633 | } |
| 634 | return p; |
| 635 | } |
| 636 | |
| 637 | |
| 638 | |
| 639 | |
| 640 | static pdkim_bodyhash * |
| 641 | arc_ams_setup_vfy_bodyhash(arc_line * ams) |
| 642 | { |
| 643 | int canon_head = -1, canon_body = -1; |
| 644 | long bodylen; |
| 645 | |
| 646 | if (!ams->c.data) ams->c.data = US"simple"; /* RFC 6376 (DKIM) default */ |
| 647 | pdkim_cstring_to_canons(ams->c.data, ams->c.len, &canon_head, &canon_body); |
| 648 | bodylen = ams->l.data |
| 649 | ? strtol(CS string_copyn(ams->l.data, ams->l.len), NULL, 10) : -1; |
| 650 | |
| 651 | return pdkim_set_bodyhash(dkim_verify_ctx, |
| 652 | pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len), |
| 653 | canon_body, |
| 654 | bodylen); |
| 655 | } |
| 656 | |
| 657 | |
| 658 | |
| 659 | /* Verify an AMS. This is a DKIM-sig header, but with an ARC i= tag |
| 660 | and without a DKIM v= tag. |
| 661 | */ |
| 662 | |
| 663 | static const uschar * |
| 664 | arc_ams_verify(arc_ctx * ctx, arc_set * as) |
| 665 | { |
| 666 | arc_line * ams = as->hdr_ams; |
| 667 | pdkim_bodyhash * b; |
| 668 | pdkim_pubkey * p; |
| 669 | blob sighash; |
| 670 | blob hhash; |
| 671 | ev_ctx vctx; |
| 672 | int hashtype; |
| 673 | const uschar * errstr; |
| 674 | |
| 675 | as->ams_verify_done = US"in-progress"; |
| 676 | |
| 677 | /* Check the AMS has all the required tags: |
| 678 | "a=" algorithm |
| 679 | "b=" signature |
| 680 | "bh=" body hash |
| 681 | "d=" domain (for key lookup) |
| 682 | "h=" headers (included in signature) |
| 683 | "s=" key-selector (for key lookup) |
| 684 | */ |
| 685 | if ( !ams->a.data || !ams->b.data || !ams->bh.data || !ams->d.data |
| 686 | || !ams->h.data || !ams->s.data) |
| 687 | { |
| 688 | as->ams_verify_done = arc_state_reason = US"required tag missing"; |
| 689 | return US"fail"; |
| 690 | } |
| 691 | |
| 692 | |
| 693 | /* The bodyhash should have been created earlier, and the dkim code should |
| 694 | have managed calculating it during message input. Find the reference to it. */ |
| 695 | |
| 696 | if (!(b = arc_ams_setup_vfy_bodyhash(ams))) |
| 697 | { |
| 698 | as->ams_verify_done = arc_state_reason = US"internal hash setup error"; |
| 699 | return US"fail"; |
| 700 | } |
| 701 | |
| 702 | DEBUG(D_acl) |
| 703 | { |
| 704 | debug_printf("ARC i=%d AMS Body bytes hashed: %lu\n" |
| 705 | " Body %.*s computed: ", |
| 706 | as->instance, b->signed_body_bytes, |
| 707 | (int)ams->a_hash.len, ams->a_hash.data); |
| 708 | pdkim_hexprint(CUS b->bh.data, b->bh.len); |
| 709 | } |
| 710 | |
| 711 | /* We know the bh-tag blob is of a nul-term string, so safe as a string */ |
| 712 | |
| 713 | if ( !ams->bh.data |
| 714 | || (pdkim_decode_base64(ams->bh.data, &sighash), sighash.len != b->bh.len) |
| 715 | || memcmp(sighash.data, b->bh.data, b->bh.len) != 0 |
| 716 | ) |
| 717 | { |
| 718 | DEBUG(D_acl) |
| 719 | { |
| 720 | debug_printf("ARC i=%d AMS Body hash from headers: ", as->instance); |
| 721 | pdkim_hexprint(sighash.data, sighash.len); |
| 722 | debug_printf("ARC i=%d AMS Body hash did NOT match\n", as->instance); |
| 723 | } |
| 724 | return as->ams_verify_done = arc_state_reason = US"AMS body hash miscompare"; |
| 725 | } |
| 726 | |
| 727 | DEBUG(D_acl) debug_printf("ARC i=%d AMS Body hash compared OK\n", as->instance); |
| 728 | |
| 729 | /* Get the public key from DNS */ |
| 730 | |
| 731 | if (!(p = arc_line_to_pubkey(ams))) |
| 732 | return as->ams_verify_done = arc_state_reason = US"pubkey problem"; |
| 733 | |
| 734 | /* We know the b-tag blob is of a nul-term string, so safe as a string */ |
| 735 | pdkim_decode_base64(ams->b.data, &sighash); |
| 736 | |
| 737 | arc_get_verify_hhash(ctx, ams, &hhash); |
| 738 | |
| 739 | /* Setup the interface to the signing library */ |
| 740 | |
| 741 | if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) |
| 742 | { |
| 743 | DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr); |
| 744 | as->ams_verify_done = arc_state_reason = US"internal sigverify init error"; |
| 745 | return US"fail"; |
| 746 | } |
| 747 | |
| 748 | hashtype = pdkim_hashname_to_hashtype(ams->a_hash.data, ams->a_hash.len); |
| 749 | if (hashtype == -1) |
| 750 | { |
| 751 | DEBUG(D_acl) debug_printf("ARC i=%d AMS verify bad a_hash\n", as->instance); |
| 752 | return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify"; |
| 753 | } |
| 754 | |
| 755 | if ((errstr = exim_dkim_verify(&vctx, |
| 756 | pdkim_hashes[hashtype].exim_hashmethod, &hhash, &sighash))) |
| 757 | { |
| 758 | DEBUG(D_acl) debug_printf("ARC i=%d AMS verify %s\n", as->instance, errstr); |
| 759 | return as->ams_verify_done = arc_state_reason = US"AMS sig nonverify"; |
| 760 | } |
| 761 | |
| 762 | DEBUG(D_acl) debug_printf("ARC i=%d AMS verify pass\n", as->instance); |
| 763 | as->ams_verify_passed = TRUE; |
| 764 | return NULL; |
| 765 | } |
| 766 | |
| 767 | |
| 768 | |
| 769 | /* Check the sets are instance-continuous and that all |
| 770 | members are present. Check that no arc_seals are "fail". |
| 771 | Set the highest instance number global. |
| 772 | Verify the latest AMS. |
| 773 | */ |
| 774 | static uschar * |
| 775 | arc_headers_check(arc_ctx * ctx) |
| 776 | { |
| 777 | arc_set * as; |
| 778 | int inst; |
| 779 | BOOL ams_fail_found = FALSE; |
| 780 | |
| 781 | if (!(as = ctx->arcset_chain_last)) |
| 782 | return US"none"; |
| 783 | |
| 784 | for(inst = as->instance; as; as = as->prev, inst--) |
| 785 | { |
| 786 | if (as->instance != inst) |
| 787 | arc_state_reason = string_sprintf("i=%d (sequence; expected %d)", |
| 788 | as->instance, inst); |
| 789 | else if (!as->hdr_aar || !as->hdr_ams || !as->hdr_as) |
| 790 | arc_state_reason = string_sprintf("i=%d (missing header)", as->instance); |
| 791 | else if (arc_cv_match(as->hdr_as, US"fail")) |
| 792 | arc_state_reason = string_sprintf("i=%d (cv)", as->instance); |
| 793 | else |
| 794 | goto good; |
| 795 | |
| 796 | DEBUG(D_acl) debug_printf("ARC chain fail at %s\n", arc_state_reason); |
| 797 | return US"fail"; |
| 798 | |
| 799 | good: |
| 800 | /* Evaluate the oldest-pass AMS validation while we're here. |
| 801 | It does not affect the AS chain validation but is reported as |
| 802 | auxilary info. */ |
| 803 | |
| 804 | if (!ams_fail_found) |
| 805 | if (arc_ams_verify(ctx, as)) |
| 806 | ams_fail_found = TRUE; |
| 807 | else |
| 808 | arc_oldest_pass = inst; |
| 809 | arc_state_reason = NULL; |
| 810 | } |
| 811 | if (inst != 0) |
| 812 | { |
| 813 | arc_state_reason = string_sprintf("(sequence; expected i=%d)", inst); |
| 814 | DEBUG(D_acl) debug_printf("ARC chain fail %s\n", arc_state_reason); |
| 815 | return US"fail"; |
| 816 | } |
| 817 | |
| 818 | arc_received = ctx->arcset_chain_last; |
| 819 | arc_received_instance = arc_received->instance; |
| 820 | |
| 821 | /* We can skip the latest-AMS validation, if we already did it. */ |
| 822 | |
| 823 | as = ctx->arcset_chain_last; |
| 824 | if (!as->ams_verify_passed) |
| 825 | { |
| 826 | if (as->ams_verify_done) |
| 827 | { |
| 828 | arc_state_reason = as->ams_verify_done; |
| 829 | return US"fail"; |
| 830 | } |
| 831 | if (!!arc_ams_verify(ctx, as)) |
| 832 | return US"fail"; |
| 833 | } |
| 834 | return NULL; |
| 835 | } |
| 836 | |
| 837 | |
| 838 | /******************************************************************************/ |
| 839 | static const uschar * |
| 840 | arc_seal_verify(arc_ctx * ctx, arc_set * as) |
| 841 | { |
| 842 | arc_line * hdr_as = as->hdr_as; |
| 843 | arc_set * as2; |
| 844 | int hashtype; |
| 845 | hctx hhash_ctx; |
| 846 | blob hhash_computed; |
| 847 | blob sighash; |
| 848 | ev_ctx vctx; |
| 849 | pdkim_pubkey * p; |
| 850 | const uschar * errstr; |
| 851 | |
| 852 | DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d\n", as->instance); |
| 853 | /* |
| 854 | 1. If the value of the "cv" tag on that seal is "fail", the |
| 855 | chain state is "fail" and the algorithm stops here. (This |
| 856 | step SHOULD be skipped if the earlier step (2.1) was |
| 857 | performed) [it was] |
| 858 | |
| 859 | 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv |
| 860 | == "none" && i != 1)) then the chain state is "fail" and the |
| 861 | algorithm stops here (note that the ordering of the logic is |
| 862 | structured for short-circuit evaluation). |
| 863 | */ |
| 864 | |
| 865 | if ( as->instance == 1 && !arc_cv_match(hdr_as, US"none") |
| 866 | || arc_cv_match(hdr_as, US"none") && as->instance != 1 |
| 867 | ) |
| 868 | { |
| 869 | arc_state_reason = US"seal cv state"; |
| 870 | return US"fail"; |
| 871 | } |
| 872 | |
| 873 | /* |
| 874 | 3. Initialize a hash function corresponding to the "a" tag of |
| 875 | the ARC-Seal. |
| 876 | */ |
| 877 | |
| 878 | hashtype = pdkim_hashname_to_hashtype(hdr_as->a_hash.data, hdr_as->a_hash.len); |
| 879 | |
| 880 | if ( hashtype == -1 |
| 881 | || !exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod)) |
| 882 | { |
| 883 | DEBUG(D_acl) |
| 884 | debug_printf("ARC: hash setup error, possibly nonhandled hashtype\n"); |
| 885 | arc_state_reason = US"seal hash setup error"; |
| 886 | return US"fail"; |
| 887 | } |
| 888 | |
| 889 | /* |
| 890 | 4. Compute the canonicalized form of the ARC header fields, in |
| 891 | the order described in Section 5.4.2, using the "relaxed" |
| 892 | header canonicalization defined in Section 3.4.2 of |
| 893 | [RFC6376]. Pass the canonicalized result to the hash |
| 894 | function. |
| 895 | |
| 896 | Headers are CRLF-separated, but the last one is not crlf-terminated. |
| 897 | */ |
| 898 | |
| 899 | DEBUG(D_acl) debug_printf("ARC: AS header data for verification:\n"); |
| 900 | for (as2 = ctx->arcset_chain; |
| 901 | as2 && as2->instance <= as->instance; |
| 902 | as2 = as2->next) |
| 903 | { |
| 904 | arc_line * al; |
| 905 | uschar * s; |
| 906 | int len; |
| 907 | |
| 908 | al = as2->hdr_aar; |
| 909 | if (!(s = al->relaxed)) |
| 910 | al->relaxed = s = pdkim_relax_header_n(al->complete->text, |
| 911 | al->complete->slen, TRUE); |
| 912 | len = Ustrlen(s); |
| 913 | DEBUG(D_acl) pdkim_quoteprint(s, len); |
| 914 | exim_sha_update(&hhash_ctx, s, len); |
| 915 | |
| 916 | al = as2->hdr_ams; |
| 917 | if (!(s = al->relaxed)) |
| 918 | al->relaxed = s = pdkim_relax_header_n(al->complete->text, |
| 919 | al->complete->slen, TRUE); |
| 920 | len = Ustrlen(s); |
| 921 | DEBUG(D_acl) pdkim_quoteprint(s, len); |
| 922 | exim_sha_update(&hhash_ctx, s, len); |
| 923 | |
| 924 | al = as2->hdr_as; |
| 925 | if (as2->instance == as->instance) |
| 926 | s = pdkim_relax_header_n(al->rawsig_no_b_val.data, |
| 927 | al->rawsig_no_b_val.len, FALSE); |
| 928 | else if (!(s = al->relaxed)) |
| 929 | al->relaxed = s = pdkim_relax_header_n(al->complete->text, |
| 930 | al->complete->slen, TRUE); |
| 931 | len = Ustrlen(s); |
| 932 | DEBUG(D_acl) pdkim_quoteprint(s, len); |
| 933 | exim_sha_update(&hhash_ctx, s, len); |
| 934 | } |
| 935 | |
| 936 | /* |
| 937 | 5. Retrieve the final digest from the hash function. |
| 938 | */ |
| 939 | |
| 940 | exim_sha_finish(&hhash_ctx, &hhash_computed); |
| 941 | DEBUG(D_acl) |
| 942 | { |
| 943 | debug_printf("ARC i=%d AS Header %.*s computed: ", |
| 944 | as->instance, (int)hdr_as->a_hash.len, hdr_as->a_hash.data); |
| 945 | pdkim_hexprint(hhash_computed.data, hhash_computed.len); |
| 946 | } |
| 947 | |
| 948 | |
| 949 | /* |
| 950 | 6. Retrieve the public key identified by the "s" and "d" tags in |
| 951 | the ARC-Seal, as described in Section 4.1.6. |
| 952 | */ |
| 953 | |
| 954 | if (!(p = arc_line_to_pubkey(hdr_as))) |
| 955 | return US"pubkey problem"; |
| 956 | |
| 957 | /* |
| 958 | 7. Determine whether the signature portion ("b" tag) of the ARC- |
| 959 | Seal and the digest computed above are valid according to the |
| 960 | public key. (See also Section Section 8.4 for failure case |
| 961 | handling) |
| 962 | |
| 963 | 8. If the signature is not valid, the chain state is "fail" and |
| 964 | the algorithm stops here. |
| 965 | */ |
| 966 | |
| 967 | /* We know the b-tag blob is of a nul-term string, so safe as a string */ |
| 968 | pdkim_decode_base64(hdr_as->b.data, &sighash); |
| 969 | |
| 970 | if ((errstr = exim_dkim_verify_init(&p->key, KEYFMT_DER, &vctx))) |
| 971 | { |
| 972 | DEBUG(D_acl) debug_printf("ARC verify init: %s\n", errstr); |
| 973 | return US"fail"; |
| 974 | } |
| 975 | |
| 976 | if ((errstr = exim_dkim_verify(&vctx, |
| 977 | pdkim_hashes[hashtype].exim_hashmethod, |
| 978 | &hhash_computed, &sighash))) |
| 979 | { |
| 980 | DEBUG(D_acl) |
| 981 | debug_printf("ARC i=%d AS headers verify: %s\n", as->instance, errstr); |
| 982 | arc_state_reason = US"seal sigverify error"; |
| 983 | return US"fail"; |
| 984 | } |
| 985 | |
| 986 | DEBUG(D_acl) debug_printf("ARC: AS vfy i=%d pass\n", as->instance); |
| 987 | return NULL; |
| 988 | } |
| 989 | |
| 990 | |
| 991 | static const uschar * |
| 992 | arc_verify_seals(arc_ctx * ctx) |
| 993 | { |
| 994 | arc_set * as = ctx->arcset_chain_last; |
| 995 | |
| 996 | if (!as) |
| 997 | return US"none"; |
| 998 | |
| 999 | for ( ; as; as = as->prev) if (arc_seal_verify(ctx, as)) return US"fail"; |
| 1000 | |
| 1001 | DEBUG(D_acl) debug_printf("ARC: AS vfy overall pass\n"); |
| 1002 | return NULL; |
| 1003 | } |
| 1004 | /******************************************************************************/ |
| 1005 | |
| 1006 | /* Do ARC verification. Called from DATA ACL, on a verify = arc |
| 1007 | condition. No arguments; we are checking globals. |
| 1008 | |
| 1009 | Return: The ARC state, or NULL on error. |
| 1010 | */ |
| 1011 | |
| 1012 | const uschar * |
| 1013 | acl_verify_arc(void) |
| 1014 | { |
| 1015 | const uschar * res; |
| 1016 | |
| 1017 | memset(&arc_verify_ctx, 0, sizeof(arc_verify_ctx)); |
| 1018 | |
| 1019 | if (!dkim_verify_ctx) |
| 1020 | { |
| 1021 | DEBUG(D_acl) debug_printf("ARC: no DKIM verify context\n"); |
| 1022 | return NULL; |
| 1023 | } |
| 1024 | |
| 1025 | /* AS evaluation, per |
| 1026 | https://tools.ietf.org/html/draft-ietf-dmarc-arc-protocol-10#section-6 |
| 1027 | */ |
| 1028 | /* 1. Collect all ARC sets currently on the message. If there were |
| 1029 | none, the ARC state is "none" and the algorithm stops here. |
| 1030 | */ |
| 1031 | |
| 1032 | if ((res = arc_vfy_collect_hdrs(&arc_verify_ctx))) |
| 1033 | goto out; |
| 1034 | |
| 1035 | /* 2. If the form of any ARC set is invalid (e.g., does not contain |
| 1036 | exactly one of each of the three ARC-specific header fields), |
| 1037 | then the chain state is "fail" and the algorithm stops here. |
| 1038 | |
| 1039 | 1. To avoid the overhead of unnecessary computation and delay |
| 1040 | from crypto and DNS operations, the cv value for all ARC- |
| 1041 | Seal(s) MAY be checked at this point. If any of the values |
| 1042 | are "fail", then the overall state of the chain is "fail" and |
| 1043 | the algorithm stops here. |
| 1044 | |
| 1045 | 3. Conduct verification of the ARC-Message-Signature header field |
| 1046 | bearing the highest instance number. If this verification fails, |
| 1047 | then the chain state is "fail" and the algorithm stops here. |
| 1048 | */ |
| 1049 | |
| 1050 | if ((res = arc_headers_check(&arc_verify_ctx))) |
| 1051 | goto out; |
| 1052 | |
| 1053 | /* 4. For each ARC-Seal from the "N"th instance to the first, apply the |
| 1054 | following logic: |
| 1055 | |
| 1056 | 1. If the value of the "cv" tag on that seal is "fail", the |
| 1057 | chain state is "fail" and the algorithm stops here. (This |
| 1058 | step SHOULD be skipped if the earlier step (2.1) was |
| 1059 | performed) |
| 1060 | |
| 1061 | 2. In Boolean nomenclature: if ((i == 1 && cv != "none") or (cv |
| 1062 | == "none" && i != 1)) then the chain state is "fail" and the |
| 1063 | algorithm stops here (note that the ordering of the logic is |
| 1064 | structured for short-circuit evaluation). |
| 1065 | |
| 1066 | 3. Initialize a hash function corresponding to the "a" tag of |
| 1067 | the ARC-Seal. |
| 1068 | |
| 1069 | 4. Compute the canonicalized form of the ARC header fields, in |
| 1070 | the order described in Section 5.4.2, using the "relaxed" |
| 1071 | header canonicalization defined in Section 3.4.2 of |
| 1072 | [RFC6376]. Pass the canonicalized result to the hash |
| 1073 | function. |
| 1074 | |
| 1075 | 5. Retrieve the final digest from the hash function. |
| 1076 | |
| 1077 | 6. Retrieve the public key identified by the "s" and "d" tags in |
| 1078 | the ARC-Seal, as described in Section 4.1.6. |
| 1079 | |
| 1080 | 7. Determine whether the signature portion ("b" tag) of the ARC- |
| 1081 | Seal and the digest computed above are valid according to the |
| 1082 | public key. (See also Section Section 8.4 for failure case |
| 1083 | handling) |
| 1084 | |
| 1085 | 8. If the signature is not valid, the chain state is "fail" and |
| 1086 | the algorithm stops here. |
| 1087 | |
| 1088 | 5. If all seals pass validation, then the chain state is "pass", and |
| 1089 | the algorithm is complete. |
| 1090 | */ |
| 1091 | |
| 1092 | if ((res = arc_verify_seals(&arc_verify_ctx))) |
| 1093 | goto out; |
| 1094 | |
| 1095 | res = US"pass"; |
| 1096 | |
| 1097 | out: |
| 1098 | return res; |
| 1099 | } |
| 1100 | |
| 1101 | /******************************************************************************/ |
| 1102 | |
| 1103 | /* Prepend the header to the rlist */ |
| 1104 | |
| 1105 | static hdr_rlist * |
| 1106 | arc_rlist_entry(hdr_rlist * list, const uschar * s, int len) |
| 1107 | { |
| 1108 | hdr_rlist * r = store_get(sizeof(hdr_rlist) + sizeof(header_line), FALSE); |
| 1109 | header_line * h = r->h = (header_line *)(r+1); |
| 1110 | |
| 1111 | r->prev = list; |
| 1112 | r->used = FALSE; |
| 1113 | h->next = NULL; |
| 1114 | h->type = 0; |
| 1115 | h->slen = len; |
| 1116 | h->text = US s; |
| 1117 | |
| 1118 | /* This works for either NL or CRLF lines; also nul-termination */ |
| 1119 | while (*++s) |
| 1120 | if (*s == '\n' && s[1] != '\t' && s[1] != ' ') break; |
| 1121 | s++; /* move past end of line */ |
| 1122 | |
| 1123 | return r; |
| 1124 | } |
| 1125 | |
| 1126 | |
| 1127 | /* Walk the given headers strings identifying each header, and construct |
| 1128 | a reverse-order list. |
| 1129 | */ |
| 1130 | |
| 1131 | static hdr_rlist * |
| 1132 | arc_sign_scan_headers(arc_ctx * ctx, gstring * sigheaders) |
| 1133 | { |
| 1134 | const uschar * s; |
| 1135 | hdr_rlist * rheaders = NULL; |
| 1136 | |
| 1137 | s = sigheaders ? sigheaders->s : NULL; |
| 1138 | if (s) while (*s) |
| 1139 | { |
| 1140 | const uschar * s2 = s; |
| 1141 | |
| 1142 | /* This works for either NL or CRLF lines; also nul-termination */ |
| 1143 | while (*++s2) |
| 1144 | if (*s2 == '\n' && s2[1] != '\t' && s2[1] != ' ') break; |
| 1145 | s2++; /* move past end of line */ |
| 1146 | |
| 1147 | rheaders = arc_rlist_entry(rheaders, s, s2 - s); |
| 1148 | s = s2; |
| 1149 | } |
| 1150 | return rheaders; |
| 1151 | } |
| 1152 | |
| 1153 | |
| 1154 | |
| 1155 | /* Return the A-R content, without identity, with line-ending and |
| 1156 | NUL termination. */ |
| 1157 | |
| 1158 | static BOOL |
| 1159 | arc_sign_find_ar(header_line * headers, const uschar * identity, blob * ret) |
| 1160 | { |
| 1161 | header_line * h; |
| 1162 | int ilen = Ustrlen(identity); |
| 1163 | |
| 1164 | ret->data = NULL; |
| 1165 | for(h = headers; h; h = h->next) |
| 1166 | { |
| 1167 | uschar * s = h->text, c; |
| 1168 | int len = h->slen; |
| 1169 | |
| 1170 | if (Ustrncmp(s, HDR_AR, HDRLEN_AR) != 0) continue; |
| 1171 | s += HDRLEN_AR, len -= HDRLEN_AR; /* header name */ |
| 1172 | while ( len > 0 |
| 1173 | && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) |
| 1174 | s++, len--; /* FWS */ |
| 1175 | if (Ustrncmp(s, identity, ilen) != 0) continue; |
| 1176 | s += ilen; len -= ilen; /* identity */ |
| 1177 | if (len <= 0) continue; |
| 1178 | if ((c = *s) && c == ';') s++, len--; /* identity terminator */ |
| 1179 | while ( len > 0 |
| 1180 | && (c = *s) && (c == ' ' || c == '\t' || c == '\r' || c == '\n')) |
| 1181 | s++, len--; /* FWS */ |
| 1182 | if (len <= 0) continue; |
| 1183 | ret->data = s; |
| 1184 | ret->len = len; |
| 1185 | return TRUE; |
| 1186 | } |
| 1187 | return FALSE; |
| 1188 | } |
| 1189 | |
| 1190 | |
| 1191 | |
| 1192 | /* Append a constructed AAR including CRLF. Add it to the arc_ctx too. */ |
| 1193 | |
| 1194 | static gstring * |
| 1195 | arc_sign_append_aar(gstring * g, arc_ctx * ctx, |
| 1196 | const uschar * identity, int instance, blob * ar) |
| 1197 | { |
| 1198 | int aar_off = g ? g->ptr : 0; |
| 1199 | arc_set * as = |
| 1200 | store_get(sizeof(arc_set) + sizeof(arc_line) + sizeof(header_line), FALSE); |
| 1201 | arc_line * al = (arc_line *)(as+1); |
| 1202 | header_line * h = (header_line *)(al+1); |
| 1203 | |
| 1204 | g = string_catn(g, ARC_HDR_AAR, ARC_HDRLEN_AAR); |
| 1205 | g = string_fmt_append(g, " i=%d; %s;\r\n\t", instance, identity); |
| 1206 | g = string_catn(g, US ar->data, ar->len); |
| 1207 | |
| 1208 | h->slen = g->ptr - aar_off; |
| 1209 | h->text = g->s + aar_off; |
| 1210 | al->complete = h; |
| 1211 | as->next = NULL; |
| 1212 | as->prev = ctx->arcset_chain_last; |
| 1213 | as->instance = instance; |
| 1214 | as->hdr_aar = al; |
| 1215 | if (instance == 1) |
| 1216 | ctx->arcset_chain = as; |
| 1217 | else |
| 1218 | ctx->arcset_chain_last->next = as; |
| 1219 | ctx->arcset_chain_last = as; |
| 1220 | |
| 1221 | DEBUG(D_transport) debug_printf("ARC: AAR '%.*s'\n", h->slen - 2, h->text); |
| 1222 | return g; |
| 1223 | } |
| 1224 | |
| 1225 | |
| 1226 | |
| 1227 | static BOOL |
| 1228 | arc_sig_from_pseudoheader(gstring * hdata, int hashtype, const uschar * privkey, |
| 1229 | blob * sig, const uschar * why) |
| 1230 | { |
| 1231 | hashmethod hm = /*sig->keytype == KEYTYPE_ED25519*/ FALSE |
| 1232 | ? HASH_SHA2_512 : pdkim_hashes[hashtype].exim_hashmethod; |
| 1233 | blob hhash; |
| 1234 | es_ctx sctx; |
| 1235 | const uschar * errstr; |
| 1236 | |
| 1237 | DEBUG(D_transport) |
| 1238 | { |
| 1239 | hctx hhash_ctx; |
| 1240 | debug_printf("ARC: %s header data for signing:\n", why); |
| 1241 | pdkim_quoteprint(hdata->s, hdata->ptr); |
| 1242 | |
| 1243 | (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod); |
| 1244 | exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr); |
| 1245 | exim_sha_finish(&hhash_ctx, &hhash); |
| 1246 | debug_printf("ARC: header hash: "); pdkim_hexprint(hhash.data, hhash.len); |
| 1247 | } |
| 1248 | |
| 1249 | if (FALSE /*need hash for Ed25519 or GCrypt signing*/ ) |
| 1250 | { |
| 1251 | hctx hhash_ctx; |
| 1252 | (void) exim_sha_init(&hhash_ctx, pdkim_hashes[hashtype].exim_hashmethod); |
| 1253 | exim_sha_update(&hhash_ctx, hdata->s, hdata->ptr); |
| 1254 | exim_sha_finish(&hhash_ctx, &hhash); |
| 1255 | } |
| 1256 | else |
| 1257 | { |
| 1258 | hhash.data = hdata->s; |
| 1259 | hhash.len = hdata->ptr; |
| 1260 | } |
| 1261 | |
| 1262 | if ( (errstr = exim_dkim_signing_init(privkey, &sctx)) |
| 1263 | || (errstr = exim_dkim_sign(&sctx, hm, &hhash, sig))) |
| 1264 | { |
| 1265 | log_write(0, LOG_MAIN, "ARC: %s signing: %s\n", why, errstr); |
| 1266 | DEBUG(D_transport) |
| 1267 | debug_printf("private key, or private-key file content, was: '%s'\n", |
| 1268 | privkey); |
| 1269 | return FALSE; |
| 1270 | } |
| 1271 | return TRUE; |
| 1272 | } |
| 1273 | |
| 1274 | |
| 1275 | |
| 1276 | static gstring * |
| 1277 | arc_sign_append_sig(gstring * g, blob * sig) |
| 1278 | { |
| 1279 | /*debug_printf("%s: raw sig ", __FUNCTION__); pdkim_hexprint(sig->data, sig->len);*/ |
| 1280 | sig->data = pdkim_encode_base64(sig); |
| 1281 | sig->len = Ustrlen(sig->data); |
| 1282 | for (;;) |
| 1283 | { |
| 1284 | int len = MIN(sig->len, 74); |
| 1285 | g = string_catn(g, sig->data, len); |
| 1286 | if ((sig->len -= len) == 0) break; |
| 1287 | sig->data += len; |
| 1288 | g = string_catn(g, US"\r\n\t ", 5); |
| 1289 | } |
| 1290 | g = string_catn(g, US";\r\n", 3); |
| 1291 | gstring_release_unused(g); |
| 1292 | string_from_gstring(g); |
| 1293 | return g; |
| 1294 | } |
| 1295 | |
| 1296 | |
| 1297 | /* Append a constructed AMS including CRLF. Add it to the arc_ctx too. */ |
| 1298 | |
| 1299 | static gstring * |
| 1300 | arc_sign_append_ams(gstring * g, arc_ctx * ctx, int instance, |
| 1301 | const uschar * identity, const uschar * selector, blob * bodyhash, |
| 1302 | hdr_rlist * rheaders, const uschar * privkey, unsigned options) |
| 1303 | { |
| 1304 | uschar * s; |
| 1305 | gstring * hdata = NULL; |
| 1306 | int col; |
| 1307 | int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ |
| 1308 | blob sig; |
| 1309 | int ams_off; |
| 1310 | arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE); |
| 1311 | header_line * h = (header_line *)(al+1); |
| 1312 | |
| 1313 | /* debug_printf("%s\n", __FUNCTION__); */ |
| 1314 | |
| 1315 | /* Construct the to-be-signed AMS pseudo-header: everything but the sig. */ |
| 1316 | |
| 1317 | ams_off = g->ptr; |
| 1318 | g = string_fmt_append(g, "%s i=%d; a=rsa-sha256; c=relaxed; d=%s; s=%s", |
| 1319 | ARC_HDR_AMS, instance, identity, selector); /*XXX hardwired a= */ |
| 1320 | if (options & ARC_SIGN_OPT_TSTAMP) |
| 1321 | g = string_fmt_append(g, "; t=%lu", (u_long)now); |
| 1322 | if (options & ARC_SIGN_OPT_EXPIRE) |
| 1323 | g = string_fmt_append(g, "; x=%lu", (u_long)expire); |
| 1324 | g = string_fmt_append(g, ";\r\n\tbh=%s;\r\n\th=", |
| 1325 | pdkim_encode_base64(bodyhash)); |
| 1326 | |
| 1327 | for(col = 3; rheaders; rheaders = rheaders->prev) |
| 1328 | { |
| 1329 | const uschar * hnames = US"DKIM-Signature:" PDKIM_DEFAULT_SIGN_HEADERS; |
| 1330 | uschar * name, * htext = rheaders->h->text; |
| 1331 | int sep = ':'; |
| 1332 | |
| 1333 | /* Spot headers of interest */ |
| 1334 | |
| 1335 | while ((name = string_nextinlist(&hnames, &sep, NULL, 0))) |
| 1336 | { |
| 1337 | int len = Ustrlen(name); |
| 1338 | if (strncasecmp(CCS htext, CCS name, len) == 0) |
| 1339 | { |
| 1340 | /* If too long, fold line in h= field */ |
| 1341 | |
| 1342 | if (col + len > 78) g = string_catn(g, US"\r\n\t ", 5), col = 3; |
| 1343 | |
| 1344 | /* Add name to h= list */ |
| 1345 | |
| 1346 | g = string_catn(g, name, len); |
| 1347 | g = string_catn(g, US":", 1); |
| 1348 | col += len + 1; |
| 1349 | |
| 1350 | /* Accumulate header for hashing/signing */ |
| 1351 | |
| 1352 | hdata = string_cat(hdata, |
| 1353 | pdkim_relax_header_n(htext, rheaders->h->slen, TRUE)); /*XXX hardwired */ |
| 1354 | break; |
| 1355 | } |
| 1356 | } |
| 1357 | } |
| 1358 | |
| 1359 | /* Lose the last colon from the h= list */ |
| 1360 | |
| 1361 | if (g->s[g->ptr - 1] == ':') g->ptr--; |
| 1362 | |
| 1363 | g = string_catn(g, US";\r\n\tb=;", 7); |
| 1364 | |
| 1365 | /* Include the pseudo-header in the accumulation */ |
| 1366 | |
| 1367 | s = pdkim_relax_header_n(g->s + ams_off, g->ptr - ams_off, FALSE); |
| 1368 | hdata = string_cat(hdata, s); |
| 1369 | |
| 1370 | /* Calculate the signature from the accumulation */ |
| 1371 | /*XXX does that need further relaxation? there are spaces embedded in the b= strings! */ |
| 1372 | |
| 1373 | if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AMS")) |
| 1374 | return NULL; |
| 1375 | |
| 1376 | /* Lose the trailing semicolon from the psuedo-header, and append the signature |
| 1377 | (folded over lines) and termination to complete it. */ |
| 1378 | |
| 1379 | g->ptr--; |
| 1380 | g = arc_sign_append_sig(g, &sig); |
| 1381 | |
| 1382 | h->slen = g->ptr - ams_off; |
| 1383 | h->text = g->s + ams_off; |
| 1384 | al->complete = h; |
| 1385 | ctx->arcset_chain_last->hdr_ams = al; |
| 1386 | |
| 1387 | DEBUG(D_transport) debug_printf("ARC: AMS '%.*s'\n", h->slen - 2, h->text); |
| 1388 | return g; |
| 1389 | } |
| 1390 | |
| 1391 | |
| 1392 | |
| 1393 | /* Look for an arc= result in an A-R header blob. We know that its data |
| 1394 | happens to be a NUL-term string. */ |
| 1395 | |
| 1396 | static uschar * |
| 1397 | arc_ar_cv_status(blob * ar) |
| 1398 | { |
| 1399 | const uschar * resinfo = ar->data; |
| 1400 | int sep = ';'; |
| 1401 | uschar * methodspec, * s; |
| 1402 | |
| 1403 | while ((methodspec = string_nextinlist(&resinfo, &sep, NULL, 0))) |
| 1404 | if (Ustrncmp(methodspec, US"arc=", 4) == 0) |
| 1405 | { |
| 1406 | uschar c; |
| 1407 | for (s = methodspec += 4; |
| 1408 | (c = *s) && c != ';' && c != ' ' && c != '\r' && c != '\n'; ) s++; |
| 1409 | return string_copyn(methodspec, s - methodspec); |
| 1410 | } |
| 1411 | return US"none"; |
| 1412 | } |
| 1413 | |
| 1414 | |
| 1415 | |
| 1416 | /* Build the AS header and prepend it */ |
| 1417 | |
| 1418 | static gstring * |
| 1419 | arc_sign_prepend_as(gstring * arcset_interim, arc_ctx * ctx, |
| 1420 | int instance, const uschar * identity, const uschar * selector, blob * ar, |
| 1421 | const uschar * privkey, unsigned options) |
| 1422 | { |
| 1423 | gstring * arcset; |
| 1424 | arc_set * as; |
| 1425 | uschar * status = arc_ar_cv_status(ar); |
| 1426 | arc_line * al = store_get(sizeof(header_line) + sizeof(arc_line), FALSE); |
| 1427 | header_line * h = (header_line *)(al+1); |
| 1428 | |
| 1429 | gstring * hdata = NULL; |
| 1430 | int hashtype = pdkim_hashname_to_hashtype(US"sha256", 6); /*XXX hardwired */ |
| 1431 | blob sig; |
| 1432 | |
| 1433 | /* |
| 1434 | - Generate AS |
| 1435 | - no body coverage |
| 1436 | - no h= tag; implicit coverage |
| 1437 | - arc status from A-R |
| 1438 | - if fail: |
| 1439 | - coverage is just the new ARC set |
| 1440 | including self (but with an empty b= in self) |
| 1441 | - if non-fail: |
| 1442 | - all ARC set headers, set-number order, aar then ams then as, |
| 1443 | including self (but with an empty b= in self) |
| 1444 | */ |
| 1445 | |
| 1446 | /* Construct the AS except for the signature */ |
| 1447 | |
| 1448 | arcset = string_append(NULL, 9, |
| 1449 | ARC_HDR_AS, |
| 1450 | US" i=", string_sprintf("%d", instance), |
| 1451 | US"; cv=", status, |
| 1452 | US"; a=rsa-sha256; d=", identity, /*XXX hardwired */ |
| 1453 | US"; s=", selector); /*XXX same as AMS */ |
| 1454 | if (options & ARC_SIGN_OPT_TSTAMP) |
| 1455 | arcset = string_append(arcset, 2, |
| 1456 | US"; t=", string_sprintf("%lu", (u_long)now)); |
| 1457 | arcset = string_cat(arcset, |
| 1458 | US";\r\n\t b=;"); |
| 1459 | |
| 1460 | h->slen = arcset->ptr; |
| 1461 | h->text = arcset->s; |
| 1462 | al->complete = h; |
| 1463 | ctx->arcset_chain_last->hdr_as = al; |
| 1464 | |
| 1465 | /* For any but "fail" chain-verify status, walk the entire chain in order by |
| 1466 | instance. For fail, only the new arc-set. Accumulate the elements walked. */ |
| 1467 | |
| 1468 | for (as = Ustrcmp(status, US"fail") == 0 |
| 1469 | ? ctx->arcset_chain_last : ctx->arcset_chain; |
| 1470 | as; as = as->next) |
| 1471 | { |
| 1472 | /* Accumulate AAR then AMS then AS. Relaxed canonicalisation |
| 1473 | is required per standard. */ |
| 1474 | |
| 1475 | h = as->hdr_aar->complete; |
| 1476 | hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); |
| 1477 | h = as->hdr_ams->complete; |
| 1478 | hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, TRUE)); |
| 1479 | h = as->hdr_as->complete; |
| 1480 | hdata = string_cat(hdata, pdkim_relax_header_n(h->text, h->slen, !!as->next)); |
| 1481 | } |
| 1482 | |
| 1483 | /* Calculate the signature from the accumulation */ |
| 1484 | |
| 1485 | if (!arc_sig_from_pseudoheader(hdata, hashtype, privkey, &sig, US"AS")) |
| 1486 | return NULL; |
| 1487 | |
| 1488 | /* Lose the trailing semicolon */ |
| 1489 | arcset->ptr--; |
| 1490 | arcset = arc_sign_append_sig(arcset, &sig); |
| 1491 | DEBUG(D_transport) debug_printf("ARC: AS '%.*s'\n", arcset->ptr - 2, arcset->s); |
| 1492 | |
| 1493 | /* Finally, append the AMS and AAR to the new AS */ |
| 1494 | |
| 1495 | return string_catn(arcset, arcset_interim->s, arcset_interim->ptr); |
| 1496 | } |
| 1497 | |
| 1498 | |
| 1499 | /**************************************/ |
| 1500 | |
| 1501 | /* Return pointer to pdkim_bodyhash for given hash method, creating new |
| 1502 | method if needed. |
| 1503 | */ |
| 1504 | |
| 1505 | void * |
| 1506 | arc_ams_setup_sign_bodyhash(void) |
| 1507 | { |
| 1508 | int canon_head, canon_body; |
| 1509 | |
| 1510 | DEBUG(D_transport) debug_printf("ARC: requesting bodyhash\n"); |
| 1511 | pdkim_cstring_to_canons(US"relaxed", 7, &canon_head, &canon_body); /*XXX hardwired */ |
| 1512 | return pdkim_set_bodyhash(&dkim_sign_ctx, |
| 1513 | pdkim_hashname_to_hashtype(US"sha256", 6), /*XXX hardwired */ |
| 1514 | canon_body, |
| 1515 | -1); |
| 1516 | } |
| 1517 | |
| 1518 | |
| 1519 | |
| 1520 | void |
| 1521 | arc_sign_init(void) |
| 1522 | { |
| 1523 | memset(&arc_sign_ctx, 0, sizeof(arc_sign_ctx)); |
| 1524 | } |
| 1525 | |
| 1526 | |
| 1527 | |
| 1528 | /* A "normal" header line, identified by DKIM processing. These arrive before |
| 1529 | the call to arc_sign(), which carries any newly-created DKIM headers - and |
| 1530 | those go textually before the normal ones in the message. |
| 1531 | |
| 1532 | We have to take the feed from DKIM as, in the transport-filter case, the |
| 1533 | headers are not in memory at the time of the call to arc_sign(). |
| 1534 | |
| 1535 | Take a copy of the header and construct a reverse-order list. |
| 1536 | Also parse ARC-chain headers and build the chain struct, retaining pointers |
| 1537 | into the copies. |
| 1538 | */ |
| 1539 | |
| 1540 | static const uschar * |
| 1541 | arc_header_sign_feed(gstring * g) |
| 1542 | { |
| 1543 | uschar * s = string_copyn(g->s, g->ptr); |
| 1544 | headers_rlist = arc_rlist_entry(headers_rlist, s, g->ptr); |
| 1545 | return arc_try_header(&arc_sign_ctx, headers_rlist->h, TRUE); |
| 1546 | } |
| 1547 | |
| 1548 | |
| 1549 | |
| 1550 | /* ARC signing. Called from the smtp transport, if the arc_sign option is set. |
| 1551 | The dkim_exim_sign() function has already been called, so will have hashed the |
| 1552 | message body for us so long as we requested a hash previously. |
| 1553 | |
| 1554 | Arguments: |
| 1555 | signspec Three-element colon-sep list: identity, selector, privkey. |
| 1556 | Optional fourth element: comma-sep list of options. |
| 1557 | Already expanded |
| 1558 | sigheaders Any signature headers already generated, eg. by DKIM, or NULL |
| 1559 | errstr Error string |
| 1560 | |
| 1561 | Return value |
| 1562 | Set of headers to prepend to the message, including the supplied sigheaders |
| 1563 | but not the plainheaders. |
| 1564 | */ |
| 1565 | |
| 1566 | gstring * |
| 1567 | arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr) |
| 1568 | { |
| 1569 | const uschar * identity, * selector, * privkey, * opts, * s; |
| 1570 | unsigned options = 0; |
| 1571 | int sep = 0; |
| 1572 | header_line * headers; |
| 1573 | hdr_rlist * rheaders; |
| 1574 | blob ar; |
| 1575 | int instance; |
| 1576 | gstring * g = NULL; |
| 1577 | pdkim_bodyhash * b; |
| 1578 | |
| 1579 | expire = now = 0; |
| 1580 | |
| 1581 | /* Parse the signing specification */ |
| 1582 | |
| 1583 | identity = string_nextinlist(&signspec, &sep, NULL, 0); |
| 1584 | selector = string_nextinlist(&signspec, &sep, NULL, 0); |
| 1585 | if ( !*identity || !*selector |
| 1586 | || !(privkey = string_nextinlist(&signspec, &sep, NULL, 0)) || !*privkey) |
| 1587 | { |
| 1588 | log_write(0, LOG_MAIN, "ARC: bad signing-specification (%s)", |
| 1589 | !*identity ? "identity" : !*selector ? "selector" : "private-key"); |
| 1590 | return sigheaders ? sigheaders : string_get(0); |
| 1591 | } |
| 1592 | if (*privkey == '/' && !(privkey = expand_file_big_buffer(privkey))) |
| 1593 | return sigheaders ? sigheaders : string_get(0); |
| 1594 | |
| 1595 | if ((opts = string_nextinlist(&signspec, &sep, NULL, 0))) |
| 1596 | { |
| 1597 | int osep = ','; |
| 1598 | while ((s = string_nextinlist(&opts, &osep, NULL, 0))) |
| 1599 | if (Ustrcmp(s, "timestamps") == 0) |
| 1600 | { |
| 1601 | options |= ARC_SIGN_OPT_TSTAMP; |
| 1602 | if (!now) now = time(NULL); |
| 1603 | } |
| 1604 | else if (Ustrncmp(s, "expire", 6) == 0) |
| 1605 | { |
| 1606 | options |= ARC_SIGN_OPT_EXPIRE; |
| 1607 | if (*(s += 6) == '=') |
| 1608 | if (*++s == '+') |
| 1609 | { |
| 1610 | if (!(expire = (time_t)atoi(CS ++s))) |
| 1611 | expire = ARC_SIGN_DEFAULT_EXPIRE_DELTA; |
| 1612 | if (!now) now = time(NULL); |
| 1613 | expire += now; |
| 1614 | } |
| 1615 | else |
| 1616 | expire = (time_t)atol(CS s); |
| 1617 | else |
| 1618 | { |
| 1619 | if (!now) now = time(NULL); |
| 1620 | expire = now + ARC_SIGN_DEFAULT_EXPIRE_DELTA; |
| 1621 | } |
| 1622 | } |
| 1623 | } |
| 1624 | |
| 1625 | DEBUG(D_transport) debug_printf("ARC: sign for %s\n", identity); |
| 1626 | |
| 1627 | /* Make an rlist of any new DKIM headers, then add the "normals" rlist to it. |
| 1628 | Then scan the list for an A-R header. */ |
| 1629 | |
| 1630 | string_from_gstring(sigheaders); |
| 1631 | if ((rheaders = arc_sign_scan_headers(&arc_sign_ctx, sigheaders))) |
| 1632 | { |
| 1633 | hdr_rlist ** rp; |
| 1634 | for (rp = &headers_rlist; *rp; ) rp = &(*rp)->prev; |
| 1635 | *rp = rheaders; |
| 1636 | } |
| 1637 | |
| 1638 | /* Finally, build a normal-order headers list */ |
| 1639 | /*XXX only needed for hunt-the-AR? */ |
| 1640 | /*XXX also, we really should be accepting any number of ADMD-matching ARs */ |
| 1641 | { |
| 1642 | header_line * hnext = NULL; |
| 1643 | for (rheaders = headers_rlist; rheaders; |
| 1644 | hnext = rheaders->h, rheaders = rheaders->prev) |
| 1645 | rheaders->h->next = hnext; |
| 1646 | headers = hnext; |
| 1647 | } |
| 1648 | |
| 1649 | if (!(arc_sign_find_ar(headers, identity, &ar))) |
| 1650 | { |
| 1651 | log_write(0, LOG_MAIN, "ARC: no Authentication-Results header for signing"); |
| 1652 | return sigheaders ? sigheaders : string_get(0); |
| 1653 | } |
| 1654 | |
| 1655 | /* We previously built the data-struct for the existing ARC chain, if any, using a headers |
| 1656 | feed from the DKIM module. Use that to give the instance number for the ARC set we are |
| 1657 | about to build. */ |
| 1658 | |
| 1659 | DEBUG(D_transport) |
| 1660 | if (arc_sign_ctx.arcset_chain_last) |
| 1661 | debug_printf("ARC: existing chain highest instance: %d\n", |
| 1662 | arc_sign_ctx.arcset_chain_last->instance); |
| 1663 | else |
| 1664 | debug_printf("ARC: no existing chain\n"); |
| 1665 | |
| 1666 | instance = arc_sign_ctx.arcset_chain_last ? arc_sign_ctx.arcset_chain_last->instance + 1 : 1; |
| 1667 | |
| 1668 | /* |
| 1669 | - Generate AAR |
| 1670 | - copy the A-R; prepend i= & identity |
| 1671 | */ |
| 1672 | |
| 1673 | g = arc_sign_append_aar(g, &arc_sign_ctx, identity, instance, &ar); |
| 1674 | |
| 1675 | /* |
| 1676 | - Generate AMS |
| 1677 | - Looks fairly like a DKIM sig |
| 1678 | - Cover all DKIM sig headers as well as the usuals |
| 1679 | - ? oversigning? |
| 1680 | - Covers the data |
| 1681 | - we must have requested a suitable bodyhash previously |
| 1682 | */ |
| 1683 | |
| 1684 | b = arc_ams_setup_sign_bodyhash(); |
| 1685 | g = arc_sign_append_ams(g, &arc_sign_ctx, instance, identity, selector, |
| 1686 | &b->bh, headers_rlist, privkey, options); |
| 1687 | |
| 1688 | /* |
| 1689 | - Generate AS |
| 1690 | - no body coverage |
| 1691 | - no h= tag; implicit coverage |
| 1692 | - arc status from A-R |
| 1693 | - if fail: |
| 1694 | - coverage is just the new ARC set |
| 1695 | including self (but with an empty b= in self) |
| 1696 | - if non-fail: |
| 1697 | - all ARC set headers, set-number order, aar then ams then as, |
| 1698 | including self (but with an empty b= in self) |
| 1699 | */ |
| 1700 | |
| 1701 | if (g) |
| 1702 | g = arc_sign_prepend_as(g, &arc_sign_ctx, instance, identity, selector, &ar, |
| 1703 | privkey, options); |
| 1704 | |
| 1705 | /* Finally, append the dkim headers and return the lot. */ |
| 1706 | |
| 1707 | if (sigheaders) g = string_catn(g, sigheaders->s, sigheaders->ptr); |
| 1708 | (void) string_from_gstring(g); |
| 1709 | gstring_release_unused(g); |
| 1710 | return g; |
| 1711 | } |
| 1712 | |
| 1713 | |
| 1714 | /******************************************************************************/ |
| 1715 | |
| 1716 | /* Check to see if the line is an AMS and if so, set up to validate it. |
| 1717 | Called from the DKIM input processing. This must be done now as the message |
| 1718 | body data is hashed during input. |
| 1719 | |
| 1720 | We call the DKIM code to request a body-hash; it has the facility already |
| 1721 | and the hash parameters might be common with other requests. |
| 1722 | */ |
| 1723 | |
| 1724 | static const uschar * |
| 1725 | arc_header_vfy_feed(gstring * g) |
| 1726 | { |
| 1727 | header_line h; |
| 1728 | arc_line al; |
| 1729 | pdkim_bodyhash * b; |
| 1730 | uschar * errstr; |
| 1731 | |
| 1732 | if (!dkim_verify_ctx) return US"no dkim context"; |
| 1733 | |
| 1734 | if (strncmpic(ARC_HDR_AMS, g->s, ARC_HDRLEN_AMS) != 0) return US"not AMS"; |
| 1735 | |
| 1736 | DEBUG(D_receive) debug_printf("ARC: spotted AMS header\n"); |
| 1737 | /* Parse the AMS header */ |
| 1738 | |
| 1739 | h.next = NULL; |
| 1740 | h.slen = g->size; |
| 1741 | h.text = g->s; |
| 1742 | memset(&al, 0, sizeof(arc_line)); |
| 1743 | if ((errstr = arc_parse_line(&al, &h, ARC_HDRLEN_AMS, FALSE))) |
| 1744 | { |
| 1745 | DEBUG(D_acl) if (errstr) debug_printf("ARC: %s\n", errstr); |
| 1746 | goto badline; |
| 1747 | } |
| 1748 | |
| 1749 | if (!al.a_hash.data) |
| 1750 | { |
| 1751 | DEBUG(D_acl) debug_printf("ARC: no a_hash from '%.*s'\n", h.slen, h.text); |
| 1752 | goto badline; |
| 1753 | } |
| 1754 | |
| 1755 | /* defaults */ |
| 1756 | if (!al.c.data) |
| 1757 | { |
| 1758 | al.c_body.data = US"simple"; al.c_body.len = 6; |
| 1759 | al.c_head = al.c_body; |
| 1760 | } |
| 1761 | |
| 1762 | /* Ask the dkim code to calc a bodyhash with those specs */ |
| 1763 | |
| 1764 | if (!(b = arc_ams_setup_vfy_bodyhash(&al))) |
| 1765 | return US"dkim hash setup fail"; |
| 1766 | |
| 1767 | /* Discard the reference; search again at verify time, knowing that one |
| 1768 | should have been created here. */ |
| 1769 | |
| 1770 | return NULL; |
| 1771 | |
| 1772 | badline: |
| 1773 | return US"line parsing error"; |
| 1774 | } |
| 1775 | |
| 1776 | |
| 1777 | |
| 1778 | /* A header line has been identified by DKIM processing. |
| 1779 | |
| 1780 | Arguments: |
| 1781 | g Header line |
| 1782 | is_vfy TRUE for verify mode or FALSE for signing mode |
| 1783 | |
| 1784 | Return: |
| 1785 | NULL for success, or an error string (probably unused) |
| 1786 | */ |
| 1787 | |
| 1788 | const uschar * |
| 1789 | arc_header_feed(gstring * g, BOOL is_vfy) |
| 1790 | { |
| 1791 | return is_vfy ? arc_header_vfy_feed(g) : arc_header_sign_feed(g); |
| 1792 | } |
| 1793 | |
| 1794 | |
| 1795 | |
| 1796 | /******************************************************************************/ |
| 1797 | |
| 1798 | /* Construct the list of domains from the ARC chain after validation */ |
| 1799 | |
| 1800 | uschar * |
| 1801 | fn_arc_domains(void) |
| 1802 | { |
| 1803 | arc_set * as; |
| 1804 | unsigned inst; |
| 1805 | gstring * g = NULL; |
| 1806 | |
| 1807 | for (as = arc_verify_ctx.arcset_chain, inst = 1; as; as = as->next, inst++) |
| 1808 | { |
| 1809 | arc_line * hdr_as = as->hdr_as; |
| 1810 | if (hdr_as) |
| 1811 | { |
| 1812 | blob * d = &hdr_as->d; |
| 1813 | |
| 1814 | for (; inst < as->instance; inst++) |
| 1815 | g = string_catn(g, US":", 1); |
| 1816 | |
| 1817 | g = d->data && d->len |
| 1818 | ? string_append_listele_n(g, ':', d->data, d->len) |
| 1819 | : string_catn(g, US":", 1); |
| 1820 | } |
| 1821 | else |
| 1822 | g = string_catn(g, US":", 1); |
| 1823 | } |
| 1824 | return g ? g->s : US""; |
| 1825 | } |
| 1826 | |
| 1827 | |
| 1828 | /* Construct an Authentication-Results header portion, for the ARC module */ |
| 1829 | |
| 1830 | gstring * |
| 1831 | authres_arc(gstring * g) |
| 1832 | { |
| 1833 | if (arc_state) |
| 1834 | { |
| 1835 | arc_line * highest_ams; |
| 1836 | int start = 0; /* Compiler quietening */ |
| 1837 | DEBUG(D_acl) start = g->ptr; |
| 1838 | |
| 1839 | g = string_append(g, 2, US";\n\tarc=", arc_state); |
| 1840 | if (arc_received_instance > 0) |
| 1841 | { |
| 1842 | g = string_fmt_append(g, " (i=%d)", arc_received_instance); |
| 1843 | if (arc_state_reason) |
| 1844 | g = string_append(g, 3, US"(", arc_state_reason, US")"); |
| 1845 | g = string_catn(g, US" header.s=", 10); |
| 1846 | highest_ams = arc_received->hdr_ams; |
| 1847 | g = string_catn(g, highest_ams->s.data, highest_ams->s.len); |
| 1848 | |
| 1849 | g = string_fmt_append(g, " arc.oldest-pass=%d", arc_oldest_pass); |
| 1850 | |
| 1851 | if (sender_host_address) |
| 1852 | g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address); |
| 1853 | } |
| 1854 | else if (arc_state_reason) |
| 1855 | g = string_append(g, 3, US" (", arc_state_reason, US")"); |
| 1856 | DEBUG(D_acl) debug_printf("ARC: authres '%.*s'\n", |
| 1857 | g->ptr - start - 3, g->s + start + 3); |
| 1858 | } |
| 1859 | else |
| 1860 | DEBUG(D_acl) debug_printf("ARC: no authres\n"); |
| 1861 | return g; |
| 1862 | } |
| 1863 | |
| 1864 | |
| 1865 | # endif /* SUPPORT_SPF */ |
| 1866 | #endif /* EXPERIMENTAL_ARC */ |
| 1867 | /* vi: aw ai sw=2 |
| 1868 | */ |