2318cc394bef45e9ffda9b1784cb492943349fb6
[users/jgh/exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.15 2010/06/12 13:54:38 jetmore Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge, 1995 - 2007 */
8 /* See the file NOTICE for conditions of use and distribution. */
9
10 /* Code for DKIM support. Other DKIM relevant code is in
11    receive.c, transport.c and transports/smtp.c */
12
13 #include "exim.h"
14
15 #ifndef DISABLE_DKIM
16
17 #include "pdkim/pdkim.h"
18
19 pdkim_ctx       *dkim_verify_ctx = NULL;
20 pdkim_signature *dkim_signatures = NULL;
21 pdkim_signature *dkim_cur_sig    = NULL;
22
23 int dkim_exim_query_dns_txt(char *name, char *answer) {
24   dns_answer dnsa;
25   dns_scan   dnss;
26   dns_record *rr;
27
28   if (dns_lookup(&dnsa, (uschar *)name, T_TXT, NULL) != DNS_SUCCEED) return PDKIM_FAIL;
29
30   /* Search for TXT record */
31   for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
32        rr != NULL;
33        rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
34     if (rr->type == T_TXT) break;
35
36   /* Copy record content to the answer buffer */
37   if (rr != NULL) {
38     int rr_offset = 0;
39     int answer_offset = 0;
40     while (rr_offset < rr->size) {
41       uschar len = (rr->data)[rr_offset++];
42       snprintf(answer+(answer_offset),
43                PDKIM_DNS_TXT_MAX_RECLEN-(answer_offset),
44                "%.*s", (int)len, (char *)((rr->data)+rr_offset));
45       rr_offset+=len;
46       answer_offset+=len;
47     }
48   }
49   else return PDKIM_FAIL;
50
51   return PDKIM_OK;
52 }
53
54
55 void dkim_exim_verify_init(void) {
56
57   /* Free previous context if there is one */
58   if (dkim_verify_ctx) pdkim_free_ctx(dkim_verify_ctx);
59
60   /* Create new context */
61   dkim_verify_ctx = pdkim_init_verify(PDKIM_INPUT_SMTP,
62                                       &dkim_exim_query_dns_txt
63                                      );
64
65   if (dkim_verify_ctx != NULL) {
66     dkim_collect_input = TRUE;
67     pdkim_set_debug_stream(dkim_verify_ctx,debug_file);
68   }
69   else dkim_collect_input = FALSE;
70
71 }
72
73
74 void dkim_exim_verify_feed(uschar *data, int len) {
75   if (dkim_collect_input &&
76       pdkim_feed(dkim_verify_ctx,
77                  (char *)data,
78                  len) != PDKIM_OK) dkim_collect_input = FALSE;
79 }
80
81
82 void dkim_exim_verify_finish(void) {
83   pdkim_signature *sig = NULL;
84   int dkim_signers_size = 0;
85   int dkim_signers_ptr = 0;
86   dkim_signers = NULL;
87
88   /* Delete eventual previous signature chain */
89   dkim_signatures = NULL;
90
91   /* If we have arrived here with dkim_collect_input == FALSE, it
92      means there was a processing error somewhere along the way.
93      Log the incident and disable futher verification. */
94   if (!dkim_collect_input) {
95     log_write(0, LOG_MAIN, "DKIM: Error while running this message through validation, disabling signature verification.");
96     dkim_disable_verify = TRUE;
97     return;
98   }
99   dkim_collect_input = FALSE;
100
101   /* Finish DKIM operation and fetch link to signatures chain */
102   if (pdkim_feed_finish(dkim_verify_ctx,&dkim_signatures) != PDKIM_OK) return;
103
104   sig = dkim_signatures;
105   while (sig != NULL) {
106     int size = 0;
107     int ptr = 0;
108     /* Log a line for each signature */
109     uschar *logmsg = string_append(NULL, &size, &ptr, 5,
110
111       string_sprintf( "d=%s s=%s c=%s/%s a=%s ",
112                       sig->domain,
113                       sig->selector,
114                       (sig->canon_headers == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
115                       (sig->canon_body    == PDKIM_CANON_SIMPLE)?"simple":"relaxed",
116                       (sig->algo          == PDKIM_ALGO_RSA_SHA256)?"rsa-sha256":"rsa-sha1"
117                     ),
118       ((sig->identity != NULL)?
119         string_sprintf("i=%s ", sig->identity)
120         :
121         US""
122       ),
123       ((sig->created > 0)?
124         string_sprintf("t=%lu ", sig->created)
125         :
126         US""
127       ),
128       ((sig->expires > 0)?
129         string_sprintf("x=%lu ", sig->expires)
130         :
131         US""
132       ),
133       ((sig->bodylength > -1)?
134         string_sprintf("l=%lu ", sig->bodylength)
135         :
136         US""
137       )
138     );
139
140     switch(sig->verify_status) {
141       case PDKIM_VERIFY_NONE:
142         logmsg = string_append(logmsg, &size, &ptr, 1, "[not verified]");
143       break;
144       case PDKIM_VERIFY_INVALID:
145         logmsg = string_append(logmsg, &size, &ptr, 1, "[invalid - ");
146         switch (sig->verify_ext_status) {
147           case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
148             logmsg = string_append(logmsg, &size, &ptr, 1, "public key record (currently?) unavailable]");
149           break;
150           case PDKIM_VERIFY_INVALID_BUFFER_SIZE:
151             logmsg = string_append(logmsg, &size, &ptr, 1, "overlong public key record]");
152           break;
153           case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
154             logmsg = string_append(logmsg, &size, &ptr, 1, "syntax error in public key record]");
155           break;
156           default:
157             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified problem]");
158         }
159       break;
160       case PDKIM_VERIFY_FAIL:
161         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification failed - ");
162         switch (sig->verify_ext_status) {
163           case PDKIM_VERIFY_FAIL_BODY:
164             logmsg = string_append(logmsg, &size, &ptr, 1, "body hash mismatch (body probably modified in transit)]");
165           break;
166           case PDKIM_VERIFY_FAIL_MESSAGE:
167             logmsg = string_append(logmsg, &size, &ptr, 1, "signature did not verify (headers probably modified in transit)]");
168           break;
169           default:
170             logmsg = string_append(logmsg, &size, &ptr, 1, "unspecified reason]");
171         }
172       break;
173       case PDKIM_VERIFY_PASS:
174         logmsg = string_append(logmsg, &size, &ptr, 1, "[verification succeeded]");
175       break;
176     }
177
178     logmsg[ptr] = '\0';
179     log_write(0, LOG_MAIN, "DKIM: %s", logmsg);
180
181     /* Build a colon-separated list of signing domains (and identities, if present) in dkim_signers */
182     dkim_signers = string_append(dkim_signers,
183                                  &dkim_signers_size,
184                                  &dkim_signers_ptr,
185                                  2,
186                                  sig->domain,
187                                  ":"
188                                 );
189
190     if (sig->identity != NULL) {
191       dkim_signers = string_append(dkim_signers,
192                                    &dkim_signers_size,
193                                    &dkim_signers_ptr,
194                                    2,
195                                    sig->identity,
196                                    ":"
197                                   );
198     }
199
200     /* Process next signature */
201     sig = sig->next;
202   }
203
204   /* NULL-terminate and chop the last colon from the domain list */
205   if (dkim_signers != NULL) {
206     dkim_signers[dkim_signers_ptr] = '\0';
207     if (Ustrlen(dkim_signers) > 0)
208       dkim_signers[Ustrlen(dkim_signers)-1] = '\0';
209   }
210 }
211
212
213 void dkim_exim_acl_setup(uschar *id) {
214   pdkim_signature *sig = dkim_signatures;
215   dkim_cur_sig = NULL;
216   dkim_cur_signer = id;
217   if (dkim_disable_verify ||
218       !id || !dkim_verify_ctx) return;
219   /* Find signature to run ACL on */
220   while (sig != NULL) {
221     uschar *cmp_val = NULL;
222     if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
223                             else cmp_val = (uschar *)sig->domain;
224     if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
225       dkim_cur_sig = sig;
226       /* The "dkim_domain" and "dkim_selector" expansion variables have
227          related globals, since they are used in the signing code too.
228          Instead of inventing separate names for verification, we set
229          them here. This is easy since a domain and selector is guaranteed
230          to be in a signature. The other dkim_* expansion items are
231          dynamically fetched from dkim_cur_sig at expansion time (see
232          function below). */
233       dkim_signing_domain   = (uschar *)sig->domain;
234       dkim_signing_selector = (uschar *)sig->selector;
235       return;
236     }
237     sig = sig->next;
238   }
239 }
240
241
242 uschar *dkim_exim_expand_query(int what) {
243
244   if (!dkim_verify_ctx ||
245       dkim_disable_verify ||
246       !dkim_cur_sig) return dkim_exim_expand_defaults(what);
247
248   switch(what) {
249     case DKIM_ALGO:
250       switch(dkim_cur_sig->algo) {
251         case PDKIM_ALGO_RSA_SHA1:
252           return US"rsa-sha1";
253         case PDKIM_ALGO_RSA_SHA256:
254         default:
255           return US"rsa-sha256";
256       }
257     case DKIM_BODYLENGTH:
258       return (dkim_cur_sig->bodylength >= 0)?
259               (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
260               :dkim_exim_expand_defaults(what);
261     case DKIM_CANON_BODY:
262       switch(dkim_cur_sig->canon_body) {
263         case PDKIM_CANON_RELAXED:
264           return US"relaxed";
265         case PDKIM_CANON_SIMPLE:
266         default:
267           return US"simple";
268       }
269     case DKIM_CANON_HEADERS:
270       switch(dkim_cur_sig->canon_headers) {
271         case PDKIM_CANON_RELAXED:
272           return US"relaxed";
273         case PDKIM_CANON_SIMPLE:
274         default:
275           return US"simple";
276       }
277     case DKIM_COPIEDHEADERS:
278       return dkim_cur_sig->copiedheaders?
279               (uschar *)(dkim_cur_sig->copiedheaders)
280               :dkim_exim_expand_defaults(what);
281     case DKIM_CREATED:
282       return (dkim_cur_sig->created > 0)?
283               (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
284               :dkim_exim_expand_defaults(what);
285     case DKIM_EXPIRES:
286       return (dkim_cur_sig->expires > 0)?
287               (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
288               :dkim_exim_expand_defaults(what);
289     case DKIM_HEADERNAMES:
290       return dkim_cur_sig->headernames?
291               (uschar *)(dkim_cur_sig->headernames)
292               :dkim_exim_expand_defaults(what);
293     case DKIM_IDENTITY:
294       return dkim_cur_sig->identity?
295               (uschar *)(dkim_cur_sig->identity)
296               :dkim_exim_expand_defaults(what);
297     case DKIM_KEY_GRANULARITY:
298       return dkim_cur_sig->pubkey?
299               (dkim_cur_sig->pubkey->granularity?
300                 (uschar *)(dkim_cur_sig->pubkey->granularity)
301                 :dkim_exim_expand_defaults(what)
302               )
303               :dkim_exim_expand_defaults(what);
304     case DKIM_KEY_SRVTYPE:
305       return dkim_cur_sig->pubkey?
306               (dkim_cur_sig->pubkey->srvtype?
307                 (uschar *)(dkim_cur_sig->pubkey->srvtype)
308                 :dkim_exim_expand_defaults(what)
309               )
310               :dkim_exim_expand_defaults(what);
311     case DKIM_KEY_NOTES:
312       return dkim_cur_sig->pubkey?
313               (dkim_cur_sig->pubkey->notes?
314                 (uschar *)(dkim_cur_sig->pubkey->notes)
315                 :dkim_exim_expand_defaults(what)
316               )
317               :dkim_exim_expand_defaults(what);
318     case DKIM_KEY_TESTING:
319       return dkim_cur_sig->pubkey?
320               (dkim_cur_sig->pubkey->testing?
321                 US"1"
322                 :dkim_exim_expand_defaults(what)
323               )
324               :dkim_exim_expand_defaults(what);
325     case DKIM_NOSUBDOMAINS:
326       return dkim_cur_sig->pubkey?
327               (dkim_cur_sig->pubkey->no_subdomaining?
328                 US"1"
329                 :dkim_exim_expand_defaults(what)
330               )
331               :dkim_exim_expand_defaults(what);
332     case DKIM_VERIFY_STATUS:
333       switch(dkim_cur_sig->verify_status) {
334         case PDKIM_VERIFY_INVALID:
335           return US"invalid";
336         case PDKIM_VERIFY_FAIL:
337           return US"fail";
338         case PDKIM_VERIFY_PASS:
339           return US"pass";
340         case PDKIM_VERIFY_NONE:
341         default:
342           return US"none";
343       }
344     case DKIM_VERIFY_REASON:
345       switch (dkim_cur_sig->verify_ext_status) {
346         case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
347           return US"pubkey_unavailable";
348         case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
349           return US"pubkey_syntax";
350         case PDKIM_VERIFY_FAIL_BODY:
351           return US"bodyhash_mismatch";
352         case PDKIM_VERIFY_FAIL_MESSAGE:
353           return US"signature_incorrect";
354       }
355     default:
356       return US"";
357   }
358 }
359
360
361 uschar *dkim_exim_expand_defaults(int what) {
362   switch(what) {
363     case DKIM_ALGO:               return US"";
364     case DKIM_BODYLENGTH:         return US"9999999999999";
365     case DKIM_CANON_BODY:         return US"";
366     case DKIM_CANON_HEADERS:      return US"";
367     case DKIM_COPIEDHEADERS:      return US"";
368     case DKIM_CREATED:            return US"0";
369     case DKIM_EXPIRES:            return US"9999999999999";
370     case DKIM_HEADERNAMES:        return US"";
371     case DKIM_IDENTITY:           return US"";
372     case DKIM_KEY_GRANULARITY:    return US"*";
373     case DKIM_KEY_SRVTYPE:        return US"*";
374     case DKIM_KEY_NOTES:          return US"";
375     case DKIM_KEY_TESTING:        return US"0";
376     case DKIM_NOSUBDOMAINS:       return US"0";
377     case DKIM_VERIFY_STATUS:      return US"none";
378     case DKIM_VERIFY_REASON:      return US"";
379     default:                      return US"";
380   }
381 }
382
383
384 uschar *dkim_exim_sign(int dkim_fd,
385                        uschar *dkim_private_key,
386                        uschar *dkim_domain,
387                        uschar *dkim_selector,
388                        uschar *dkim_canon,
389                        uschar *dkim_sign_headers) {
390   int sep = 0;
391   uschar *seen_items = NULL;
392   int seen_items_size = 0;
393   int seen_items_offset = 0;
394   uschar itembuf[256];
395   uschar *dkim_canon_expanded;
396   uschar *dkim_sign_headers_expanded;
397   uschar *dkim_private_key_expanded;
398   pdkim_ctx *ctx = NULL;
399   uschar *rc = NULL;
400   uschar *sigbuf = NULL;
401   int sigsize = 0;
402   int sigptr = 0;
403   pdkim_signature *signature;
404   int pdkim_canon;
405   int pdkim_rc;
406   int sread;
407   char buf[4096];
408   int save_errno = 0;
409   int old_pool = store_pool;
410
411   store_pool = POOL_MAIN;
412
413   dkim_domain = expand_string(dkim_domain);
414   if (dkim_domain == NULL) {
415     /* expansion error, do not send message. */
416     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
417           "dkim_domain: %s", expand_string_message);
418     rc = NULL;
419     goto CLEANUP;
420   }
421
422   /* Set $dkim_domain expansion variable to each unique domain in list. */
423   while ((dkim_signing_domain = string_nextinlist(&dkim_domain, &sep,
424                                                   itembuf,
425                                                   sizeof(itembuf))) != NULL) {
426     if (!dkim_signing_domain || (dkim_signing_domain[0] == '\0')) continue;
427     /* Only sign once for each domain, no matter how often it
428        appears in the expanded list. */
429     if (seen_items != NULL) {
430       uschar *seen_items_list = seen_items;
431       if (match_isinlist(dkim_signing_domain,
432                          &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK)
433         continue;
434       seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":");
435     }
436     seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,dkim_signing_domain);
437     seen_items[seen_items_offset] = '\0';
438
439     /* Set up $dkim_selector expansion variable. */
440     dkim_signing_selector = expand_string(dkim_selector);
441     if (dkim_signing_selector == NULL) {
442       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
443                 "dkim_selector: %s", expand_string_message);
444       rc = NULL;
445       goto CLEANUP;
446     }
447
448     /* Get canonicalization to use */
449     dkim_canon_expanded = expand_string(dkim_canon?dkim_canon:US"relaxed");
450     if (dkim_canon_expanded == NULL) {
451       /* expansion error, do not send message. */
452       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
453                 "dkim_canon: %s", expand_string_message);
454       rc = NULL;
455       goto CLEANUP;
456     }
457     if (Ustrcmp(dkim_canon_expanded, "relaxed") == 0)
458       pdkim_canon = PDKIM_CANON_RELAXED;
459     else if (Ustrcmp(dkim_canon_expanded, "simple") == 0)
460       pdkim_canon = PDKIM_CANON_SIMPLE;
461     else {
462       log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon_expanded);
463       pdkim_canon = PDKIM_CANON_RELAXED;
464     }
465
466     if (dkim_sign_headers) {
467       dkim_sign_headers_expanded = expand_string(dkim_sign_headers);
468       if (dkim_sign_headers_expanded == NULL) {
469         log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
470                   "dkim_sign_headers: %s", expand_string_message);
471         rc = NULL;
472         goto CLEANUP;
473       }
474     }
475     else {
476       /* pass NULL, which means default header list */
477       dkim_sign_headers_expanded = NULL;
478     }
479
480     /* Get private key to use. */
481     dkim_private_key_expanded = expand_string(dkim_private_key);
482     if (dkim_private_key_expanded == NULL) {
483       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
484                 "dkim_private_key: %s", expand_string_message);
485       rc = NULL;
486       goto CLEANUP;
487     }
488     if ( (Ustrlen(dkim_private_key_expanded) == 0) ||
489          (Ustrcmp(dkim_private_key_expanded,"0") == 0) ||
490          (Ustrcmp(dkim_private_key_expanded,"false") == 0) ) {
491       /* don't sign, but no error */
492       continue;
493     }
494
495     if (dkim_private_key_expanded[0] == '/') {
496       int privkey_fd = 0;
497       /* Looks like a filename, load the private key. */
498       memset(big_buffer,0,big_buffer_size);
499       privkey_fd = open(CS dkim_private_key_expanded,O_RDONLY);
500       if (privkey_fd < 0) {
501         log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
502                   "private key file for reading: %s", dkim_private_key_expanded);
503         rc = NULL;
504         goto CLEANUP;
505       }
506       (void)read(privkey_fd,big_buffer,(big_buffer_size-2));
507       (void)close(privkey_fd);
508       dkim_private_key_expanded = big_buffer;
509     }
510
511     ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
512                           (char *)dkim_signing_domain,
513                           (char *)dkim_signing_selector,
514                           (char *)dkim_private_key_expanded
515                          );
516
517     pdkim_set_debug_stream(ctx,debug_file);
518
519     pdkim_set_optional(ctx,
520                        (char *)dkim_sign_headers_expanded,
521                        NULL,
522                        pdkim_canon,
523                        pdkim_canon,
524                        -1,
525                        PDKIM_ALGO_RSA_SHA256,
526                        0,
527                        0);
528
529     lseek(dkim_fd, 0, SEEK_SET);
530     while((sread = read(dkim_fd,&buf,4096)) > 0) {
531       if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
532         rc = NULL;
533         goto CLEANUP;
534       }
535     }
536     /* Handle failed read above. */
537     if (sread == -1) {
538       debug_printf("DKIM: Error reading -K file.\n");
539       save_errno = errno;
540       rc = NULL;
541       goto CLEANUP;
542     }
543
544     pdkim_rc = pdkim_feed_finish(ctx,&signature);
545     if (pdkim_rc != PDKIM_OK) {
546       log_write(0, LOG_MAIN|LOG_PANIC, "DKIM: signing failed (RC %d)", pdkim_rc);
547       rc = NULL;
548       goto CLEANUP;
549     }
550
551     sigbuf = string_append(sigbuf, &sigsize, &sigptr, 2,
552                            US signature->signature_header,
553                            US"\r\n");
554
555     pdkim_free_ctx(ctx);
556     ctx = NULL;
557   }
558
559   if (sigbuf != NULL) {
560     sigbuf[sigptr] = '\0';
561     rc = sigbuf;
562   } else
563     rc = US"";
564
565   CLEANUP:
566   if (ctx != NULL)
567     pdkim_free_ctx(ctx);
568   store_pool = old_pool;
569   errno = save_errno;
570   return rc;
571 }
572
573 #endif