Bugzilla 663: explain that $spam_score and $spam_score_int may appear to disagree.
[exim.git] / src / src / dkim-exim.c
CommitLineData
ca15ec2f 1/* $Cambridge: exim/src/src/dkim-exim.c,v 1.2 2007/10/09 14:10:34 tom Exp $ */
f7572e5a
TK
2
3/*************************************************
4* Exim - an Internet mail transport agent *
5*************************************************/
6
7/* Copyright (c) University of Cambridge 1995 - 2007 */
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#ifdef EXPERIMENTAL_DKIM
16
17/* Globals related to the DKIM reference library. */
18DKIMContext *dkim_context = NULL;
19DKIMSignOptions *dkim_sign_options = NULL;
20DKIMVerifyOptions *dkim_verify_options = NULL;
21int dkim_verify_result = DKIM_NEUTRAL;
22int dkim_internal_status = DKIM_SUCCESS;
23
24/* Global char buffer for getc/ungetc functions. We need
25 to accumulate some chars to be able to match EOD and
26 doubled SMTP dots. Those must not be fed to the validation
27 engine. */
28int dkimbuff[6] = {256,256,256,256,256,256};
29
30/* receive_getc() wrapper that feeds DKIM while Exim reads
31 the message. */
32int dkim_receive_getc(void) {
33 int i;
34
35#ifdef EXPERIMENTAL_DOMAINKEYS
36 int c = dk_receive_getc();
37#else
38 int c = receive_getc();
39#endif
40
41 if ((dkim_context != NULL) &&
42 (dkim_internal_status == DKIM_SUCCESS)) {
43 /* Send oldest byte */
44 if (dkimbuff[0] < 256) {
45 DKIMVerifyProcess(dkim_context,(char *)&dkimbuff[0],1);
46 /* debug_printf("%c",(int)dkimbuff[0]); */
47 }
48 /* rotate buffer */
49 for (i=0;i<5;i++) dkimbuff[i]=dkimbuff[i+1];
50 dkimbuff[5]=c;
51 /* look for our candidate patterns */
52 if ( (dkimbuff[1] == '\r') &&
53 (dkimbuff[2] == '\n') &&
54 (dkimbuff[3] == '.') &&
55 (dkimbuff[4] == '\r') &&
56 (dkimbuff[5] == '\n') ) {
57 /* End of DATA */
58 dkimbuff[1] = 256;
59 dkimbuff[2] = 256;
60 dkimbuff[3] = 256;
61 dkimbuff[4] = 256;
62 dkimbuff[5] = 256;
63 }
64 if ( (dkimbuff[2] == '\r') &&
65 (dkimbuff[3] == '\n') &&
66 (dkimbuff[4] == '.') &&
67 (dkimbuff[5] == '.') ) {
68 /* doubled dot, skip this char */
69 dkimbuff[5] = 256;
70 }
71 }
72
73 return c;
74}
75
76/* When exim puts a char back in the fd, we
77 must rotate our buffer back. */
78int dkim_receive_ungetc(int c) {
79
80 if ((dkim_context != NULL) &&
81 (dkim_internal_status == DKIM_SUCCESS)) {
82 int i;
83 /* rotate buffer back */
84 for (i=5;i>0;i--) dkimbuff[i]=dkimbuff[i-1];
85 dkimbuff[0]=256;
86 }
87
88#ifdef EXPERIMENTAL_DOMAINKEYS
89 return dk_receive_ungetc(c);
90#else
91 return receive_ungetc(c);
92#endif
93}
94
95
96void dkim_exim_verify_init(void) {
97 int old_pool = store_pool;
98
99 /* Bail out unless we got perfect conditions */
100 if (!(smtp_input &&
101 !smtp_batched_input &&
102 dkim_do_verify)) {
103 return;
104 }
105
106 store_pool = POOL_PERM;
107
108 dkim_context = NULL;
109 dkim_verify_options = NULL;
110
111 dkim_context = store_get(sizeof(DKIMContext));
112 dkim_verify_options = store_get(sizeof(DKIMVerifyOptions));
113
114 if (!dkim_context ||
115 !dkim_verify_options) {
116 debug_printf("DKIM: Can't allocate memory for verifying.\n");
117 dkim_context = NULL;
118 }
119
120 memset(dkim_context,0,sizeof(DKIMContext));
121 memset(dkim_verify_options,0,sizeof(DKIMVerifyOptions));
122
123 dkim_verify_options->nHonorBodyLengthTag = 1; /* Honor the l= tag */
124 dkim_verify_options->nCheckPolicy = 1; /* Fetch sender's policy */
125 dkim_verify_options->nSubjectRequired = 1; /* Do not require Subject header inclusion */
126
127 dkim_verify_options->pfnSelectorCallback = NULL;
128 dkim_verify_options->pfnPolicyCallback = NULL;
129
130 dkim_status_wrap( DKIMVerifyInit(dkim_context, dkim_verify_options),
131 "error calling DKIMVerifyInit()" );
132
133 if (dkim_internal_status != DKIM_SUCCESS) {
134 /* Invalidate context */
135 dkim_context = NULL;
136 }
137
138 store_pool = old_pool;
139}
140
141
142void dkim_exim_verify_finish(void) {
143 int i;
144 int old_pool = store_pool;
145
146 if (!dkim_do_verify ||
147 (!(smtp_input && !smtp_batched_input)) ||
148 (dkim_context == NULL) ||
149 (dkim_internal_status != DKIM_SUCCESS)) return;
150
151 store_pool = POOL_PERM;
152
153 /* Flush eventual remaining input chars */
154 for (i=0;i<6;i++)
155 if (dkimbuff[i] < 256)
156 DKIMVerifyProcess(dkim_context,(char *)&dkimbuff[i],1);
157
158 /* Fetch global result. Can be one of:
159 DKIM_SUCCESS
160 DKIM_PARTIAL_SUCCESS
161 DKIM_NEUTRAL
162 DKIM_FAIL
163 */
164 dkim_verify_result = DKIMVerifyResults(dkim_context);
165
166 store_pool = old_pool;
167}
168
169
170/* Lookup result for a given domain (or identity) */
171int dkim_exim_verify_result(uschar *domain, uschar **result, uschar **error) {
172 int sig_count = 0;
173 int i,rc;
174 char policy[512];
175 DKIMVerifyDetails *dkim_verify_details = NULL;
176
177 if (!dkim_do_verify ||
178 (!(smtp_input && !smtp_batched_input)) ||
179 (dkim_context == NULL) ||
180 (dkim_internal_status != DKIM_SUCCESS)) {
181 rc = DKIM_EXIM_UNVERIFIED;
182 goto YIELD;
183 }
184
185 DKIMVerifyGetDetails(dkim_context,
186 &sig_count,
187 &dkim_verify_details,
188 policy);
189
190
191 rc = DKIM_EXIM_UNSIGNED;
192
193 debug_printf("DKIM: We have %d signature(s)\n",sig_count);
194 for (i=0;i<sig_count;i++) {
195 debug_printf( "DKIM: [%d] ", i + 1 );
196 if (!dkim_verify_details[i].Domain) {
197 debug_printf("parse error (no domain)\n");
198 continue;
199 }
200
201 if (dkim_verify_details[i].nResult >= 0) {
202 debug_printf( "GOOD d=%s i=%s\n",
203 dkim_verify_details[i].Domain,
204 dkim_verify_details[i].IdentityDomain );
205 }
206 else {
207 debug_printf( "FAIL d=%s i=%s c=%d\n",
208 dkim_verify_details[i].Domain,
209 dkim_verify_details[i].IdentityDomain,
210 dkim_verify_details[i].nResult
211 );
212
213 }
214
215 if ( (strcmpic(domain,dkim_verify_details[i].Domain) == 0) ||
216 (strcmpic(domain,dkim_verify_details[i].IdentityDomain) == 0) ) {
217 if (dkim_verify_details[i].nResult >= 0) {
218 rc = DKIM_EXIM_GOOD;
219 /* TODO: Add From: domain check */
220 }
221 else {
222 /* Return DEFER for temp. error types */
223 if (dkim_verify_details[i].nResult == DKIM_SELECTOR_DNS_TEMP_FAILURE) {
224 rc = DKIM_EXIM_DEFER;
225 }
226 else {
227 rc = DKIM_EXIM_FAIL;
228 }
229 }
230 }
231 }
232
233 YIELD:
234 switch (rc) {
235 case DKIM_EXIM_FAIL:
236 *result = "bad";
237 break;
238 case DKIM_EXIM_DEFER:
239 *result = "defer";
240 break;
241 case DKIM_EXIM_UNVERIFIED:
242 *result = "unverified";
243 break;
244 case DKIM_EXIM_UNSIGNED:
245 *result = "unsigned";
246 break;
247 case DKIM_EXIM_GOOD:
248 *result = "good";
249 break;
250 }
251
252 return rc;
253}
254
255
256
257uschar *dkim_exim_sign_headers = NULL;
258int dkim_exim_header_callback(const char* header) {
259 int sep = 0;
260 uschar *hdr_ptr = dkim_exim_sign_headers;
261 uschar *hdr_itr = NULL;
262 uschar hdr_buf[512];
263 uschar *hdr_name = string_copy(US header);
264 char *colon_pos = strchr(hdr_name,':');
265
266 if (colon_pos == NULL) return 0;
267 *colon_pos = '\0';
268
269 debug_printf("DKIM: header '%s' ",hdr_name);
270 while ((hdr_itr = string_nextinlist(&hdr_ptr, &sep,
271 hdr_buf,
272 sizeof(hdr_buf))) != NULL) {
273 if (strcmpic((uschar *)hdr_name,hdr_itr) == 0) {
274 debug_printf("included in signature.\n");
275 return 1;
276 }
277 }
278 debug_printf("NOT included in signature.\n");
279 return 0;
280}
281
282uschar *dkim_exim_sign(int dkim_fd,
283 uschar *dkim_private_key,
284 uschar *dkim_domain,
285 uschar *dkim_selector,
286 uschar *dkim_canon,
287 uschar *dkim_sign_headers) {
288
289 uschar *rc = NULL;
290 char buf[4096];
291 int seen_lf = 0;
292 int seen_lfdot = 0;
293 int save_errno = 0;
294 int sread;
295 char *signature;
296 int old_pool = store_pool;
297 store_pool = POOL_PERM;
298
299 dkim_context = NULL;
300 dkim_sign_options = NULL;
301
302 dkim_context = store_get(sizeof(DKIMContext));
303 dkim_sign_options = store_get(sizeof(DKIMSignOptions));
304
ca15ec2f
TK
305 memset(dkim_sign_options,0,sizeof(DKIMSignOptions));
306 memset(dkim_context,0,sizeof(DKIMContext));
307
f7572e5a
TK
308 dkim_sign_options->nIncludeBodyLengthTag = 0;
309 dkim_sign_options->nIncludeCopiedHeaders = 0;
310 dkim_sign_options->nHash = DKIM_HASH_SHA256;
311 dkim_sign_options->nIncludeTimeStamp = 0;
312 dkim_sign_options->nIncludeQueryMethod = 0;
313 dkim_sign_options->pfnHeaderCallback = dkim_exim_header_callback;
314 dkim_sign_options->nIncludeBodyHash = DKIM_BODYHASH_IETF_1;
315
316
317 dkim_domain = expand_string(dkim_domain);
318 if (dkim_domain == NULL) {
319 /* expansion error, do not send message. */
320 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
321 "dkim_domain: %s", expand_string_message);
322 rc = NULL;
323 goto CLEANUP;
324 }
325 /* Set up $dkim_domain expansion variable. */
326 dkim_signing_domain = dkim_domain;
327 Ustrncpy((uschar *)dkim_sign_options->szDomain,dkim_domain,255);
328
329
330 /* Get selector to use. */
331 dkim_selector = expand_string(dkim_selector);
332 if (dkim_selector == NULL) {
333 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
334 "dkim_selector: %s", expand_string_message);
335 rc = NULL;
336 goto CLEANUP;
337 }
338 /* Set up $dkim_selector expansion variable. */
339 dkim_signing_selector = dkim_selector;
340 Ustrncpy((uschar *)dkim_sign_options->szSelector,dkim_selector,79);
341
342 /* Expand provided options */
343 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
344 if (dkim_canon == NULL) {
345 /* expansion error, do not send message. */
346 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
347 "dkim_canon: %s", expand_string_message);
348 rc = NULL;
349 goto CLEANUP;
350 }
351 if (Ustrcmp(dkim_canon, "relaxed") == 0)
352 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
353 else if (Ustrcmp(dkim_canon, "simple") == 0)
354 dkim_sign_options->nCanon = DKIM_SIGN_SIMPLE;
355 else {
356 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
357 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
358 }
359
360 /* Expand signing headers once */
361 if (dkim_sign_headers != NULL) {
362 dkim_sign_headers = expand_string(dkim_sign_headers);
363 if (dkim_sign_headers == NULL) {
364 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
365 "dkim_sign_headers: %s", expand_string_message);
366 rc = NULL;
367 goto CLEANUP;
368 }
369 }
370
371 if (dkim_sign_headers == NULL) {
372 /* Use RFC defaults */
373 dkim_sign_headers = US"from:sender:reply-to:subject:date:"
374 "message-id:to:cc:mime-version:content-type:"
375 "content-transfer-encoding:content-id:"
376 "content-description:resent-date:resent-from:"
377 "resent-sender:resent-to:resent-cc:resent-message-id:"
378 "in-reply-to:references:"
379 "list-id:list-help:list-unsubscribe:"
380 "list-subscribe:list-post:list-owner:list-archive";
381 }
382 dkim_exim_sign_headers = dkim_sign_headers;
383
384 /* Get private key to use. */
385 dkim_private_key = expand_string(dkim_private_key);
386 if (dkim_private_key == NULL) {
387 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
388 "dkim_private_key: %s", expand_string_message);
389 rc = NULL;
390 goto CLEANUP;
391 }
392
393 if ( (Ustrlen(dkim_private_key) == 0) ||
394 (Ustrcmp(dkim_private_key,"0") == 0) ||
395 (Ustrcmp(dkim_private_key,"false") == 0) ) {
396 /* don't sign, but no error */
397 rc = US"";
398 goto CLEANUP;
399 }
400
401 if (dkim_private_key[0] == '/') {
402 int privkey_fd = 0;
403 /* Looks like a filename, load the private key. */
404 memset(big_buffer,0,big_buffer_size);
405 privkey_fd = open(CS dkim_private_key,O_RDONLY);
406 (void)read(privkey_fd,big_buffer,16383);
407 (void)close(privkey_fd);
408 dkim_private_key = big_buffer;
409 }
410
411 /* Initialize signing context. */
412 dkim_status_wrap( DKIMSignInit(dkim_context, dkim_sign_options),
413 "error calling DKIMSignInit()" );
414
415 if (dkim_internal_status != DKIM_SUCCESS) {
416 /* Invalidate context */
417 dkim_context = NULL;
418 goto CLEANUP;
419 }
420
421 while((sread = read(dkim_fd,&buf,4096)) > 0) {
422 int pos = 0;
423 char c;
424
425 while (pos < sread) {
426 c = buf[pos++];
427
428 if ((c == '.') && seen_lfdot) {
429 /* escaped dot, write "\n.", continue */
430 dkim_internal_status = DKIMSignProcess(dkim_context,"\n.",2);
431 seen_lf = 0;
432 seen_lfdot = 0;
433 continue;
434 }
435
436 if (seen_lfdot) {
437 /* EOM, write "\n" and break */
438 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
439 break;
440 }
441
442 if ((c == '.') && seen_lf) {
443 seen_lfdot = 1;
444 continue;
445 }
446
447 if (seen_lf) {
448 /* normal lf, just send it */
449 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
450 seen_lf = 0;
451 }
452
453 if (c == '\n') {
454 seen_lf = 1;
455 continue;
456 }
457
458 /* write the char */
459 dkim_internal_status = DKIMSignProcess(dkim_context,&c,1);
460 }
461 }
462
463 /* Handle failed read above. */
464 if (sread == -1) {
465 debug_printf("DKIM: Error reading -K file.\n");
466 save_errno = errno;
467 rc = NULL;
468 goto CLEANUP;
469 }
470
471 if (!dkim_status_wrap(dkim_internal_status,
472 "error while processing message data")) {
473 rc = NULL;
474 goto CLEANUP;
475 }
476
477 if (!dkim_status_wrap( DKIMSignGetSig2( dkim_context, dkim_private_key, &signature ),
478 "error while signing message" ) ) {
479 rc = NULL;
480 goto CLEANUP;
481 }
482
483 log_write(0, LOG_MAIN, "Message signed with DKIM: %s\n",signature);
484
485 rc = store_get(strlen(signature)+3);
486 Ustrcpy(rc,US signature);
487 Ustrcat(rc,US"\r\n");
488
489 CLEANUP:
490 if (dkim_context != NULL) {
491 dkim_context = NULL;
492 }
493 store_pool = old_pool;
494 errno = save_errno;
495 return rc;
496}
497
498unsigned int dkim_status_wrap(int stat, uschar *text) {
499 char *p = DKIMGetErrorString(stat);
500
501 if (stat != DKIM_SUCCESS) {
502 debug_printf("DKIM: %s",text?text:US"");
503 if (p) debug_printf(" (%s)",p);
504 debug_printf("\n");
505 }
506 dkim_internal_status = stat;
507 return (dkim_internal_status==DKIM_SUCCESS)?1:0;
508}
509
510#endif