1 /* $Cambridge: exim/src/src/dk.c,v 1.12 2007/01/08 10:50:18 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2007 */
8 /* See the file NOTICE for conditions of use and distribution. */
10 /* Code for DomainKeys support. Other DK relevant code is in
11 receive.c, transport.c and transports/smtp.c */
15 #ifdef EXPERIMENTAL_DOMAINKEYS
17 /* Globals related to the DK reference library. */
18 DK
*dk_context
= NULL
;
19 DK_LIB
*dk_lib
= NULL
;
21 DK_STAT dk_internal_status
;
23 /* Globals related to Exim DK implementation. */
24 dk_exim_verify_block
*dk_verify_block
= NULL
;
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
30 int dkbuff
[6] = {256,256,256,256,256,256};
32 /* receive_getc() wrapper that feeds DK while Exim reads
34 int dk_receive_getc(void) {
36 int c
= receive_getc();
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
, CUS
&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
));
46 for (i
=0;i
<5;i
++) dkbuff
[i
]=dkbuff
[i
+1];
48 /* look for our candidate patterns */
49 if ( (dkbuff
[1] == '\r') &&
50 (dkbuff
[2] == '\n') &&
52 (dkbuff
[4] == '\r') &&
53 (dkbuff
[5] == '\n') ) {
59 if ( (dkbuff
[2] == '\r') &&
60 (dkbuff
[3] == '\n') &&
62 (dkbuff
[5] == '.') ) {
63 /* doubled dot, skip this char */
70 /* When exim puts a char back in the fd, we
71 must rotate our buffer back. */
72 int dk_receive_ungetc(int c
) {
74 if (dk_context
!= NULL
) {
75 /* rotate buffer back */
76 for (i
=5;i
>0;i
--) dkbuff
[i
]=dkbuff
[i
-1];
79 return receive_ungetc(c
);
83 void dk_exim_verify_init(void) {
84 int old_pool
= store_pool
;
85 store_pool
= POOL_PERM
;
87 /* Reset DK state in any case. */
90 dk_verify_block
= NULL
;
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
));
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
));
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
));
113 memset(dk_verify_block
, 0, sizeof(dk_exim_verify_block
));
118 store_pool
= old_pool
;
122 void dk_exim_verify_finish(void) {
125 int old_pool
= store_pool
;
127 /* Bail out if context could not be set up earlier. */
128 if (dk_context
== NULL
)
131 store_pool
= POOL_PERM
;
133 /* Send remaining bytes from input which are still in the buffer. */
136 dk_internal_status
= dk_message(dk_context
, CUS
&dkbuff
[i
], 1);
138 /* Flag end-of-message. */
139 dk_internal_status
= dk_end(dk_context
, &dk_flags
);
141 /* dk_flags now has the selector flags (if there was one).
142 It seems that currently only the "t=" flag is supported
144 if (dk_flags
& DK_FLAG_SET
)
145 if (dk_flags
& DK_FLAG_TESTING
)
146 dk_verify_block
->testing
= TRUE
;
148 /* Grab address/domain information. */
149 p
= dk_address(dk_context
);
153 dk_verify_block
->address_source
= DK_EXIM_ADDRESS_NONE
;
156 dk_verify_block
->address_source
= DK_EXIM_ADDRESS_FROM_SENDER
;
159 dk_verify_block
->address_source
= DK_EXIM_ADDRESS_FROM_FROM
;
164 dk_verify_block
->address
= string_copy((uschar
*)p
);
166 if ((q
!= NULL
) && (*(q
+1) != '\0')) {
167 dk_verify_block
->domain
= string_copy((uschar
*)(q
+1));
169 dk_verify_block
->local_part
= string_copy((uschar
*)p
);
175 /* Now grab the domain-wide DK policy */
176 dk_flags
= dk_policy(dk_context
);
178 if (dk_flags
& DK_FLAG_SET
) {
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
)
182 dk_verify_block
->testing
= TRUE
;
183 if (dk_flags
& DK_FLAG_SIGNSALL
)
184 dk_verify_block
->signsall
= TRUE
;
187 /* Set up main result. */
188 switch(dk_internal_status
)
191 dk_verify_block
->is_signed
= FALSE
;
192 dk_verify_block
->result
= DK_EXIM_RESULT_NO_SIGNATURE
;
195 dk_verify_block
->is_signed
= TRUE
;
196 dk_verify_block
->result
= DK_EXIM_RESULT_GOOD
;
199 dk_verify_block
->is_signed
= TRUE
;
200 dk_verify_block
->result
= DK_EXIM_RESULT_BAD
;
202 case DK_STAT_REVOKED
:
203 dk_verify_block
->is_signed
= TRUE
;
204 dk_verify_block
->result
= DK_EXIM_RESULT_REVOKED
;
208 dk_verify_block
->is_signed
= TRUE
;
209 /* Syntax -> Bad format? */
210 dk_verify_block
->result
= DK_EXIM_RESULT_BAD_FORMAT
;
213 dk_verify_block
->is_signed
= TRUE
;
214 dk_verify_block
->result
= DK_EXIM_RESULT_NO_KEY
;
216 case DK_STAT_NORESOURCE
:
217 case DK_STAT_INTERNAL
:
219 case DK_STAT_CANTVRFY
:
220 dk_verify_block
->result
= DK_EXIM_RESULT_ERR
;
222 /* This is missing DK_EXIM_RESULT_NON_PARTICIPANT. The lib does not
223 report such a status. */
226 /* Set up human readable result string. */
227 dk_verify_block
->result_string
= string_copy((uschar
*)DK_STAT_to_string(dk_internal_status
));
229 /* All done, reset dk_context. */
230 dk_free(dk_context
,1);
233 store_pool
= old_pool
;
236 uschar
*dk_exim_sign(int dk_fd
,
237 uschar
*dk_private_key
,
242 uschar
*headers
= NULL
;
244 int dk_canon_int
= DK_CANON_SIMPLE
;
251 int old_pool
= store_pool
;
252 store_pool
= POOL_PERM
;
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
));
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
;
264 else dk_canon
= US
"simple";
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
) {
269 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status
));
274 while((sread
= read(dk_fd
,&buf
,4096)) > 0) {
278 while (pos
< sread
) {
281 if ((c
== '.') && seen_lfdot
) {
282 /* escaped dot, write "\n.", continue */
283 dk_message(dk_context
, CUS
"\n.", 2);
290 /* EOM, write "\n" and break */
291 dk_message(dk_context
, CUS
"\n", 1);
295 if ((c
== '.') && seen_lf
) {
301 /* normal lf, just send it */
302 dk_message(dk_context
, CUS
"\n", 1);
312 dk_message(dk_context
, CUS
&c
, 1);
316 /* Handle failed read above. */
318 debug_printf("DK: Error reading -K file.\n");
324 /* Flag end-of-message. */
325 dk_internal_status
= dk_end(dk_context
, NULL
);
326 /* TODO: check status */
329 /* Get domain to use, unless overridden. */
330 if (dk_domain
== NULL
) {
331 dk_domain
= US
dk_address(dk_context
);
332 switch(dk_domain
[0]) {
333 case 'N': dk_domain
= NULL
; break;
337 dk_domain
= Ustrrchr(dk_domain
,'@');
338 if (dk_domain
!= NULL
) {
342 while (*p
!= 0) { *p
= tolower(*p
); p
++; }
346 if (dk_domain
== NULL
) {
347 debug_printf("DK: Could not determine domain to use for signing from message headers.\n");
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
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");
366 /* Set up $dk_domain expansion variable. */
367 dk_signing_domain
= dk_domain
;
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
);
378 /* Set up $dk_selector expansion variable. */
379 dk_signing_selector
= dk_selector
;
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
);
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 */
398 if (dk_private_key
[0] == '/') {
400 /* Looks like a filename, load the private key. */
401 memset(big_buffer
,0,big_buffer_size
);
402 privkey_fd
= open(CS dk_private_key
,O_RDONLY
);
403 (void)read(privkey_fd
,big_buffer
,16383);
404 (void)close(privkey_fd
);
405 dk_private_key
= big_buffer
;
408 /* Get the signature. */
409 dk_internal_status
= dk_getsig(dk_context
, dk_private_key
, sig
, 1024);
411 /* Check for unuseable key */
412 if (dk_internal_status
!= DK_STAT_OK
) {
413 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status
));
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
);
422 /* Build DomainKey-Signature header to return. */
423 (void)string_format(rc
, 1024+256+headers_len
, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s; s=%s; d=%s;\r\n"
425 "\tb=%s;\r\n", dk_canon
, dk_selector
, dk_domain
, headers
, sig
);
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
);
431 if (dk_context
!= NULL
) {
432 dk_free(dk_context
,1);
435 store_pool
= old_pool
;