30dfc78be81291556bf5f3a042661c371e69fb9f
[exim.git] / src / src / dk.c
1 /* $Cambridge: exim/src/src/dk.c,v 1.4 2005/06/24 08:23:21 tom Exp $ */
2
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 1995 - 2005 */
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 /* Grab address/domain information. */
142 p = dk_address(dk_context);
143 if (p != NULL) {
144 switch(p[0]) {
145 case 'N':
146 dk_verify_block->address_source = DK_EXIM_ADDRESS_NONE;
147 break;
148 case 'S':
149 dk_verify_block->address_source = DK_EXIM_ADDRESS_FROM_SENDER;
150 break;
151 case 'F':
152 dk_verify_block->address_source = DK_EXIM_ADDRESS_FROM_FROM;
153 break;
154 }
155 p++;
156 if (*p != '\0') {
157 dk_verify_block->address = string_copy((uschar *)p);
158 q = strrchr(p,'@');
159 if ((q != NULL) && (*(q+1) != '\0')) {
160 dk_verify_block->domain = string_copy((uschar *)(q+1));
161 *q = '\0';
162 dk_verify_block->local_part = string_copy((uschar *)p);
163 *q = '@';
164 }
165 }
166 }
167
168 /* TODO: This call should be removed with lib version >= 0.67 */
169 dk_flags = dk_policy(dk_context);
170
171 /* Grab domain policy */
172 if (dk_flags & DK_FLAG_SET) {
173 if (dk_flags & DK_FLAG_TESTING)
174 dk_verify_block->testing = TRUE;
175 if (dk_flags & DK_FLAG_SIGNSALL)
176 dk_verify_block->signsall = TRUE;
177 }
178
179 /* Set up main result. */
180 switch(dk_internal_status)
181 {
182 case DK_STAT_NOSIG:
183 dk_verify_block->is_signed = FALSE;
184 dk_verify_block->result = DK_EXIM_RESULT_NO_SIGNATURE;
185 break;
186 case DK_STAT_OK:
187 dk_verify_block->is_signed = TRUE;
188 dk_verify_block->result = DK_EXIM_RESULT_GOOD;
189 break;
190 case DK_STAT_BADSIG:
191 dk_verify_block->is_signed = TRUE;
192 dk_verify_block->result = DK_EXIM_RESULT_BAD;
193 break;
194 case DK_STAT_REVOKED:
195 dk_verify_block->is_signed = TRUE;
196 dk_verify_block->result = DK_EXIM_RESULT_REVOKED;
197 break;
198 case DK_STAT_BADKEY:
199 case DK_STAT_SYNTAX:
200 dk_verify_block->is_signed = TRUE;
201 /* Syntax -> Bad format? */
202 dk_verify_block->result = DK_EXIM_RESULT_BAD_FORMAT;
203 break;
204 case DK_STAT_NOKEY:
205 dk_verify_block->is_signed = TRUE;
206 dk_verify_block->result = DK_EXIM_RESULT_NO_KEY;
207 break;
208 case DK_STAT_NORESOURCE:
209 case DK_STAT_INTERNAL:
210 case DK_STAT_ARGS:
211 case DK_STAT_CANTVRFY:
212 dk_verify_block->result = DK_EXIM_RESULT_ERR;
213 break;
214 /* This is missing DK_EXIM_RESULT_NON_PARTICIPANT. The lib does not
215 report such a status. */
216 }
217
218 /* Set up human readable result string. */
219 dk_verify_block->result_string = string_copy((uschar *)DK_STAT_to_string(dk_internal_status));
220
221 /* All done, reset dk_context. */
222 dk_free(dk_context);
223 dk_context = NULL;
224
225 store_pool = old_pool;
226 }
227
228 uschar *dk_exim_sign(int dk_fd,
229 uschar *dk_private_key,
230 uschar *dk_domain,
231 uschar *dk_selector,
232 uschar *dk_canon) {
233 uschar *rc = NULL;
234 int dk_canon_int = DK_CANON_SIMPLE;
235 char c;
236 int seen_lf = 0;
237 int seen_lfdot = 0;
238 uschar sig[1024];
239 int save_errno = 0;
240 int sread;
241 int old_pool = store_pool;
242 store_pool = POOL_PERM;
243
244 dk_lib = dk_init(&dk_internal_status);
245 if (dk_internal_status != DK_STAT_OK) {
246 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
247 rc = NULL;
248 goto CLEANUP;
249 }
250
251 /* Figure out what canonicalization to use. Unfortunately
252 we must do this BEFORE knowing which domain we sign for. */
253 if ((dk_canon != NULL) && (Ustrcmp(dk_canon, "nofws") == 0)) dk_canon_int = DK_CANON_NOFWS;
254 else dk_canon = US "simple";
255
256 /* Initialize signing context. */
257 dk_context = dk_sign(dk_lib, &dk_internal_status, dk_canon_int);
258 if (dk_internal_status != DK_STAT_OK) {
259 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
260 dk_context = NULL;
261 goto CLEANUP;
262 }
263
264 while((sread = read(dk_fd,&c,1)) > 0) {
265
266 if ((c == '.') && seen_lfdot) {
267 /* escaped dot, write "\n.", continue */
268 dk_message(dk_context, CUS "\n.", 2);
269 seen_lf = 0;
270 seen_lfdot = 0;
271 continue;
272 }
273
274 if (seen_lfdot) {
275 /* EOM, write "\n" and break */
276 dk_message(dk_context, CUS "\n", 1);
277 break;
278 }
279
280 if ((c == '.') && seen_lf) {
281 seen_lfdot = 1;
282 continue;
283 }
284
285 if (seen_lf) {
286 /* normal lf, just send it */
287 dk_message(dk_context, CUS "\n", 1);
288 seen_lf = 0;
289 }
290
291 if (c == '\n') {
292 seen_lf = 1;
293 continue;
294 }
295
296 /* write the char */
297 dk_message(dk_context, CUS &c, 1);
298 }
299
300 /* Handle failed read above. */
301 if (sread == -1) {
302 debug_printf("DK: Error reading -K file.\n");
303 save_errno = errno;
304 rc = NULL;
305 goto CLEANUP;
306 }
307
308 /* Flag end-of-message. */
309 dk_internal_status = dk_end(dk_context, NULL);
310 /* TODO: check status */
311
312
313 /* Get domain to use, unless overridden. */
314 if (dk_domain == NULL) {
315 dk_domain = US dk_address(dk_context);
316 switch(dk_domain[0]) {
317 case 'N': dk_domain = NULL; break;
318 case 'F':
319 case 'S':
320 dk_domain++;
321 dk_domain = Ustrrchr(dk_domain,'@');
322 if (dk_domain != NULL) {
323 uschar *p;
324 dk_domain++;
325 p = dk_domain;
326 while (*p != 0) { *p = tolower(*p); p++; }
327 }
328 break;
329 }
330 if (dk_domain == NULL) {
331 debug_printf("DK: Could not determine domain to use for signing from message headers.\n");
332 /* In this case, we return "OK" by sending up an empty string as the
333 DomainKey-Signature header. If there is no domain to sign for, we
334 can send the message anyway since the recipient has no policy to
335 apply ... */
336 rc = US"";
337 goto CLEANUP;
338 }
339 }
340 else {
341 dk_domain = expand_string(dk_domain);
342 if (dk_domain == NULL) {
343 /* expansion error, do not send message. */
344 debug_printf("DK: Error while expanding dk_domain option.\n");
345 rc = NULL;
346 goto CLEANUP;
347 }
348 }
349
350 /* Set up $dk_domain expansion variable. */
351 dk_signing_domain = dk_domain;
352
353 /* Get selector to use. */
354 dk_selector = expand_string(dk_selector);
355 if (dk_selector == NULL) {
356 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
357 "dk_selector: %s", expand_string_message);
358 rc = NULL;
359 goto CLEANUP;
360 }
361
362 /* Set up $dk_selector expansion variable. */
363 dk_signing_selector = dk_selector;
364
365 /* Get private key to use. */
366 dk_private_key = expand_string(dk_private_key);
367 if (dk_private_key == NULL) {
368 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
369 "dk_private_key: %s", expand_string_message);
370 rc = NULL;
371 goto CLEANUP;
372 }
373
374 if ( (Ustrlen(dk_private_key) == 0) ||
375 (Ustrcmp(dk_private_key,"0") == 0) ||
376 (Ustrcmp(dk_private_key,"false") == 0) ) {
377 /* don't sign, but no error */
378 rc = US"";
379 goto CLEANUP;
380 }
381
382 if (dk_private_key[0] == '/') {
383 int privkey_fd = 0;
384 /* Looks like a filename, load the private key. */
385 memset(big_buffer,0,big_buffer_size);
386 privkey_fd = open(CS dk_private_key,O_RDONLY);
387 read(privkey_fd,big_buffer,16383);
388 close(privkey_fd);
389 dk_private_key = big_buffer;
390 }
391
392 /* Get the signature. */
393 dk_internal_status = dk_getsig(dk_context, dk_private_key, sig, 8192);
394
395 /* Check for unuseable key */
396 if (dk_internal_status != DK_STAT_OK) {
397 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
398 rc = NULL;
399 goto CLEANUP;
400 }
401
402 rc = store_get(1024);
403 /* Build DomainKey-Signature header to return. */
404 snprintf(CS rc, 1024, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s;\r\n"
405 "\ts=%s; d=%s;\r\n"
406 "\tb=%s;\r\n", dk_canon, dk_selector, dk_domain, sig);
407
408 log_write(0, LOG_MAIN, "DK: message signed using a=rsa-sha1; q=dns; c=%s; s=%s; d=%s;", dk_canon, dk_selector, dk_domain);
409
410 CLEANUP:
411 if (dk_context != NULL) {
412 dk_free(dk_context);
413 dk_context = NULL;
414 }
415 store_pool = old_pool;
416 errno = save_errno;
417 return rc;
418 }
419
420 #endif