Fix DKIM verify operation in -bh test mode. Bug 2017
[exim.git] / src / src / dkim.c
... / ...
CommitLineData
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
5/* Copyright (c) University of Cambridge, 1995 - 2016 */
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* Code for DKIM support. Other DKIM relevant code is in
9 receive.c, transport.c and transports/smtp.c */
10
11#include "exim.h"
12
13#ifndef DISABLE_DKIM
14
15#include "pdkim/pdkim.h"
16
17int dkim_verify_oldpool;
18pdkim_ctx *dkim_verify_ctx = NULL;
19pdkim_signature *dkim_signatures = NULL;
20pdkim_signature *dkim_cur_sig = NULL;
21
22static int
23dkim_exim_query_dns_txt(char *name, char *answer)
24{
25dns_answer dnsa;
26dns_scan dnss;
27dns_record *rr;
28
29lookup_dnssec_authenticated = NULL;
30if (dns_lookup(&dnsa, US name, T_TXT, NULL) != DNS_SUCCEED)
31 return PDKIM_FAIL; /*XXX better error detail? logging? */
32
33/* Search for TXT record */
34
35for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
36 rr;
37 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
38 if (rr->type == T_TXT)
39 {
40 int rr_offset = 0;
41 int answer_offset = 0;
42
43 /* Copy record content to the answer buffer */
44
45 while (rr_offset < rr->size)
46 {
47 uschar len = rr->data[rr_offset++];
48 snprintf(answer + answer_offset,
49 PDKIM_DNS_TXT_MAX_RECLEN - answer_offset,
50 "%.*s", (int)len, (char *) (rr->data + rr_offset));
51 rr_offset += len;
52 answer_offset += len;
53 if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN)
54 return PDKIM_FAIL; /*XXX better error detail? logging? */
55 }
56 return PDKIM_OK;
57 }
58
59return PDKIM_FAIL; /*XXX better error detail? logging? */
60}
61
62
63void
64dkim_exim_init(void)
65{
66pdkim_init();
67}
68
69
70
71void
72dkim_exim_verify_init(BOOL dot_stuffing)
73{
74/* There is a store-reset between header & body reception
75so cannot use the main pool. Any allocs done by Exim
76memory-handling must use the perm pool. */
77
78dkim_verify_oldpool = store_pool;
79store_pool = POOL_PERM;
80
81/* Free previous context if there is one */
82
83if (dkim_verify_ctx)
84 pdkim_free_ctx(dkim_verify_ctx);
85
86/* Create new context */
87
88dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
89dkim_collect_input = !!dkim_verify_ctx;
90
91/* Start feed up with any cached data */
92receive_get_cache();
93
94store_pool = dkim_verify_oldpool;
95}
96
97
98void
99dkim_exim_verify_feed(uschar * data, int len)
100{
101int rc;
102
103store_pool = POOL_PERM;
104if ( dkim_collect_input
105 && (rc = pdkim_feed(dkim_verify_ctx, CS data, len)) != PDKIM_OK)
106 {
107 log_write(0, LOG_MAIN,
108 "DKIM: validation error: %.100s", pdkim_errstr(rc));
109 dkim_collect_input = FALSE;
110 }
111store_pool = dkim_verify_oldpool;
112}
113
114
115void
116dkim_exim_verify_finish(void)
117{
118pdkim_signature *sig = NULL;
119int dkim_signers_size = 0;
120int dkim_signers_ptr = 0;
121dkim_signers = NULL;
122int rc;
123
124store_pool = POOL_PERM;
125
126/* Delete eventual previous signature chain */
127
128dkim_signatures = NULL;
129
130/* If we have arrived here with dkim_collect_input == FALSE, it
131means there was a processing error somewhere along the way.
132Log the incident and disable further verification. */
133
134if (!dkim_collect_input)
135 {
136 log_write(0, LOG_MAIN,
137 "DKIM: Error while running this message through validation,"
138 " disabling signature verification.");
139 dkim_disable_verify = TRUE;
140 goto out;
141 }
142
143dkim_collect_input = FALSE;
144
145/* Finish DKIM operation and fetch link to signatures chain */
146
147if ((rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures)) != PDKIM_OK)
148 {
149 log_write(0, LOG_MAIN,
150 "DKIM: validation error: %.100s", pdkim_errstr(rc));
151 goto out;
152 }
153
154for (sig = dkim_signatures; sig; sig = sig->next)
155 {
156 int size = 0;
157 int ptr = 0;
158
159 /* Log a line for each signature */
160
161 uschar *logmsg = string_append(NULL, &size, &ptr, 5,
162 string_sprintf("d=%s s=%s c=%s/%s a=%s b=%d ",
163 sig->domain,
164 sig->selector,
165 sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
166 sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
167 sig->algo == PDKIM_ALGO_RSA_SHA256
168 ? "rsa-sha256"
169 : sig->algo == PDKIM_ALGO_RSA_SHA1 ? "rsa-sha1" : "err",
170 (int)sig->sigdata.len > -1 ? sig->sigdata.len * 8 : 0
171 ),
172
173 sig->identity ? string_sprintf("i=%s ", sig->identity) : US"",
174 sig->created > 0 ? string_sprintf("t=%lu ", sig->created) : US"",
175 sig->expires > 0 ? string_sprintf("x=%lu ", sig->expires) : US"",
176 sig->bodylength > -1 ? string_sprintf("l=%lu ", sig->bodylength) : US""
177 );
178
179 switch (sig->verify_status)
180 {
181 case PDKIM_VERIFY_NONE:
182 logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
183 break;
184
185 case PDKIM_VERIFY_INVALID:
186 logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
187 switch (sig->verify_ext_status)
188 {
189 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
190 logmsg = string_append(logmsg, &size, &ptr, 1,
191 "public key record (currently?) unavailable]");
192 break;
193
194 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
195 logmsg = string_append(logmsg, &size, &ptr, 1,
196 "overlong public key record]");
197 break;
198
199 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
200 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
201 logmsg = string_append(logmsg, &size, &ptr, 1,
202 "syntax error in public key record]");
203 break;
204
205 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
206 logmsg = string_append(logmsg, &size, &ptr, 1,
207 "signature tag missing or invalid]");
208 break;
209
210 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
211 logmsg = string_append(logmsg, &size, &ptr, 1,
212 "unsupported DKIM version]");
213 break;
214
215 default:
216 logmsg = string_append(logmsg, &size, &ptr, 1,
217 "unspecified problem]");
218 }
219 break;
220
221 case PDKIM_VERIFY_FAIL:
222 logmsg =
223 string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
224 switch (sig->verify_ext_status)
225 {
226 case PDKIM_VERIFY_FAIL_BODY:
227 logmsg = string_append(logmsg, &size, &ptr, 1,
228 "body hash mismatch (body probably modified in transit)]");
229 break;
230
231 case PDKIM_VERIFY_FAIL_MESSAGE:
232 logmsg = string_append(logmsg, &size, &ptr, 1,
233 "signature did not verify (headers probably modified in transit)]");
234 break;
235
236 default:
237 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
238 }
239 break;
240
241 case PDKIM_VERIFY_PASS:
242 logmsg =
243 string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
244 break;
245 }
246
247 logmsg[ptr] = '\0';
248 log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
249
250 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
251
252 dkim_signers = string_append(dkim_signers,
253 &dkim_signers_size,
254 &dkim_signers_ptr, 2, sig->domain, ":");
255
256 if (sig->identity)
257 dkim_signers = string_append(dkim_signers,
258 &dkim_signers_size,
259 &dkim_signers_ptr, 2, sig->identity, ":");
260
261 /* Process next signature */
262 }
263
264/* NULL-terminate and chop the last colon from the domain list */
265
266if (dkim_signers)
267 {
268 dkim_signers[dkim_signers_ptr] = '\0';
269 if (Ustrlen(dkim_signers) > 0)
270 dkim_signers[Ustrlen(dkim_signers) - 1] = '\0';
271 }
272
273out:
274store_pool = dkim_verify_oldpool;
275}
276
277
278void
279dkim_exim_acl_setup(uschar * id)
280{
281pdkim_signature * sig;
282uschar * cmp_val;
283
284dkim_cur_sig = NULL;
285dkim_cur_signer = id;
286
287if (dkim_disable_verify || !id || !dkim_verify_ctx)
288 return;
289
290/* Find signature to run ACL on */
291
292for (sig = dkim_signatures; sig; sig = sig->next)
293 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
294 && strcmpic(cmp_val, id) == 0
295 )
296 {
297 dkim_cur_sig = sig;
298
299 /* The "dkim_domain" and "dkim_selector" expansion variables have
300 related globals, since they are used in the signing code too.
301 Instead of inventing separate names for verification, we set
302 them here. This is easy since a domain and selector is guaranteed
303 to be in a signature. The other dkim_* expansion items are
304 dynamically fetched from dkim_cur_sig at expansion time (see
305 function below). */
306
307 dkim_signing_domain = US sig->domain;
308 dkim_signing_selector = US sig->selector;
309 dkim_key_length = sig->sigdata.len * 8;
310 return;
311 }
312}
313
314
315static uschar *
316dkim_exim_expand_defaults(int what)
317{
318switch (what)
319 {
320 case DKIM_ALGO: return US"";
321 case DKIM_BODYLENGTH: return US"9999999999999";
322 case DKIM_CANON_BODY: return US"";
323 case DKIM_CANON_HEADERS: return US"";
324 case DKIM_COPIEDHEADERS: return US"";
325 case DKIM_CREATED: return US"0";
326 case DKIM_EXPIRES: return US"9999999999999";
327 case DKIM_HEADERNAMES: return US"";
328 case DKIM_IDENTITY: return US"";
329 case DKIM_KEY_GRANULARITY: return US"*";
330 case DKIM_KEY_SRVTYPE: return US"*";
331 case DKIM_KEY_NOTES: return US"";
332 case DKIM_KEY_TESTING: return US"0";
333 case DKIM_NOSUBDOMAINS: return US"0";
334 case DKIM_VERIFY_STATUS: return US"none";
335 case DKIM_VERIFY_REASON: return US"";
336 default: return US"";
337 }
338}
339
340
341uschar *
342dkim_exim_expand_query(int what)
343{
344if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
345 return dkim_exim_expand_defaults(what);
346
347switch (what)
348 {
349 case DKIM_ALGO:
350 switch (dkim_cur_sig->algo)
351 {
352 case PDKIM_ALGO_RSA_SHA1: return US"rsa-sha1";
353 case PDKIM_ALGO_RSA_SHA256:
354 default: return US"rsa-sha256";
355 }
356
357 case DKIM_BODYLENGTH:
358 return dkim_cur_sig->bodylength >= 0
359 ? string_sprintf(OFF_T_FMT, (LONGLONG_T) dkim_cur_sig->bodylength)
360 : dkim_exim_expand_defaults(what);
361
362 case DKIM_CANON_BODY:
363 switch (dkim_cur_sig->canon_body)
364 {
365 case PDKIM_CANON_RELAXED: return US"relaxed";
366 case PDKIM_CANON_SIMPLE:
367 default: return US"simple";
368 }
369
370 case DKIM_CANON_HEADERS:
371 switch (dkim_cur_sig->canon_headers)
372 {
373 case PDKIM_CANON_RELAXED: return US"relaxed";
374 case PDKIM_CANON_SIMPLE:
375 default: return US"simple";
376 }
377
378 case DKIM_COPIEDHEADERS:
379 return dkim_cur_sig->copiedheaders
380 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
381
382 case DKIM_CREATED:
383 return dkim_cur_sig->created > 0
384 ? string_sprintf("%llu", dkim_cur_sig->created)
385 : dkim_exim_expand_defaults(what);
386
387 case DKIM_EXPIRES:
388 return dkim_cur_sig->expires > 0
389 ? string_sprintf("%llu", dkim_cur_sig->expires)
390 : dkim_exim_expand_defaults(what);
391
392 case DKIM_HEADERNAMES:
393 return dkim_cur_sig->headernames
394 ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
395
396 case DKIM_IDENTITY:
397 return dkim_cur_sig->identity
398 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
399
400 case DKIM_KEY_GRANULARITY:
401 return dkim_cur_sig->pubkey
402 ? dkim_cur_sig->pubkey->granularity
403 ? US dkim_cur_sig->pubkey->granularity
404 : dkim_exim_expand_defaults(what)
405 : dkim_exim_expand_defaults(what);
406
407 case DKIM_KEY_SRVTYPE:
408 return dkim_cur_sig->pubkey
409 ? dkim_cur_sig->pubkey->srvtype
410 ? US dkim_cur_sig->pubkey->srvtype
411 : dkim_exim_expand_defaults(what)
412 : dkim_exim_expand_defaults(what);
413
414 case DKIM_KEY_NOTES:
415 return dkim_cur_sig->pubkey
416 ? dkim_cur_sig->pubkey->notes
417 ? US dkim_cur_sig->pubkey->notes
418 : dkim_exim_expand_defaults(what)
419 : dkim_exim_expand_defaults(what);
420
421 case DKIM_KEY_TESTING:
422 return dkim_cur_sig->pubkey
423 ? dkim_cur_sig->pubkey->testing
424 ? US"1"
425 : dkim_exim_expand_defaults(what)
426 : dkim_exim_expand_defaults(what);
427
428 case DKIM_NOSUBDOMAINS:
429 return dkim_cur_sig->pubkey
430 ? dkim_cur_sig->pubkey->no_subdomaining
431 ? US"1"
432 : dkim_exim_expand_defaults(what)
433 : dkim_exim_expand_defaults(what);
434
435 case DKIM_VERIFY_STATUS:
436 switch (dkim_cur_sig->verify_status)
437 {
438 case PDKIM_VERIFY_INVALID: return US"invalid";
439 case PDKIM_VERIFY_FAIL: return US"fail";
440 case PDKIM_VERIFY_PASS: return US"pass";
441 case PDKIM_VERIFY_NONE:
442 default: return US"none";
443 }
444
445 case DKIM_VERIFY_REASON:
446 switch (dkim_cur_sig->verify_ext_status)
447 {
448 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
449 return US"pubkey_unavailable";
450 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
451 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
452 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
453 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
454 }
455
456 default:
457 return US"";
458 }
459}
460
461
462uschar *
463dkim_exim_sign(int dkim_fd, struct ob_dkim * dkim)
464{
465const uschar * dkim_domain;
466int sep = 0;
467uschar *seen_items = NULL;
468int seen_items_size = 0;
469int seen_items_offset = 0;
470uschar itembuf[256];
471uschar *dkim_canon_expanded;
472uschar *dkim_sign_headers_expanded;
473uschar *dkim_private_key_expanded;
474pdkim_ctx *ctx = NULL;
475uschar *rc = NULL;
476uschar *sigbuf = NULL;
477int sigsize = 0;
478int sigptr = 0;
479pdkim_signature *signature;
480int pdkim_canon;
481int pdkim_rc;
482int sread;
483char buf[4096];
484int save_errno = 0;
485int old_pool = store_pool;
486
487store_pool = POOL_MAIN;
488
489if (!(dkim_domain = expand_cstring(dkim->dkim_domain)))
490 {
491 /* expansion error, do not send message. */
492 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
493 "dkim_domain: %s", expand_string_message);
494 goto bad;
495 }
496
497/* Set $dkim_domain expansion variable to each unique domain in list. */
498
499while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
500 itembuf, sizeof(itembuf))))
501 {
502 if (!dkim_signing_domain || dkim_signing_domain[0] == '\0')
503 continue;
504
505 /* Only sign once for each domain, no matter how often it
506 appears in the expanded list. */
507
508 if (seen_items)
509 {
510 const uschar *seen_items_list = seen_items;
511 if (match_isinlist(dkim_signing_domain,
512 &seen_items_list, 0, NULL, NULL, MCL_STRING, TRUE,
513 NULL) == OK)
514 continue;
515
516 seen_items =
517 string_append(seen_items, &seen_items_size, &seen_items_offset, 1, ":");
518 }
519
520 seen_items =
521 string_append(seen_items, &seen_items_size, &seen_items_offset, 1,
522 dkim_signing_domain);
523 seen_items[seen_items_offset] = '\0';
524
525 /* Set up $dkim_selector expansion variable. */
526
527 if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
528 {
529 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
530 "dkim_selector: %s", expand_string_message);
531 goto bad;
532 }
533
534 /* Get canonicalization to use */
535
536 dkim_canon_expanded = dkim->dkim_canon
537 ? expand_string(dkim->dkim_canon) : US"relaxed";
538 if (!dkim_canon_expanded)
539 {
540 /* expansion error, do not send message. */
541 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
542 "dkim_canon: %s", expand_string_message);
543 goto bad;
544 }
545
546 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
547 pdkim_canon = PDKIM_CANON_RELAXED;
548 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
549 pdkim_canon = PDKIM_CANON_SIMPLE;
550 else
551 {
552 log_write(0, LOG_MAIN,
553 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
554 dkim_canon_expanded);
555 pdkim_canon = PDKIM_CANON_RELAXED;
556 }
557
558 dkim_sign_headers_expanded = NULL;
559 if (dkim->dkim_sign_headers)
560 if (!(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
561 {
562 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
563 "dkim_sign_headers: %s", expand_string_message);
564 goto bad;
565 }
566 /* else pass NULL, which means default header list */
567
568 /* Get private key to use. */
569
570 if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
571 {
572 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand "
573 "dkim_private_key: %s", expand_string_message);
574 goto bad;
575 }
576
577 if ( Ustrlen(dkim_private_key_expanded) == 0
578 || Ustrcmp(dkim_private_key_expanded, "0") == 0
579 || Ustrcmp(dkim_private_key_expanded, "false") == 0
580 )
581 continue; /* don't sign, but no error */
582
583 if (dkim_private_key_expanded[0] == '/')
584 {
585 int privkey_fd = 0;
586
587 /* Looks like a filename, load the private key. */
588
589 memset(big_buffer, 0, big_buffer_size);
590
591 if ((privkey_fd = open(CS dkim_private_key_expanded, O_RDONLY)) < 0)
592 {
593 log_write(0, LOG_MAIN | LOG_PANIC, "unable to open "
594 "private key file for reading: %s",
595 dkim_private_key_expanded);
596 goto bad;
597 }
598
599 if (read(privkey_fd, big_buffer, big_buffer_size - 2) < 0)
600 {
601 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
602 dkim_private_key_expanded);
603 goto bad;
604 }
605
606 (void) close(privkey_fd);
607 dkim_private_key_expanded = big_buffer;
608 }
609
610 ctx = pdkim_init_sign(CS dkim_signing_domain,
611 CS dkim_signing_selector,
612 CS dkim_private_key_expanded,
613 PDKIM_ALGO_RSA_SHA256,
614 dkim->dot_stuffed,
615 &dkim_exim_query_dns_txt
616 );
617 dkim_private_key_expanded[0] = '\0';
618 pdkim_set_optional(ctx,
619 CS dkim_sign_headers_expanded,
620 NULL,
621 pdkim_canon,
622 pdkim_canon, -1, 0, 0);
623
624 lseek(dkim_fd, 0, SEEK_SET);
625
626 while ((sread = read(dkim_fd, &buf, sizeof(buf))) > 0)
627 if ((pdkim_rc = pdkim_feed(ctx, buf, sread)) != PDKIM_OK)
628 goto pk_bad;
629
630 /* Handle failed read above. */
631 if (sread == -1)
632 {
633 debug_printf("DKIM: Error reading -K file.\n");
634 save_errno = errno;
635 goto bad;
636 }
637
638 if ((pdkim_rc = pdkim_feed_finish(ctx, &signature)) != PDKIM_OK)
639 goto pk_bad;
640
641 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
642 US signature->signature_header, US"\r\n");
643
644 pdkim_free_ctx(ctx);
645 ctx = NULL;
646 }
647
648if (sigbuf)
649 {
650 sigbuf[sigptr] = '\0';
651 rc = sigbuf;
652 }
653else
654 rc = US"";
655
656CLEANUP:
657 if (ctx)
658 pdkim_free_ctx(ctx);
659 store_pool = old_pool;
660 errno = save_errno;
661 return rc;
662
663pk_bad:
664 log_write(0, LOG_MAIN|LOG_PANIC,
665 "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
666bad:
667 rc = NULL;
668 goto CLEANUP;
669}
670
671#endif