Compiler quietening
[exim.git] / src / src / dkim.c
CommitLineData
80a47a2c
TK
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge, 1995 - 2018 */
80a47a2c
TK
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
970424a5
JH
15# include "pdkim/pdkim.h"
16
17# ifdef MACRO_PREDEF
5f69a529 18# include "macro_predef.h"
970424a5
JH
19
20void
a2701501 21params_dkim(void)
970424a5
JH
22{
23builtin_macro_create_var(US"_DKIM_SIGN_HEADERS", US PDKIM_DEFAULT_SIGN_HEADERS);
24}
25# else /*!MACRO_PREDEF*/
26
27
28
617d3932 29pdkim_ctx dkim_sign_ctx;
80a47a2c 30
ca9cb170 31int dkim_verify_oldpool;
1cfe5c1c 32pdkim_ctx *dkim_verify_ctx = NULL;
80a47a2c 33pdkim_signature *dkim_signatures = NULL;
1cfe5c1c 34pdkim_signature *dkim_cur_sig = NULL;
b9df1829 35static const uschar * dkim_collect_error = NULL;
80a47a2c 36
d73e45df
JH
37
38
39/*XXX the caller only uses the first record if we return multiple.
d73e45df
JH
40*/
41
617d3932
JH
42uschar *
43dkim_exim_query_dns_txt(uschar * name)
1cfe5c1c
JH
44{
45dns_answer dnsa;
46dns_scan dnss;
47dns_record *rr;
1eedc10f 48gstring * g = NULL;
80a47a2c 49
1cfe5c1c 50lookup_dnssec_authenticated = NULL;
617d3932 51if (dns_lookup(&dnsa, name, T_TXT, NULL) != DNS_SUCCEED)
1eedc10f 52 return NULL; /*XXX better error detail? logging? */
80a47a2c 53
1cfe5c1c 54/* Search for TXT record */
80a47a2c 55
1cfe5c1c
JH
56for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
57 rr;
58 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
59 if (rr->type == T_TXT)
60 {
80a47a2c 61 int rr_offset = 0;
1cfe5c1c
JH
62
63 /* Copy record content to the answer buffer */
64
65 while (rr_offset < rr->size)
66 {
67 uschar len = rr->data[rr_offset++];
1eedc10f
JH
68
69 g = string_catn(g, US(rr->data + rr_offset), len);
70 if (g->ptr >= PDKIM_DNS_TXT_MAX_RECLEN)
71 goto bad;
72
1cfe5c1c 73 rr_offset += len;
4263f395 74 }
dd33c4e6
HSHR
75
76 /* check if this looks like a DKIM record */
d011368a 77 if (Ustrncmp(g->s, "v=", 2) != 0 || strncasecmp(CS g->s, "v=dkim", 6) == 0)
1eedc10f 78 {
617d3932 79 gstring_reset_unused(g);
1eedc10f
JH
80 return string_from_gstring(g);
81 }
82
83 if (g) g->ptr = 0; /* overwrite previous record */
80a47a2c 84 }
80a47a2c 85
1eedc10f
JH
86bad:
87if (g) store_reset(g);
88return NULL; /*XXX better error detail? logging? */
80a47a2c
TK
89}
90
91
1cfe5c1c 92void
2592e6c0
JH
93dkim_exim_init(void)
94{
95pdkim_init();
96}
97
98
ca9cb170 99
2592e6c0 100void
e983e85a 101dkim_exim_verify_init(BOOL dot_stuffing)
1cfe5c1c 102{
ca9cb170
JH
103/* There is a store-reset between header & body reception
104so cannot use the main pool. Any allocs done by Exim
105memory-handling must use the perm pool. */
106
107dkim_verify_oldpool = store_pool;
108store_pool = POOL_PERM;
109
1cfe5c1c 110/* Free previous context if there is one */
80a47a2c 111
1cfe5c1c
JH
112if (dkim_verify_ctx)
113 pdkim_free_ctx(dkim_verify_ctx);
80a47a2c 114
1cfe5c1c 115/* Create new context */
80a47a2c 116
e983e85a 117dkim_verify_ctx = pdkim_init_verify(&dkim_exim_query_dns_txt, dot_stuffing);
3b957582 118dkim_collect_input = !!dkim_verify_ctx;
b9df1829 119dkim_collect_error = NULL;
ca9cb170 120
584e96c6
JH
121/* Start feed up with any cached data */
122receive_get_cache();
123
ca9cb170 124store_pool = dkim_verify_oldpool;
80a47a2c
TK
125}
126
127
1cfe5c1c
JH
128void
129dkim_exim_verify_feed(uschar * data, int len)
130{
f7302073
JH
131int rc;
132
ca9cb170 133store_pool = POOL_PERM;
1cfe5c1c 134if ( dkim_collect_input
ef698bf6 135 && (rc = pdkim_feed(dkim_verify_ctx, data, len)) != PDKIM_OK)
f7302073 136 {
b9df1829 137 dkim_collect_error = pdkim_errstr(rc);
f7302073 138 log_write(0, LOG_MAIN,
b9df1829 139 "DKIM: validation error: %.100s", dkim_collect_error);
1cfe5c1c 140 dkim_collect_input = FALSE;
f7302073 141 }
ca9cb170 142store_pool = dkim_verify_oldpool;
80a47a2c
TK
143}
144
145
a79d8834
JH
146/* Log the result for the given signature */
147static void
148dkim_exim_verify_log_sig(pdkim_signature * sig)
1cfe5c1c 149{
cc55f420 150gstring * logmsg;
a79d8834
JH
151uschar * s;
152
cc55f420
JH
153if (!sig) return;
154
6678c382 155/* Remember the domain for the first pass result */
dfbcb5ac 156
2c47372f
JH
157if ( !dkim_verify_overall
158 && dkim_verify_status
159 ? Ustrcmp(dkim_verify_status, US"pass") == 0
160 : sig->verify_status == PDKIM_VERIFY_PASS
161 )
162 dkim_verify_overall = string_copy(sig->domain);
163
6678c382
JH
164/* Rewrite the sig result if the ACL overrode it. This is only
165needed because the DMARC code (sigh) peeks at the dkim sigs.
166Mark the sig for this having been done. */
167
168if ( dkim_verify_status
169 && ( dkim_verify_status != dkim_exim_expand_query(DKIM_VERIFY_STATUS)
170 || dkim_verify_reason != dkim_exim_expand_query(DKIM_VERIFY_REASON)
171 ) )
172 { /* overridden by ACL */
173 sig->verify_ext_status = -1;
174 if (Ustrcmp(dkim_verify_status, US"fail") == 0)
175 sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_FAIL;
176 else if (Ustrcmp(dkim_verify_status, US"invalid") == 0)
177 sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_INVALID;
178 else if (Ustrcmp(dkim_verify_status, US"none") == 0)
179 sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_NONE;
180 else if (Ustrcmp(dkim_verify_status, US"pass") == 0)
181 sig->verify_status = PDKIM_VERIFY_POLICY | PDKIM_VERIFY_PASS;
182 else
183 sig->verify_status = -1;
184 }
185
2c47372f
JH
186if (!LOGGING(dkim_verbose)) return;
187
6678c382 188
aaaa94ea 189logmsg = string_catn(NULL, US"DKIM: ", 6);
a79d8834
JH
190if (!(s = sig->domain)) s = US"<UNSET>";
191logmsg = string_append(logmsg, 2, "d=", s);
192if (!(s = sig->selector)) s = US"<UNSET>";
193logmsg = string_append(logmsg, 2, " s=", s);
dd33c4e6 194logmsg = string_append(logmsg, 7,
dfbcb5ac
JH
195 " c=", sig->canon_headers == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
196 "/", sig->canon_body == PDKIM_CANON_SIMPLE ? "simple" : "relaxed",
197 " a=", dkim_sig_to_a_tag(sig),
a79d8834
JH
198string_sprintf(" b=" SIZE_T_FMT,
199 (int)sig->sighash.len > -1 ? sig->sighash.len * 8 : 0));
200if ((s= sig->identity)) logmsg = string_append(logmsg, 2, " i=", s);
201if (sig->created > 0) logmsg = string_cat(logmsg,
202 string_sprintf(" t=%lu", sig->created));
203if (sig->expires > 0) logmsg = string_cat(logmsg,
204 string_sprintf(" x=%lu", sig->expires));
205if (sig->bodylength > -1) logmsg = string_cat(logmsg,
206 string_sprintf(" l=%lu", sig->bodylength));
207
dfbcb5ac
JH
208if (sig->verify_status & PDKIM_VERIFY_POLICY)
209 logmsg = string_append(logmsg, 5,
210 US" [", dkim_verify_status, US" - ", dkim_verify_reason, US"]");
211else
1cfe5c1c
JH
212 switch (sig->verify_status)
213 {
214 case PDKIM_VERIFY_NONE:
aaaa94ea 215 logmsg = string_cat(logmsg, US" [not verified]");
80a47a2c 216 break;
1cfe5c1c
JH
217
218 case PDKIM_VERIFY_INVALID:
aaaa94ea 219 logmsg = string_cat(logmsg, US" [invalid - ");
1cfe5c1c
JH
220 switch (sig->verify_ext_status)
221 {
222 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
acec9514 223 logmsg = string_cat(logmsg,
aaaa94ea 224 US"public key record (currently?) unavailable]");
1cfe5c1c
JH
225 break;
226
227 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
aaaa94ea 228 logmsg = string_cat(logmsg, US"overlong public key record]");
1cfe5c1c
JH
229 break;
230
df3def24
JH
231 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
232 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
aaaa94ea 233 logmsg = string_cat(logmsg, US"syntax error in public key record]");
1cfe5c1c 234 break;
07eeb4df 235
a79d8834 236 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
aaaa94ea 237 logmsg = string_cat(logmsg, US"signature tag missing or invalid]");
a79d8834 238 break;
07eeb4df 239
a79d8834 240 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
aaaa94ea 241 logmsg = string_cat(logmsg, US"unsupported DKIM version]");
a79d8834 242 break;
1cfe5c1c
JH
243
244 default:
aaaa94ea 245 logmsg = string_cat(logmsg, US"unspecified problem]");
1cfe5c1c 246 }
80a47a2c 247 break;
1cfe5c1c
JH
248
249 case PDKIM_VERIFY_FAIL:
aaaa94ea 250 logmsg = string_cat(logmsg, US" [verification failed - ");
1cfe5c1c
JH
251 switch (sig->verify_ext_status)
252 {
253 case PDKIM_VERIFY_FAIL_BODY:
acec9514 254 logmsg = string_cat(logmsg,
aaaa94ea 255 US"body hash mismatch (body probably modified in transit)]");
1cfe5c1c
JH
256 break;
257
258 case PDKIM_VERIFY_FAIL_MESSAGE:
acec9514 259 logmsg = string_cat(logmsg,
aaaa94ea
JH
260 US"signature did not verify "
261 "(headers probably modified in transit)]");
dfbcb5ac 262 break;
1cfe5c1c
JH
263
264 default:
aaaa94ea 265 logmsg = string_cat(logmsg, US"unspecified reason]");
1cfe5c1c 266 }
80a47a2c 267 break;
1cfe5c1c
JH
268
269 case PDKIM_VERIFY_PASS:
aaaa94ea 270 logmsg = string_cat(logmsg, US" [verification succeeded]");
80a47a2c
TK
271 break;
272 }
a79d8834 273
aaaa94ea 274log_write(0, LOG_MAIN, "%s", string_from_gstring(logmsg));
a79d8834
JH
275return;
276}
277
80a47a2c 278
a79d8834
JH
279/* Log a line for each signature */
280void
281dkim_exim_verify_log_all(void)
282{
283pdkim_signature * sig;
284for (sig = dkim_signatures; sig; sig = sig->next) dkim_exim_verify_log_sig(sig);
285}
1cfe5c1c 286
80a47a2c 287
a79d8834
JH
288void
289dkim_exim_verify_finish(void)
290{
291pdkim_signature * sig;
292int rc;
293gstring * g = NULL;
286b9d5f 294const uschar * errstr = NULL;
a79d8834
JH
295
296store_pool = POOL_PERM;
297
298/* Delete eventual previous signature chain */
299
300dkim_signers = NULL;
301dkim_signatures = NULL;
302
303if (dkim_collect_error)
304 {
305 log_write(0, LOG_MAIN,
306 "DKIM: Error during validation, disabling signature verification: %.100s",
307 dkim_collect_error);
308 dkim_disable_verify = TRUE;
309 goto out;
310 }
311
312dkim_collect_input = FALSE;
313
314/* Finish DKIM operation and fetch link to signatures chain */
315
316rc = pdkim_feed_finish(dkim_verify_ctx, &dkim_signatures, &errstr);
286b9d5f
JH
317if (rc != PDKIM_OK && errstr)
318 log_write(0, LOG_MAIN, "DKIM: validation error: %s", errstr);
a79d8834
JH
319
320/* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
321
322for (sig = dkim_signatures; sig; sig = sig->next)
323 {
324 if (sig->domain) g = string_append_listele(g, ':', sig->domain);
325 if (sig->identity) g = string_append_listele(g, ':', sig->identity);
80a47a2c
TK
326 }
327
acec9514
JH
328if (g) dkim_signers = g->s;
329
ca9cb170
JH
330out:
331store_pool = dkim_verify_oldpool;
80a47a2c
TK
332}
333
334
18067c75
JH
335
336/* Args as per dkim_exim_acl_run() below */
337static int
338dkim_acl_call(uschar * id, gstring ** res_ptr,
339 uschar ** user_msgptr, uschar ** log_msgptr)
340{
341int rc;
342DEBUG(D_receive)
343 debug_printf("calling acl_smtp_dkim for dkim_cur_signer='%s'\n", id);
344
345rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, user_msgptr, log_msgptr);
346dkim_exim_verify_log_sig(dkim_cur_sig);
347*res_ptr = string_append_listele(*res_ptr, ':', dkim_verify_status);
348return rc;
349}
350
351
352
353/* For the given identity, run the DKIM ACL once for each matching signature.
354
355Arguments
356 id Identity to look for in dkim signatures
357 res_ptr ptr to growable string-list of status results,
358 appended to per ACL run
359 user_msgptr where to put a user error (for SMTP response)
360 log_msgptr where to put a logging message (not for SMTP response)
361
362Returns: OK access is granted by an ACCEPT verb
363 DISCARD access is granted by a DISCARD verb
364 FAIL access is denied
365 FAIL_DROP access is denied; drop the connection
366 DEFER can't tell at the moment
367 ERROR disaster
368*/
369
370int
371dkim_exim_acl_run(uschar * id, gstring ** res_ptr,
372 uschar ** user_msgptr, uschar ** log_msgptr)
1cfe5c1c
JH
373{
374pdkim_signature * sig;
375uschar * cmp_val;
18067c75 376int rc = -1;
1cfe5c1c 377
cc55f420
JH
378dkim_verify_status = US"none";
379dkim_verify_reason = US"";
1cfe5c1c
JH
380dkim_cur_signer = id;
381
382if (dkim_disable_verify || !id || !dkim_verify_ctx)
18067c75 383 return OK;
1cfe5c1c 384
18067c75 385/* Find signatures to run ACL on */
1cfe5c1c
JH
386
387for (sig = dkim_signatures; sig; sig = sig->next)
388 if ( (cmp_val = Ustrchr(id, '@') != NULL ? US sig->identity : US sig->domain)
389 && strcmpic(cmp_val, id) == 0
390 )
391 {
1cfe5c1c 392 /* The "dkim_domain" and "dkim_selector" expansion variables have
18067c75
JH
393 related globals, since they are used in the signing code too.
394 Instead of inventing separate names for verification, we set
395 them here. This is easy since a domain and selector is guaranteed
396 to be in a signature. The other dkim_* expansion items are
397 dynamically fetched from dkim_cur_sig at expansion time (see
398 function below). */
1cfe5c1c 399
18067c75 400 dkim_cur_sig = sig;
1cfe5c1c
JH
401 dkim_signing_domain = US sig->domain;
402 dkim_signing_selector = US sig->selector;
dcd03763 403 dkim_key_length = sig->sighash.len * 8;
a79d8834
JH
404
405 /* These two return static strings, so we can compare the addr
406 later to see if the ACL overwrote them. Check that when logging */
407
408 dkim_verify_status = dkim_exim_expand_query(DKIM_VERIFY_STATUS);
409 dkim_verify_reason = dkim_exim_expand_query(DKIM_VERIFY_REASON);
dd33c4e6 410
18067c75
JH
411 if ((rc = dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr)) != OK)
412 return rc;
80a47a2c 413 }
18067c75
JH
414
415if (rc != -1)
416 return rc;
417
418/* No matching sig found. Call ACL once anyway. */
419
420dkim_cur_sig = NULL;
421return dkim_acl_call(id, res_ptr, user_msgptr, log_msgptr);
80a47a2c
TK
422}
423
424
1cfe5c1c
JH
425static uschar *
426dkim_exim_expand_defaults(int what)
427{
428switch (what)
429 {
430 case DKIM_ALGO: return US"";
431 case DKIM_BODYLENGTH: return US"9999999999999";
432 case DKIM_CANON_BODY: return US"";
433 case DKIM_CANON_HEADERS: return US"";
434 case DKIM_COPIEDHEADERS: return US"";
435 case DKIM_CREATED: return US"0";
436 case DKIM_EXPIRES: return US"9999999999999";
437 case DKIM_HEADERNAMES: return US"";
438 case DKIM_IDENTITY: return US"";
439 case DKIM_KEY_GRANULARITY: return US"*";
440 case DKIM_KEY_SRVTYPE: return US"*";
441 case DKIM_KEY_NOTES: return US"";
442 case DKIM_KEY_TESTING: return US"0";
443 case DKIM_NOSUBDOMAINS: return US"0";
444 case DKIM_VERIFY_STATUS: return US"none";
445 case DKIM_VERIFY_REASON: return US"";
446 default: return US"";
447 }
448}
80a47a2c 449
80a47a2c 450
1cfe5c1c
JH
451uschar *
452dkim_exim_expand_query(int what)
453{
454if (!dkim_verify_ctx || dkim_disable_verify || !dkim_cur_sig)
455 return dkim_exim_expand_defaults(what);
456
457switch (what)
458 {
459 case DKIM_ALGO:
d73e45df 460 return dkim_sig_to_a_tag(dkim_cur_sig);
1cfe5c1c
JH
461
462 case DKIM_BODYLENGTH:
463 return dkim_cur_sig->bodylength >= 0
bb07bcd3 464 ? string_sprintf("%ld", dkim_cur_sig->bodylength)
1cfe5c1c
JH
465 : dkim_exim_expand_defaults(what);
466
467 case DKIM_CANON_BODY:
468 switch (dkim_cur_sig->canon_body)
469 {
470 case PDKIM_CANON_RELAXED: return US"relaxed";
471 case PDKIM_CANON_SIMPLE:
472 default: return US"simple";
80a47a2c 473 }
1cfe5c1c
JH
474
475 case DKIM_CANON_HEADERS:
a79d8834
JH
476 switch (dkim_cur_sig->canon_headers)
477 {
478 case PDKIM_CANON_RELAXED: return US"relaxed";
479 case PDKIM_CANON_SIMPLE:
480 default: return US"simple";
481 }
1cfe5c1c
JH
482
483 case DKIM_COPIEDHEADERS:
484 return dkim_cur_sig->copiedheaders
485 ? US dkim_cur_sig->copiedheaders : dkim_exim_expand_defaults(what);
486
487 case DKIM_CREATED:
488 return dkim_cur_sig->created > 0
bb07bcd3 489 ? string_sprintf("%lu", dkim_cur_sig->created)
1cfe5c1c
JH
490 : dkim_exim_expand_defaults(what);
491
492 case DKIM_EXPIRES:
493 return dkim_cur_sig->expires > 0
bb07bcd3 494 ? string_sprintf("%lu", dkim_cur_sig->expires)
1cfe5c1c
JH
495 : dkim_exim_expand_defaults(what);
496
497 case DKIM_HEADERNAMES:
498 return dkim_cur_sig->headernames
2592e6c0 499 ? dkim_cur_sig->headernames : dkim_exim_expand_defaults(what);
1cfe5c1c
JH
500
501 case DKIM_IDENTITY:
502 return dkim_cur_sig->identity
503 ? US dkim_cur_sig->identity : dkim_exim_expand_defaults(what);
504
505 case DKIM_KEY_GRANULARITY:
506 return dkim_cur_sig->pubkey
507 ? dkim_cur_sig->pubkey->granularity
508 ? US dkim_cur_sig->pubkey->granularity
509 : dkim_exim_expand_defaults(what)
510 : dkim_exim_expand_defaults(what);
511
512 case DKIM_KEY_SRVTYPE:
513 return dkim_cur_sig->pubkey
514 ? dkim_cur_sig->pubkey->srvtype
515 ? US dkim_cur_sig->pubkey->srvtype
516 : dkim_exim_expand_defaults(what)
517 : dkim_exim_expand_defaults(what);
518
519 case DKIM_KEY_NOTES:
520 return dkim_cur_sig->pubkey
521 ? dkim_cur_sig->pubkey->notes
522 ? US dkim_cur_sig->pubkey->notes
523 : dkim_exim_expand_defaults(what)
524 : dkim_exim_expand_defaults(what);
525
526 case DKIM_KEY_TESTING:
527 return dkim_cur_sig->pubkey
528 ? dkim_cur_sig->pubkey->testing
529 ? US"1"
530 : dkim_exim_expand_defaults(what)
531 : dkim_exim_expand_defaults(what);
532
533 case DKIM_NOSUBDOMAINS:
534 return dkim_cur_sig->pubkey
535 ? dkim_cur_sig->pubkey->no_subdomaining
536 ? US"1"
537 : dkim_exim_expand_defaults(what)
538 : dkim_exim_expand_defaults(what);
539
540 case DKIM_VERIFY_STATUS:
541 switch (dkim_cur_sig->verify_status)
542 {
543 case PDKIM_VERIFY_INVALID: return US"invalid";
544 case PDKIM_VERIFY_FAIL: return US"fail";
545 case PDKIM_VERIFY_PASS: return US"pass";
546 case PDKIM_VERIFY_NONE:
547 default: return US"none";
80a47a2c 548 }
80a47a2c 549
1cfe5c1c
JH
550 case DKIM_VERIFY_REASON:
551 switch (dkim_cur_sig->verify_ext_status)
552 {
553 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
554 return US"pubkey_unavailable";
df3def24
JH
555 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:return US"pubkey_dns_syntax";
556 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT: return US"pubkey_der_syntax";
1cfe5c1c
JH
557 case PDKIM_VERIFY_FAIL_BODY: return US"bodyhash_mismatch";
558 case PDKIM_VERIFY_FAIL_MESSAGE: return US"signature_incorrect";
559 }
80a47a2c 560
1cfe5c1c
JH
561 default:
562 return US"";
80a47a2c
TK
563 }
564}
565
566
617d3932
JH
567void
568dkim_exim_sign_init(void)
569{
570int old_pool = store_pool;
571store_pool = POOL_MAIN;
572pdkim_init_context(&dkim_sign_ctx, FALSE, &dkim_exim_query_dns_txt);
573store_pool = old_pool;
574}
575
576
0b8f4f1a 577/* Generate signatures for the given file.
42055a33 578If a prefix is given, prepend it to the file for the calculations.
0b8f4f1a
JH
579
580Return:
581 NULL: error; error string written
582 string: signature header(s), or a zero-length string (not an error)
42055a33
JH
583*/
584
acec9514 585gstring *
42055a33
JH
586dkim_exim_sign(int fd, off_t off, uschar * prefix,
587 struct ob_dkim * dkim, const uschar ** errstr)
55414b25 588{
617d3932 589const uschar * dkim_domain = NULL;
1cfe5c1c 590int sep = 0;
acec9514 591gstring * seen_doms = NULL;
9e70917d 592pdkim_signature * sig;
acec9514 593gstring * sigbuf;
1cfe5c1c
JH
594int pdkim_rc;
595int sread;
ef698bf6 596uschar buf[4096];
1cfe5c1c
JH
597int save_errno = 0;
598int old_pool = store_pool;
7c6ec81b 599uschar * errwhen;
617d3932 600const uschar * s;
1cfe5c1c 601
617d3932
JH
602if (dkim->dot_stuffed)
603 dkim_sign_ctx.flags |= PDKIM_DOT_TERM;
1cfe5c1c 604
617d3932 605store_pool = POOL_MAIN;
9e70917d 606
617d3932 607if ((s = dkim->dkim_domain) && !(dkim_domain = expand_cstring(s)))
1cfe5c1c 608 /* expansion error, do not send message. */
7c6ec81b 609 { errwhen = US"dkim_domain"; goto expand_bad; }
1cfe5c1c
JH
610
611/* Set $dkim_domain expansion variable to each unique domain in list. */
612
617d3932
JH
613if (dkim_domain)
614 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep, NULL, 0)))
1cfe5c1c 615 {
9e70917d
JH
616 const uschar * dkim_sel;
617 int sel_sep = 0;
618
42055a33 619 if (dkim_signing_domain[0] == '\0')
1cfe5c1c
JH
620 continue;
621
622 /* Only sign once for each domain, no matter how often it
623 appears in the expanded list. */
624
9e70917d
JH
625 if (match_isinlist(dkim_signing_domain, CUSS &seen_doms,
626 0, NULL, NULL, MCL_STRING, TRUE, NULL) == OK)
627 continue;
1cfe5c1c 628
acec9514 629 seen_doms = string_append_listele(seen_doms, ':', dkim_signing_domain);
1cfe5c1c 630
9e70917d
JH
631 /* Set $dkim_selector expansion variable to each selector in list,
632 for this domain. */
1cfe5c1c 633
9e70917d 634 if (!(dkim_sel = expand_string(dkim->dkim_selector)))
e983e85a 635 if (!(dkim_signing_selector = expand_string(dkim->dkim_selector)))
7c6ec81b 636 { errwhen = US"dkim_selector"; goto expand_bad; }
1cfe5c1c 637
9e70917d
JH
638 while ((dkim_signing_selector = string_nextinlist(&dkim_sel, &sel_sep,
639 NULL, 0)))
1cfe5c1c 640 {
9e70917d
JH
641 uschar * dkim_canon_expanded;
642 int pdkim_canon;
643 uschar * dkim_sign_headers_expanded = NULL;
644 uschar * dkim_private_key_expanded;
645 uschar * dkim_hash_expanded;
7c6ec81b 646 uschar * dkim_identity_expanded = NULL;
80a47a2c 647
9e70917d 648 /* Get canonicalization to use */
1cfe5c1c 649
9e70917d
JH
650 dkim_canon_expanded = dkim->dkim_canon
651 ? expand_string(dkim->dkim_canon) : US"relaxed";
7c6ec81b
JH
652 if (!dkim_canon_expanded) /* expansion error, do not send message. */
653 { errwhen = US"dkim_canon"; goto expand_bad; }
1cfe5c1c 654
9e70917d
JH
655 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
656 pdkim_canon = PDKIM_CANON_RELAXED;
657 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
658 pdkim_canon = PDKIM_CANON_SIMPLE;
659 else
660 {
661 log_write(0, LOG_MAIN,
662 "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",
663 dkim_canon_expanded);
664 pdkim_canon = PDKIM_CANON_RELAXED;
665 }
1cfe5c1c 666
7c6ec81b
JH
667 if ( dkim->dkim_sign_headers
668 && !(dkim_sign_headers_expanded = expand_string(dkim->dkim_sign_headers)))
669 { errwhen = US"dkim_sign_header"; goto expand_bad; }
670 /* else pass NULL, which means default header list */
1cfe5c1c 671
9e70917d 672 /* Get private key to use. */
e983e85a 673
9e70917d 674 if (!(dkim_private_key_expanded = expand_string(dkim->dkim_private_key)))
7c6ec81b 675 { errwhen = US"dkim_private_key"; goto expand_bad; }
80a47a2c 676
9e70917d
JH
677 if ( Ustrlen(dkim_private_key_expanded) == 0
678 || Ustrcmp(dkim_private_key_expanded, "0") == 0
679 || Ustrcmp(dkim_private_key_expanded, "false") == 0
680 )
681 continue; /* don't sign, but no error */
682
617d3932
JH
683 if ( dkim_private_key_expanded[0] == '/'
684 && !(dkim_private_key_expanded =
685 expand_file_big_buffer(dkim_private_key_expanded)))
686 goto bad;
80a47a2c 687
9e70917d 688 if (!(dkim_hash_expanded = expand_string(dkim->dkim_hash)))
7c6ec81b
JH
689 { errwhen = US"dkim_hash"; goto expand_bad; }
690
691 if (dkim->dkim_identity)
692 if (!(dkim_identity_expanded = expand_string(dkim->dkim_identity)))
693 { errwhen = US"dkim_identity"; goto expand_bad; }
694 else if (!*dkim_identity_expanded)
695 dkim_identity_expanded = NULL;
1cfe5c1c 696
617d3932 697 if (!(sig = pdkim_init_sign(&dkim_sign_ctx, dkim_signing_domain,
9e70917d
JH
698 dkim_signing_selector,
699 dkim_private_key_expanded,
700 dkim_hash_expanded,
701 errstr
702 )))
703 goto bad;
704 dkim_private_key_expanded[0] = '\0';
705
706 pdkim_set_optional(sig,
707 CS dkim_sign_headers_expanded,
27fd1318 708 CS dkim_identity_expanded,
9e70917d
JH
709 pdkim_canon,
710 pdkim_canon, -1, 0, 0);
711
617d3932 712 if (!pdkim_set_sig_bodyhash(&dkim_sign_ctx, sig))
cf1cce5e
JH
713 goto bad;
714
617d3932
JH
715 if (!dkim_sign_ctx.sig) /* link sig to context chain */
716 dkim_sign_ctx.sig = sig;
9e70917d
JH
717 else
718 {
617d3932 719 pdkim_signature * n = dkim_sign_ctx.sig;
9e70917d
JH
720 while (n->next) n = n->next;
721 n->next = sig;
722 }
80a47a2c 723 }
9e70917d 724 }
617d3932
JH
725
726/* We may need to carry on with the data-feed even if there are no DKIM sigs to
727produce, if some other package (eg. ARC) is signing. */
728
729if (!dkim_sign_ctx.sig && !dkim->force_bodyhash)
0b8f4f1a
JH
730 {
731 DEBUG(D_transport) debug_printf("DKIM: no viable signatures to use\n");
732 sigbuf = string_get(1); /* return a zero-len string */
0b8f4f1a 733 }
9e70917d 734else
9e70917d 735 {
617d3932
JH
736 if (prefix && (pdkim_rc = pdkim_feed(&dkim_sign_ctx, prefix, Ustrlen(prefix))) != PDKIM_OK)
737 goto pk_bad;
a8e1eeba 738
617d3932
JH
739 if (lseek(fd, off, SEEK_SET) < 0)
740 sread = -1;
741 else
742 while ((sread = read(fd, &buf, sizeof(buf))) > 0)
743 if ((pdkim_rc = pdkim_feed(&dkim_sign_ctx, buf, sread)) != PDKIM_OK)
744 goto pk_bad;
9e70917d 745
617d3932
JH
746 /* Handle failed read above. */
747 if (sread == -1)
748 {
749 debug_printf("DKIM: Error reading -K file.\n");
750 save_errno = errno;
751 goto bad;
752 }
753
754 /* Build string of headers, one per signature */
9e70917d 755
617d3932
JH
756 if ((pdkim_rc = pdkim_feed_finish(&dkim_sign_ctx, &sig, errstr)) != PDKIM_OK)
757 goto pk_bad;
758
759 if (!sig)
760 {
761 DEBUG(D_transport) debug_printf("DKIM: no signatures to use\n");
762 sigbuf = string_get(1); /* return a zero-len string */
763 }
764 else for (sigbuf = NULL; sig; sig = sig->next)
765 sigbuf = string_append(sigbuf, 2, US sig->signature_header, US"\r\n");
766 }
9e70917d 767
1cfe5c1c 768CLEANUP:
0b8f4f1a 769 (void) string_from_gstring(sigbuf);
f7302073
JH
770 store_pool = old_pool;
771 errno = save_errno;
9e70917d 772 return sigbuf;
f7302073
JH
773
774pk_bad:
775 log_write(0, LOG_MAIN|LOG_PANIC,
776 "DKIM: signing failed: %.100s", pdkim_errstr(pdkim_rc));
777bad:
9e70917d 778 sigbuf = NULL;
f7302073 779 goto CLEANUP;
7c6ec81b
JH
780
781expand_bad:
782 log_write(0, LOG_MAIN | LOG_PANIC, "failed to expand %s: %s",
783 errwhen, expand_string_message);
784 goto bad;
93f2d376 785}
80a47a2c 786
dfbcb5ac
JH
787
788
789
790gstring *
791authres_dkim(gstring * g)
792{
793pdkim_signature * sig;
0ae2cff6 794int start = 0; /* compiler quietening */
617d3932
JH
795
796DEBUG(D_acl) start = g->ptr;
dfbcb5ac
JH
797
798for (sig = dkim_signatures; sig; sig = sig->next)
799 {
e34f8ca2 800 g = string_catn(g, US";\n\tdkim=", 8);
dfbcb5ac
JH
801
802 if (sig->verify_status & PDKIM_VERIFY_POLICY)
803 g = string_append(g, 5,
804 US"policy (", dkim_verify_status, US" - ", dkim_verify_reason, US")");
805 else switch(sig->verify_status)
806 {
807 case PDKIM_VERIFY_NONE: g = string_cat(g, US"none"); break;
808 case PDKIM_VERIFY_INVALID:
809 switch (sig->verify_ext_status)
810 {
811 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
617d3932 812 g = string_cat(g, US"tmperror (pubkey unavailable)\n\t\t"); break;
dfbcb5ac 813 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
617d3932 814 g = string_cat(g, US"permerror (overlong public key record)\n\t\t"); break;
dfbcb5ac
JH
815 case PDKIM_VERIFY_INVALID_PUBKEY_DNSRECORD:
816 case PDKIM_VERIFY_INVALID_PUBKEY_IMPORT:
617d3932 817 g = string_cat(g, US"neutral (syntax error in public key record)\n\t\t");
dfbcb5ac
JH
818 break;
819 case PDKIM_VERIFY_INVALID_SIGNATURE_ERROR:
617d3932 820 g = string_cat(g, US"neutral (signature tag missing or invalid)\n\t\t");
dfbcb5ac
JH
821 break;
822 case PDKIM_VERIFY_INVALID_DKIM_VERSION:
617d3932 823 g = string_cat(g, US"neutral (unsupported DKIM version)\n\t\t");
dfbcb5ac
JH
824 break;
825 default:
617d3932 826 g = string_cat(g, US"permerror (unspecified problem)\n\t\t"); break;
dfbcb5ac
JH
827 }
828 break;
829 case PDKIM_VERIFY_FAIL:
830 switch (sig->verify_ext_status)
831 {
832 case PDKIM_VERIFY_FAIL_BODY:
833 g = string_cat(g,
617d3932 834 US"fail (body hash mismatch; body probably modified in transit)\n\t\t");
dfbcb5ac
JH
835 break;
836 case PDKIM_VERIFY_FAIL_MESSAGE:
837 g = string_cat(g,
617d3932 838 US"fail (signature did not verify; headers probably modified in transit)\n\t\t");
dfbcb5ac
JH
839 break;
840 default:
617d3932 841 g = string_cat(g, US"fail (unspecified reason)\n\t\t");
dfbcb5ac
JH
842 break;
843 }
844 break;
845 case PDKIM_VERIFY_PASS: g = string_cat(g, US"pass"); break;
846 default: g = string_cat(g, US"permerror"); break;
847 }
848 if (sig->domain) g = string_append(g, 2, US" header.d=", sig->domain);
849 if (sig->identity) g = string_append(g, 2, US" header.i=", sig->identity);
850 if (sig->selector) g = string_append(g, 2, US" header.s=", sig->selector);
851 g = string_append(g, 2, US" header.a=", dkim_sig_to_a_tag(sig));
852 }
617d3932
JH
853
854DEBUG(D_acl)
855 if (g->ptr == start)
856 debug_printf("DKIM: no authres\n");
857 else
858 debug_printf("DKIM: authres '%.*s'\n", g->ptr - start - 3, g->s + start + 3);
dfbcb5ac
JH
859return g;
860}
861
862
970424a5
JH
863# endif /*!MACRO_PREDEF*/
864#endif /*!DISABLE_DKIM*/