1 /* $Cambridge: exim/src/src/dkim-exim.c,v 1.1 2007/09/28 12:21:57 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 dkim_sign_options->nIncludeBodyLengthTag = 0;
306 dkim_sign_options->nIncludeCopiedHeaders = 0;
307 dkim_sign_options->nHash = DKIM_HASH_SHA256;
308 dkim_sign_options->nIncludeTimeStamp = 0;
309 dkim_sign_options->nIncludeQueryMethod = 0;
310 dkim_sign_options->pfnHeaderCallback = dkim_exim_header_callback;
311 dkim_sign_options->nIncludeBodyHash = DKIM_BODYHASH_IETF_1;
314 dkim_domain = expand_string(dkim_domain);
315 if (dkim_domain == NULL) {
316 /* expansion error, do not send message. */
317 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
318 "dkim_domain: %s", expand_string_message);
322 /* Set up $dkim_domain expansion variable. */
323 dkim_signing_domain = dkim_domain;
324 Ustrncpy((uschar *)dkim_sign_options->szDomain,dkim_domain,255);
327 /* Get selector to use. */
328 dkim_selector = expand_string(dkim_selector);
329 if (dkim_selector == NULL) {
330 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
331 "dkim_selector: %s", expand_string_message);
335 /* Set up $dkim_selector expansion variable. */
336 dkim_signing_selector = dkim_selector;
337 Ustrncpy((uschar *)dkim_sign_options->szSelector,dkim_selector,79);
339 /* Expand provided options */
340 dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
341 if (dkim_canon == NULL) {
342 /* expansion error, do not send message. */
343 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
344 "dkim_canon: %s", expand_string_message);
348 if (Ustrcmp(dkim_canon, "relaxed") == 0)
349 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
350 else if (Ustrcmp(dkim_canon, "simple") == 0)
351 dkim_sign_options->nCanon = DKIM_SIGN_SIMPLE;
353 log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
354 dkim_sign_options->nCanon = DKIM_SIGN_RELAXED;
357 /* Expand signing headers once */
358 if (dkim_sign_headers != NULL) {
359 dkim_sign_headers = expand_string(dkim_sign_headers);
360 if (dkim_sign_headers == NULL) {
361 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
362 "dkim_sign_headers: %s", expand_string_message);
368 if (dkim_sign_headers == NULL) {
369 /* Use RFC defaults */
370 dkim_sign_headers = US"from:sender:reply-to:subject:date:"
371 "message-id:to:cc:mime-version:content-type:"
372 "content-transfer-encoding:content-id:"
373 "content-description:resent-date:resent-from:"
374 "resent-sender:resent-to:resent-cc:resent-message-id:"
375 "in-reply-to:references:"
376 "list-id:list-help:list-unsubscribe:"
377 "list-subscribe:list-post:list-owner:list-archive";
379 dkim_exim_sign_headers = dkim_sign_headers;
381 /* Get private key to use. */
382 dkim_private_key = expand_string(dkim_private_key);
383 if (dkim_private_key == NULL) {
384 log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
385 "dkim_private_key: %s", expand_string_message);
390 if ( (Ustrlen(dkim_private_key) == 0) ||
391 (Ustrcmp(dkim_private_key,"0") == 0) ||
392 (Ustrcmp(dkim_private_key,"false") == 0) ) {
393 /* don't sign, but no error */
398 if (dkim_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 dkim_private_key,O_RDONLY);
403 (void)read(privkey_fd,big_buffer,16383);
404 (void)close(privkey_fd);
405 dkim_private_key = big_buffer;
408 /* Initialize signing context. */
409 dkim_status_wrap( DKIMSignInit(dkim_context, dkim_sign_options),
410 "error calling DKIMSignInit()" );
412 if (dkim_internal_status != DKIM_SUCCESS) {
413 /* Invalidate context */
418 while((sread = read(dkim_fd,&buf,4096)) > 0) {
422 while (pos < sread) {
425 if ((c == '.') && seen_lfdot) {
426 /* escaped dot, write "\n.", continue */
427 dkim_internal_status = DKIMSignProcess(dkim_context,"\n.",2);
434 /* EOM, write "\n" and break */
435 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
439 if ((c == '.') && seen_lf) {
445 /* normal lf, just send it */
446 dkim_internal_status = DKIMSignProcess(dkim_context,"\n",1);
456 dkim_internal_status = DKIMSignProcess(dkim_context,&c,1);
460 /* Handle failed read above. */
462 debug_printf("DKIM: Error reading -K file.\n");
468 if (!dkim_status_wrap(dkim_internal_status,
469 "error while processing message data")) {
474 if (!dkim_status_wrap( DKIMSignGetSig2( dkim_context, dkim_private_key, &signature ),
475 "error while signing message" ) ) {
480 log_write(0, LOG_MAIN, "Message signed with DKIM: %s\n",signature);
482 rc = store_get(strlen(signature)+3);
483 Ustrcpy(rc,US signature);
484 Ustrcat(rc,US"\r\n");
487 if (dkim_context != NULL) {
490 store_pool = old_pool;
495 unsigned int dkim_status_wrap(int stat, uschar *text) {
496 char *p = DKIMGetErrorString(stat);
498 if (stat != DKIM_SUCCESS) {
499 debug_printf("DKIM: %s",text?text:US"");
500 if (p) debug_printf(" (%s)",p);
503 dkim_internal_status = stat;
504 return (dkim_internal_status==DKIM_SUCCESS)?1:0;