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