Commit | Line | Data |
---|---|---|
a8530a10 | 1 | /* $Cambridge: exim/src/src/dk.c,v 1.11 2006/10/30 22:06:33 tom Exp $ */ |
fb2274d4 TK |
2 | |
3 | /************************************************* | |
4 | * Exim - an Internet mail transport agent * | |
5 | *************************************************/ | |
6 | ||
d7d7b7b9 | 7 | /* Copyright (c) University of Cambridge 1995 - 2006 */ |
fb2274d4 TK |
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(); | |
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 | } | |
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 | ||
84330b7b | 83 | void 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 | ||
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; | |
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 | 229 | /* All done, reset dk_context. */ |
a7bac71d | 230 | dk_free(dk_context,1); |
fb2274d4 | 231 | dk_context = NULL; |
84330b7b | 232 | |
fb2274d4 TK |
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; | |
75fa1910 PH |
242 | uschar *headers = NULL; |
243 | int headers_len; | |
fb2274d4 | 244 | int dk_canon_int = DK_CANON_SIMPLE; |
a8530a10 | 245 | char buf[4096]; |
fb2274d4 TK |
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; | |
84330b7b | 253 | |
fb2274d4 TK |
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; | |
a8d97c8a | 264 | else dk_canon = US "simple"; |
84330b7b | 265 | |
fb2274d4 TK |
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) { | |
84330b7b | 269 | debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status)); |
fb2274d4 TK |
270 | dk_context = NULL; |
271 | goto CLEANUP; | |
272 | } | |
84330b7b | 273 | |
a8530a10 TK |
274 | while((sread = read(dk_fd,&buf,4096)) > 0) { |
275 | int pos = 0; | |
276 | char c; | |
84330b7b | 277 | |
a8530a10 TK |
278 | while (pos < sread) { |
279 | c = buf[pos++]; | |
84330b7b | 280 | |
a8530a10 TK |
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 | } | |
fb2274d4 | 288 | |
a8530a10 TK |
289 | if (seen_lfdot) { |
290 | /* EOM, write "\n" and break */ | |
291 | dk_message(dk_context, CUS "\n", 1); | |
292 | break; | |
293 | } | |
84330b7b | 294 | |
a8530a10 TK |
295 | if ((c == '.') && seen_lf) { |
296 | seen_lfdot = 1; | |
297 | continue; | |
298 | } | |
84330b7b | 299 | |
a8530a10 TK |
300 | if (seen_lf) { |
301 | /* normal lf, just send it */ | |
302 | dk_message(dk_context, CUS "\n", 1); | |
303 | seen_lf = 0; | |
304 | } | |
84330b7b | 305 | |
a8530a10 TK |
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 | } | |
fb2274d4 | 314 | } |
84330b7b | 315 | |
fb2274d4 TK |
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 | } | |
84330b7b | 323 | |
fb2274d4 TK |
324 | /* Flag end-of-message. */ |
325 | dk_internal_status = dk_end(dk_context, NULL); | |
326 | /* TODO: check status */ | |
84330b7b PH |
327 | |
328 | ||
fb2274d4 TK |
329 | /* Get domain to use, unless overridden. */ |
330 | if (dk_domain == NULL) { | |
a8d97c8a | 331 | dk_domain = US dk_address(dk_context); |
fb2274d4 TK |
332 | switch(dk_domain[0]) { |
333 | case 'N': dk_domain = NULL; break; | |
334 | case 'F': | |
335 | case 'S': | |
336 | dk_domain++; | |
a8d97c8a | 337 | dk_domain = Ustrrchr(dk_domain,'@'); |
fb2274d4 TK |
338 | if (dk_domain != NULL) { |
339 | uschar *p; | |
340 | dk_domain++; | |
341 | p = dk_domain; | |
84330b7b | 342 | while (*p != 0) { *p = tolower(*p); p++; } |
fb2274d4 TK |
343 | } |
344 | break; | |
345 | } | |
346 | if (dk_domain == NULL) { | |
84330b7b | 347 | debug_printf("DK: Could not determine domain to use for signing from message headers.\n"); |
fb2274d4 TK |
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 ... */ | |
a8d97c8a | 352 | rc = US""; |
fb2274d4 TK |
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; | |
84330b7b | 363 | } |
fb2274d4 | 364 | } |
84330b7b PH |
365 | |
366 | /* Set up $dk_domain expansion variable. */ | |
fb2274d4 TK |
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 | } | |
84330b7b | 377 | |
fb2274d4 TK |
378 | /* Set up $dk_selector expansion variable. */ |
379 | dk_signing_selector = dk_selector; | |
84330b7b | 380 | |
fb2274d4 TK |
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 | } | |
84330b7b | 389 | |
fb2274d4 TK |
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 */ | |
a8d97c8a | 394 | rc = US""; |
fb2274d4 TK |
395 | goto CLEANUP; |
396 | } | |
84330b7b | 397 | |
fb2274d4 TK |
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); | |
a8d97c8a | 402 | privkey_fd = open(CS dk_private_key,O_RDONLY); |
f1e894f3 PH |
403 | (void)read(privkey_fd,big_buffer,16383); |
404 | (void)close(privkey_fd); | |
fb2274d4 TK |
405 | dk_private_key = big_buffer; |
406 | } | |
84330b7b | 407 | |
fb2274d4 | 408 | /* Get the signature. */ |
75fa1910 | 409 | dk_internal_status = dk_getsig(dk_context, dk_private_key, sig, 1024); |
fb2274d4 TK |
410 | |
411 | /* Check for unuseable key */ | |
412 | if (dk_internal_status != DK_STAT_OK) { | |
84330b7b | 413 | debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status)); |
fb2274d4 TK |
414 | rc = NULL; |
415 | goto CLEANUP; | |
416 | } | |
84330b7b | 417 | |
75fa1910 PH |
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); | |
fb2274d4 | 422 | /* Build DomainKey-Signature header to return. */ |
75fa1910 PH |
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); | |
84330b7b | 426 | |
75fa1910 PH |
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); | |
fb2274d4 TK |
429 | |
430 | CLEANUP: | |
431 | if (dk_context != NULL) { | |
a7bac71d | 432 | dk_free(dk_context,1); |
fb2274d4 TK |
433 | dk_context = NULL; |
434 | } | |
435 | store_pool = old_pool; | |
436 | errno = save_errno; | |
437 | return rc; | |
438 | } | |
439 | ||
440 | #endif |