1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge, 1995 - 2017 */
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)
61 int answer_offset = 0;
63 /* Copy record content to the answer buffer */
65 while (rr_offset < rr->size)
67 uschar len = rr->data[rr_offset++];
69 g = string_catn(g, US(rr->data + rr_offset), len);
70 if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
76 /* check if this looks like a DKIM record */
77 if (strncmp(g->s, "v=", 2) != 0 || strncasecmp(g->s, "v=dkim", 6) == 0)
79 store_reset(g->s + g->ptr + 1);
80 return string_from_gstring(g);
83 if (g) g->ptr = 0; /* overwrite previous record */
87 if (g) store_reset(g);
88 return NULL; /*XXX better error detail? logging? */
101 dkim_exim_verify_init(BOOL dot_stuffing)
103 /* There is a store-reset between header & body reception
104 so cannot use the main pool. Any allocs done by Exim
105 memory-handling must use the perm pool. */
107 dkim_verify_oldpool = store_pool;
108 store_pool = POOL_PERM;
110 /* Free previous context if there is one */
113 pdkim_free_ctx(dkim_verify_ctx);
115 /* Create new context */
117 dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
118 dkim_collect_input = !!dkim_verify_ctx;
119 dkim_collect_error = NULL;
121 /* Start feed up with any cached data */
124 store_pool = dkim_verify_oldpool;
129 dkim_exim_verify_feed(uschar * data, int len)
133 store_pool = POOL_PERM;
134 if ( dkim_collect_input
135 && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
137 dkim_collect_error = pdkim_errstr(rc);
138 log_write(0, LOG_MAIN,
139 "DKIM: validation error: %.100s", dkim_collect_error);
140 dkim_collect_input = FALSE;
142 store_pool = dkim_verify_oldpool;
146 /* Log the result for the given signature */
148 dkim_exim_verify_log_sig(pdkim_signature * sig)
155 if ( !dkim_verify_overall
156 && dkim_verify_status
157 ? Ustrcmp(dkim_verify_status, US"pass") == 0
158 : sig->verify_status == PDKIM_VERIFY_PASS
160 dkim_verify_overall = string_copy(sig->domain);
162 if (!LOGGING(dkim_verbose)) return;
164 logmsg = string_catn(NULL, US"DKIM: ", 6);
165 if (!(s = sig->domain)) s = US"<UNSET>";
166 logmsg = string_append(logmsg, 2, "d=", s);
167 if (!(s = sig->selector)) s = US"<UNSET>";
168 logmsg = string_append(logmsg, 2, " s=", s);
169 logmsg = string_append(logmsg, 7,
170 " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
171 "/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
172 " a=", dkim_sig_to_a_tag(sig),
173 string_sprintf(" b=" SIZE_T_FMT,
174 (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
175 if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
176 if (sig->created > 0) logmsg = string_cat(logmsg,
177 string_sprintf(" t=%lu", sig->created));
178 if (sig->expires > 0) logmsg = string_cat(logmsg,
179 string_sprintf(" x=%lu", sig->expires));
180 if (sig->bodylength > -1) logmsg = string_cat(logmsg,
181 string_sprintf(" l=%lu", sig->bodylength));
183 if ( !dkim_verify_status
184 || ( dkim_verify_status == dkim_exim_expand_query(DKIM_VERIFY_STATUS)
185 && dkim_verify_reason == dkim_exim_expand_query(DKIM_VERIFY_REASON)
187 switch (sig->verify_status)
189 case PDKIM_VERIFY_NONE:
190 logmsg = string_cat(logmsg, US" [not verified]");
193 case PDKIM_VERIFY_INVALID:
194 logmsg = string_cat(logmsg, US" [invalid - ");
195 switch (sig->verify_ext_status)
197 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
198 logmsg = string_cat(logmsg,
199 US"public key record (currently?) unavailable]");
202 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
203 logmsg = string_cat(logmsg, US"overlong public key record]");
206 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
207 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
208 logmsg = string_cat(logmsg, US"syntax error in public key record]");
211 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
212 logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
215 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
216 logmsg = string_cat(logmsg, US"unsupported DKIM version]");
220 logmsg = string_cat(logmsg, US"unspecified problem]");
224 case PDKIM_VERIFY_FAIL:
225 logmsg = string_cat(logmsg, US" [verification failed - ");
226 switch (sig->verify_ext_status)
228 case PDKIM_VERIFY_FAIL_BODY:
229 logmsg = string_cat(logmsg,
230 US"body hash mismatch (body probably modified in transit)]");
233 case PDKIM_VERIFY_FAIL_MESSAGE:
234 logmsg = string_cat(logmsg,
235 US"signature did not verify "
236 "(headers probably modified in transit)]");
240 logmsg = string_cat(logmsg, US"unspecified reason]");
244 case PDKIM_VERIFY_PASS:
245 logmsg = string_cat(logmsg, US" [verification succeeded]");
249 logmsg = string_append(logmsg, 5,
250 US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
252 log_write(0, LOG_MAIN, "%s", string_from_gstring(logmsg));
257 /* Log a line for each signature */
259 dkim_exim_verify_log_all(void)
261 pdkim_signature * sig;
262 for (sig = dkim_signatures; sig; sig = sig->next) dkim_exim_verify_log_sig(sig);
267 dkim_exim_verify_finish(void)
269 pdkim_signature * sig;
272 const uschar * errstr;
274 store_pool = POOL_PERM;
276 /* Delete eventual previous signature chain */
279 dkim_signatures = NULL;
281 if (dkim_collect_error)
283 log_write(0, LOG_MAIN,
284 "DKIM: Error during validation, disabling signature verification: %.100s",
286 dkim_disable_verify = TRUE;
290 dkim_collect_input = FALSE;
292 /* Finish DKIM operation and fetch link to signatures chain */
294 rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures, &errstr);
297 log_write(0, LOG_MAIN, "DKIM: validation error: %.100s%s%s", pdkim_errstr(rc),
298 errstr ? ": " : "", errstr ? errstr : US"");
302 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
304 for (sig = dkim_signatures; sig; sig = sig->next)
306 if (sig->domain) g = string_append_listele(g, ':', sig->domain);
307 if (sig->identity) g = string_append_listele(g, ':', sig->identity);
310 if (g) dkim_signers = g->s;
313 store_pool = dkim_verify_oldpool;
318 /* Args as per dkim_exim_acl_run() below */
320 dkim_acl_call(uschar * id, gstring ** res_ptr,
321 uschar ** user_msgptr, uschar ** log_msgptr)
325 debug_printf("calling acl_smtp_dkim for dkim_cur_signer='%s'\n", id);
327 rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
328 dkim_exim_verify_log_sig(dkim_cur_sig);
329 *res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
335 /* For the given identity, run the DKIM ACL once for each matching signature.
338 id Identity to look for in dkim signatures
339 res_ptr ptr to growable string-list of status results,
340 appended to per ACL run
341 user_msgptr where to put a user error (for SMTP response)
342 log_msgptr where to put a logging message (not for SMTP response)
344 Returns: OK access is granted by an ACCEPT verb
345 DISCARD access is granted by a DISCARD verb
346 FAIL access is denied
347 FAIL_DROP access is denied; drop the connection
348 DEFER can't tell at the moment
353 dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
354 uschar ** user_msgptr, uschar ** log_msgptr)
356 pdkim_signature * sig;
360 dkim_verify_status = US"none";
361 dkim_verify_reason = US"";
362 dkim_cur_signer = id;
364 if (dkim_disable_verify || !id || !dkim_verify_ctx)
367 /* Find signatures to run ACL on */
369 for (sig = dkim_signatures; sig; sig = sig->next)
370 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
371 && strcmpic(cmp_val, id) == 0
374 /* The "dkim_domain" and "dkim_selector" expansion variables have
375 related globals, since they are used in the signing code too.
376 Instead of inventing separate names for verification, we set
377 them here. This is easy since a domain and selector is guaranteed
378 to be in a signature. The other dkim_* expansion items are
379 dynamically fetched from dkim_cur_sig at expansion time (see
383 dkim_signing_domain = US sig->domain;
384 dkim_signing_selector = US sig->selector;
385 dkim_key_length = sig->sighash.len * 8;
387 /* These two return static strings, so we can compare the addr
388 later to see if the ACL overwrote them. Check that when logging */
390 dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
391 dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
393 if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
400 /* No matching sig found. Call ACL once anyway. */
403 return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
408 dkim_exim_expand_defaults(int what)
412 case DKIM_ALGO: return US"";
413 case DKIM_BODYLENGTH: return US"9999999999999";
414 case DKIM_CANON_BODY: return US"";
415 case DKIM_CANON_HEADERS: return US"";
416 case DKIM_COPIEDHEADERS: return US"";
417 case DKIM_CREATED: return US"0";
418 case DKIM_EXPIRES: return US"9999999999999";
419 case DKIM_HEADERNAMES: return US"";
420 case DKIM_IDENTITY: return US"";
421 case DKIM_KEY_GRANULARITY: return US"*";
422 case DKIM_KEY_SRVTYPE: return US"*";
423 case DKIM_KEY_NOTES: return US"";
424 case DKIM_KEY_TESTING: return US"0";
425 case DKIM_NOSUBDOMAINS: return US"0";
426 case DKIM_VERIFY_STATUS: return US"none";
427 case DKIM_VERIFY_REASON: return US"";
428 default: return US"";
434 dkim_exim_expand_query(int what)
436 if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
437 return dkim_exim_expand_defaults(what);
442 return dkim_sig_to_a_tag(dkim_cur_sig);
444 case DKIM_BODYLENGTH:
445 return dkim_cur_sig->bodylength >= 0
446 ? string_sprintf("%ld", dkim_cur_sig->bodylength)
447 : dkim_exim_expand_defaults(what);
449 case DKIM_CANON_BODY:
450 switch (dkim_cur_sig->canon_body)
452 case PDKIM_CANON_RELAXED: return US"relaxed";
453 case PDKIM_CANON_SIMPLE:
454 default: return US"simple";
457 case DKIM_CANON_HEADERS:
458 switch (dkim_cur_sig->canon_headers)
460 case PDKIM_CANON_RELAXED: return US"relaxed";
461 case PDKIM_CANON_SIMPLE:
462 default: return US"simple";
465 case DKIM_COPIEDHEADERS:
466 return dkim_cur_sig->copiedheaders
467 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
470 return dkim_cur_sig->created > 0
471 ? string_sprintf("%lu", dkim_cur_sig->created)
472 : dkim_exim_expand_defaults(what);
475 return dkim_cur_sig->expires > 0
476 ? string_sprintf("%lu", dkim_cur_sig->expires)
477 : dkim_exim_expand_defaults(what);
479 case DKIM_HEADERNAMES:
480 return dkim_cur_sig->headernames
481 ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
484 return dkim_cur_sig->identity
485 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
487 case DKIM_KEY_GRANULARITY:
488 return dkim_cur_sig->pubkey
489 ? dkim_cur_sig->pubkey->granularity
490 ? US dkim_cur_sig->pubkey->granularity
491 : dkim_exim_expand_defaults(what)
492 : dkim_exim_expand_defaults(what);
494 case DKIM_KEY_SRVTYPE:
495 return dkim_cur_sig->pubkey
496 ? dkim_cur_sig->pubkey->srvtype
497 ? US dkim_cur_sig->pubkey->srvtype
498 : dkim_exim_expand_defaults(what)
499 : dkim_exim_expand_defaults(what);
502 return dkim_cur_sig->pubkey
503 ? dkim_cur_sig->pubkey->notes
504 ? US dkim_cur_sig->pubkey->notes
505 : dkim_exim_expand_defaults(what)
506 : dkim_exim_expand_defaults(what);
508 case DKIM_KEY_TESTING:
509 return dkim_cur_sig->pubkey
510 ? dkim_cur_sig->pubkey->testing
512 : dkim_exim_expand_defaults(what)
513 : dkim_exim_expand_defaults(what);
515 case DKIM_NOSUBDOMAINS:
516 return dkim_cur_sig->pubkey
517 ? dkim_cur_sig->pubkey->no_subdomaining
519 : dkim_exim_expand_defaults(what)
520 : dkim_exim_expand_defaults(what);
522 case DKIM_VERIFY_STATUS:
523 switch (dkim_cur_sig->verify_status)
525 case PDKIM_VERIFY_INVALID: return US"invalid";
526 case PDKIM_VERIFY_FAIL: return US"fail";
527 case PDKIM_VERIFY_PASS: return US"pass";
528 case PDKIM_VERIFY_NONE:
529 default: return US"none";
532 case DKIM_VERIFY_REASON:
533 switch (dkim_cur_sig->verify_ext_status)
535 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
536 return US"pubkey_unavailable";
537 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
538 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
539 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
540 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
549 /* Generate signatures for the given file.
550 If a prefix is given, prepend it to the file for the calculations.
553 NULL: error; error string written
554 string: signature header(s), or a zero-length string (not an error)
558 dkim_exim_sign(int fd, off_t off, uschar * prefix,
559 struct ob_dkim * dkim, const uschar ** errstr)
561 const uschar * dkim_domain;
563 gstring * seen_doms = NULL;
565 pdkim_signature * sig;
571 int old_pool = store_pool;
574 store_pool = POOL_MAIN;
576 pdkim_init_context(&ctx, dkim->dot_stuffed, &dkim_exim_query_dns_txt);
578 if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
579 /* expansion error, do not send message. */
580 { errwhen = US"dkim_domain"; goto expand_bad; }
582 /* Set $dkim_domain expansion variable to each unique domain in list. */
584 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
586 const uschar * dkim_sel;
589 if (dkim_signing_domain[0] == '\0')
592 /* Only sign once for each domain, no matter how often it
593 appears in the expanded list. */
595 if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
596 0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
599 seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
601 /* Set $dkim_selector expansion variable to each selector in list,
604 if (!(dkim_sel = expand_string(dkim->dkim_selector)))
605 if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
606 { errwhen = US"dkim_selector"; goto expand_bad; }
608 while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
611 uschar * dkim_canon_expanded;
613 uschar * dkim_sign_headers_expanded = NULL;
614 uschar * dkim_private_key_expanded;
615 uschar * dkim_hash_expanded;
616 uschar * dkim_identity_expanded = NULL;
618 /* Get canonicalization to use */
620 dkim_canon_expanded = dkim->dkim_canon
621 ? expand_string(dkim->dkim_canon) : US"relaxed";
622 if (!dkim_canon_expanded) /* expansion error, do not send message. */
623 { errwhen = US"dkim_canon"; goto expand_bad; }
625 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
626 pdkim_canon = PDKIM_CANON_RELAXED;
627 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
628 pdkim_canon = PDKIM_CANON_SIMPLE;
631 log_write(0, LOG_MAIN,
632 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
633 dkim_canon_expanded);
634 pdkim_canon = PDKIM_CANON_RELAXED;
637 if ( dkim->dkim_sign_headers
638 && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
639 { errwhen = US"dkim_sign_header"; goto expand_bad; }
640 /* else pass NULL, which means default header list */
642 /* Get private key to use. */
644 if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
645 { errwhen = US"dkim_private_key"; goto expand_bad; }
647 if ( Ustrlen(dkim_private_key_expanded) == 0
648 || Ustrcmp(dkim_private_key_expanded, "0") == 0
649 || Ustrcmp(dkim_private_key_expanded, "false") == 0
651 continue; /* don't sign, but no error */
653 if (dkim_private_key_expanded[0] == '/')
655 int privkey_fd, off = 0, len;
657 /* Looks like a filename, load the private key. */
659 memset(big_buffer, 0, big_buffer_size);
661 if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
663 log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
664 "private key file for reading: %s",
665 dkim_private_key_expanded);
671 if ((len = read(privkey_fd, big_buffer + off, big_buffer_size - 2 - off)) < 0)
673 (void) close(privkey_fd);
674 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
675 dkim_private_key_expanded);
682 (void) close(privkey_fd);
683 big_buffer[off] = '\0';
684 dkim_private_key_expanded = big_buffer;
687 if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
688 { errwhen = US"dkim_hash"; goto expand_bad; }
690 if (dkim->dkim_identity)
691 if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
692 { errwhen = US"dkim_identity"; goto expand_bad; }
693 else if (!*dkim_identity_expanded)
694 dkim_identity_expanded = NULL;
696 /*XXX so we currently nail signing to RSA + this hash.
697 Need to extract algo from privkey and check for disallowed combos. */
699 if (!(sig = pdkim_init_sign(&ctx, dkim_signing_domain,
700 dkim_signing_selector,
701 dkim_private_key_expanded,
706 dkim_private_key_expanded[0] = '\0';
708 pdkim_set_optional(sig,
709 CS dkim_sign_headers_expanded,
710 CS dkim_identity_expanded,
712 pdkim_canon, -1, 0, 0);
714 if (!ctx.sig) /* link sig to context chain */
718 pdkim_signature * n = ctx.sig;
719 while (n->next) n = n->next;
726 DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
727 sigbuf = string_get(1); /* return a zero-len string */
731 if (prefix && (pdkim_feed(&ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
734 if (lseek(fd, off, SEEK_SET) < 0)
737 while ((sread = read(fd, &buf, sizeof(buf))) > 0)
738 if ((pdkim_rc = pdkim_feed(&ctx, buf, sread)) != PDKIM_OK)
741 /* Handle failed read above. */
744 debug_printf("DKIM: Error reading -K file.\n");
749 /* Build string of headers, one per signature */
751 if ((pdkim_rc = pdkim_feed_finish(&ctx, &sig, errstr)) != PDKIM_OK)
754 for (sigbuf = NULL; sig; sig = sig->next)
755 sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
758 (void) string_from_gstring(sigbuf);
759 store_pool = old_pool;
764 log_write(0, LOG_MAIN|LOG_PANIC,
765 "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
771 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand %s: %s",
772 errwhen, expand_string_message);
776 # endif /*!MACRO_PREDEF*/
777 #endif /*!DISABLE_DKIM*/