Don\'t use ||= operator
[exim.git] / src / src / dkim.c
... / ...
CommitLineData
1/* $Cambridge: exim/src/src/dkim.c,v 1.4 2009/10/13 18:32:05 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 switch(dkim_cur_sig->algo) {
239 case PDKIM_ALGO_RSA_SHA1:
240 return US"rsa-sha1";
241 case PDKIM_ALGO_RSA_SHA256:
242 default:
243 return US"rsa-sha256";
244 }
245 case DKIM_BODYLENGTH:
246 return (dkim_cur_sig->bodylength >= 0)?
247 (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
248 :dkim_exim_expand_defaults(what);
249 case DKIM_CANON_BODY:
250 switch(dkim_cur_sig->canon_body) {
251 case PDKIM_CANON_RELAXED:
252 return US"relaxed";
253 case PDKIM_CANON_SIMPLE:
254 default:
255 return US"simple";
256 }
257 case DKIM_CANON_HEADERS:
258 switch(dkim_cur_sig->canon_headers) {
259 case PDKIM_CANON_RELAXED:
260 return US"relaxed";
261 case PDKIM_CANON_SIMPLE:
262 default:
263 return US"simple";
264 }
265 case DKIM_COPIEDHEADERS:
266 return dkim_cur_sig->copiedheaders?
267 (uschar *)(dkim_cur_sig->copiedheaders)
268 :dkim_exim_expand_defaults(what);
269 case DKIM_CREATED:
270 return (dkim_cur_sig->created > 0)?
271 (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
272 :dkim_exim_expand_defaults(what);
273 case DKIM_EXPIRES:
274 return (dkim_cur_sig->expires > 0)?
275 (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
276 :dkim_exim_expand_defaults(what);
277 case DKIM_HEADERNAMES:
278 return dkim_cur_sig->headernames?
279 (uschar *)(dkim_cur_sig->headernames)
280 :dkim_exim_expand_defaults(what);
281 case DKIM_IDENTITY:
282 return dkim_cur_sig->identity?
283 (uschar *)(dkim_cur_sig->identity)
284 :dkim_exim_expand_defaults(what);
285 case DKIM_KEY_GRANULARITY:
286 return dkim_cur_sig->pubkey?
287 (dkim_cur_sig->pubkey->granularity?
288 (uschar *)(dkim_cur_sig->pubkey->granularity)
289 :dkim_exim_expand_defaults(what)
290 )
291 :dkim_exim_expand_defaults(what);
292 case DKIM_KEY_SRVTYPE:
293 return dkim_cur_sig->pubkey?
294 (dkim_cur_sig->pubkey->srvtype?
295 (uschar *)(dkim_cur_sig->pubkey->srvtype)
296 :dkim_exim_expand_defaults(what)
297 )
298 :dkim_exim_expand_defaults(what);
299 case DKIM_KEY_NOTES:
300 return dkim_cur_sig->pubkey?
301 (dkim_cur_sig->pubkey->notes?
302 (uschar *)(dkim_cur_sig->pubkey->notes)
303 :dkim_exim_expand_defaults(what)
304 )
305 :dkim_exim_expand_defaults(what);
306 case DKIM_KEY_TESTING:
307 return dkim_cur_sig->pubkey?
308 (dkim_cur_sig->pubkey->testing?
309 US"1"
310 :dkim_exim_expand_defaults(what)
311 )
312 :dkim_exim_expand_defaults(what);
313 case DKIM_NOSUBDOMAINS:
314 return dkim_cur_sig->pubkey?
315 (dkim_cur_sig->pubkey->no_subdomaining?
316 US"1"
317 :dkim_exim_expand_defaults(what)
318 )
319 :dkim_exim_expand_defaults(what);
320 case DKIM_VERIFY_STATUS:
321 switch(dkim_cur_sig->verify_status) {
322 case PDKIM_VERIFY_INVALID:
323 return US"invalid";
324 case PDKIM_VERIFY_FAIL:
325 return US"fail";
326 case PDKIM_VERIFY_PASS:
327 return US"pass";
328 case PDKIM_VERIFY_NONE:
329 default:
330 return US"none";
331 }
332 case DKIM_VERIFY_REASON:
333 switch (dkim_cur_sig->verify_ext_status) {
334 case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
335 return US"pubkey_unavailable";
336 case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
337 return US"pubkey_syntax";
338 case PDKIM_VERIFY_FAIL_BODY:
339 return US"bodyhash_mismatch";
340 case PDKIM_VERIFY_FAIL_MESSAGE:
341 return US"signature_incorrect";
342 }
343 default:
344 return US"";
345 }
346}
347
348
349uschar *dkim_exim_expand_defaults(int what) {
350 switch(what) {
351 case DKIM_ALGO: return US"";
352 case DKIM_BODYLENGTH: return US"9999999999999";
353 case DKIM_CANON_BODY: return US"";
354 case DKIM_CANON_HEADERS: return US"";
355 case DKIM_COPIEDHEADERS: return US"";
356 case DKIM_CREATED: return US"0";
357 case DKIM_EXPIRES: return US"9999999999999";
358 case DKIM_HEADERNAMES: return US"";
359 case DKIM_IDENTITY: return US"";
360 case DKIM_KEY_GRANULARITY: return US"*";
361 case DKIM_KEY_SRVTYPE: return US"*";
362 case DKIM_KEY_NOTES: return US"";
363 case DKIM_KEY_TESTING: return US"0";
364 case DKIM_NOSUBDOMAINS: return US"0";
365 case DKIM_VERIFY_STATUS: return US"none";
366 case DKIM_VERIFY_REASON: return US"";
367 default: return US"";
368 }
369}
370
371
372uschar *dkim_exim_sign(int dkim_fd,
373 uschar *dkim_private_key,
374 uschar *dkim_domain,
375 uschar *dkim_selector,
376 uschar *dkim_canon,
377 uschar *dkim_sign_headers) {
378 pdkim_ctx *ctx = NULL;
379 uschar *rc = NULL;
380 pdkim_signature *signature;
381 int pdkim_canon;
382 int sread;
383 char buf[4096];
384 int save_errno = 0;
385 int old_pool = store_pool;
386
387 dkim_domain = expand_string(dkim_domain);
388 if (dkim_domain == NULL) {
389 /* expansion error, do not send message. */
390 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
391 "dkim_domain: %s", expand_string_message);
392 rc = NULL;
393 goto CLEANUP;
394 }
395 /* Set up $dkim_domain expansion variable. */
396 dkim_signing_domain = dkim_domain;
397
398 /* Get selector to use. */
399 dkim_selector = expand_string(dkim_selector);
400 if (dkim_selector == NULL) {
401 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
402 "dkim_selector: %s", expand_string_message);
403 rc = NULL;
404 goto CLEANUP;
405 }
406 /* Set up $dkim_selector expansion variable. */
407 dkim_signing_selector = dkim_selector;
408
409 /* Get canonicalization to use */
410 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
411 if (dkim_canon == NULL) {
412 /* expansion error, do not send message. */
413 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
414 "dkim_canon: %s", expand_string_message);
415 rc = NULL;
416 goto CLEANUP;
417 }
418 if (Ustrcmp(dkim_canon, "relaxed") == 0)
419 pdkim_canon = PDKIM_CANON_RELAXED;
420 else if (Ustrcmp(dkim_canon, "simple") == 0)
421 pdkim_canon = PDKIM_CANON_RELAXED;
422 else {
423 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
424 pdkim_canon = PDKIM_CANON_RELAXED;
425 }
426
427 /* Expand signing headers once */
428 if (dkim_sign_headers != NULL) {
429 dkim_sign_headers = expand_string(dkim_sign_headers);
430 if (dkim_sign_headers == NULL) {
431 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
432 "dkim_sign_headers: %s", expand_string_message);
433 rc = NULL;
434 goto CLEANUP;
435 }
436 }
437
438 /* Get private key to use. */
439 dkim_private_key = expand_string(dkim_private_key);
440 if (dkim_private_key == NULL) {
441 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
442 "dkim_private_key: %s", expand_string_message);
443 rc = NULL;
444 goto CLEANUP;
445 }
446 if ( (Ustrlen(dkim_private_key) == 0) ||
447 (Ustrcmp(dkim_private_key,"0") == 0) ||
448 (Ustrcmp(dkim_private_key,"false") == 0) ) {
449 /* don't sign, but no error */
450 rc = US"";
451 goto CLEANUP;
452 }
453
454 if (dkim_private_key[0] == '/') {
455 int privkey_fd = 0;
456 /* Looks like a filename, load the private key. */
457 memset(big_buffer,0,big_buffer_size);
458 privkey_fd = open(CS dkim_private_key,O_RDONLY);
459 if (privkey_fd < 0) {
460 log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
461 "private key file for reading: %s", dkim_private_key);
462 rc = NULL;
463 goto CLEANUP;
464 }
465 (void)read(privkey_fd,big_buffer,(big_buffer_size-2));
466 (void)close(privkey_fd);
467 dkim_private_key = big_buffer;
468 }
469
470 ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
471 (char *)dkim_signing_domain,
472 (char *)dkim_signing_selector,
473 (char *)dkim_private_key
474 );
475
476 pdkim_set_debug_stream(ctx,debug_file);
477
478 pdkim_set_optional(ctx,
479 (char *)dkim_sign_headers,
480 NULL,
481 pdkim_canon,
482 pdkim_canon,
483 -1,
484 PDKIM_ALGO_RSA_SHA256,
485 0,
486 0);
487
488 while((sread = read(dkim_fd,&buf,4096)) > 0) {
489 if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
490 rc = NULL;
491 goto CLEANUP;
492 }
493 }
494 /* Handle failed read above. */
495 if (sread == -1) {
496 debug_printf("DKIM: Error reading -K file.\n");
497 save_errno = errno;
498 rc = NULL;
499 goto CLEANUP;
500 }
501
502 if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
503 goto CLEANUP;
504
505 rc = store_get(strlen(signature->signature_header)+3);
506 Ustrcpy(rc,US signature->signature_header);
507 Ustrcat(rc,US"\r\n");
508
509 CLEANUP:
510 if (ctx != NULL) {
511 pdkim_free_ctx(ctx);
512 }
513 store_pool = old_pool;
514 errno = save_errno;
515 return rc;
516};
517
518#endif