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