Merge native DKIM support (from DEVEL_PDKIM)
[exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.2 2009/06/10 07:34:04 tom Exp $ */
2
3 /*************************************************
4 *     Exim - an Internet mail transport agent    *
5 *************************************************/
6
7 /* Copyright (c) University of Cambridge 2009 */
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_signing_domains_size = 0;
85   int dkim_signing_domains_ptr = 0;
86   dkim_signing_domains = 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|LOG_PANIC, "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( "DKIM: 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, (char *)logmsg);
180
181     /* Build a colon-separated list of signing domains in dkim_signing_domains */
182     dkim_signing_domains = string_append(dkim_signing_domains,
183                                          &dkim_signing_domains_size,
184                                          &dkim_signing_domains_ptr,
185                                          2,
186                                          sig->domain,
187                                          ":"
188                                         );
189
190     /* Process next signature */
191     sig = sig->next;
192   }
193
194   /* Chop the last colon from the domain list */
195   if ((dkim_signing_domains != NULL) &&
196       (Ustrlen(dkim_signing_domains) > 0))
197     dkim_signing_domains[Ustrlen(dkim_signing_domains)-1] = '\0';
198 }
199
200
201 void dkim_exim_acl_setup(uschar *id) {
202   pdkim_signature *sig = dkim_signatures;
203   dkim_cur_sig = NULL;
204   if (dkim_disable_verify ||
205       !id || !sig ||
206       !dkim_verify_ctx) return;
207   /* Find signature to run ACL on */
208   while (sig != NULL) {
209     uschar *cmp_val = NULL;
210     if (Ustrchr(id,'@') != NULL) cmp_val = (uschar *)sig->identity;
211                             else cmp_val = (uschar *)sig->domain;
212     if (cmp_val && (strcmpic(cmp_val,id) == 0)) {
213       dkim_cur_sig = sig;
214       /* The "dkim_domain" and "dkim_selector" expansion variables have
215          related globals, since they are used in the signing code too.
216          Instead of inventing separate names for verification, we set
217          them here. This is easy since a domain and selector is guaranteed
218          to be in a signature. The other dkim_* expansion items are
219          dynamically fetched from dkim_cur_sig at expansion time (see
220          function below). */
221       dkim_signing_domain   = (uschar *)sig->domain;
222       dkim_signing_selector = (uschar *)sig->selector;
223       return;
224     }
225     sig = sig->next;
226   }
227 }
228
229
230 uschar *dkim_exim_expand_query(int what) {
231
232   if (!dkim_verify_ctx ||
233       dkim_disable_verify ||
234       !dkim_cur_sig) return dkim_exim_expand_defaults(what);
235
236   switch(what) {
237     case DKIM_ALGO:
238       return dkim_cur_sig->algo?
239               (uschar *)(dkim_cur_sig->algo)
240               :dkim_exim_expand_defaults(what);
241     case DKIM_BODYLENGTH:
242       return (dkim_cur_sig->bodylength >= 0)?
243               (uschar *)string_sprintf(OFF_T_FMT,(LONGLONG_T)dkim_cur_sig->bodylength)
244               :dkim_exim_expand_defaults(what);
245     case DKIM_CANON_BODY:
246       return dkim_cur_sig->canon_body?
247               (uschar *)(dkim_cur_sig->canon_body)
248               :dkim_exim_expand_defaults(what);
249     case DKIM_CANON_HEADERS:
250       return dkim_cur_sig->canon_headers?
251               (uschar *)(dkim_cur_sig->canon_headers)
252               :dkim_exim_expand_defaults(what);
253     case DKIM_COPIEDHEADERS:
254       return dkim_cur_sig->copiedheaders?
255               (uschar *)(dkim_cur_sig->copiedheaders)
256               :dkim_exim_expand_defaults(what);
257     case DKIM_CREATED:
258       return (dkim_cur_sig->created > 0)?
259               (uschar *)string_sprintf("%llu",dkim_cur_sig->created)
260               :dkim_exim_expand_defaults(what);
261     case DKIM_EXPIRES:
262       return (dkim_cur_sig->expires > 0)?
263               (uschar *)string_sprintf("%llu",dkim_cur_sig->expires)
264               :dkim_exim_expand_defaults(what);
265     case DKIM_HEADERNAMES:
266       return dkim_cur_sig->headernames?
267               (uschar *)(dkim_cur_sig->headernames)
268               :dkim_exim_expand_defaults(what);
269     case DKIM_IDENTITY:
270       return dkim_cur_sig->identity?
271               (uschar *)(dkim_cur_sig->identity)
272               :dkim_exim_expand_defaults(what);
273     case DKIM_KEY_GRANULARITY:
274       return dkim_cur_sig->pubkey?
275               (dkim_cur_sig->pubkey->granularity?
276                 (uschar *)(dkim_cur_sig->pubkey->granularity)
277                 :dkim_exim_expand_defaults(what)
278               )
279               :dkim_exim_expand_defaults(what);
280     case DKIM_KEY_SRVTYPE:
281       return dkim_cur_sig->pubkey?
282               (dkim_cur_sig->pubkey->srvtype?
283                 (uschar *)(dkim_cur_sig->pubkey->srvtype)
284                 :dkim_exim_expand_defaults(what)
285               )
286               :dkim_exim_expand_defaults(what);
287     case DKIM_KEY_NOTES:
288       return dkim_cur_sig->pubkey?
289               (dkim_cur_sig->pubkey->notes?
290                 (uschar *)(dkim_cur_sig->pubkey->notes)
291                 :dkim_exim_expand_defaults(what)
292               )
293               :dkim_exim_expand_defaults(what);
294     case DKIM_KEY_TESTING:
295       return dkim_cur_sig->pubkey?
296               (dkim_cur_sig->pubkey->testing?
297                 US"1"
298                 :dkim_exim_expand_defaults(what)
299               )
300               :dkim_exim_expand_defaults(what);
301     case DKIM_NOSUBDOMAINS:
302       return dkim_cur_sig->pubkey?
303               (dkim_cur_sig->pubkey->no_subdomaining?
304                 US"1"
305                 :dkim_exim_expand_defaults(what)
306               )
307               :dkim_exim_expand_defaults(what);
308     case DKIM_VERIFY_STATUS:
309       switch(dkim_cur_sig->verify_status) {
310         case PDKIM_VERIFY_INVALID:
311           return US"invalid";
312         case PDKIM_VERIFY_FAIL:
313           return US"fail";
314         case PDKIM_VERIFY_PASS:
315           return US"pass";
316         case PDKIM_VERIFY_NONE:
317         default:
318           return US"none";
319       }
320     case DKIM_VERIFY_REASON:
321       switch (dkim_cur_sig->verify_ext_status) {
322         case PDKIM_VERIFY_INVALID_PUBKEY_UNAVAILABLE:
323           return US"pubkey_unavailable";
324         case PDKIM_VERIFY_INVALID_PUBKEY_PARSING:
325           return US"pubkey_syntax";
326         case PDKIM_VERIFY_FAIL_BODY:
327           return US"bodyhash_mismatch";
328         case PDKIM_VERIFY_FAIL_MESSAGE:
329           return US"signature_incorrect";
330       }
331     default:
332       return US"";
333   }
334 }
335
336
337 uschar *dkim_exim_expand_defaults(int what) {
338   switch(what) {
339     case DKIM_ALGO:               return US"";
340     case DKIM_BODYLENGTH:         return US"9999999999999";
341     case DKIM_CANON_BODY:         return US"";
342     case DKIM_CANON_HEADERS:      return US"";
343     case DKIM_COPIEDHEADERS:      return US"";
344     case DKIM_CREATED:            return US"0";
345     case DKIM_EXPIRES:            return US"9999999999999";
346     case DKIM_HEADERNAMES:        return US"";
347     case DKIM_IDENTITY:           return US"";
348     case DKIM_KEY_GRANULARITY:    return US"*";
349     case DKIM_KEY_SRVTYPE:        return US"*";
350     case DKIM_KEY_NOTES:          return US"";
351     case DKIM_KEY_TESTING:        return US"0";
352     case DKIM_NOSUBDOMAINS:       return US"0";
353     case DKIM_VERIFY_STATUS:      return US"none";
354     case DKIM_VERIFY_REASON:      return US"";
355     default:                      return US"";
356   }
357 }
358
359
360 uschar *dkim_exim_sign(int dkim_fd,
361                        uschar *dkim_private_key,
362                        uschar *dkim_domain,
363                        uschar *dkim_selector,
364                        uschar *dkim_canon,
365                        uschar *dkim_sign_headers) {
366   pdkim_ctx *ctx = NULL;
367   uschar *rc = NULL;
368   pdkim_signature *signature;
369   int pdkim_canon;
370   int sread;
371   char buf[4096];
372   int save_errno = 0;
373   int old_pool = store_pool;
374
375   dkim_domain = expand_string(dkim_domain);
376   if (dkim_domain == NULL) {
377     /* expansion error, do not send message. */
378     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
379           "dkim_domain: %s", expand_string_message);
380     rc = NULL;
381     goto CLEANUP;
382   }
383   /* Set up $dkim_domain expansion variable. */
384   dkim_signing_domain = dkim_domain;
385
386   /* Get selector to use. */
387   dkim_selector = expand_string(dkim_selector);
388   if (dkim_selector == NULL) {
389     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
390       "dkim_selector: %s", expand_string_message);
391     rc = NULL;
392     goto CLEANUP;
393   }
394   /* Set up $dkim_selector expansion variable. */
395   dkim_signing_selector = dkim_selector;
396
397   /* Get canonicalization to use */
398   dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
399   if (dkim_canon == NULL) {
400     /* expansion error, do not send message. */
401     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
402           "dkim_canon: %s", expand_string_message);
403     rc = NULL;
404     goto CLEANUP;
405   }
406   if (Ustrcmp(dkim_canon, "relaxed") == 0)
407     pdkim_canon = PDKIM_CANON_RELAXED;
408   else if (Ustrcmp(dkim_canon, "simple") == 0)
409     pdkim_canon = PDKIM_CANON_RELAXED;
410   else {
411     log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
412     pdkim_canon = PDKIM_CANON_RELAXED;
413   }
414
415   /* Expand signing headers once */
416   if (dkim_sign_headers != NULL) {
417     dkim_sign_headers = expand_string(dkim_sign_headers);
418     if (dkim_sign_headers == NULL) {
419       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
420         "dkim_sign_headers: %s", expand_string_message);
421       rc = NULL;
422       goto CLEANUP;
423     }
424   }
425
426   /* Get private key to use. */
427   dkim_private_key = expand_string(dkim_private_key);
428   if (dkim_private_key == NULL) {
429     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
430       "dkim_private_key: %s", expand_string_message);
431     rc = NULL;
432     goto CLEANUP;
433   }
434   if ( (Ustrlen(dkim_private_key) == 0) ||
435        (Ustrcmp(dkim_private_key,"0") == 0) ||
436        (Ustrcmp(dkim_private_key,"false") == 0) ) {
437     /* don't sign, but no error */
438     rc = US"";
439     goto CLEANUP;
440   }
441
442   if (dkim_private_key[0] == '/') {
443     int privkey_fd = 0;
444     /* Looks like a filename, load the private key. */
445     memset(big_buffer,0,big_buffer_size);
446     privkey_fd = open(CS dkim_private_key,O_RDONLY);
447     (void)read(privkey_fd,big_buffer,16383);
448     (void)close(privkey_fd);
449     dkim_private_key = big_buffer;
450   }
451
452   ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
453                         (char *)dkim_signing_domain,
454                         (char *)dkim_signing_selector,
455                         (char *)dkim_private_key
456                        );
457
458   pdkim_set_debug_stream(ctx,debug_file);
459
460   pdkim_set_optional(ctx,
461                      (char *)dkim_sign_headers,
462                      NULL,
463                      pdkim_canon,
464                      pdkim_canon,
465                      -1,
466                      PDKIM_ALGO_RSA_SHA256,
467                      0,
468                      0);
469
470   while((sread = read(dkim_fd,&buf,4096)) > 0) {
471     if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
472       rc = NULL;
473       goto CLEANUP;
474     }
475   }
476   /* Handle failed read above. */
477   if (sread == -1) {
478     debug_printf("DKIM: Error reading -K file.\n");
479     save_errno = errno;
480     rc = NULL;
481     goto CLEANUP;
482   }
483
484   if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
485     goto CLEANUP;
486
487   rc = store_get(strlen(signature->signature_header)+3);
488   Ustrcpy(rc,US signature->signature_header);
489   Ustrcat(rc,US"\r\n");
490
491   CLEANUP:
492   if (ctx != NULL) {
493     pdkim_free_ctx(ctx);
494   }
495   store_pool = old_pool;
496   errno = save_errno;
497   return rc;
498 };
499
500 #endif