1 /* $Cambridge: exim/src/src/dkim-exim.c,v 1.2 2007/10/09 14:10:34 tom 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 DKIM support. Other DKIM relevant code is in
11 receive.c, transport.c and transports/smtp.c */
15 #ifdef EXPERIMENTAL_DKIM
17 /* Globals related to the DKIM reference library. */
18 DKIMContext *dkim_context = NULL;
19 DKIMSignOptions *dkim_sign_options = NULL;
20 DKIMVerifyOptions *dkim_verify_options = NULL;
21 int dkim_verify_result = DKIM_NEUTRAL;
22 int dkim_internal_status = DKIM_SUCCESS;
24 /* Global char buffer for getc/ungetc functions. We need
25 to accumulate some chars to be able to match EOD and
26 doubled SMTP dots. Those must not be fed to the validation
28 int dkimbuff[6] = {256,256,256,256,256,256};
30 /* receive_getc() wrapper that feeds DKIM while Exim reads
32 int dkim_receive_getc(void) {
35 #ifdef EXPERIMENTAL_DOMAINKEYS
36 int c = dk_receive_getc();
38 int c = receive_getc();
41 if ((dkim_context != NULL) &&
42 (dkim_internal_status == DKIM_SUCCESS)) {
43 /* Send oldest byte */
44 if (dkimbuff[0] < 256) {
45 DKIMVerifyProcess(dkim_context,(char *)&dkimbuff[0],1);
46 /* debug_printf("%c",(int)dkimbuff[0]); */
49 for (i=0;i<5;i++) dkimbuff[i]=dkimbuff[i+1];
51 /* look for our candidate patterns */
52 if ( (dkimbuff[1] == '\r') &&
53 (dkimbuff[2] == '\n') &&
54 (dkimbuff[3] == '.') &&
55 (dkimbuff[4] == '\r') &&
56 (dkimbuff[5] == '\n') ) {
64 if ( (dkimbuff[2] == '\r') &&
65 (dkimbuff[3] == '\n') &&
66 (dkimbuff[4] == '.') &&
67 (dkimbuff[5] == '.') ) {
68 /* doubled dot, skip this char */
76 /* When exim puts a char back in the fd, we
77 must rotate our buffer back. */
78 int dkim_receive_ungetc(int c) {
80 if ((dkim_context != NULL) &&
81 (dkim_internal_status == DKIM_SUCCESS)) {
83 /* rotate buffer back */
84 for (i=5;i>0;i--) dkimbuff[i]=dkimbuff[i-1];
88 #ifdef EXPERIMENTAL_DOMAINKEYS
89 return dk_receive_ungetc(c);
91 return receive_ungetc(c);
96 void dkim_exim_verify_init(void) {
97 int old_pool = store_pool;
99 /* Bail out unless we got perfect conditions */
101 !smtp_batched_input &&
106 store_pool = POOL_PERM;
109 dkim_verify_options = NULL;
111 dkim_context = store_get(sizeof(DKIMContext));
112 dkim_verify_options = store_get(sizeof(DKIMVerifyOptions));
115 !dkim_verify_options) {
116 debug_printf("DKIM: Can't allocate memory for verifying.\n");
120 memset(dkim_context,0,sizeof(DKIMContext));
121 memset(dkim_verify_options,0,sizeof(DKIMVerifyOptions));
123 dkim_verify_options->nHonorBodyLengthTag = 1; /* Honor the l= tag */
124 dkim_verify_options->nCheckPolicy = 1; /* Fetch sender's policy */
125 dkim_verify_options->nSubjectRequired = 1; /* Do not require Subject header inclusion */
127 dkim_verify_options->pfnSelectorCallback = NULL;
128 dkim_verify_options->pfnPolicyCallback = NULL;
130 dkim_status_wrap( DKIMVerifyInit(dkim_context, dkim_verify_options),
131 "error calling DKIMVerifyInit()" );
133 if (dkim_internal_status != DKIM_SUCCESS) {
134 /* Invalidate context */
138 store_pool = old_pool;
142 void dkim_exim_verify_finish(void) {
144 int old_pool = store_pool;
146 if (!dkim_do_verify ||
147 (!(smtp_input && !smtp_batched_input)) ||
148 (dkim_context == NULL) ||
149 (dkim_internal_status != DKIM_SUCCESS)) return;
151 store_pool = POOL_PERM;
153 /* Flush eventual remaining input chars */
155 if (dkimbuff[i] < 256)
156 DKIMVerifyProcess(dkim_context,(char *)&dkimbuff[i],1);
158 /* Fetch global result. Can be one of:
164 dkim_verify_result = DKIMVerifyResults(dkim_context);
166 store_pool = old_pool;
170 /* Lookup result for a given domain (or identity) */
171 int dkim_exim_verify_result(uschar *domain, uschar **result, uschar **error) {
175 DKIMVerifyDetails *dkim_verify_details = NULL;
177 if (!dkim_do_verify ||
178 (!(smtp_input && !smtp_batched_input)) ||
179 (dkim_context == NULL) ||
180 (dkim_internal_status != DKIM_SUCCESS)) {
181 rc = DKIM_EXIM_UNVERIFIED;
185 DKIMVerifyGetDetails(dkim_context,
187 &dkim_verify_details,
191 rc = DKIM_EXIM_UNSIGNED;
193 debug_printf("DKIM: We have %d signature(s)\n",sig_count);
194 for (i=0;i<sig_count;i++) {
195 debug_printf( "DKIM: [%d] ", i + 1 );
196 if (!dkim_verify_details[i].Domain) {
197 debug_printf("parse error (no domain)\n");
201 if (dkim_verify_details[i].nResult >= 0) {
202 debug_printf( "GOOD d=%s i=%s\n",
203 dkim_verify_details[i].Domain,
204 dkim_verify_details[i].IdentityDomain );
207 debug_printf( "FAIL d=%s i=%s c=%d\n",
208 dkim_verify_details[i].Domain,
209 dkim_verify_details[i].IdentityDomain,
210 dkim_verify_details[i].nResult
215 if ( (strcmpic(domain,dkim_verify_details[i].Domain) == 0) ||
216 (strcmpic(domain,dkim_verify_details[i].IdentityDomain) == 0) ) {
217 if (dkim_verify_details[i].nResult >= 0) {
219 /* TODO: Add From: domain check */
222 /* Return DEFER for temp. error types */
223 if (dkim_verify_details[i].nResult == DKIM_SELECTOR_DNS_TEMP_FAILURE) {
224 rc = DKIM_EXIM_DEFER;
238 case DKIM_EXIM_DEFER:
241 case DKIM_EXIM_UNVERIFIED:
242 *result = "unverified";
244 case DKIM_EXIM_UNSIGNED:
245 *result = "unsigned";
257 uschar *dkim_exim_sign_headers = NULL;
258 int dkim_exim_header_callback(const char* header) {
260 uschar *hdr_ptr = dkim_exim_sign_headers;
261 uschar *hdr_itr = NULL;
263 uschar *hdr_name = string_copy(US header);
264 char *colon_pos = strchr(hdr_name,':');
266 if (colon_pos == NULL) return 0;
269 debug_printf("DKIM: header '%s' ",hdr_name);
270 while ((hdr_itr = string_nextinlist(&hdr_ptr, &sep,
272 sizeof(hdr_buf))) != NULL) {
273 if (strcmpic((uschar *)hdr_name,hdr_itr) == 0) {
274 debug_printf("included in signature.\n");
278 debug_printf("NOT included in signature.\n");
282 uschar *dkim_exim_sign(int dkim_fd,
283 uschar *dkim_private_key,
285 uschar *dkim_selector,
287 uschar *dkim_sign_headers) {
296 int old_pool = store_pool;
297 store_pool = POOL_PERM;
300 dkim_sign_options = NULL;
302 dkim_context = store_get(sizeof(DKIMContext));
303 dkim_sign_options = store_get(sizeof(DKIMSignOptions));
305 memset(dkim_sign_options,0,sizeof(DKIMSignOptions));
306 memset(dkim_context,0,sizeof(DKIMContext));
308 dkim_sign_options->nIncludeBodyLengthTag = 0;
309 dkim_sign_options->nIncludeCopiedHeaders = 0;
310 dkim_sign_options->nHash = DKIM_HASH_SHA256;
311 dkim_sign_options->nIncludeTimeStamp = 0;
312 dkim_sign_options->nIncludeQueryMethod = 0;
313 dkim_sign_options->pfnHeaderCallback = dkim_exim_header_callback;
314 dkim_sign_options->nIncludeBodyHash = DKIM_BODYHASH_IETF_1;
317 dkim_domain = expand_string(dkim_domain);
318 if (dkim_domain == NULL) {
319 /* expansion error, do not send message. */
320 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
321 "dkim_domain: %s", expand_string_message);
325 /* Set up $dkim_domain expansion variable. */
326 dkim_signing_domain = dkim_domain;
327 Ustrncpy((uschar *)dkim_sign_options->szDomain,dkim_domain,255);
330 /* Get selector to use. */
331 dkim_selector = expand_string(dkim_selector);
332 if (dkim_selector == NULL) {
333 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
334 "dkim_selector: %s", expand_string_message);
338 /* Set up $dkim_selector expansion variable. */
339 dkim_signing_selector = dkim_selector;
340 Ustrncpy((uschar *)dkim_sign_options->szSelector,dkim_selector,79);
342 /* Expand provided options */
343 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
344 if (dkim_canon == NULL) {
345 /* expansion error, do not send message. */
346 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
347 "dkim_canon: %s", expand_string_message);
351 if (Ustrcmp(dkim_canon, "relaxed") == 0)
352 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
353 else if (Ustrcmp(dkim_canon, "simple") == 0)
354 dkim_sign_options->nCanon = DKIM_SIGN_SIMPLE;
356 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
357 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
360 /* Expand signing headers once */
361 if (dkim_sign_headers != NULL) {
362 dkim_sign_headers = expand_string(dkim_sign_headers);
363 if (dkim_sign_headers == NULL) {
364 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
365 "dkim_sign_headers: %s", expand_string_message);
371 if (dkim_sign_headers == NULL) {
372 /* Use RFC defaults */
373 dkim_sign_headers = US"from:sender:reply-to:subject:date:"
374 "message-id:to:cc:mime-version:content-type:"
375 "content-transfer-encoding:content-id:"
376 "content-description:resent-date:resent-from:"
377 "resent-sender:resent-to:resent-cc:resent-message-id:"
378 "in-reply-to:references:"
379 "list-id:list-help:list-unsubscribe:"
380 "list-subscribe:list-post:list-owner:list-archive";
382 dkim_exim_sign_headers = dkim_sign_headers;
384 /* Get private key to use. */
385 dkim_private_key = expand_string(dkim_private_key);
386 if (dkim_private_key == NULL) {
387 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
388 "dkim_private_key: %s", expand_string_message);
393 if ( (Ustrlen(dkim_private_key) == 0) ||
394 (Ustrcmp(dkim_private_key,"0") == 0) ||
395 (Ustrcmp(dkim_private_key,"false") == 0) ) {
396 /* don't sign, but no error */
401 if (dkim_private_key[0] == '/') {
403 /* Looks like a filename, load the private key. */
404 memset(big_buffer,0,big_buffer_size);
405 privkey_fd = open(CS dkim_private_key,O_RDONLY);
406 (void)read(privkey_fd,big_buffer,16383);
407 (void)close(privkey_fd);
408 dkim_private_key = big_buffer;
411 /* Initialize signing context. */
412 dkim_status_wrap( DKIMSignInit(dkim_context, dkim_sign_options),
413 "error calling DKIMSignInit()" );
415 if (dkim_internal_status != DKIM_SUCCESS) {
416 /* Invalidate context */
421 while((sread = read(dkim_fd,&buf,4096)) > 0) {
425 while (pos < sread) {
428 if ((c == '.') && seen_lfdot) {
429 /* escaped dot, write "\n.", continue */
430 dkim_internal_status = DKIMSignProcess(dkim_context,"\n.",2);
437 /* EOM, write "\n" and break */
438 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
442 if ((c == '.') && seen_lf) {
448 /* normal lf, just send it */
449 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
459 dkim_internal_status = DKIMSignProcess(dkim_context,&c,1);
463 /* Handle failed read above. */
465 debug_printf("DKIM: Error reading -K file.\n");
471 if (!dkim_status_wrap(dkim_internal_status,
472 "error while processing message data")) {
477 if (!dkim_status_wrap( DKIMSignGetSig2( dkim_context, dkim_private_key, &signature ),
478 "error while signing message" ) ) {
483 log_write(0, LOG_MAIN, "Message signed with DKIM: %s\n",signature);
485 rc = store_get(strlen(signature)+3);
486 Ustrcpy(rc,US signature);
487 Ustrcat(rc,US"\r\n");
490 if (dkim_context != NULL) {
493 store_pool = old_pool;
498 unsigned int dkim_status_wrap(int stat, uschar *text) {
499 char *p = DKIMGetErrorString(stat);
501 if (stat != DKIM_SUCCESS) {
502 debug_printf("DKIM: %s",text?text:US"");
503 if (p) debug_printf(" (%s)",p);
506 dkim_internal_status = stat;
507 return (dkim_internal_status==DKIM_SUCCESS)?1:0;