989c769f8620c47f27352494197ab6c65c714866
[users/heiko/exim.git] / src / src / dkim-exim.c
1 /* $Cambridge: exim/src/src/dkim-exim.c,v 1.2 2007/10/09 14:10:34 tom Exp $ */
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. */
18 DKIMContext          *dkim_context           = NULL;
19 DKIMSignOptions      *dkim_sign_options      = NULL;
20 DKIMVerifyOptions    *dkim_verify_options    = NULL;
21 int                   dkim_verify_result     = DKIM_NEUTRAL;
22 int                   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. */
28 int dkimbuff[6] = {256,256,256,256,256,256};
29
30 /* receive_getc() wrapper that feeds DKIM while Exim reads
31    the message. */
32 int 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. */
78 int 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
96 void 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
142 void 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) */
171 int 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
257 uschar *dkim_exim_sign_headers = NULL;
258 int 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
282 uschar *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
305   memset(dkim_sign_options,0,sizeof(DKIMSignOptions));
306   memset(dkim_context,0,sizeof(DKIMContext));
307
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
498 unsigned 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