Merge native DKIM support (from DEVEL_PDKIM)
[exim.git] / src / src / dkim.c
CommitLineData
80a47a2c
TK
1/* $Cambridge: exim/src/src/dkim.c,v 1.2 2009/06/10 07:34:04 tom Exp $ */
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
7/* Copyright (c) University of Cambridge 2009 */
8/* See the file NOTICE for conditions of use and distribution. */
9
10/* Code for DKIM support. Other DKIM relevant code is in
11 receive.c, transport.c and transports/smtp.c */
12
13#include "exim.h"
14
15#ifndef DISABLE_DKIM
16
17#include "pdkim/pdkim.h"
18
19pdkim_ctx *dkim_verify_ctx = NULL;
20pdkim_signature *dkim_signatures = NULL;
21pdkim_signature *dkim_cur_sig = NULL;
22
23int dkim_exim_query_dns_txt(char *name, char *answer) {
24 dns_answer dnsa;
25 dns_scan dnss;
26 dns_record *rr;
27
28 if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
29
30 /* Search for TXT record */
31 for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
32 rr != NULL;
33 rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
34 if (rr->type == T_TXT) break;
35
36 /* Copy record content to the answer buffer */
37 if (rr != NULL) {
38 int rr_offset = 0;
39 int answer_offset = 0;
40 while (rr_offset < rr->size) {
41 uschar len = (rr->data)[rr_offset++];
42 snprintf(answer+(answer_offset),
43 PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
44 "%.*s", (int)len, (char *)((rr->data)+rr_offset));
45 rr_offset+=len;
46 answer_offset+=len;
47 }
48 }
49 else return PDKIM_FAIL;
50
51 return PDKIM_OK;
52}
53
54
55void dkim_exim_verify_init(void) {
56
57 /* Free previous context if there is one */
58 if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
59
60 /* Create new context */
61 dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
62 &dkim_exim_query_dns_txt
63 );
64
65 if (dkim_verify_ctx != NULL) {
66 dkim_collect_input = TRUE;
67 pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
68 }
69 else dkim_collect_input = FALSE;
70
71}
72
73
74void dkim_exim_verify_feed(uschar *data, int len) {
75 if (dkim_collect_input &&
76 pdkim_feed(dkim_verify_ctx,
77 (char *)data,
78 len) != PDKIM_OK) dkim_collect_input = FALSE;
79}
80
81
82void dkim_exim_verify_finish(void) {
83 pdkim_signature *sig = NULL;
84 int dkim_signing_domains_size = 0;
85 int dkim_signing_domains_ptr = 0;
86 dkim_signing_domains = NULL;
87
88 /* Delete eventual previous signature chain */
89 dkim_signatures = NULL;
90
91 /* If we have arrived here with dkim_collect_input == FALSE, it
92 means there was a processing error somewhere along the way.
93 Log the incident and disable futher verification. */
94 if (!dkim_collect_input) {
95 log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: Error while running this message through validation, disabling signature verification.");
96 dkim_disable_verify = TRUE;
97 return;
98 }
99 dkim_collect_input = FALSE;
100
101 /* Finish DKIM operation and fetch link to signatures chain */
102 if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
103
104 sig = dkim_signatures;
105 while (sig != NULL) {
106 int size = 0;
107 int ptr = 0;
108 /* Log a line for each signature */
109 uschar *logmsg = string_append(NULL, &size, &ptr, 5,
110
111 string_sprintf( "DKIM: d=%s s=%s c=%s/%s a=%s ",
112 sig->domain,
113 sig->selector,
114 (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
115 (sig->canon_body == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
116 (sig->algo == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
117 ),
118 ((sig->identity != NULL)?
119 string_sprintf("i=%s ", sig->identity)
120 :
121 US""
122 ),
123 ((sig->created > 0)?
124 string_sprintf("t=%lu ", sig->created)
125 :
126 US""
127 ),
128 ((sig->expires > 0)?
129 string_sprintf("x=%lu ", sig->expires)
130 :
131 US""
132 ),
133 ((sig->bodylength > -1)?
134 string_sprintf("l=%lu ", sig->bodylength)
135 :
136 US""
137 )
138 );
139
140 switch(sig->verify_status) {
141 case PDKIM_VERIFY_NONE:
142 logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
143 break;
144 case PDKIM_VERIFY_INVALID:
145 logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
146 switch (sig->verify_ext_status) {
147 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
148 logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
149 break;
150 case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
151 logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
152 break;
153 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
154 logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
155 break;
156 default:
157 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
158 }
159 break;
160 case PDKIM_VERIFY_FAIL:
161 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
162 switch (sig->verify_ext_status) {
163 case PDKIM_VERIFY_FAIL_BODY:
164 logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
165 break;
166 case PDKIM_VERIFY_FAIL_MESSAGE:
167 logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
168 break;
169 default:
170 logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
171 }
172 break;
173 case PDKIM_VERIFY_PASS:
174 logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
175 break;
176 }
177
178 logmsg[ptr] = '\0';
179 log_write(0, LOG_MAIN, (char *)logmsg);
180
181 /* Build a colon-separated list of signing domains in dkim_signing_domains */
182 dkim_signing_domains = string_append(dkim_signing_domains,
183 &dkim_signing_domains_size,
184 &dkim_signing_domains_ptr,
185 2,
186 sig->domain,
187 ":"
188 );
189
190 /* Process next signature */
191 sig = sig->next;
192 }
193
194 /* Chop the last colon from the domain list */
195 if ((dkim_signing_domains != NULL) &&
196 (Ustrlen(dkim_signing_domains) > 0))
197 dkim_signing_domains[Ustrlen(dkim_signing_domains)-1] = '\0';
198}
199
200
201void dkim_exim_acl_setup(uschar *id) {
202 pdkim_signature *sig = dkim_signatures;
203 dkim_cur_sig = NULL;
204 if (dkim_disable_verify ||
205 !id || !sig ||
206 !dkim_verify_ctx) return;
207 /* Find signature to run ACL on */
208 while (sig != NULL) {
209 uschar *cmp_val = NULL;
210 if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
211 else cmp_val = (uschar *)sig->domain;
212 if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
213 dkim_cur_sig = sig;
214 /* The "dkim_domain" and "dkim_selector" expansion variables have
215 related globals, since they are used in the signing code too.
216 Instead of inventing separate names for verification, we set
217 them here. This is easy since a domain and selector is guaranteed
218 to be in a signature. The other dkim_* expansion items are
219 dynamically fetched from dkim_cur_sig at expansion time (see
220 function below). */
221 dkim_signing_domain = (uschar *)sig->domain;
222 dkim_signing_selector = (uschar *)sig->selector;
223 return;
224 }
225 sig = sig->next;
226 }
227}
228
229
230uschar *dkim_exim_expand_query(int what) {
231
232 if (!dkim_verify_ctx ||
233 dkim_disable_verify ||
234 !dkim_cur_sig) return dkim_exim_expand_defaults(what);
235
236 switch(what) {
237 case DKIM_ALGO:
238 return dkim_cur_sig->algo?
239 (uschar *)(dkim_cur_sig->algo)
240 :dkim_exim_expand_defaults(what);
241 case DKIM_BODYLENGTH:
242 return (dkim_cur_sig->bodylength >= 0)?
243 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
244 :dkim_exim_expand_defaults(what);
245 case DKIM_CANON_BODY:
246 return dkim_cur_sig->canon_body?
247 (uschar *)(dkim_cur_sig->canon_body)
248 :dkim_exim_expand_defaults(what);
249 case DKIM_CANON_HEADERS:
250 return dkim_cur_sig->canon_headers?
251 (uschar *)(dkim_cur_sig->canon_headers)
252 :dkim_exim_expand_defaults(what);
253 case DKIM_COPIEDHEADERS:
254 return dkim_cur_sig->copiedheaders?
255 (uschar *)(dkim_cur_sig->copiedheaders)
256 :dkim_exim_expand_defaults(what);
257 case DKIM_CREATED:
258 return (dkim_cur_sig->created > 0)?
259 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
260 :dkim_exim_expand_defaults(what);
261 case DKIM_EXPIRES:
262 return (dkim_cur_sig->expires > 0)?
263 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
264 :dkim_exim_expand_defaults(what);
265 case DKIM_HEADERNAMES:
266 return dkim_cur_sig->headernames?
267 (uschar *)(dkim_cur_sig->headernames)
268 :dkim_exim_expand_defaults(what);
269 case DKIM_IDENTITY:
270 return dkim_cur_sig->identity?
271 (uschar *)(dkim_cur_sig->identity)
272 :dkim_exim_expand_defaults(what);
273 case DKIM_KEY_GRANULARITY:
274 return dkim_cur_sig->pubkey?
275 (dkim_cur_sig->pubkey->granularity?
276 (uschar *)(dkim_cur_sig->pubkey->granularity)
277 :dkim_exim_expand_defaults(what)
278 )
279 :dkim_exim_expand_defaults(what);
280 case DKIM_KEY_SRVTYPE:
281 return dkim_cur_sig->pubkey?
282 (dkim_cur_sig->pubkey->srvtype?
283 (uschar *)(dkim_cur_sig->pubkey->srvtype)
284 :dkim_exim_expand_defaults(what)
285 )
286 :dkim_exim_expand_defaults(what);
287 case DKIM_KEY_NOTES:
288 return dkim_cur_sig->pubkey?
289 (dkim_cur_sig->pubkey->notes?
290 (uschar *)(dkim_cur_sig->pubkey->notes)
291 :dkim_exim_expand_defaults(what)
292 )
293 :dkim_exim_expand_defaults(what);
294 case DKIM_KEY_TESTING:
295 return dkim_cur_sig->pubkey?
296 (dkim_cur_sig->pubkey->testing?
297 US"1"
298 :dkim_exim_expand_defaults(what)
299 )
300 :dkim_exim_expand_defaults(what);
301 case DKIM_NOSUBDOMAINS:
302 return dkim_cur_sig->pubkey?
303 (dkim_cur_sig->pubkey->no_subdomaining?
304 US"1"
305 :dkim_exim_expand_defaults(what)
306 )
307 :dkim_exim_expand_defaults(what);
308 case DKIM_VERIFY_STATUS:
309 switch(dkim_cur_sig->verify_status) {
310 case PDKIM_VERIFY_INVALID:
311 return US"invalid";
312 case PDKIM_VERIFY_FAIL:
313 return US"fail";
314 case PDKIM_VERIFY_PASS:
315 return US"pass";
316 case PDKIM_VERIFY_NONE:
317 default:
318 return US"none";
319 }
320 case DKIM_VERIFY_REASON:
321 switch (dkim_cur_sig->verify_ext_status) {
322 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
323 return US"pubkey_unavailable";
324 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
325 return US"pubkey_syntax";
326 case PDKIM_VERIFY_FAIL_BODY:
327 return US"bodyhash_mismatch";
328 case PDKIM_VERIFY_FAIL_MESSAGE:
329 return US"signature_incorrect";
330 }
331 default:
332 return US"";
333 }
334}
335
336
337uschar *dkim_exim_expand_defaults(int what) {
338 switch(what) {
339 case DKIM_ALGO: return US"";
340 case DKIM_BODYLENGTH: return US"9999999999999";
341 case DKIM_CANON_BODY: return US"";
342 case DKIM_CANON_HEADERS: return US"";
343 case DKIM_COPIEDHEADERS: return US"";
344 case DKIM_CREATED: return US"0";
345 case DKIM_EXPIRES: return US"9999999999999";
346 case DKIM_HEADERNAMES: return US"";
347 case DKIM_IDENTITY: return US"";
348 case DKIM_KEY_GRANULARITY: return US"*";
349 case DKIM_KEY_SRVTYPE: return US"*";
350 case DKIM_KEY_NOTES: return US"";
351 case DKIM_KEY_TESTING: return US"0";
352 case DKIM_NOSUBDOMAINS: return US"0";
353 case DKIM_VERIFY_STATUS: return US"none";
354 case DKIM_VERIFY_REASON: return US"";
355 default: return US"";
356 }
357}
358
359
360uschar *dkim_exim_sign(int dkim_fd,
361 uschar *dkim_private_key,
362 uschar *dkim_domain,
363 uschar *dkim_selector,
364 uschar *dkim_canon,
365 uschar *dkim_sign_headers) {
366 pdkim_ctx *ctx = NULL;
367 uschar *rc = NULL;
368 pdkim_signature *signature;
369 int pdkim_canon;
370 int sread;
371 char buf[4096];
372 int save_errno = 0;
373 int old_pool = store_pool;
374
375 dkim_domain = expand_string(dkim_domain);
376 if (dkim_domain == NULL) {
377 /* expansion error, do not send message. */
378 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
379 "dkim_domain: %s", expand_string_message);
380 rc = NULL;
381 goto CLEANUP;
382 }
383 /* Set up $dkim_domain expansion variable. */
384 dkim_signing_domain = dkim_domain;
385
386 /* Get selector to use. */
387 dkim_selector = expand_string(dkim_selector);
388 if (dkim_selector == NULL) {
389 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
390 "dkim_selector: %s", expand_string_message);
391 rc = NULL;
392 goto CLEANUP;
393 }
394 /* Set up $dkim_selector expansion variable. */
395 dkim_signing_selector = dkim_selector;
396
397 /* Get canonicalization to use */
398 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
399 if (dkim_canon == NULL) {
400 /* expansion error, do not send message. */
401 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
402 "dkim_canon: %s", expand_string_message);
403 rc = NULL;
404 goto CLEANUP;
405 }
406 if (Ustrcmp(dkim_canon, "relaxed") == 0)
407 pdkim_canon = PDKIM_CANON_RELAXED;
408 else if (Ustrcmp(dkim_canon, "simple") == 0)
409 pdkim_canon = PDKIM_CANON_RELAXED;
410 else {
411 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
412 pdkim_canon = PDKIM_CANON_RELAXED;
413 }
414
415 /* Expand signing headers once */
416 if (dkim_sign_headers != NULL) {
417 dkim_sign_headers = expand_string(dkim_sign_headers);
418 if (dkim_sign_headers == NULL) {
419 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
420 "dkim_sign_headers: %s", expand_string_message);
421 rc = NULL;
422 goto CLEANUP;
423 }
424 }
425
426 /* Get private key to use. */
427 dkim_private_key = expand_string(dkim_private_key);
428 if (dkim_private_key == NULL) {
429 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
430 "dkim_private_key: %s", expand_string_message);
431 rc = NULL;
432 goto CLEANUP;
433 }
434 if ( (Ustrlen(dkim_private_key) == 0) ||
435 (Ustrcmp(dkim_private_key,"0") == 0) ||
436 (Ustrcmp(dkim_private_key,"false") == 0) ) {
437 /* don't sign, but no error */
438 rc = US"";
439 goto CLEANUP;
440 }
441
442 if (dkim_private_key[0] == '/') {
443 int privkey_fd = 0;
444 /* Looks like a filename, load the private key. */
445 memset(big_buffer,0,big_buffer_size);
446 privkey_fd = open(CS dkim_private_key,O_RDONLY);
447 (void)read(privkey_fd,big_buffer,16383);
448 (void)close(privkey_fd);
449 dkim_private_key = big_buffer;
450 }
451
452 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
453 (char *)dkim_signing_domain,
454 (char *)dkim_signing_selector,
455 (char *)dkim_private_key
456 );
457
458 pdkim_set_debug_stream(ctx,debug_file);
459
460 pdkim_set_optional(ctx,
461 (char *)dkim_sign_headers,
462 NULL,
463 pdkim_canon,
464 pdkim_canon,
465 -1,
466 PDKIM_ALGO_RSA_SHA256,
467 0,
468 0);
469
470 while((sread = read(dkim_fd,&buf,4096)) > 0) {
471 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
472 rc = NULL;
473 goto CLEANUP;
474 }
475 }
476 /* Handle failed read above. */
477 if (sread == -1) {
478 debug_printf("DKIM: Error reading -K file.\n");
479 save_errno = errno;
480 rc = NULL;
481 goto CLEANUP;
482 }
483
484 if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
485 goto CLEANUP;
486
487 rc = store_get(strlen(signature->signature_header)+3);
488 Ustrcpy(rc,US signature->signature_header);
489 Ustrcat(rc,US"\r\n");
490
491 CLEANUP:
492 if (ctx != NULL) {
493 pdkim_free_ctx(ctx);
494 }
495 store_pool = old_pool;
496 errno = save_errno;
497 return rc;
498};
499
500#endif