1 /* $Cambridge: exim/src/src/dk.c,v 1.9 2006/02/07 11:19:00 ph10 Exp $ */
3 /*************************************************
4 * Exim - an Internet mail transport agent *
5 *************************************************/
7 /* Copyright (c) University of Cambridge 1995 - 2006 */
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 int dk_canon_int = DK_CANON_SIMPLE;
249 int old_pool = store_pool;
250 store_pool = POOL_PERM;
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));
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;
262 else dk_canon = US "simple";
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) {
267 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
272 while((sread = read(dk_fd,&c,1)) > 0) {
274 if ((c == '.') && seen_lfdot) {
275 /* escaped dot, write "\n.", continue */
276 dk_message(dk_context, CUS "\n.", 2);
283 /* EOM, write "\n" and break */
284 dk_message(dk_context, CUS "\n", 1);
288 if ((c == '.') && seen_lf) {
294 /* normal lf, just send it */
295 dk_message(dk_context, CUS "\n", 1);
305 dk_message(dk_context, CUS &c, 1);
308 /* Handle failed read above. */
310 debug_printf("DK: Error reading -K file.\n");
316 /* Flag end-of-message. */
317 dk_internal_status = dk_end(dk_context, NULL);
318 /* TODO: check status */
321 /* Get domain to use, unless overridden. */
322 if (dk_domain == NULL) {
323 dk_domain = US dk_address(dk_context);
324 switch(dk_domain[0]) {
325 case 'N': dk_domain = NULL; break;
329 dk_domain = Ustrrchr(dk_domain,'@');
330 if (dk_domain != NULL) {
334 while (*p != 0) { *p = tolower(*p); p++; }
338 if (dk_domain == NULL) {
339 debug_printf("DK: Could not determine domain to use for signing from message headers.\n");
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
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");
358 /* Set up $dk_domain expansion variable. */
359 dk_signing_domain = dk_domain;
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);
370 /* Set up $dk_selector expansion variable. */
371 dk_signing_selector = dk_selector;
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);
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 */
390 if (dk_private_key[0] == '/') {
392 /* Looks like a filename, load the private key. */
393 memset(big_buffer,0,big_buffer_size);
394 privkey_fd = open(CS dk_private_key,O_RDONLY);
395 (void)read(privkey_fd,big_buffer,16383);
396 (void)close(privkey_fd);
397 dk_private_key = big_buffer;
400 /* Get the signature. */
401 dk_internal_status = dk_getsig(dk_context, dk_private_key, sig, 8192);
403 /* Check for unuseable key */
404 if (dk_internal_status != DK_STAT_OK) {
405 debug_printf("DK: %s\n", DK_STAT_to_string(dk_internal_status));
410 rc = store_get(1024);
411 /* Build DomainKey-Signature header to return. */
412 (void)string_format(rc, 1024, "DomainKey-Signature: a=rsa-sha1; q=dns; c=%s;\r\n"
414 "\tb=%s;\r\n", dk_canon, dk_selector, dk_domain, sig);
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);
419 if (dk_context != NULL) {
420 dk_free(dk_context,1);
423 store_pool = old_pool;