42b14b5da65f5d7cae07239a10c8a7579d2e496f
[exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.6 2009/10/15 08:27:37 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
19 pdkim_ctx *dkim_verify_ctx = NULL;
20 pdkim_signature *dkim_signatures = NULL;
21 pdkim_signature *dkim_cur_sig = NULL;
22
23 int 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
55 void 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
74 void 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
82 void dkim_exim_verify_finish(void) {
83 pdkim_signature *sig = NULL;
84 int dkim_signers_size = 0;
85 int dkim_signers_ptr = 0;
86 dkim_signers = 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 (and identities, if present) in dkim_signers */
182 dkim_signers = string_append(dkim_signers,
183 &dkim_signers_size,
184 &dkim_signers_ptr,
185 2,
186 sig->domain,
187 ":"
188 );
189
190 if (sig->identity != NULL) {
191 dkim_signers = string_append(dkim_signers,
192 &dkim_signers_size,
193 &dkim_signers_ptr,
194 2,
195 sig->identity,
196 ":"
197 );
198 }
199
200 /* Process next signature */
201 sig = sig->next;
202 }
203
204 /* Chop the last colon from the domain list */
205 if ((dkim_signers != NULL) &&
206 (Ustrlen(dkim_signers) > 0))
207 dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
208 }
209
210
211 void dkim_exim_acl_setup(uschar *id) {
212 pdkim_signature *sig = dkim_signatures;
213 dkim_cur_sig = NULL;
214 dkim_cur_signer = id;
215 if (dkim_disable_verify ||
216 !id || !dkim_verify_ctx) return;
217 /* Find signature to run ACL on */
218 while (sig != NULL) {
219 uschar *cmp_val = NULL;
220 if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
221 else cmp_val = (uschar *)sig->domain;
222 if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
223 dkim_cur_sig = sig;
224 /* The "dkim_domain" and "dkim_selector" expansion variables have
225 related globals, since they are used in the signing code too.
226 Instead of inventing separate names for verification, we set
227 them here. This is easy since a domain and selector is guaranteed
228 to be in a signature. The other dkim_* expansion items are
229 dynamically fetched from dkim_cur_sig at expansion time (see
230 function below). */
231 dkim_signing_domain = (uschar *)sig->domain;
232 dkim_signing_selector = (uschar *)sig->selector;
233 return;
234 }
235 sig = sig->next;
236 }
237 }
238
239
240 uschar *dkim_exim_expand_query(int what) {
241
242 if (!dkim_verify_ctx ||
243 dkim_disable_verify ||
244 !dkim_cur_sig) return dkim_exim_expand_defaults(what);
245
246 switch(what) {
247 case DKIM_ALGO:
248 switch(dkim_cur_sig->algo) {
249 case PDKIM_ALGO_RSA_SHA1:
250 return US"rsa-sha1";
251 case PDKIM_ALGO_RSA_SHA256:
252 default:
253 return US"rsa-sha256";
254 }
255 case DKIM_BODYLENGTH:
256 return (dkim_cur_sig->bodylength >= 0)?
257 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
258 :dkim_exim_expand_defaults(what);
259 case DKIM_CANON_BODY:
260 switch(dkim_cur_sig->canon_body) {
261 case PDKIM_CANON_RELAXED:
262 return US"relaxed";
263 case PDKIM_CANON_SIMPLE:
264 default:
265 return US"simple";
266 }
267 case DKIM_CANON_HEADERS:
268 switch(dkim_cur_sig->canon_headers) {
269 case PDKIM_CANON_RELAXED:
270 return US"relaxed";
271 case PDKIM_CANON_SIMPLE:
272 default:
273 return US"simple";
274 }
275 case DKIM_COPIEDHEADERS:
276 return dkim_cur_sig->copiedheaders?
277 (uschar *)(dkim_cur_sig->copiedheaders)
278 :dkim_exim_expand_defaults(what);
279 case DKIM_CREATED:
280 return (dkim_cur_sig->created > 0)?
281 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
282 :dkim_exim_expand_defaults(what);
283 case DKIM_EXPIRES:
284 return (dkim_cur_sig->expires > 0)?
285 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
286 :dkim_exim_expand_defaults(what);
287 case DKIM_HEADERNAMES:
288 return dkim_cur_sig->headernames?
289 (uschar *)(dkim_cur_sig->headernames)
290 :dkim_exim_expand_defaults(what);
291 case DKIM_IDENTITY:
292 return dkim_cur_sig->identity?
293 (uschar *)(dkim_cur_sig->identity)
294 :dkim_exim_expand_defaults(what);
295 case DKIM_KEY_GRANULARITY:
296 return dkim_cur_sig->pubkey?
297 (dkim_cur_sig->pubkey->granularity?
298 (uschar *)(dkim_cur_sig->pubkey->granularity)
299 :dkim_exim_expand_defaults(what)
300 )
301 :dkim_exim_expand_defaults(what);
302 case DKIM_KEY_SRVTYPE:
303 return dkim_cur_sig->pubkey?
304 (dkim_cur_sig->pubkey->srvtype?
305 (uschar *)(dkim_cur_sig->pubkey->srvtype)
306 :dkim_exim_expand_defaults(what)
307 )
308 :dkim_exim_expand_defaults(what);
309 case DKIM_KEY_NOTES:
310 return dkim_cur_sig->pubkey?
311 (dkim_cur_sig->pubkey->notes?
312 (uschar *)(dkim_cur_sig->pubkey->notes)
313 :dkim_exim_expand_defaults(what)
314 )
315 :dkim_exim_expand_defaults(what);
316 case DKIM_KEY_TESTING:
317 return dkim_cur_sig->pubkey?
318 (dkim_cur_sig->pubkey->testing?
319 US"1"
320 :dkim_exim_expand_defaults(what)
321 )
322 :dkim_exim_expand_defaults(what);
323 case DKIM_NOSUBDOMAINS:
324 return dkim_cur_sig->pubkey?
325 (dkim_cur_sig->pubkey->no_subdomaining?
326 US"1"
327 :dkim_exim_expand_defaults(what)
328 )
329 :dkim_exim_expand_defaults(what);
330 case DKIM_VERIFY_STATUS:
331 switch(dkim_cur_sig->verify_status) {
332 case PDKIM_VERIFY_INVALID:
333 return US"invalid";
334 case PDKIM_VERIFY_FAIL:
335 return US"fail";
336 case PDKIM_VERIFY_PASS:
337 return US"pass";
338 case PDKIM_VERIFY_NONE:
339 default:
340 return US"none";
341 }
342 case DKIM_VERIFY_REASON:
343 switch (dkim_cur_sig->verify_ext_status) {
344 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
345 return US"pubkey_unavailable";
346 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
347 return US"pubkey_syntax";
348 case PDKIM_VERIFY_FAIL_BODY:
349 return US"bodyhash_mismatch";
350 case PDKIM_VERIFY_FAIL_MESSAGE:
351 return US"signature_incorrect";
352 }
353 default:
354 return US"";
355 }
356 }
357
358
359 uschar *dkim_exim_expand_defaults(int what) {
360 switch(what) {
361 case DKIM_ALGO: return US"";
362 case DKIM_BODYLENGTH: return US"9999999999999";
363 case DKIM_CANON_BODY: return US"";
364 case DKIM_CANON_HEADERS: return US"";
365 case DKIM_COPIEDHEADERS: return US"";
366 case DKIM_CREATED: return US"0";
367 case DKIM_EXPIRES: return US"9999999999999";
368 case DKIM_HEADERNAMES: return US"";
369 case DKIM_IDENTITY: return US"";
370 case DKIM_KEY_GRANULARITY: return US"*";
371 case DKIM_KEY_SRVTYPE: return US"*";
372 case DKIM_KEY_NOTES: return US"";
373 case DKIM_KEY_TESTING: return US"0";
374 case DKIM_NOSUBDOMAINS: return US"0";
375 case DKIM_VERIFY_STATUS: return US"none";
376 case DKIM_VERIFY_REASON: return US"";
377 default: return US"";
378 }
379 }
380
381
382 uschar *dkim_exim_sign(int dkim_fd,
383 uschar *dkim_private_key,
384 uschar *dkim_domain,
385 uschar *dkim_selector,
386 uschar *dkim_canon,
387 uschar *dkim_sign_headers) {
388 pdkim_ctx *ctx = NULL;
389 uschar *rc = NULL;
390 pdkim_signature *signature;
391 int pdkim_canon;
392 int sread;
393 char buf[4096];
394 int save_errno = 0;
395 int old_pool = store_pool;
396
397 dkim_domain = expand_string(dkim_domain);
398 if (dkim_domain == NULL) {
399 /* expansion error, do not send message. */
400 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
401 "dkim_domain: %s", expand_string_message);
402 rc = NULL;
403 goto CLEANUP;
404 }
405 /* Set up $dkim_domain expansion variable. */
406 dkim_signing_domain = dkim_domain;
407
408 /* Get selector to use. */
409 dkim_selector = expand_string(dkim_selector);
410 if (dkim_selector == NULL) {
411 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
412 "dkim_selector: %s", expand_string_message);
413 rc = NULL;
414 goto CLEANUP;
415 }
416 /* Set up $dkim_selector expansion variable. */
417 dkim_signing_selector = dkim_selector;
418
419 /* Get canonicalization to use */
420 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
421 if (dkim_canon == NULL) {
422 /* expansion error, do not send message. */
423 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
424 "dkim_canon: %s", expand_string_message);
425 rc = NULL;
426 goto CLEANUP;
427 }
428 if (Ustrcmp(dkim_canon, "relaxed") == 0)
429 pdkim_canon = PDKIM_CANON_RELAXED;
430 else if (Ustrcmp(dkim_canon, "simple") == 0)
431 pdkim_canon = PDKIM_CANON_RELAXED;
432 else {
433 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
434 pdkim_canon = PDKIM_CANON_RELAXED;
435 }
436
437 /* Expand signing headers once */
438 if (dkim_sign_headers != NULL) {
439 dkim_sign_headers = expand_string(dkim_sign_headers);
440 if (dkim_sign_headers == NULL) {
441 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
442 "dkim_sign_headers: %s", expand_string_message);
443 rc = NULL;
444 goto CLEANUP;
445 }
446 }
447
448 /* Get private key to use. */
449 dkim_private_key = expand_string(dkim_private_key);
450 if (dkim_private_key == NULL) {
451 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
452 "dkim_private_key: %s", expand_string_message);
453 rc = NULL;
454 goto CLEANUP;
455 }
456 if ( (Ustrlen(dkim_private_key) == 0) ||
457 (Ustrcmp(dkim_private_key,"0") == 0) ||
458 (Ustrcmp(dkim_private_key,"false") == 0) ) {
459 /* don't sign, but no error */
460 rc = US"";
461 goto CLEANUP;
462 }
463
464 if (dkim_private_key[0] == '/') {
465 int privkey_fd = 0;
466 /* Looks like a filename, load the private key. */
467 memset(big_buffer,0,big_buffer_size);
468 privkey_fd = open(CS dkim_private_key,O_RDONLY);
469 if (privkey_fd < 0) {
470 log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
471 "private key file for reading: %s", dkim_private_key);
472 rc = NULL;
473 goto CLEANUP;
474 }
475 (void)read(privkey_fd,big_buffer,(big_buffer_size-2));
476 (void)close(privkey_fd);
477 dkim_private_key = big_buffer;
478 }
479
480 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
481 (char *)dkim_signing_domain,
482 (char *)dkim_signing_selector,
483 (char *)dkim_private_key
484 );
485
486 pdkim_set_debug_stream(ctx,debug_file);
487
488 pdkim_set_optional(ctx,
489 (char *)dkim_sign_headers,
490 NULL,
491 pdkim_canon,
492 pdkim_canon,
493 -1,
494 PDKIM_ALGO_RSA_SHA256,
495 0,
496 0);
497
498 while((sread = read(dkim_fd,&buf,4096)) > 0) {
499 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
500 rc = NULL;
501 goto CLEANUP;
502 }
503 }
504 /* Handle failed read above. */
505 if (sread == -1) {
506 debug_printf("DKIM: Error reading -K file.\n");
507 save_errno = errno;
508 rc = NULL;
509 goto CLEANUP;
510 }
511
512 if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
513 goto CLEANUP;
514
515 rc = store_get(strlen(signature->signature_header)+3);
516 Ustrcpy(rc,US signature->signature_header);
517 Ustrcat(rc,US"\r\n");
518
519 CLEANUP:
520 if (ctx != NULL) {
521 pdkim_free_ctx(ctx);
522 }
523 store_pool = old_pool;
524 errno = save_errno;
525 return rc;
526 };
527
528 #endif