1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge, 1995 - 2018 */
6 /* See the file NOTICE for conditions of use and distribution. */
8 /* Code for DKIM support. Other DKIM relevant code is in
9 receive.c, transport.c and transports/smtp.c */
15 # include "pdkim/pdkim.h"
18 # include "macro_predef.h"
23 builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
25 # else /*!MACRO_PREDEF*/
30 int dkim_verify_oldpool;
31 pdkim_ctx *dkim_verify_ctx = NULL;
32 pdkim_signature *dkim_signatures = NULL;
33 pdkim_signature *dkim_cur_sig = NULL;
34 static const uschar * dkim_collect_error = NULL;
38 /*XXX the caller only uses the first record if we return multiple.
42 dkim_exim_query_dns_txt(char * name)
49 lookup_dnssec_authenticated = NULL;
50 if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
51 return NULL; /*XXX better error detail? logging? */
53 /* Search for TXT record */
55 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
57 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
58 if (rr->type == T_TXT)
62 /* Copy record content to the answer buffer */
64 while (rr_offset < rr->size)
66 uschar len = rr->data[rr_offset++];
68 g = string_catn(g, US(rr->data + rr_offset), len);
69 if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
75 /* check if this looks like a DKIM record */
76 if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
78 store_reset(g->s + g->ptr + 1);
79 return string_from_gstring(g);
82 if (g) g->ptr = 0; /* overwrite previous record */
86 if (g) store_reset(g);
87 return NULL; /*XXX better error detail? logging? */
100 dkim_exim_verify_init(BOOL dot_stuffing)
102 /* There is a store-reset between header & body reception
103 so cannot use the main pool. Any allocs done by Exim
104 memory-handling must use the perm pool. */
106 dkim_verify_oldpool = store_pool;
107 store_pool = POOL_PERM;
109 /* Free previous context if there is one */
112 pdkim_free_ctx(dkim_verify_ctx);
114 /* Create new context */
116 dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
117 dkim_collect_input = !!dkim_verify_ctx;
118 dkim_collect_error = NULL;
120 /* Start feed up with any cached data */
123 store_pool = dkim_verify_oldpool;
128 dkim_exim_verify_feed(uschar * data, int len)
132 store_pool = POOL_PERM;
133 if ( dkim_collect_input
134 && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
136 dkim_collect_error = pdkim_errstr(rc);
137 log_write(0, LOG_MAIN,
138 "DKIM: validation error: %.100s", dkim_collect_error);
139 dkim_collect_input = FALSE;
141 store_pool = dkim_verify_oldpool;
145 /* Log the result for the given signature */
147 dkim_exim_verify_log_sig(pdkim_signature * sig)
154 if ( dkim_verify_status
155 && ( dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
156 || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
158 sig->verify_status |= PDKIM_VERIFY_POLICY;
160 if ( !dkim_verify_overall
161 && dkim_verify_status
162 ? Ustrcmp(dkim_verify_status, US"pass") == 0
163 : sig->verify_status == PDKIM_VERIFY_PASS
165 dkim_verify_overall = string_copy(sig->domain);
167 if (!LOGGING(dkim_verbose)) return;
169 logmsg = string_catn(NULL, US"DKIM: ", 6);
170 if (!(s = sig->domain)) s = US"<UNSET>";
171 logmsg = string_append(logmsg, 2, "d=", s);
172 if (!(s = sig->selector)) s = US"<UNSET>";
173 logmsg = string_append(logmsg, 2, " s=", s);
174 logmsg = string_append(logmsg, 7,
175 " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
176 "/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
177 " a=", dkim_sig_to_a_tag(sig),
178 string_sprintf(" b=" SIZE_T_FMT,
179 (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
180 if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
181 if (sig->created > 0) logmsg = string_cat(logmsg,
182 string_sprintf(" t=%lu", sig->created));
183 if (sig->expires > 0) logmsg = string_cat(logmsg,
184 string_sprintf(" x=%lu", sig->expires));
185 if (sig->bodylength > -1) logmsg = string_cat(logmsg,
186 string_sprintf(" l=%lu", sig->bodylength));
188 if (sig->verify_status & PDKIM_VERIFY_POLICY)
189 logmsg = string_append(logmsg, 5,
190 US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
192 switch (sig->verify_status)
194 case PDKIM_VERIFY_NONE:
195 logmsg = string_cat(logmsg, US" [not verified]");
198 case PDKIM_VERIFY_INVALID:
199 logmsg = string_cat(logmsg, US" [invalid - ");
200 switch (sig->verify_ext_status)
202 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
203 logmsg = string_cat(logmsg,
204 US"public key record (currently?) unavailable]");
207 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
208 logmsg = string_cat(logmsg, US"overlong public key record]");
211 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
212 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
213 logmsg = string_cat(logmsg, US"syntax error in public key record]");
216 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
217 logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
220 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
221 logmsg = string_cat(logmsg, US"unsupported DKIM version]");
225 logmsg = string_cat(logmsg, US"unspecified problem]");
229 case PDKIM_VERIFY_FAIL:
230 logmsg = string_cat(logmsg, US" [verification failed - ");
231 switch (sig->verify_ext_status)
233 case PDKIM_VERIFY_FAIL_BODY:
234 logmsg = string_cat(logmsg,
235 US"body hash mismatch (body probably modified in transit)]");
238 case PDKIM_VERIFY_FAIL_MESSAGE:
239 logmsg = string_cat(logmsg,
240 US"signature did not verify "
241 "(headers probably modified in transit)]");
245 logmsg = string_cat(logmsg, US"unspecified reason]");
249 case PDKIM_VERIFY_PASS:
250 logmsg = string_cat(logmsg, US" [verification succeeded]");
254 log_write(0, LOG_MAIN, "%s", string_from_gstring(logmsg));
259 /* Log a line for each signature */
261 dkim_exim_verify_log_all(void)
263 pdkim_signature * sig;
264 for (sig = dkim_signatures; sig; sig = sig->next) dkim_exim_verify_log_sig(sig);
269 dkim_exim_verify_finish(void)
271 pdkim_signature * sig;
274 const uschar * errstr = NULL;
276 store_pool = POOL_PERM;
278 /* Delete eventual previous signature chain */
281 dkim_signatures = NULL;
283 if (dkim_collect_error)
285 log_write(0, LOG_MAIN,
286 "DKIM: Error during validation, disabling signature verification: %.100s",
288 dkim_disable_verify = TRUE;
292 dkim_collect_input = FALSE;
294 /* Finish DKIM operation and fetch link to signatures chain */
296 rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures, &errstr);
297 if (rc != PDKIM_OK && errstr)
298 log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
300 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
302 for (sig = dkim_signatures; sig; sig = sig->next)
304 if (sig->domain) g = string_append_listele(g, ':', sig->domain);
305 if (sig->identity) g = string_append_listele(g, ':', sig->identity);
308 if (g) dkim_signers = g->s;
311 store_pool = dkim_verify_oldpool;
316 /* Args as per dkim_exim_acl_run() below */
318 dkim_acl_call(uschar * id, gstring ** res_ptr,
319 uschar ** user_msgptr, uschar ** log_msgptr)
323 debug_printf("calling acl_smtp_dkim for dkim_cur_signer='%s'\n", id);
325 rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
326 dkim_exim_verify_log_sig(dkim_cur_sig);
327 *res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
333 /* For the given identity, run the DKIM ACL once for each matching signature.
336 id Identity to look for in dkim signatures
337 res_ptr ptr to growable string-list of status results,
338 appended to per ACL run
339 user_msgptr where to put a user error (for SMTP response)
340 log_msgptr where to put a logging message (not for SMTP response)
342 Returns: OK access is granted by an ACCEPT verb
343 DISCARD access is granted by a DISCARD verb
344 FAIL access is denied
345 FAIL_DROP access is denied; drop the connection
346 DEFER can't tell at the moment
351 dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
352 uschar ** user_msgptr, uschar ** log_msgptr)
354 pdkim_signature * sig;
358 dkim_verify_status = US"none";
359 dkim_verify_reason = US"";
360 dkim_cur_signer = id;
362 if (dkim_disable_verify || !id || !dkim_verify_ctx)
365 /* Find signatures to run ACL on */
367 for (sig = dkim_signatures; sig; sig = sig->next)
368 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
369 && strcmpic(cmp_val, id) == 0
372 /* The "dkim_domain" and "dkim_selector" expansion variables have
373 related globals, since they are used in the signing code too.
374 Instead of inventing separate names for verification, we set
375 them here. This is easy since a domain and selector is guaranteed
376 to be in a signature. The other dkim_* expansion items are
377 dynamically fetched from dkim_cur_sig at expansion time (see
381 dkim_signing_domain = US sig->domain;
382 dkim_signing_selector = US sig->selector;
383 dkim_key_length = sig->sighash.len * 8;
385 /* These two return static strings, so we can compare the addr
386 later to see if the ACL overwrote them. Check that when logging */
388 dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
389 dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
391 if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
398 /* No matching sig found. Call ACL once anyway. */
401 return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
406 dkim_exim_expand_defaults(int what)
410 case DKIM_ALGO: return US"";
411 case DKIM_BODYLENGTH: return US"9999999999999";
412 case DKIM_CANON_BODY: return US"";
413 case DKIM_CANON_HEADERS: return US"";
414 case DKIM_COPIEDHEADERS: return US"";
415 case DKIM_CREATED: return US"0";
416 case DKIM_EXPIRES: return US"9999999999999";
417 case DKIM_HEADERNAMES: return US"";
418 case DKIM_IDENTITY: return US"";
419 case DKIM_KEY_GRANULARITY: return US"*";
420 case DKIM_KEY_SRVTYPE: return US"*";
421 case DKIM_KEY_NOTES: return US"";
422 case DKIM_KEY_TESTING: return US"0";
423 case DKIM_NOSUBDOMAINS: return US"0";
424 case DKIM_VERIFY_STATUS: return US"none";
425 case DKIM_VERIFY_REASON: return US"";
426 default: return US"";
432 dkim_exim_expand_query(int what)
434 if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
435 return dkim_exim_expand_defaults(what);
440 return dkim_sig_to_a_tag(dkim_cur_sig);
442 case DKIM_BODYLENGTH:
443 return dkim_cur_sig->bodylength >= 0
444 ? string_sprintf("%ld", dkim_cur_sig->bodylength)
445 : dkim_exim_expand_defaults(what);
447 case DKIM_CANON_BODY:
448 switch (dkim_cur_sig->canon_body)
450 case PDKIM_CANON_RELAXED: return US"relaxed";
451 case PDKIM_CANON_SIMPLE:
452 default: return US"simple";
455 case DKIM_CANON_HEADERS:
456 switch (dkim_cur_sig->canon_headers)
458 case PDKIM_CANON_RELAXED: return US"relaxed";
459 case PDKIM_CANON_SIMPLE:
460 default: return US"simple";
463 case DKIM_COPIEDHEADERS:
464 return dkim_cur_sig->copiedheaders
465 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
468 return dkim_cur_sig->created > 0
469 ? string_sprintf("%lu", dkim_cur_sig->created)
470 : dkim_exim_expand_defaults(what);
473 return dkim_cur_sig->expires > 0
474 ? string_sprintf("%lu", dkim_cur_sig->expires)
475 : dkim_exim_expand_defaults(what);
477 case DKIM_HEADERNAMES:
478 return dkim_cur_sig->headernames
479 ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
482 return dkim_cur_sig->identity
483 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
485 case DKIM_KEY_GRANULARITY:
486 return dkim_cur_sig->pubkey
487 ? dkim_cur_sig->pubkey->granularity
488 ? US dkim_cur_sig->pubkey->granularity
489 : dkim_exim_expand_defaults(what)
490 : dkim_exim_expand_defaults(what);
492 case DKIM_KEY_SRVTYPE:
493 return dkim_cur_sig->pubkey
494 ? dkim_cur_sig->pubkey->srvtype
495 ? US dkim_cur_sig->pubkey->srvtype
496 : dkim_exim_expand_defaults(what)
497 : dkim_exim_expand_defaults(what);
500 return dkim_cur_sig->pubkey
501 ? dkim_cur_sig->pubkey->notes
502 ? US dkim_cur_sig->pubkey->notes
503 : dkim_exim_expand_defaults(what)
504 : dkim_exim_expand_defaults(what);
506 case DKIM_KEY_TESTING:
507 return dkim_cur_sig->pubkey
508 ? dkim_cur_sig->pubkey->testing
510 : dkim_exim_expand_defaults(what)
511 : dkim_exim_expand_defaults(what);
513 case DKIM_NOSUBDOMAINS:
514 return dkim_cur_sig->pubkey
515 ? dkim_cur_sig->pubkey->no_subdomaining
517 : dkim_exim_expand_defaults(what)
518 : dkim_exim_expand_defaults(what);
520 case DKIM_VERIFY_STATUS:
521 switch (dkim_cur_sig->verify_status)
523 case PDKIM_VERIFY_INVALID: return US"invalid";
524 case PDKIM_VERIFY_FAIL: return US"fail";
525 case PDKIM_VERIFY_PASS: return US"pass";
526 case PDKIM_VERIFY_NONE:
527 default: return US"none";
530 case DKIM_VERIFY_REASON:
531 switch (dkim_cur_sig->verify_ext_status)
533 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
534 return US"pubkey_unavailable";
535 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
536 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
537 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
538 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
547 /* Generate signatures for the given file.
548 If a prefix is given, prepend it to the file for the calculations.
551 NULL: error; error string written
552 string: signature header(s), or a zero-length string (not an error)
556 dkim_exim_sign(int fd, off_t off, uschar * prefix,
557 struct ob_dkim * dkim, const uschar ** errstr)
559 const uschar * dkim_domain;
561 gstring * seen_doms = NULL;
563 pdkim_signature * sig;
569 int old_pool = store_pool;
572 store_pool = POOL_MAIN;
574 pdkim_init_context(&ctx, dkim->dot_stuffed, &dkim_exim_query_dns_txt);
576 if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
577 /* expansion error, do not send message. */
578 { errwhen = US"dkim_domain"; goto expand_bad; }
580 /* Set $dkim_domain expansion variable to each unique domain in list. */
582 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
584 const uschar * dkim_sel;
587 if (dkim_signing_domain[0] == '\0')
590 /* Only sign once for each domain, no matter how often it
591 appears in the expanded list. */
593 if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
594 0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
597 seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
599 /* Set $dkim_selector expansion variable to each selector in list,
602 if (!(dkim_sel = expand_string(dkim->dkim_selector)))
603 if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
604 { errwhen = US"dkim_selector"; goto expand_bad; }
606 while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
609 uschar * dkim_canon_expanded;
611 uschar * dkim_sign_headers_expanded = NULL;
612 uschar * dkim_private_key_expanded;
613 uschar * dkim_hash_expanded;
614 uschar * dkim_identity_expanded = NULL;
616 /* Get canonicalization to use */
618 dkim_canon_expanded = dkim->dkim_canon
619 ? expand_string(dkim->dkim_canon) : US"relaxed";
620 if (!dkim_canon_expanded) /* expansion error, do not send message. */
621 { errwhen = US"dkim_canon"; goto expand_bad; }
623 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
624 pdkim_canon = PDKIM_CANON_RELAXED;
625 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
626 pdkim_canon = PDKIM_CANON_SIMPLE;
629 log_write(0, LOG_MAIN,
630 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
631 dkim_canon_expanded);
632 pdkim_canon = PDKIM_CANON_RELAXED;
635 if ( dkim->dkim_sign_headers
636 && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
637 { errwhen = US"dkim_sign_header"; goto expand_bad; }
638 /* else pass NULL, which means default header list */
640 /* Get private key to use. */
642 if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
643 { errwhen = US"dkim_private_key"; goto expand_bad; }
645 if ( Ustrlen(dkim_private_key_expanded) == 0
646 || Ustrcmp(dkim_private_key_expanded, "0") == 0
647 || Ustrcmp(dkim_private_key_expanded, "false") == 0
649 continue; /* don't sign, but no error */
651 if (dkim_private_key_expanded[0] == '/')
653 int privkey_fd, off = 0, len;
655 /* Looks like a filename, load the private key. */
657 memset(big_buffer, 0, big_buffer_size);
659 if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
661 log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
662 "private key file for reading: %s",
663 dkim_private_key_expanded);
669 if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
671 (void) close(privkey_fd);
672 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
673 dkim_private_key_expanded);
680 (void) close(privkey_fd);
681 big_buffer[off] = '\0';
682 dkim_private_key_expanded = big_buffer;
685 if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
686 { errwhen = US"dkim_hash"; goto expand_bad; }
688 if (dkim->dkim_identity)
689 if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
690 { errwhen = US"dkim_identity"; goto expand_bad; }
691 else if (!*dkim_identity_expanded)
692 dkim_identity_expanded = NULL;
694 /*XXX so we currently nail signing to RSA + this hash.
695 Need to extract algo from privkey and check for disallowed combos. */
697 if (!(sig = pdkim_init_sign(&ctx, dkim_signing_domain,
698 dkim_signing_selector,
699 dkim_private_key_expanded,
704 dkim_private_key_expanded[0] = '\0';
706 pdkim_set_optional(sig,
707 CS dkim_sign_headers_expanded,
708 CS dkim_identity_expanded,
710 pdkim_canon, -1, 0, 0);
712 if (!pdkim_set_bodyhash(&ctx, sig))
715 if (!ctx.sig) /* link sig to context chain */
719 pdkim_signature * n = ctx.sig;
720 while (n->next) n = n->next;
727 DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
728 sigbuf = string_get(1); /* return a zero-len string */
732 if (prefix && (pdkim_rc = pdkim_feed(&ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
735 if (lseek(fd, off, SEEK_SET) < 0)
738 while ((sread = read(fd, &buf, sizeof(buf))) > 0)
739 if ((pdkim_rc = pdkim_feed(&ctx, buf, sread)) != PDKIM_OK)
742 /* Handle failed read above. */
745 debug_printf("DKIM: Error reading -K file.\n");
750 /* Build string of headers, one per signature */
752 if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK)
755 for (sigbuf = NULL; sig; sig = sig->next)
756 sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
759 (void) string_from_gstring(sigbuf);
760 store_pool = old_pool;
765 log_write(0, LOG_MAIN|LOG_PANIC,
766 "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
772 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand %s: %s",
773 errwhen, expand_string_message);
781 authres_dkim(gstring * g)
783 pdkim_signature * sig;
785 for (sig = dkim_signatures; sig; sig = sig->next)
787 g = string_catn(g, US";\\n\\tdkim=", 10);
789 if (sig->verify_status & PDKIM_VERIFY_POLICY)
790 g = string_append(g, 5,
791 US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
792 else switch(sig->verify_status)
794 case PDKIM_VERIFY_NONE: g = string_cat(g, US"none"); break;
795 case PDKIM_VERIFY_INVALID:
796 switch (sig->verify_ext_status)
798 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
799 g = string_cat(g, US"tmperror (pubkey unavailable)"); break;
800 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
801 g = string_cat(g, US"permerror (overlong public key record)"); break;
802 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
803 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
804 g = string_cat(g, US"neutral (syntax error in public key record)");
806 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
807 g = string_cat(g, US"neutral (signature tag missing or invalid)");
809 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
810 g = string_cat(g, US"neutral (unsupported DKIM version)");
813 g = string_cat(g, US"permerror (unspecified problem)"); break;
816 case PDKIM_VERIFY_FAIL:
817 switch (sig->verify_ext_status)
819 case PDKIM_VERIFY_FAIL_BODY:
821 US"fail (body hash mismatch; body probably modified in transit)");
823 case PDKIM_VERIFY_FAIL_MESSAGE:
825 US"fail (signature did not verify; headers probably modified in transit)");
828 g = string_cat(g, US"fail (unspecified reason)");
832 case PDKIM_VERIFY_PASS: g = string_cat(g, US"pass"); break;
833 default: g = string_cat(g, US"permerror"); break;
835 if (sig->domain) g = string_append(g, 2, US" header.d=", sig->domain);
836 if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
837 if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
838 g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
844 # endif /*!MACRO_PREDEF*/
845 #endif /*!DISABLE_DKIM*/