Apply John Jetmore's patch to allow tls-on-connect and STARTTLS to be
[exim.git] / src / src / dk.c
1 /* $Cambridge: exim/src/src/dk.c,v 1.11 2006/10/30 22:06:33 tom Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2006 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10 /* Code for DomainKeys support. Other DK relevant code is in
11    receive.c, transport.c and transports/smtp.c */
12
13 #include "exim.h"
14
15 #ifdef EXPERIMENTAL_DOMAINKEYS
16
17 /* Globals related to the DK reference library. */
18 DK                   *dk_context             = NULL;
19 DK_LIB               *dk_lib                 = NULL;
20 DK_FLAGS              dk_flags;
21 DK_STAT               dk_internal_status;
22
23 /* Globals related to Exim DK implementation. */
24 dk_exim_verify_block *dk_verify_block        = NULL;
25
26 /* Global char buffer for getc/ungetc functions. We need
27    to accumulate some chars to be able to match EOD and
28    doubled SMTP dots. Those must not be fed to the validation
29    engine. */
30 int dkbuff[6] = {256,256,256,256,256,256};
31
32 /* receive_getc() wrapper that feeds DK while Exim reads
33    the message. */
34 int dk_receive_getc(void) {
35   int i;
36   int c = receive_getc();
37
38   if (dk_context != NULL) {
39     /* Send oldest byte */
40     if ((dkbuff[0] < 256) && (dk_internal_status == DK_STAT_OK)) {
41       dk_internal_status = dk_message(dk_context, CUS &dkbuff[0], 1);
42       if (dk_internal_status != DK_STAT_OK)
43         DEBUG(D_receive) debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
44     }
45     /* rotate buffer */
46     for (i=0;i<5;i++) dkbuff[i]=dkbuff[i+1];
47     dkbuff[5]=c;
48     /* look for our candidate patterns */
49     if ( (dkbuff[1] == '\r') &&
50          (dkbuff[2] == '\n') &&
51          (dkbuff[3] == '.') &&
52          (dkbuff[4] == '\r') &&
53          (dkbuff[5] == '\n') ) {
54       /* End of DATA */
55       dkbuff[3] = 256;
56       dkbuff[4] = 256;
57       dkbuff[5] = 256;
58     }
59     if ( (dkbuff[2] == '\r') &&
60          (dkbuff[3] == '\n') &&
61          (dkbuff[4] == '.') &&
62          (dkbuff[5] == '.') ) {
63       /* doubled dot, skip this char */
64       dkbuff[5] = 256;
65     }
66   }
67 return c;
68 }
69
70 /* When exim puts a char back in the fd, we
71    must rotate our buffer back. */
72 int dk_receive_ungetc(int c) {
73   int i;
74   if (dk_context != NULL) {
75     /* rotate buffer back */
76     for (i=5;i>0;i--) dkbuff[i]=dkbuff[i-1];
77     dkbuff[0]=256;
78   }
79   return receive_ungetc(c);
80 }
81
82
83 void dk_exim_verify_init(void) {
84   int old_pool = store_pool;
85   store_pool = POOL_PERM;
86
87   /* Reset DK state in any case. */
88   dk_context = NULL;
89   dk_lib = NULL;
90   dk_verify_block = NULL;
91
92   /* Set up DK context if DK was requested and input is SMTP. */
93   if (smtp_input && !smtp_batched_input && dk_do_verify) {
94     /* initialize library */
95     dk_lib = dk_init(&dk_internal_status);
96     if (dk_internal_status != DK_STAT_OK)
97       debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
98     else {
99       /* initialize verification context */
100       dk_context = dk_verify(dk_lib, &dk_internal_status);
101       if (dk_internal_status != DK_STAT_OK) {
102         debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
103         dk_context = NULL;
104       }
105       else {
106         /* Reserve some space for the verify block. */
107         dk_verify_block = store_get(sizeof(dk_exim_verify_block));
108         if (dk_verify_block == NULL) {
109           debug_printf("DK: Can't allocate %d bytes.\n",sizeof(dk_exim_verify_block));
110           dk_context = NULL;
111         }
112         else {
113           memset(dk_verify_block, 0, sizeof(dk_exim_verify_block));
114         }
115       }
116     }
117   }
118   store_pool = old_pool;
119 }
120
121
122 void dk_exim_verify_finish(void) {
123   char *p,*q;
124   int i;
125   int old_pool = store_pool;
126
127   /* Bail out if context could not be set up earlier. */
128   if (dk_context == NULL)
129     return;
130
131   store_pool = POOL_PERM;
132
133   /* Send remaining bytes from input which are still in the buffer. */
134   for (i=0;i<6;i++)
135     if (dkbuff[i] < 256)
136       dk_internal_status = dk_message(dk_context, CUS &dkbuff[i], 1);
137
138   /* Flag end-of-message. */
139   dk_internal_status = dk_end(dk_context, &dk_flags);
140
141   /* dk_flags now has the selector flags (if there was one).
142      It seems that currently only the "t=" flag is supported
143      in selectors. */
144   if (dk_flags & DK_FLAG_SET)
145     if (dk_flags & DK_FLAG_TESTING)
146       dk_verify_block->testing = TRUE;
147
148   /* Grab address/domain information. */
149   p = dk_address(dk_context);
150   if (p != NULL) {
151     switch(p[0]) {
152       case 'N':
153         dk_verify_block->address_source = DK_EXIM_ADDRESS_NONE;
154       break;
155       case 'S':
156         dk_verify_block->address_source = DK_EXIM_ADDRESS_FROM_SENDER;
157       break;
158       case 'F':
159         dk_verify_block->address_source = DK_EXIM_ADDRESS_FROM_FROM;
160       break;
161     }
162     p++;
163     if (*p != '\0') {
164       dk_verify_block->address = string_copy((uschar *)p);
165       q = strrchr(p,'@');
166       if ((q != NULL) && (*(q+1) != '\0')) {
167         dk_verify_block->domain = string_copy((uschar *)(q+1));
168         *q = '\0';
169         dk_verify_block->local_part = string_copy((uschar *)p);
170         *q = '@';
171       }
172     }
173   }
174
175   /* Now grab the domain-wide DK policy */
176   dk_flags = dk_policy(dk_context);
177
178   if (dk_flags & DK_FLAG_SET) {
179     /* Selector "t=" flag has precedence, don't overwrite it if
180        the selector has set it above. */
181     if ((dk_flags & DK_FLAG_TESTING) && !dk_verify_block->testing)
182       dk_verify_block->testing = TRUE;
183     if (dk_flags & DK_FLAG_SIGNSALL)
184       dk_verify_block->signsall = TRUE;
185   }
186
187   /* Set up main result. */
188   switch(dk_internal_status)
189     {
190     case DK_STAT_NOSIG:
191       dk_verify_block->is_signed = FALSE;
192       dk_verify_block->result = DK_EXIM_RESULT_NO_SIGNATURE;
193     break;
194     case DK_STAT_OK:
195       dk_verify_block->is_signed = TRUE;
196       dk_verify_block->result = DK_EXIM_RESULT_GOOD;
197     break;
198     case DK_STAT_BADSIG:
199       dk_verify_block->is_signed = TRUE;
200       dk_verify_block->result = DK_EXIM_RESULT_BAD;
201     break;
202     case DK_STAT_REVOKED:
203       dk_verify_block->is_signed = TRUE;
204       dk_verify_block->result = DK_EXIM_RESULT_REVOKED;
205     break;
206     case DK_STAT_BADKEY:
207     case DK_STAT_SYNTAX:
208       dk_verify_block->is_signed = TRUE;
209       /* Syntax -> Bad format? */
210       dk_verify_block->result = DK_EXIM_RESULT_BAD_FORMAT;
211     break;
212     case DK_STAT_NOKEY:
213       dk_verify_block->is_signed = TRUE;
214       dk_verify_block->result = DK_EXIM_RESULT_NO_KEY;
215     break;
216     case DK_STAT_NORESOURCE:
217     case DK_STAT_INTERNAL:
218     case DK_STAT_ARGS:
219     case DK_STAT_CANTVRFY:
220       dk_verify_block->result = DK_EXIM_RESULT_ERR;
221     break;
222     /* This is missing DK_EXIM_RESULT_NON_PARTICIPANT. The lib does not
223        report such a status. */
224     }
225
226   /* Set up human readable result string. */
227   dk_verify_block->result_string = string_copy((uschar *)DK_STAT_to_string(dk_internal_status));
228
229   /* All done, reset dk_context. */
230   dk_free(dk_context,1);
231   dk_context = NULL;
232
233   store_pool = old_pool;
234 }
235
236 uschar *dk_exim_sign(int dk_fd,
237                      uschar *dk_private_key,
238                      uschar *dk_domain,
239                      uschar *dk_selector,
240                      uschar *dk_canon) {
241   uschar *rc = NULL;
242   uschar *headers = NULL;
243   int headers_len;
244   int dk_canon_int = DK_CANON_SIMPLE;
245   char buf[4096];
246   int seen_lf = 0;
247   int seen_lfdot = 0;
248   uschar sig[1024];
249   int save_errno = 0;
250   int sread;
251   int old_pool = store_pool;
252   store_pool = POOL_PERM;
253
254   dk_lib = dk_init(&dk_internal_status);
255   if (dk_internal_status != DK_STAT_OK) {
256     debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
257     rc = NULL;
258     goto CLEANUP;
259   }
260
261   /* Figure out what canonicalization to use. Unfortunately
262      we must do this BEFORE knowing which domain we sign for. */
263   if ((dk_canon != NULL) && (Ustrcmp(dk_canon, "nofws") == 0)) dk_canon_int = DK_CANON_NOFWS;
264   else dk_canon = US "simple";
265
266   /* Initialize signing context. */
267   dk_context = dk_sign(dk_lib, &dk_internal_status, dk_canon_int);
268   if (dk_internal_status != DK_STAT_OK) {
269     debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
270     dk_context = NULL;
271     goto CLEANUP;
272   }
273
274   while((sread = read(dk_fd,&buf,4096)) > 0) {
275     int pos = 0;
276     char c;
277
278     while (pos < sread) {
279       c = buf[pos++];
280
281       if ((c == '.') && seen_lfdot) {
282         /* escaped dot, write "\n.", continue */
283         dk_message(dk_context, CUS "\n.", 2);
284         seen_lf = 0;
285         seen_lfdot = 0;
286         continue;
287       }
288
289       if (seen_lfdot) {
290         /* EOM, write "\n" and break */
291         dk_message(dk_context, CUS "\n", 1);
292         break;
293       }
294
295       if ((c == '.') && seen_lf) {
296         seen_lfdot = 1;
297         continue;
298       }
299
300       if (seen_lf) {
301         /* normal lf, just send it */
302         dk_message(dk_context, CUS "\n", 1);
303         seen_lf = 0;
304       }
305
306       if (c == '\n') {
307         seen_lf = 1;
308         continue;
309       }
310
311       /* write the char */
312       dk_message(dk_context, CUS &c, 1);
313     }
314   }
315
316   /* Handle failed read above. */
317   if (sread == -1) {
318     debug_printf("DK: Error reading -K file.\n");
319     save_errno = errno;
320     rc = NULL;
321     goto CLEANUP;
322   }
323
324   /* Flag end-of-message. */
325   dk_internal_status = dk_end(dk_context, NULL);
326   /* TODO: check status */
327
328
329   /* Get domain to use, unless overridden. */
330   if (dk_domain == NULL) {
331     dk_domain = US dk_address(dk_context);
332     switch(dk_domain[0]) {
333       case 'N': dk_domain = NULL; break;
334       case 'F':
335       case 'S':
336         dk_domain++;
337         dk_domain = Ustrrchr(dk_domain,'@');
338         if (dk_domain != NULL) {
339           uschar *p;
340           dk_domain++;
341           p = dk_domain;
342           while (*p != 0) { *p = tolower(*p); p++; }
343         }
344       break;
345     }
346     if (dk_domain == NULL) {
347       debug_printf("DK: Could not determine domain to use for signing from message headers.\n");
348       /* In this case, we return "OK" by sending up an empty string as the
349          DomainKey-Signature header. If there is no domain to sign for, we
350          can send the message anyway since the recipient has no policy to
351          apply ... */
352       rc = US"";
353       goto CLEANUP;
354     }
355   }
356   else {
357     dk_domain = expand_string(dk_domain);
358     if (dk_domain == NULL) {
359       /* expansion error, do not send message. */
360       debug_printf("DK: Error while expanding dk_domain option.\n");
361       rc = NULL;
362       goto CLEANUP;
363     }
364   }
365
366   /* Set up $dk_domain expansion variable. */
367   dk_signing_domain = dk_domain;
368
369   /* Get selector to use. */
370   dk_selector = expand_string(dk_selector);
371   if (dk_selector == NULL) {
372     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
373       "dk_selector: %s", expand_string_message);
374     rc = NULL;
375     goto CLEANUP;
376   }
377
378   /* Set up $dk_selector expansion variable. */
379   dk_signing_selector = dk_selector;
380
381   /* Get private key to use. */
382   dk_private_key = expand_string(dk_private_key);
383   if (dk_private_key == NULL) {
384     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
385       "dk_private_key: %s", expand_string_message);
386     rc = NULL;
387     goto CLEANUP;
388   }
389
390   if ( (Ustrlen(dk_private_key) == 0) ||
391        (Ustrcmp(dk_private_key,"0") == 0) ||
392        (Ustrcmp(dk_private_key,"false") == 0) ) {
393     /* don't sign, but no error */
394     rc = US"";
395     goto CLEANUP;
396   }
397
398   if (dk_private_key[0] == '/') {
399     int privkey_fd = 0;
400     /* Looks like a filename, load the private key. */
401     memset(big_buffer,0,big_buffer_size);
402     privkey_fd = open(CS dk_private_key,O_RDONLY);
403     (void)read(privkey_fd,big_buffer,16383);
404     (void)close(privkey_fd);
405     dk_private_key = big_buffer;
406   }
407
408   /* Get the signature. */
409   dk_internal_status = dk_getsig(dk_context, dk_private_key, sig, 1024);
410
411   /* Check for unuseable key */
412   if (dk_internal_status != DK_STAT_OK) {
413     debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
414     rc = NULL;
415     goto CLEANUP;
416   }
417
418   headers_len = dk_headers(dk_context, NULL);
419   rc = store_get(1024+256+headers_len);
420   headers = store_malloc(headers_len);
421   dk_headers(dk_context, CS headers);
422   /* Build DomainKey-Signature header to return. */
423   (void)string_format(rc, 1024+256+headers_len, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s; s=%s; d=%s;\r\n"
424                      "\th=%s;\r\n"
425                      "\tb=%s;\r\n", dk_canon, dk_selector, dk_domain, headers, sig);
426
427   log_write(0, LOG_MAIN, "DK: message signed using a=rsa-sha1; q=dns; c=%s; s=%s; d=%s; h=%s;", dk_canon, dk_selector, dk_domain, headers);
428   store_free(headers);
429
430   CLEANUP:
431   if (dk_context != NULL) {
432     dk_free(dk_context,1);
433     dk_context = NULL;
434   }
435   store_pool = old_pool;
436   errno = save_errno;
437   return rc;
438 }
439
440 #endif