Update version number to 4.53.
[exim.git] / src / src / dk.c
CommitLineData
69ca8fd0 1/* $Cambridge: exim/src/src/dk.c,v 1.6 2005/06/27 15:11:59 tom Exp $ */
fb2274d4
TK
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. */
18DK *dk_context = NULL;
19DK_LIB *dk_lib = NULL;
20DK_FLAGS dk_flags;
21DK_STAT dk_internal_status;
22
23/* Globals related to Exim DK implementation. */
24dk_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. */
30int dkbuff[6] = {256,256,256,256,256,256};
31
32/* receive_getc() wrapper that feeds DK while Exim reads
33 the message. */
34int dk_receive_getc(void) {
35 int i;
36 int c = receive_getc();
84330b7b 37
fb2274d4
TK
38 if (dk_context != NULL) {
39 /* Send oldest byte */
40 if ((dkbuff[0] < 256) && (dk_internal_status == DK_STAT_OK)) {
a8d97c8a 41 dk_internal_status = dk_message(dk_context, CUS &dkbuff[0], 1);
fb2274d4
TK
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;
84330b7b 58 }
fb2274d4
TK
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 }
67return c;
68}
69
70/* When exim puts a char back in the fd, we
71 must rotate our buffer back. */
72int 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
84330b7b 83void dk_exim_verify_init(void) {
fb2274d4
TK
84 int old_pool = store_pool;
85 store_pool = POOL_PERM;
84330b7b 86
fb2274d4
TK
87 /* Reset DK state in any case. */
88 dk_context = NULL;
89 dk_lib = NULL;
90 dk_verify_block = NULL;
84330b7b 91
fb2274d4
TK
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
122void 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;
84330b7b 130
fb2274d4 131 store_pool = POOL_PERM;
84330b7b 132
fb2274d4
TK
133 /* Send remaining bytes from input which are still in the buffer. */
134 for (i=0;i<6;i++)
135 if (dkbuff[i] < 256)
a8d97c8a 136 dk_internal_status = dk_message(dk_context, CUS &dkbuff[i], 1);
fb2274d4
TK
137
138 /* Flag end-of-message. */
00916a93 139 dk_internal_status = dk_end(dk_context, &dk_flags);
84330b7b 140
69ca8fd0
TK
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
fb2274d4
TK
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);
00916a93 170 *q = '@';
fb2274d4
TK
171 }
172 }
173 }
174
69ca8fd0 175 /* Now grab the domain-wide DK policy */
fb2274d4
TK
176 dk_flags = dk_policy(dk_context);
177
fb2274d4 178 if (dk_flags & DK_FLAG_SET) {
69ca8fd0
TK
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)
fb2274d4 182 dk_verify_block->testing = TRUE;
84330b7b 183 if (dk_flags & DK_FLAG_SIGNSALL)
fb2274d4
TK
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 }
84330b7b 225
fb2274d4
TK
226 /* Set up human readable result string. */
227 dk_verify_block->result_string = string_copy((uschar *)DK_STAT_to_string(dk_internal_status));
84330b7b 228
fb2274d4
TK
229 /* All done, reset dk_context. */
230 dk_free(dk_context);
231 dk_context = NULL;
84330b7b 232
fb2274d4
TK
233 store_pool = old_pool;
234}
235
236uschar *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 int dk_canon_int = DK_CANON_SIMPLE;
243 char c;
244 int seen_lf = 0;
245 int seen_lfdot = 0;
246 uschar sig[1024];
247 int save_errno = 0;
248 int sread;
249 int old_pool = store_pool;
250 store_pool = POOL_PERM;
84330b7b 251
fb2274d4
TK
252 dk_lib = dk_init(&dk_internal_status);
253 if (dk_internal_status != DK_STAT_OK) {
254 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
255 rc = NULL;
256 goto CLEANUP;
257 }
258
259 /* Figure out what canonicalization to use. Unfortunately
260 we must do this BEFORE knowing which domain we sign for. */
261 if ((dk_canon != NULL) && (Ustrcmp(dk_canon, "nofws") == 0)) dk_canon_int = DK_CANON_NOFWS;
a8d97c8a 262 else dk_canon = US "simple";
84330b7b 263
fb2274d4
TK
264 /* Initialize signing context. */
265 dk_context = dk_sign(dk_lib, &dk_internal_status, dk_canon_int);
266 if (dk_internal_status != DK_STAT_OK) {
84330b7b 267 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
fb2274d4
TK
268 dk_context = NULL;
269 goto CLEANUP;
270 }
84330b7b 271
fb2274d4 272 while((sread = read(dk_fd,&c,1)) > 0) {
84330b7b 273
fb2274d4
TK
274 if ((c == '.') && seen_lfdot) {
275 /* escaped dot, write "\n.", continue */
a8d97c8a 276 dk_message(dk_context, CUS "\n.", 2);
fb2274d4
TK
277 seen_lf = 0;
278 seen_lfdot = 0;
279 continue;
280 }
84330b7b 281
fb2274d4
TK
282 if (seen_lfdot) {
283 /* EOM, write "\n" and break */
a8d97c8a 284 dk_message(dk_context, CUS "\n", 1);
fb2274d4
TK
285 break;
286 }
287
288 if ((c == '.') && seen_lf) {
289 seen_lfdot = 1;
290 continue;
291 }
84330b7b 292
fb2274d4
TK
293 if (seen_lf) {
294 /* normal lf, just send it */
a8d97c8a 295 dk_message(dk_context, CUS "\n", 1);
fb2274d4
TK
296 seen_lf = 0;
297 }
84330b7b 298
fb2274d4
TK
299 if (c == '\n') {
300 seen_lf = 1;
301 continue;
302 }
84330b7b 303
fb2274d4 304 /* write the char */
a8d97c8a 305 dk_message(dk_context, CUS &c, 1);
fb2274d4 306 }
84330b7b 307
fb2274d4
TK
308 /* Handle failed read above. */
309 if (sread == -1) {
310 debug_printf("DK: Error reading -K file.\n");
311 save_errno = errno;
312 rc = NULL;
313 goto CLEANUP;
314 }
84330b7b 315
fb2274d4
TK
316 /* Flag end-of-message. */
317 dk_internal_status = dk_end(dk_context, NULL);
318 /* TODO: check status */
84330b7b
PH
319
320
fb2274d4
TK
321 /* Get domain to use, unless overridden. */
322 if (dk_domain == NULL) {
a8d97c8a 323 dk_domain = US dk_address(dk_context);
fb2274d4
TK
324 switch(dk_domain[0]) {
325 case 'N': dk_domain = NULL; break;
326 case 'F':
327 case 'S':
328 dk_domain++;
a8d97c8a 329 dk_domain = Ustrrchr(dk_domain,'@');
fb2274d4
TK
330 if (dk_domain != NULL) {
331 uschar *p;
332 dk_domain++;
333 p = dk_domain;
84330b7b 334 while (*p != 0) { *p = tolower(*p); p++; }
fb2274d4
TK
335 }
336 break;
337 }
338 if (dk_domain == NULL) {
84330b7b 339 debug_printf("DK: Could not determine domain to use for signing from message headers.\n");
fb2274d4
TK
340 /* In this case, we return "OK" by sending up an empty string as the
341 DomainKey-Signature header. If there is no domain to sign for, we
342 can send the message anyway since the recipient has no policy to
343 apply ... */
a8d97c8a 344 rc = US"";
fb2274d4
TK
345 goto CLEANUP;
346 }
347 }
348 else {
349 dk_domain = expand_string(dk_domain);
350 if (dk_domain == NULL) {
351 /* expansion error, do not send message. */
352 debug_printf("DK: Error while expanding dk_domain option.\n");
353 rc = NULL;
354 goto CLEANUP;
84330b7b 355 }
fb2274d4 356 }
84330b7b
PH
357
358 /* Set up $dk_domain expansion variable. */
fb2274d4
TK
359 dk_signing_domain = dk_domain;
360
361 /* Get selector to use. */
362 dk_selector = expand_string(dk_selector);
363 if (dk_selector == NULL) {
364 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
365 "dk_selector: %s", expand_string_message);
366 rc = NULL;
367 goto CLEANUP;
368 }
84330b7b 369
fb2274d4
TK
370 /* Set up $dk_selector expansion variable. */
371 dk_signing_selector = dk_selector;
84330b7b 372
fb2274d4
TK
373 /* Get private key to use. */
374 dk_private_key = expand_string(dk_private_key);
375 if (dk_private_key == NULL) {
376 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
377 "dk_private_key: %s", expand_string_message);
378 rc = NULL;
379 goto CLEANUP;
380 }
84330b7b 381
fb2274d4
TK
382 if ( (Ustrlen(dk_private_key) == 0) ||
383 (Ustrcmp(dk_private_key,"0") == 0) ||
384 (Ustrcmp(dk_private_key,"false") == 0) ) {
385 /* don't sign, but no error */
a8d97c8a 386 rc = US"";
fb2274d4
TK
387 goto CLEANUP;
388 }
84330b7b 389
fb2274d4
TK
390 if (dk_private_key[0] == '/') {
391 int privkey_fd = 0;
392 /* Looks like a filename, load the private key. */
393 memset(big_buffer,0,big_buffer_size);
a8d97c8a 394 privkey_fd = open(CS dk_private_key,O_RDONLY);
f1e894f3
PH
395 (void)read(privkey_fd,big_buffer,16383);
396 (void)close(privkey_fd);
fb2274d4
TK
397 dk_private_key = big_buffer;
398 }
84330b7b 399
fb2274d4
TK
400 /* Get the signature. */
401 dk_internal_status = dk_getsig(dk_context, dk_private_key, sig, 8192);
402
403 /* Check for unuseable key */
404 if (dk_internal_status != DK_STAT_OK) {
84330b7b 405 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
fb2274d4
TK
406 rc = NULL;
407 goto CLEANUP;
408 }
84330b7b 409
fb2274d4
TK
410 rc = store_get(1024);
411 /* Build DomainKey-Signature header to return. */
a8d97c8a 412 snprintf(CS rc, 1024, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s;\r\n"
84330b7b 413 "\ts=%s; d=%s;\r\n"
fb2274d4 414 "\tb=%s;\r\n", dk_canon, dk_selector, dk_domain, sig);
84330b7b 415
fb2274d4
TK
416 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);
417
418 CLEANUP:
419 if (dk_context != NULL) {
420 dk_free(dk_context);
421 dk_context = NULL;
422 }
423 store_pool = old_pool;
424 errno = save_errno;
425 return rc;
426}
427
428#endif