Commit | Line | Data |
---|---|---|
a7bac71d | 1 | /* $Cambridge: exim/src/src/dk.c,v 1.8 2005/09/01 08:39:03 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. */ | |
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; | |
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. */ | |
b07e6aa3 | 412 | (void)string_format(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) { | |
a7bac71d | 420 | dk_free(dk_context,1); |
fb2274d4 TK |
421 | dk_context = NULL; |
422 | } | |
423 | store_pool = old_pool; | |
424 | errno = save_errno; | |
425 | return rc; | |
426 | } | |
427 | ||
428 | #endif |