Commit | Line | Data |
---|---|---|
f1e894f3 | 1 | /* $Cambridge: exim/src/src/dk.c,v 1.5 2005/06/27 14:29:43 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)) { | |
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 | |
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); | |
00916a93 | 163 | *q = '@'; |
fb2274d4 TK |
164 | } |
165 | } | |
166 | } | |
167 | ||
00916a93 | 168 | /* TODO: This call should be removed with lib version >= 0.67 */ |
fb2274d4 TK |
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; | |
84330b7b | 175 | if (dk_flags & DK_FLAG_SIGNSALL) |
fb2274d4 TK |
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 | } | |
84330b7b | 217 | |
fb2274d4 TK |
218 | /* Set up human readable result string. */ |
219 | dk_verify_block->result_string = string_copy((uschar *)DK_STAT_to_string(dk_internal_status)); | |
84330b7b | 220 | |
fb2274d4 TK |
221 | /* All done, reset dk_context. */ |
222 | dk_free(dk_context); | |
223 | dk_context = NULL; | |
84330b7b | 224 | |
fb2274d4 TK |
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; | |
84330b7b | 243 | |
fb2274d4 TK |
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; | |
a8d97c8a | 254 | else dk_canon = US "simple"; |
84330b7b | 255 | |
fb2274d4 TK |
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) { | |
84330b7b | 259 | debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status)); |
fb2274d4 TK |
260 | dk_context = NULL; |
261 | goto CLEANUP; | |
262 | } | |
84330b7b | 263 | |
fb2274d4 | 264 | while((sread = read(dk_fd,&c,1)) > 0) { |
84330b7b | 265 | |
fb2274d4 TK |
266 | if ((c == '.') && seen_lfdot) { |
267 | /* escaped dot, write "\n.", continue */ | |
a8d97c8a | 268 | dk_message(dk_context, CUS "\n.", 2); |
fb2274d4 TK |
269 | seen_lf = 0; |
270 | seen_lfdot = 0; | |
271 | continue; | |
272 | } | |
84330b7b | 273 | |
fb2274d4 TK |
274 | if (seen_lfdot) { |
275 | /* EOM, write "\n" and break */ | |
a8d97c8a | 276 | dk_message(dk_context, CUS "\n", 1); |
fb2274d4 TK |
277 | break; |
278 | } | |
279 | ||
280 | if ((c == '.') && seen_lf) { | |
281 | seen_lfdot = 1; | |
282 | continue; | |
283 | } | |
84330b7b | 284 | |
fb2274d4 TK |
285 | if (seen_lf) { |
286 | /* normal lf, just send it */ | |
a8d97c8a | 287 | dk_message(dk_context, CUS "\n", 1); |
fb2274d4 TK |
288 | seen_lf = 0; |
289 | } | |
84330b7b | 290 | |
fb2274d4 TK |
291 | if (c == '\n') { |
292 | seen_lf = 1; | |
293 | continue; | |
294 | } | |
84330b7b | 295 | |
fb2274d4 | 296 | /* write the char */ |
a8d97c8a | 297 | dk_message(dk_context, CUS &c, 1); |
fb2274d4 | 298 | } |
84330b7b | 299 | |
fb2274d4 TK |
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 | } | |
84330b7b | 307 | |
fb2274d4 TK |
308 | /* Flag end-of-message. */ |
309 | dk_internal_status = dk_end(dk_context, NULL); | |
310 | /* TODO: check status */ | |
84330b7b PH |
311 | |
312 | ||
fb2274d4 TK |
313 | /* Get domain to use, unless overridden. */ |
314 | if (dk_domain == NULL) { | |
a8d97c8a | 315 | dk_domain = US dk_address(dk_context); |
fb2274d4 TK |
316 | switch(dk_domain[0]) { |
317 | case 'N': dk_domain = NULL; break; | |
318 | case 'F': | |
319 | case 'S': | |
320 | dk_domain++; | |
a8d97c8a | 321 | dk_domain = Ustrrchr(dk_domain,'@'); |
fb2274d4 TK |
322 | if (dk_domain != NULL) { |
323 | uschar *p; | |
324 | dk_domain++; | |
325 | p = dk_domain; | |
84330b7b | 326 | while (*p != 0) { *p = tolower(*p); p++; } |
fb2274d4 TK |
327 | } |
328 | break; | |
329 | } | |
330 | if (dk_domain == NULL) { | |
84330b7b | 331 | debug_printf("DK: Could not determine domain to use for signing from message headers.\n"); |
fb2274d4 TK |
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 ... */ | |
a8d97c8a | 336 | rc = US""; |
fb2274d4 TK |
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; | |
84330b7b | 347 | } |
fb2274d4 | 348 | } |
84330b7b PH |
349 | |
350 | /* Set up $dk_domain expansion variable. */ | |
fb2274d4 TK |
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 | } | |
84330b7b | 361 | |
fb2274d4 TK |
362 | /* Set up $dk_selector expansion variable. */ |
363 | dk_signing_selector = dk_selector; | |
84330b7b | 364 | |
fb2274d4 TK |
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 | } | |
84330b7b | 373 | |
fb2274d4 TK |
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 */ | |
a8d97c8a | 378 | rc = US""; |
fb2274d4 TK |
379 | goto CLEANUP; |
380 | } | |
84330b7b | 381 | |
fb2274d4 TK |
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); | |
a8d97c8a | 386 | privkey_fd = open(CS dk_private_key,O_RDONLY); |
f1e894f3 PH |
387 | (void)read(privkey_fd,big_buffer,16383); |
388 | (void)close(privkey_fd); | |
fb2274d4 TK |
389 | dk_private_key = big_buffer; |
390 | } | |
84330b7b | 391 | |
fb2274d4 TK |
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) { | |
84330b7b | 397 | debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status)); |
fb2274d4 TK |
398 | rc = NULL; |
399 | goto CLEANUP; | |
400 | } | |
84330b7b | 401 | |
fb2274d4 TK |
402 | rc = store_get(1024); |
403 | /* Build DomainKey-Signature header to return. */ | |
a8d97c8a | 404 | snprintf(CS rc, 1024, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s;\r\n" |
84330b7b | 405 | "\ts=%s; d=%s;\r\n" |
fb2274d4 | 406 | "\tb=%s;\r\n", dk_canon, dk_selector, dk_domain, sig); |
84330b7b | 407 | |
fb2274d4 TK |
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 |