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