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 if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
28 /* Search for TXT record */
29 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
31 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
32 if (rr->type == T_TXT) break;
34 /* Copy record content to the answer buffer */
37 int answer_offset = 0;
38 while (rr_offset < rr->size) {
39 uschar len = (rr->data)[rr_offset++];
40 snprintf(answer+(answer_offset),
41 PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
42 "%.*s", (int)len, (char *)((rr->data)+rr_offset));
45 if (answer_offset >= PDKIM_DNS_TXT_MAX_RECLEN) {
50 else return PDKIM_FAIL;
56 void dkim_exim_verify_init(void) {
58 /* Free previous context if there is one */
59 if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
61 /* Create new context */
62 dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
63 &dkim_exim_query_dns_txt
66 if (dkim_verify_ctx != NULL) {
67 dkim_collect_input = TRUE;
68 pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
70 else dkim_collect_input = FALSE;
75 void dkim_exim_verify_feed(uschar *data, int len) {
76 if (dkim_collect_input &&
77 pdkim_feed(dkim_verify_ctx,
79 len) != PDKIM_OK) dkim_collect_input = FALSE;
83 void dkim_exim_verify_finish(void) {
84 pdkim_signature *sig = NULL;
85 int dkim_signers_size = 0;
86 int dkim_signers_ptr = 0;
89 /* Delete eventual previous signature chain */
90 dkim_signatures = NULL;
92 /* If we have arrived here with dkim_collect_input == FALSE, it
93 means there was a processing error somewhere along the way.
94 Log the incident and disable futher verification. */
95 if (!dkim_collect_input) {
96 log_write(0, LOG_MAIN, "DKIM: Error while running this message through validation, disabling signature verification.");
97 dkim_disable_verify = TRUE;
100 dkim_collect_input = FALSE;
102 /* Finish DKIM operation and fetch link to signatures chain */
103 if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
105 sig = dkim_signatures;
106 while (sig != NULL) {
109 /* Log a line for each signature */
110 uschar *logmsg = string_append(NULL, &size, &ptr, 5,
112 string_sprintf( "d=%s s=%s c=%s/%s a=%s ",
115 (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
116 (sig->canon_body == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
117 (sig->algo == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
119 ((sig->identity != NULL)?
120 string_sprintf("i=%s ", sig->identity)
125 string_sprintf("t=%lu ", sig->created)
130 string_sprintf("x=%lu ", sig->expires)
134 ((sig->bodylength > -1)?
135 string_sprintf("l=%lu ", sig->bodylength)
141 switch(sig->verify_status) {
142 case PDKIM_VERIFY_NONE:
143 logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
145 case PDKIM_VERIFY_INVALID:
146 logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
147 switch (sig->verify_ext_status) {
148 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
149 logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
151 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
152 logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
154 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
155 logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
158 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
161 case PDKIM_VERIFY_FAIL:
162 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
163 switch (sig->verify_ext_status) {
164 case PDKIM_VERIFY_FAIL_BODY:
165 logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
167 case PDKIM_VERIFY_FAIL_MESSAGE:
168 logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
171 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
174 case PDKIM_VERIFY_PASS:
175 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
180 log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
182 /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
183 dkim_signers = string_append(dkim_signers,
191 if (sig->identity != NULL) {
192 dkim_signers = string_append(dkim_signers,
201 /* Process next signature */
205 /* NULL-terminate and chop the last colon from the domain list */
206 if (dkim_signers != NULL) {
207 dkim_signers[dkim_signers_ptr] = '\0';
208 if (Ustrlen(dkim_signers) > 0)
209 dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
214 void dkim_exim_acl_setup(uschar *id) {
215 pdkim_signature *sig = dkim_signatures;
217 dkim_cur_signer = id;
218 if (dkim_disable_verify ||
219 !id || !dkim_verify_ctx) return;
220 /* Find signature to run ACL on */
221 while (sig != NULL) {
222 uschar *cmp_val = NULL;
223 if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
224 else cmp_val = (uschar *)sig->domain;
225 if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
227 /* The "dkim_domain" and "dkim_selector" expansion variables have
228 related globals, since they are used in the signing code too.
229 Instead of inventing separate names for verification, we set
230 them here. This is easy since a domain and selector is guaranteed
231 to be in a signature. The other dkim_* expansion items are
232 dynamically fetched from dkim_cur_sig at expansion time (see
234 dkim_signing_domain = (uschar *)sig->domain;
235 dkim_signing_selector = (uschar *)sig->selector;
243 uschar *dkim_exim_expand_query(int what) {
245 if (!dkim_verify_ctx ||
246 dkim_disable_verify ||
247 !dkim_cur_sig) return dkim_exim_expand_defaults(what);
251 switch(dkim_cur_sig->algo) {
252 case PDKIM_ALGO_RSA_SHA1:
254 case PDKIM_ALGO_RSA_SHA256:
256 return US"rsa-sha256";
258 case DKIM_BODYLENGTH:
259 return (dkim_cur_sig->bodylength >= 0)?
260 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
261 :dkim_exim_expand_defaults(what);
262 case DKIM_CANON_BODY:
263 switch(dkim_cur_sig->canon_body) {
264 case PDKIM_CANON_RELAXED:
266 case PDKIM_CANON_SIMPLE:
270 case DKIM_CANON_HEADERS:
271 switch(dkim_cur_sig->canon_headers) {
272 case PDKIM_CANON_RELAXED:
274 case PDKIM_CANON_SIMPLE:
278 case DKIM_COPIEDHEADERS:
279 return dkim_cur_sig->copiedheaders?
280 (uschar *)(dkim_cur_sig->copiedheaders)
281 :dkim_exim_expand_defaults(what);
283 return (dkim_cur_sig->created > 0)?
284 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
285 :dkim_exim_expand_defaults(what);
287 return (dkim_cur_sig->expires > 0)?
288 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
289 :dkim_exim_expand_defaults(what);
290 case DKIM_HEADERNAMES:
291 return dkim_cur_sig->headernames?
292 (uschar *)(dkim_cur_sig->headernames)
293 :dkim_exim_expand_defaults(what);
295 return dkim_cur_sig->identity?
296 (uschar *)(dkim_cur_sig->identity)
297 :dkim_exim_expand_defaults(what);
298 case DKIM_KEY_GRANULARITY:
299 return dkim_cur_sig->pubkey?
300 (dkim_cur_sig->pubkey->granularity?
301 (uschar *)(dkim_cur_sig->pubkey->granularity)
302 :dkim_exim_expand_defaults(what)
304 :dkim_exim_expand_defaults(what);
305 case DKIM_KEY_SRVTYPE:
306 return dkim_cur_sig->pubkey?
307 (dkim_cur_sig->pubkey->srvtype?
308 (uschar *)(dkim_cur_sig->pubkey->srvtype)
309 :dkim_exim_expand_defaults(what)
311 :dkim_exim_expand_defaults(what);
313 return dkim_cur_sig->pubkey?
314 (dkim_cur_sig->pubkey->notes?
315 (uschar *)(dkim_cur_sig->pubkey->notes)
316 :dkim_exim_expand_defaults(what)
318 :dkim_exim_expand_defaults(what);
319 case DKIM_KEY_TESTING:
320 return dkim_cur_sig->pubkey?
321 (dkim_cur_sig->pubkey->testing?
323 :dkim_exim_expand_defaults(what)
325 :dkim_exim_expand_defaults(what);
326 case DKIM_NOSUBDOMAINS:
327 return dkim_cur_sig->pubkey?
328 (dkim_cur_sig->pubkey->no_subdomaining?
330 :dkim_exim_expand_defaults(what)
332 :dkim_exim_expand_defaults(what);
333 case DKIM_VERIFY_STATUS:
334 switch(dkim_cur_sig->verify_status) {
335 case PDKIM_VERIFY_INVALID:
337 case PDKIM_VERIFY_FAIL:
339 case PDKIM_VERIFY_PASS:
341 case PDKIM_VERIFY_NONE:
345 case DKIM_VERIFY_REASON:
346 switch (dkim_cur_sig->verify_ext_status) {
347 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
348 return US"pubkey_unavailable";
349 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
350 return US"pubkey_syntax";
351 case PDKIM_VERIFY_FAIL_BODY:
352 return US"bodyhash_mismatch";
353 case PDKIM_VERIFY_FAIL_MESSAGE:
354 return US"signature_incorrect";
362 uschar *dkim_exim_expand_defaults(int what) {
364 case DKIM_ALGO: return US"";
365 case DKIM_BODYLENGTH: return US"9999999999999";
366 case DKIM_CANON_BODY: return US"";
367 case DKIM_CANON_HEADERS: return US"";
368 case DKIM_COPIEDHEADERS: return US"";
369 case DKIM_CREATED: return US"0";
370 case DKIM_EXPIRES: return US"9999999999999";
371 case DKIM_HEADERNAMES: return US"";
372 case DKIM_IDENTITY: return US"";
373 case DKIM_KEY_GRANULARITY: return US"*";
374 case DKIM_KEY_SRVTYPE: return US"*";
375 case DKIM_KEY_NOTES: return US"";
376 case DKIM_KEY_TESTING: return US"0";
377 case DKIM_NOSUBDOMAINS: return US"0";
378 case DKIM_VERIFY_STATUS: return US"none";
379 case DKIM_VERIFY_REASON: return US"";
380 default: return US"";
385 uschar *dkim_exim_sign(int dkim_fd,
386 uschar *dkim_private_key,
388 uschar *dkim_selector,
390 uschar *dkim_sign_headers) {
392 uschar *seen_items = NULL;
393 int seen_items_size = 0;
394 int seen_items_offset = 0;
396 uschar *dkim_canon_expanded;
397 uschar *dkim_sign_headers_expanded;
398 uschar *dkim_private_key_expanded;
399 pdkim_ctx *ctx = NULL;
401 uschar *sigbuf = NULL;
404 pdkim_signature *signature;
410 int old_pool = store_pool;
412 store_pool = POOL_MAIN;
414 dkim_domain = expand_string(dkim_domain);
415 if (dkim_domain == NULL) {
416 /* expansion error, do not send message. */
417 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
418 "dkim_domain: %s", expand_string_message);
423 /* Set $dkim_domain expansion variable to each unique domain in list. */
424 while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
426 sizeof(itembuf))) != NULL) {
427 if (!dkim_signing_domain || (dkim_signing_domain[0] == '\0')) continue;
428 /* Only sign once for each domain, no matter how often it
429 appears in the expanded list. */
430 if (seen_items != NULL) {
431 uschar *seen_items_list = seen_items;
432 if (match_isinlist(dkim_signing_domain,
433 &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
435 seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
437 seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,dkim_signing_domain);
438 seen_items[seen_items_offset] = '\0';
440 /* Set up $dkim_selector expansion variable. */
441 dkim_signing_selector = expand_string(dkim_selector);
442 if (dkim_signing_selector == NULL) {
443 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
444 "dkim_selector: %s", expand_string_message);
449 /* Get canonicalization to use */
450 dkim_canon_expanded = expand_string(dkim_canon?dkim_canon:US"relaxed");
451 if (dkim_canon_expanded == NULL) {
452 /* expansion error, do not send message. */
453 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
454 "dkim_canon: %s", expand_string_message);
458 if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
459 pdkim_canon = PDKIM_CANON_RELAXED;
460 else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
461 pdkim_canon = PDKIM_CANON_SIMPLE;
463 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon_expanded);
464 pdkim_canon = PDKIM_CANON_RELAXED;
467 if (dkim_sign_headers) {
468 dkim_sign_headers_expanded = expand_string(dkim_sign_headers);
469 if (dkim_sign_headers_expanded == NULL) {
470 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
471 "dkim_sign_headers: %s", expand_string_message);
477 /* pass NULL, which means default header list */
478 dkim_sign_headers_expanded = NULL;
481 /* Get private key to use. */
482 dkim_private_key_expanded = expand_string(dkim_private_key);
483 if (dkim_private_key_expanded == NULL) {
484 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
485 "dkim_private_key: %s", expand_string_message);
489 if ( (Ustrlen(dkim_private_key_expanded) == 0) ||
490 (Ustrcmp(dkim_private_key_expanded,"0") == 0) ||
491 (Ustrcmp(dkim_private_key_expanded,"false") == 0) ) {
492 /* don't sign, but no error */
496 if (dkim_private_key_expanded[0] == '/') {
498 /* Looks like a filename, load the private key. */
499 memset(big_buffer,0,big_buffer_size);
500 privkey_fd = open(CS dkim_private_key_expanded,O_RDONLY);
501 if (privkey_fd < 0) {
502 log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
503 "private key file for reading: %s", dkim_private_key_expanded);
507 if (read(privkey_fd,big_buffer,(big_buffer_size-2)) < 0) {
508 log_write(0, LOG_MAIN|LOG_PANIC, "unable to read private key file: %s",
509 dkim_private_key_expanded);
513 (void)close(privkey_fd);
514 dkim_private_key_expanded = big_buffer;
517 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
518 (char *)dkim_signing_domain,
519 (char *)dkim_signing_selector,
520 (char *)dkim_private_key_expanded
523 pdkim_set_debug_stream(ctx,debug_file);
525 pdkim_set_optional(ctx,
526 (char *)dkim_sign_headers_expanded,
531 PDKIM_ALGO_RSA_SHA256,
535 lseek(dkim_fd, 0, SEEK_SET);
536 while((sread = read(dkim_fd,&buf,4096)) > 0) {
537 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
542 /* Handle failed read above. */
544 debug_printf("DKIM: Error reading -K file.\n");
550 pdkim_rc = pdkim_feed_finish(ctx,&signature);
551 if (pdkim_rc != PDKIM_OK) {
552 log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
557 sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
558 US signature->signature_header,
565 if (sigbuf != NULL) {
566 sigbuf[sigptr] = '\0';
574 store_pool = old_pool;