1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge, 1995 - 2007 */
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"
17 pdkim_ctx *dkim_verify_ctx = NULL;
18 pdkim_signature *dkim_signatures = NULL;
19 pdkim_signature *dkim_cur_sig = NULL;
21 int dkim_exim_query_dns_txt(char *name, char *answer) {
26 lookup_dnssec_authenticated = NULL;
27 if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
29 /* Search for TXT record */
30 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
32 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
33 if (rr->type == T_TXT) break;
35 /* Copy record content to the answer buffer */
38 int answer_offset = 0;
39 while (rr_offset < rr->size) {
40 uschar len = (rr->data)[rr_offset++];
41 snprintf(answer+(answer_offset),
42 PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
43 "%.*s", (int)len, (char *)((rr->data)+rr_offset));
46 if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN) {
51 else return PDKIM_FAIL;
57 void dkim_exim_verify_init(void) {
59 /* Free previous context if there is one */
60 if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
62 /* Create new context */
63 dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
64 &dkim_exim_query_dns_txt
67 if (dkim_verify_ctx != NULL) {
68 dkim_collect_input = TRUE;
69 pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
71 else dkim_collect_input = FALSE;
76 void dkim_exim_verify_feed(uschar *data, int len) {
77 if (dkim_collect_input &&
78 pdkim_feed(dkim_verify_ctx,
80 len) != PDKIM_OK) dkim_collect_input = FALSE;
84 void dkim_exim_verify_finish(void) {
85 pdkim_signature *sig = NULL;
86 int dkim_signers_size = 0;
87 int dkim_signers_ptr = 0;
90 /* Delete eventual previous signature chain */
91 dkim_signatures = NULL;
93 /* If we have arrived here with dkim_collect_input == FALSE, it
94 means there was a processing error somewhere along the way.
95 Log the incident and disable futher verification. */
96 if (!dkim_collect_input) {
97 log_write(0, LOG_MAIN, "DKIM: Error while running this message through validation, disabling signature verification.");
98 dkim_disable_verify = TRUE;
101 dkim_collect_input = FALSE;
103 /* Finish DKIM operation and fetch link to signatures chain */
104 if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
106 sig = dkim_signatures;
107 while (sig != NULL) {
110 /* Log a line for each signature */
111 uschar *logmsg = string_append(NULL, &size, &ptr, 5,
113 string_sprintf( "d=%s s=%s c=%s/%s a=%s ",
116 (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
117 (sig->canon_body == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
118 (sig->algo == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
120 ((sig->identity != NULL)?
121 string_sprintf("i=%s ", sig->identity)
126 string_sprintf("t=%lu ", sig->created)
131 string_sprintf("x=%lu ", sig->expires)
135 ((sig->bodylength > -1)?
136 string_sprintf("l=%lu ", sig->bodylength)
142 switch(sig->verify_status) {
143 case PDKIM_VERIFY_NONE:
144 logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
146 case PDKIM_VERIFY_INVALID:
147 logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
148 switch (sig->verify_ext_status) {
149 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
150 logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
152 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
153 logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
155 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
156 logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
159 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
162 case PDKIM_VERIFY_FAIL:
163 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
164 switch (sig->verify_ext_status) {
165 case PDKIM_VERIFY_FAIL_BODY:
166 logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
168 case PDKIM_VERIFY_FAIL_MESSAGE:
169 logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
172 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
175 case PDKIM_VERIFY_PASS:
176 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
181 log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
183 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
184 dkim_signers = string_append(dkim_signers,
192 if (sig->identity != NULL) {
193 dkim_signers = string_append(dkim_signers,
202 /* Process next signature */
206 /* NULL-terminate and chop the last colon from the domain list */
207 if (dkim_signers != NULL) {
208 dkim_signers[dkim_signers_ptr] = '\0';
209 if (Ustrlen(dkim_signers) > 0)
210 dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
215 void dkim_exim_acl_setup(uschar *id) {
216 pdkim_signature *sig = dkim_signatures;
218 dkim_cur_signer = id;
219 if (dkim_disable_verify ||
220 !id || !dkim_verify_ctx) return;
221 /* Find signature to run ACL on */
222 while (sig != NULL) {
223 uschar *cmp_val = NULL;
224 if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
225 else cmp_val = (uschar *)sig->domain;
226 if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
228 /* The "dkim_domain" and "dkim_selector" expansion variables have
229 related globals, since they are used in the signing code too.
230 Instead of inventing separate names for verification, we set
231 them here. This is easy since a domain and selector is guaranteed
232 to be in a signature. The other dkim_* expansion items are
233 dynamically fetched from dkim_cur_sig at expansion time (see
235 dkim_signing_domain = (uschar *)sig->domain;
236 dkim_signing_selector = (uschar *)sig->selector;
244 uschar *dkim_exim_expand_query(int what) {
246 if (!dkim_verify_ctx ||
247 dkim_disable_verify ||
248 !dkim_cur_sig) return dkim_exim_expand_defaults(what);
252 switch(dkim_cur_sig->algo) {
253 case PDKIM_ALGO_RSA_SHA1:
255 case PDKIM_ALGO_RSA_SHA256:
257 return US"rsa-sha256";
259 case DKIM_BODYLENGTH:
260 return (dkim_cur_sig->bodylength >= 0)?
261 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
262 :dkim_exim_expand_defaults(what);
263 case DKIM_CANON_BODY:
264 switch(dkim_cur_sig->canon_body) {
265 case PDKIM_CANON_RELAXED:
267 case PDKIM_CANON_SIMPLE:
271 case DKIM_CANON_HEADERS:
272 switch(dkim_cur_sig->canon_headers) {
273 case PDKIM_CANON_RELAXED:
275 case PDKIM_CANON_SIMPLE:
279 case DKIM_COPIEDHEADERS:
280 return dkim_cur_sig->copiedheaders?
281 (uschar *)(dkim_cur_sig->copiedheaders)
282 :dkim_exim_expand_defaults(what);
284 return (dkim_cur_sig->created > 0)?
285 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
286 :dkim_exim_expand_defaults(what);
288 return (dkim_cur_sig->expires > 0)?
289 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
290 :dkim_exim_expand_defaults(what);
291 case DKIM_HEADERNAMES:
292 return dkim_cur_sig->headernames?
293 (uschar *)(dkim_cur_sig->headernames)
294 :dkim_exim_expand_defaults(what);
296 return dkim_cur_sig->identity?
297 (uschar *)(dkim_cur_sig->identity)
298 :dkim_exim_expand_defaults(what);
299 case DKIM_KEY_GRANULARITY:
300 return dkim_cur_sig->pubkey?
301 (dkim_cur_sig->pubkey->granularity?
302 (uschar *)(dkim_cur_sig->pubkey->granularity)
303 :dkim_exim_expand_defaults(what)
305 :dkim_exim_expand_defaults(what);
306 case DKIM_KEY_SRVTYPE:
307 return dkim_cur_sig->pubkey?
308 (dkim_cur_sig->pubkey->srvtype?
309 (uschar *)(dkim_cur_sig->pubkey->srvtype)
310 :dkim_exim_expand_defaults(what)
312 :dkim_exim_expand_defaults(what);
314 return dkim_cur_sig->pubkey?
315 (dkim_cur_sig->pubkey->notes?
316 (uschar *)(dkim_cur_sig->pubkey->notes)
317 :dkim_exim_expand_defaults(what)
319 :dkim_exim_expand_defaults(what);
320 case DKIM_KEY_TESTING:
321 return dkim_cur_sig->pubkey?
322 (dkim_cur_sig->pubkey->testing?
324 :dkim_exim_expand_defaults(what)
326 :dkim_exim_expand_defaults(what);
327 case DKIM_NOSUBDOMAINS:
328 return dkim_cur_sig->pubkey?
329 (dkim_cur_sig->pubkey->no_subdomaining?
331 :dkim_exim_expand_defaults(what)
333 :dkim_exim_expand_defaults(what);
334 case DKIM_VERIFY_STATUS:
335 switch(dkim_cur_sig->verify_status) {
336 case PDKIM_VERIFY_INVALID:
338 case PDKIM_VERIFY_FAIL:
340 case PDKIM_VERIFY_PASS:
342 case PDKIM_VERIFY_NONE:
346 case DKIM_VERIFY_REASON:
347 switch (dkim_cur_sig->verify_ext_status) {
348 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
349 return US"pubkey_unavailable";
350 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
351 return US"pubkey_syntax";
352 case PDKIM_VERIFY_FAIL_BODY:
353 return US"bodyhash_mismatch";
354 case PDKIM_VERIFY_FAIL_MESSAGE:
355 return US"signature_incorrect";
363 uschar *dkim_exim_expand_defaults(int what) {
365 case DKIM_ALGO: return US"";
366 case DKIM_BODYLENGTH: return US"9999999999999";
367 case DKIM_CANON_BODY: return US"";
368 case DKIM_CANON_HEADERS: return US"";
369 case DKIM_COPIEDHEADERS: return US"";
370 case DKIM_CREATED: return US"0";
371 case DKIM_EXPIRES: return US"9999999999999";
372 case DKIM_HEADERNAMES: return US"";
373 case DKIM_IDENTITY: return US"";
374 case DKIM_KEY_GRANULARITY: return US"*";
375 case DKIM_KEY_SRVTYPE: return US"*";
376 case DKIM_KEY_NOTES: return US"";
377 case DKIM_KEY_TESTING: return US"0";
378 case DKIM_NOSUBDOMAINS: return US"0";
379 case DKIM_VERIFY_STATUS: return US"none";
380 case DKIM_VERIFY_REASON: return US"";
381 default: return US"";
386 uschar *dkim_exim_sign(int dkim_fd,
387 uschar *dkim_private_key,
389 uschar *dkim_selector,
391 uschar *dkim_sign_headers) {
393 uschar *seen_items = NULL;
394 int seen_items_size = 0;
395 int seen_items_offset = 0;
397 uschar *dkim_canon_expanded;
398 uschar *dkim_sign_headers_expanded;
399 uschar *dkim_private_key_expanded;
400 pdkim_ctx *ctx = NULL;
402 uschar *sigbuf = NULL;
405 pdkim_signature *signature;
411 int old_pool = store_pool;
413 store_pool = POOL_MAIN;
415 dkim_domain = expand_string(dkim_domain);
416 if (dkim_domain == NULL) {
417 /* expansion error, do not send message. */
418 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
419 "dkim_domain: %s", expand_string_message);
424 /* Set $dkim_domain expansion variable to each unique domain in list. */
425 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
427 sizeof(itembuf))) != NULL) {
428 if (!dkim_signing_domain || (dkim_signing_domain[0] == '\0')) continue;
429 /* Only sign once for each domain, no matter how often it
430 appears in the expanded list. */
431 if (seen_items != NULL) {
432 uschar *seen_items_list = seen_items;
433 if (match_isinlist(dkim_signing_domain,
434 &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
436 seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
438 seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,dkim_signing_domain);
439 seen_items[seen_items_offset] = '\0';
441 /* Set up $dkim_selector expansion variable. */
442 dkim_signing_selector = expand_string(dkim_selector);
443 if (dkim_signing_selector == NULL) {
444 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
445 "dkim_selector: %s", expand_string_message);
450 /* Get canonicalization to use */
451 dkim_canon_expanded = expand_string(dkim_canon?dkim_canon:US"relaxed");
452 if (dkim_canon_expanded == NULL) {
453 /* expansion error, do not send message. */
454 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
455 "dkim_canon: %s", expand_string_message);
459 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
460 pdkim_canon = PDKIM_CANON_RELAXED;
461 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
462 pdkim_canon = PDKIM_CANON_SIMPLE;
464 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon_expanded);
465 pdkim_canon = PDKIM_CANON_RELAXED;
468 if (dkim_sign_headers) {
469 dkim_sign_headers_expanded = expand_string(dkim_sign_headers);
470 if (dkim_sign_headers_expanded == NULL) {
471 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
472 "dkim_sign_headers: %s", expand_string_message);
478 /* pass NULL, which means default header list */
479 dkim_sign_headers_expanded = NULL;
482 /* Get private key to use. */
483 dkim_private_key_expanded = expand_string(dkim_private_key);
484 if (dkim_private_key_expanded == NULL) {
485 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
486 "dkim_private_key: %s", expand_string_message);
490 if ( (Ustrlen(dkim_private_key_expanded) == 0) ||
491 (Ustrcmp(dkim_private_key_expanded,"0") == 0) ||
492 (Ustrcmp(dkim_private_key_expanded,"false") == 0) ) {
493 /* don't sign, but no error */
497 if (dkim_private_key_expanded[0] == '/') {
499 /* Looks like a filename, load the private key. */
500 memset(big_buffer,0,big_buffer_size);
501 privkey_fd = open(CS dkim_private_key_expanded,O_RDONLY);
502 if (privkey_fd < 0) {
503 log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
504 "private key file for reading: %s", dkim_private_key_expanded);
508 if (read(privkey_fd,big_buffer,(big_buffer_size-2)) < 0) {
509 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
510 dkim_private_key_expanded);
514 (void)close(privkey_fd);
515 dkim_private_key_expanded = big_buffer;
518 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
519 (char *)dkim_signing_domain,
520 (char *)dkim_signing_selector,
521 (char *)dkim_private_key_expanded
524 pdkim_set_debug_stream(ctx,debug_file);
526 pdkim_set_optional(ctx,
527 (char *)dkim_sign_headers_expanded,
532 PDKIM_ALGO_RSA_SHA256,
536 lseek(dkim_fd, 0, SEEK_SET);
537 while((sread = read(dkim_fd,&buf,4096)) > 0) {
538 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
543 /* Handle failed read above. */
545 debug_printf("DKIM: Error reading -K file.\n");
551 pdkim_rc = pdkim_feed_finish(ctx,&signature);
552 if (pdkim_rc != PDKIM_OK) {
553 log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
558 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
559 US signature->signature_header,
566 if (sigbuf != NULL) {
567 sigbuf[sigptr] = '\0';
575 store_pool = old_pool;