1 /* $Cambridge: exim/src/src/dkim.c,v 1.5 2009/10/15 08:06:23 tom Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 2009 */
8 /* See the file NOTICE for conditions of use and distribution. */
10 /* Code for DKIM support. Other DKIM relevant code is in
11 receive.c, transport.c and transports/smtp.c */
17 #include "pdkim/pdkim.h"
19 pdkim_ctx *dkim_verify_ctx = NULL;
20 pdkim_signature *dkim_signatures = NULL;
21 pdkim_signature *dkim_cur_sig = NULL;
22 uschar *dkim_cur_signer = NULL;
24 int dkim_exim_query_dns_txt(char *name, char *answer) {
29 if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
31 /* Search for TXT record */
32 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
34 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
35 if (rr->type == T_TXT) break;
37 /* Copy record content to the answer buffer */
40 int answer_offset = 0;
41 while (rr_offset < rr->size) {
42 uschar len = (rr->data)[rr_offset++];
43 snprintf(answer+(answer_offset),
44 PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
45 "%.*s", (int)len, (char *)((rr->data)+rr_offset));
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|LOG_PANIC, "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( "DKIM: 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, (char *)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 /* Chop the last colon from the domain list */
206 if ((dkim_signers != NULL) &&
207 (Ustrlen(dkim_signers) > 0))
208 dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
212 void dkim_exim_acl_setup(uschar *id) {
213 pdkim_signature *sig = dkim_signatures;
215 dkim_cur_signer = id;
216 if (dkim_disable_verify ||
217 !id || !dkim_verify_ctx) return;
218 /* Find signature to run ACL on */
219 while (sig != NULL) {
220 uschar *cmp_val = NULL;
221 if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
222 else cmp_val = (uschar *)sig->domain;
223 if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
225 /* The "dkim_domain" and "dkim_selector" expansion variables have
226 related globals, since they are used in the signing code too.
227 Instead of inventing separate names for verification, we set
228 them here. This is easy since a domain and selector is guaranteed
229 to be in a signature. The other dkim_* expansion items are
230 dynamically fetched from dkim_cur_sig at expansion time (see
232 dkim_signing_domain = (uschar *)sig->domain;
233 dkim_signing_selector = (uschar *)sig->selector;
241 uschar *dkim_exim_expand_query(int what) {
243 if (!dkim_verify_ctx ||
244 dkim_disable_verify ||
245 !dkim_cur_sig) return dkim_exim_expand_defaults(what);
249 switch(dkim_cur_sig->algo) {
250 case PDKIM_ALGO_RSA_SHA1:
252 case PDKIM_ALGO_RSA_SHA256:
254 return US"rsa-sha256";
256 case DKIM_BODYLENGTH:
257 return (dkim_cur_sig->bodylength >= 0)?
258 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
259 :dkim_exim_expand_defaults(what);
260 case DKIM_CANON_BODY:
261 switch(dkim_cur_sig->canon_body) {
262 case PDKIM_CANON_RELAXED:
264 case PDKIM_CANON_SIMPLE:
268 case DKIM_CANON_HEADERS:
269 switch(dkim_cur_sig->canon_headers) {
270 case PDKIM_CANON_RELAXED:
272 case PDKIM_CANON_SIMPLE:
276 case DKIM_COPIEDHEADERS:
277 return dkim_cur_sig->copiedheaders?
278 (uschar *)(dkim_cur_sig->copiedheaders)
279 :dkim_exim_expand_defaults(what);
281 return (dkim_cur_sig->created > 0)?
282 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
283 :dkim_exim_expand_defaults(what);
285 return (dkim_cur_sig->expires > 0)?
286 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
287 :dkim_exim_expand_defaults(what);
288 case DKIM_HEADERNAMES:
289 return dkim_cur_sig->headernames?
290 (uschar *)(dkim_cur_sig->headernames)
291 :dkim_exim_expand_defaults(what);
293 return dkim_cur_sig->identity?
294 (uschar *)(dkim_cur_sig->identity)
295 :dkim_exim_expand_defaults(what);
296 case DKIM_KEY_GRANULARITY:
297 return dkim_cur_sig->pubkey?
298 (dkim_cur_sig->pubkey->granularity?
299 (uschar *)(dkim_cur_sig->pubkey->granularity)
300 :dkim_exim_expand_defaults(what)
302 :dkim_exim_expand_defaults(what);
303 case DKIM_KEY_SRVTYPE:
304 return dkim_cur_sig->pubkey?
305 (dkim_cur_sig->pubkey->srvtype?
306 (uschar *)(dkim_cur_sig->pubkey->srvtype)
307 :dkim_exim_expand_defaults(what)
309 :dkim_exim_expand_defaults(what);
311 return dkim_cur_sig->pubkey?
312 (dkim_cur_sig->pubkey->notes?
313 (uschar *)(dkim_cur_sig->pubkey->notes)
314 :dkim_exim_expand_defaults(what)
316 :dkim_exim_expand_defaults(what);
317 case DKIM_KEY_TESTING:
318 return dkim_cur_sig->pubkey?
319 (dkim_cur_sig->pubkey->testing?
321 :dkim_exim_expand_defaults(what)
323 :dkim_exim_expand_defaults(what);
324 case DKIM_NOSUBDOMAINS:
325 return dkim_cur_sig->pubkey?
326 (dkim_cur_sig->pubkey->no_subdomaining?
328 :dkim_exim_expand_defaults(what)
330 :dkim_exim_expand_defaults(what);
331 case DKIM_VERIFY_STATUS:
332 switch(dkim_cur_sig->verify_status) {
333 case PDKIM_VERIFY_INVALID:
335 case PDKIM_VERIFY_FAIL:
337 case PDKIM_VERIFY_PASS:
339 case PDKIM_VERIFY_NONE:
343 case DKIM_VERIFY_REASON:
344 switch (dkim_cur_sig->verify_ext_status) {
345 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
346 return US"pubkey_unavailable";
347 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
348 return US"pubkey_syntax";
349 case PDKIM_VERIFY_FAIL_BODY:
350 return US"bodyhash_mismatch";
351 case PDKIM_VERIFY_FAIL_MESSAGE:
352 return US"signature_incorrect";
360 uschar *dkim_exim_expand_defaults(int what) {
362 case DKIM_ALGO: return US"";
363 case DKIM_BODYLENGTH: return US"9999999999999";
364 case DKIM_CANON_BODY: return US"";
365 case DKIM_CANON_HEADERS: return US"";
366 case DKIM_COPIEDHEADERS: return US"";
367 case DKIM_CREATED: return US"0";
368 case DKIM_EXPIRES: return US"9999999999999";
369 case DKIM_HEADERNAMES: return US"";
370 case DKIM_IDENTITY: return US"";
371 case DKIM_KEY_GRANULARITY: return US"*";
372 case DKIM_KEY_SRVTYPE: return US"*";
373 case DKIM_KEY_NOTES: return US"";
374 case DKIM_KEY_TESTING: return US"0";
375 case DKIM_NOSUBDOMAINS: return US"0";
376 case DKIM_VERIFY_STATUS: return US"none";
377 case DKIM_VERIFY_REASON: return US"";
378 default: return US"";
383 uschar *dkim_exim_sign(int dkim_fd,
384 uschar *dkim_private_key,
386 uschar *dkim_selector,
388 uschar *dkim_sign_headers) {
389 pdkim_ctx *ctx = NULL;
391 pdkim_signature *signature;
396 int old_pool = store_pool;
398 dkim_domain = expand_string(dkim_domain);
399 if (dkim_domain == NULL) {
400 /* expansion error, do not send message. */
401 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
402 "dkim_domain: %s", expand_string_message);
406 /* Set up $dkim_domain expansion variable. */
407 dkim_signing_domain = dkim_domain;
409 /* Get selector to use. */
410 dkim_selector = expand_string(dkim_selector);
411 if (dkim_selector == NULL) {
412 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
413 "dkim_selector: %s", expand_string_message);
417 /* Set up $dkim_selector expansion variable. */
418 dkim_signing_selector = dkim_selector;
420 /* Get canonicalization to use */
421 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
422 if (dkim_canon == NULL) {
423 /* expansion error, do not send message. */
424 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
425 "dkim_canon: %s", expand_string_message);
429 if (Ustrcmp(dkim_canon, "relaxed") == 0)
430 pdkim_canon = PDKIM_CANON_RELAXED;
431 else if (Ustrcmp(dkim_canon, "simple") == 0)
432 pdkim_canon = PDKIM_CANON_RELAXED;
434 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
435 pdkim_canon = PDKIM_CANON_RELAXED;
438 /* Expand signing headers once */
439 if (dkim_sign_headers != NULL) {
440 dkim_sign_headers = expand_string(dkim_sign_headers);
441 if (dkim_sign_headers == NULL) {
442 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
443 "dkim_sign_headers: %s", expand_string_message);
449 /* Get private key to use. */
450 dkim_private_key = expand_string(dkim_private_key);
451 if (dkim_private_key == NULL) {
452 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
453 "dkim_private_key: %s", expand_string_message);
457 if ( (Ustrlen(dkim_private_key) == 0) ||
458 (Ustrcmp(dkim_private_key,"0") == 0) ||
459 (Ustrcmp(dkim_private_key,"false") == 0) ) {
460 /* don't sign, but no error */
465 if (dkim_private_key[0] == '/') {
467 /* Looks like a filename, load the private key. */
468 memset(big_buffer,0,big_buffer_size);
469 privkey_fd = open(CS dkim_private_key,O_RDONLY);
470 if (privkey_fd < 0) {
471 log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
472 "private key file for reading: %s", dkim_private_key);
476 (void)read(privkey_fd,big_buffer,(big_buffer_size-2));
477 (void)close(privkey_fd);
478 dkim_private_key = big_buffer;
481 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
482 (char *)dkim_signing_domain,
483 (char *)dkim_signing_selector,
484 (char *)dkim_private_key
487 pdkim_set_debug_stream(ctx,debug_file);
489 pdkim_set_optional(ctx,
490 (char *)dkim_sign_headers,
495 PDKIM_ALGO_RSA_SHA256,
499 while((sread = read(dkim_fd,&buf,4096)) > 0) {
500 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
505 /* Handle failed read above. */
507 debug_printf("DKIM: Error reading -K file.\n");
513 if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
516 rc = store_get(strlen(signature->signature_header)+3);
517 Ustrcpy(rc,US signature->signature_header);
518 Ustrcat(rc,US"\r\n");
524 store_pool = old_pool;