Base64 decode bug fixes. Fixes: #39
[users/jgh/exim.git] / src / src / dkim.c
1 /* $Cambridge: exim/src/src/dkim.c,v 1.7 2009/10/15 15:44:51 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_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|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 (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   pdkim_ctx *ctx = NULL;
391   uschar *rc = NULL;
392   pdkim_signature *signature;
393   int pdkim_canon;
394   int sread;
395   char buf[4096];
396   int save_errno = 0;
397   int old_pool = store_pool;
398
399   dkim_domain = expand_string(dkim_domain);
400   if (dkim_domain == NULL) {
401     /* expansion error, do not send message. */
402     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
403           "dkim_domain: %s", expand_string_message);
404     rc = NULL;
405     goto CLEANUP;
406   }
407   /* Set up $dkim_domain expansion variable. */
408   dkim_signing_domain = dkim_domain;
409
410   /* Get selector to use. */
411   dkim_selector = expand_string(dkim_selector);
412   if (dkim_selector == NULL) {
413     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
414       "dkim_selector: %s", expand_string_message);
415     rc = NULL;
416     goto CLEANUP;
417   }
418   /* Set up $dkim_selector expansion variable. */
419   dkim_signing_selector = dkim_selector;
420
421   /* Get canonicalization to use */
422   dkim_canon = expand_string(dkim_canon?dkim_canon:US"relaxed");
423   if (dkim_canon == NULL) {
424     /* expansion error, do not send message. */
425     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
426           "dkim_canon: %s", expand_string_message);
427     rc = NULL;
428     goto CLEANUP;
429   }
430   if (Ustrcmp(dkim_canon, "relaxed") == 0)
431     pdkim_canon = PDKIM_CANON_RELAXED;
432   else if (Ustrcmp(dkim_canon, "simple") == 0)
433     pdkim_canon = PDKIM_CANON_RELAXED;
434   else {
435     log_write(0, LOG_MAIN, "DKIM: unknown canonicalization method '%s', defaulting to 'relaxed'.\n",dkim_canon);
436     pdkim_canon = PDKIM_CANON_RELAXED;
437   }
438
439   /* Expand signing headers once */
440   if (dkim_sign_headers != NULL) {
441     dkim_sign_headers = expand_string(dkim_sign_headers);
442     if (dkim_sign_headers == NULL) {
443       log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
444         "dkim_sign_headers: %s", expand_string_message);
445       rc = NULL;
446       goto CLEANUP;
447     }
448   }
449
450   /* Get private key to use. */
451   dkim_private_key = expand_string(dkim_private_key);
452   if (dkim_private_key == NULL) {
453     log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
454       "dkim_private_key: %s", expand_string_message);
455     rc = NULL;
456     goto CLEANUP;
457   }
458   if ( (Ustrlen(dkim_private_key) == 0) ||
459        (Ustrcmp(dkim_private_key,"0") == 0) ||
460        (Ustrcmp(dkim_private_key,"false") == 0) ) {
461     /* don't sign, but no error */
462     rc = US"";
463     goto CLEANUP;
464   }
465
466   if (dkim_private_key[0] == '/') {
467     int privkey_fd = 0;
468     /* Looks like a filename, load the private key. */
469     memset(big_buffer,0,big_buffer_size);
470     privkey_fd = open(CS dkim_private_key,O_RDONLY);
471     if (privkey_fd < 0) {
472       log_write(0, LOG_MAIN|LOG_PANIC, "unable to open "
473         "private key file for reading: %s", dkim_private_key);
474       rc = NULL;
475       goto CLEANUP;
476     }
477     (void)read(privkey_fd,big_buffer,(big_buffer_size-2));
478     (void)close(privkey_fd);
479     dkim_private_key = big_buffer;
480   }
481
482   ctx = pdkim_init_sign(PDKIM_INPUT_SMTP,
483                         (char *)dkim_signing_domain,
484                         (char *)dkim_signing_selector,
485                         (char *)dkim_private_key
486                        );
487
488   pdkim_set_debug_stream(ctx,debug_file);
489
490   pdkim_set_optional(ctx,
491                      (char *)dkim_sign_headers,
492                      NULL,
493                      pdkim_canon,
494                      pdkim_canon,
495                      -1,
496                      PDKIM_ALGO_RSA_SHA256,
497                      0,
498                      0);
499
500   while((sread = read(dkim_fd,&buf,4096)) > 0) {
501     if (pdkim_feed(ctx,buf,sread) != PDKIM_OK) {
502       rc = NULL;
503       goto CLEANUP;
504     }
505   }
506   /* Handle failed read above. */
507   if (sread == -1) {
508     debug_printf("DKIM: Error reading -K file.\n");
509     save_errno = errno;
510     rc = NULL;
511     goto CLEANUP;
512   }
513
514   if (pdkim_feed_finish(ctx,&signature) != PDKIM_OK)
515     goto CLEANUP;
516
517   rc = store_get(strlen(signature->signature_header)+3);
518   Ustrcpy(rc,US signature->signature_header);
519   Ustrcat(rc,US"\r\n");
520
521   CLEANUP:
522   if (ctx != NULL) {
523     pdkim_free_ctx(ctx);
524   }
525   store_pool = old_pool;
526   errno = save_errno;
527   return rc;
528 };
529
530 #endif