2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <openssl/err.h>
17 #include <openssl/evp.h>
18 #include <openssl/pem.h>
19 #include <openssl/sha.h>
32 #include "openbsd-compat.h"
33 #include "opensmtpd.h"
36 struct dkim_signature {
47 size_t body_whitelines;
49 struct dkim_signature signature;
53 /* RFC 6376 section 5.4.1 */
54 static char *dsign_headers[] = {
75 static char **sign_headers = dsign_headers;
76 static size_t nsign_headers = sizeof(dsign_headers) / sizeof(*dsign_headers);
78 static char *hashalg = "sha256";
79 static char *cryptalg = "rsa";
81 #define CANON_SIMPLE 0
82 #define CANON_RELAXED 1
83 static int canonheader = CANON_SIMPLE;
84 static int canonbody = CANON_SIMPLE;
86 static int addtime = 0;
87 static long long addexpire = 0;
88 static int addheaders = 0;
90 static char **domain = NULL;
91 static size_t ndomains = 0;
92 static char *selector = NULL;
94 static EVP_PKEY *pkey;
95 static const EVP_MD *hash_md;
96 static int keyid = EVP_PKEY_RSA;
97 static int sephash = 0;
99 #define DKIM_SIGNATURE_LINELEN 78
102 void dkim_adddomain(char *);
103 void dkim_headers_set(char *);
104 int dkim_dataline(struct osmtpd_ctx *, const char *);
105 void *dkim_message_new(struct osmtpd_ctx *);
106 void dkim_message_free(struct osmtpd_ctx *, void *);
107 void dkim_parse_header(struct dkim_message *, char *, int);
108 void dkim_parse_body(struct dkim_message *, char *);
109 void dkim_sign(struct osmtpd_ctx *);
110 void dkim_signature_printheader(struct dkim_message *, const char *);
111 void dkim_signature_printf(struct dkim_message *, char *, ...)
112 __attribute__((__format__ (printf, 2, 3)));
113 void dkim_signature_normalize(struct dkim_message *);
114 const char *dkim_domain_select(struct dkim_message *, char *);
115 void dkim_signature_need(struct dkim_message *, size_t);
116 int dkim_sign_init(struct dkim_message *);
119 main(int argc, char *argv[])
128 while ((ch = getopt(argc, argv, "a:c:D:d:h:k:s:tx:z")) != -1) {
131 if (strncmp(optarg, "rsa-", 4) == 0) {
133 hashalg = optarg + 4;
134 keyid = EVP_PKEY_RSA;
136 } else if (strncmp(optarg, "ed25519-", 8) == 0) {
137 hashalg = optarg + 8;
138 cryptalg = "ed25519";
139 keyid = EVP_PKEY_ED25519;
142 osmtpd_errx(1, "invalid algorithm");
145 if (strncmp(optarg, "simple", 6) == 0) {
146 canonheader = CANON_SIMPLE;
148 } else if (strncmp(optarg, "relaxed", 7) == 0) {
149 canonheader = CANON_RELAXED;
152 osmtpd_errx(1, "Invalid canonicalization");
153 if (optarg[0] == '/') {
154 if (strcmp(optarg + 1, "simple") == 0)
155 canonbody = CANON_SIMPLE;
156 else if (strcmp(optarg + 1, "relaxed") == 0)
157 canonbody = CANON_RELAXED;
160 "Invalid canonicalization");
161 } else if (optarg[0] == '\0')
162 canonbody = CANON_SIMPLE;
164 osmtpd_errx(1, "Invalid canonicalization");
167 if ((file = fopen(optarg, "r")) == NULL)
168 osmtpd_err(1, "Can't open domain file (%s)",
173 linelen = getline(&line, &linesz, file);
175 if (line[linelen - 1] == '\n')
176 line[linelen - 1] = '\0';
179 dkim_adddomain(line);
181 } while (linelen != -1);
183 osmtpd_err(1, "Error reading domain file (%s)",
188 dkim_adddomain(optarg);
191 dkim_headers_set(optarg);
194 if ((file = fopen(optarg, "r")) == NULL)
195 osmtpd_err(1, "Can't open key file (%s)",
197 pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL);
199 osmtpd_errx(1, "Can't read key file");
209 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
211 osmtpd_errx(1, "Expire offset is %s", errstr);
221 OpenSSL_add_all_digests();
223 if (pledge("tmppath stdio", NULL) == -1)
224 osmtpd_err(1, "pledge");
226 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
227 osmtpd_errx(1, "Can't find hash: %s", hashalg);
229 if (domain == NULL || selector == NULL || pkey == NULL)
232 if (EVP_PKEY_id(pkey) != keyid)
233 osmtpd_errx(1, "Key is not of type %s", cryptalg);
235 osmtpd_register_filter_dataline(dkim_dataline);
236 osmtpd_local_message(dkim_message_new, dkim_message_free);
243 dkim_adddomain(char *d)
245 domain = reallocarray(domain, ndomains + 1, sizeof(*domain));
247 osmtpd_err(1, "reallocarray");
248 domain[ndomains++] = d;
252 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
254 struct dkim_message *message = ctx->local_message;
258 linelen = strlen(line);
259 if (fprintf(message->origf, "%s\n", line) < (int) linelen) {
260 osmtpd_warnx(ctx, "Couldn't write to tempfile");
264 if (line[0] == '.' && line[1] =='\0') {
266 } else if (linelen != 0 && message->parsing_headers) {
269 if ((linedup = strdup(line)) == NULL)
270 osmtpd_err(1, "strdup");
271 dkim_parse_header(message, linedup, 0);
273 } else if (linelen == 0 && message->parsing_headers) {
275 dkim_signature_printf(message, "; ");
276 message->parsing_headers = 0;
280 if ((linedup = strdup(line)) == NULL)
281 osmtpd_err(1, "strdup");
282 dkim_parse_body(message, linedup);
290 dkim_message_new(struct osmtpd_ctx *ctx)
292 struct dkim_message *message;
294 if ((message = calloc(1, sizeof(*message))) == NULL) {
295 osmtpd_warn(ctx, "calloc");
299 if ((message->origf = tmpfile()) == NULL) {
300 osmtpd_warn(ctx, "Failed to open tempfile");
303 message->parsing_headers = 1;
305 message->body_whitelines = 0;
306 message->headers = calloc(1, sizeof(*(message->headers)));
307 if (message->headers == NULL) {
308 osmtpd_warn(ctx, "calloc");
311 message->lastheader = 0;
312 message->signature.signature = NULL;
313 message->signature.size = 0;
314 message->signature.len = 0;
316 dkim_signature_printf(message,
317 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
319 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
320 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector);
322 dkim_signature_printf(message, "z=");
324 if ((message->dctx = EVP_MD_CTX_new()) == NULL) {
325 osmtpd_warnx(ctx, "EVP_MD_CTX_new");
328 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0) {
329 osmtpd_warnx(ctx, "EVP_DigestInit_ex");
335 dkim_message_free(ctx, message);
340 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
342 struct dkim_message *message = data;
345 fclose(message->origf);
346 EVP_MD_CTX_free(message->dctx);
347 free(message->signature.signature);
348 for (i = 0; message->headers != NULL &&
349 message->headers[i] != NULL; i++)
350 free(message->headers[i]);
351 free(message->headers);
356 dkim_headers_set(char *headers)
363 for (i = 0; headers[i] != '\0'; i++) {
364 /* RFC 5322 field-name */
365 if (!(headers[i] >= 33 && headers[i] <= 126))
366 osmtpd_errx(1, "-h: invalid character");
367 if (headers[i] == ':') {
368 /* Test for empty headers */
369 if (i == 0 || headers[i - 1] == ':')
370 osmtpd_errx(1, "-h: header can't be empty");
373 headers[i] = tolower(headers[i]);
375 if (headers[i - 1] == ':')
376 osmtpd_errx(1, "-h: header can't be empty");
378 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
379 sizeof(*sign_headers))) == NULL)
380 osmtpd_errx(1, "reallocarray");
382 for (i = 0; i < nsign_headers; i++) {
383 sign_headers[i] = headers;
384 if (i != nsign_headers - 1) {
385 headers = strchr(headers, ':');
388 if (strcasecmp(sign_headers[i], "from") == 0)
392 osmtpd_errx(1, "From header must be included");
396 dkim_parse_header(struct dkim_message *message, char *line, int force)
408 if (addheaders == 2 && !force)
409 dkim_signature_printheader(message, line);
411 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
413 if ((line[0] != ' ' && line[0] != '\t')) {
414 message->lastheader = 0;
415 for (i = 0; i < nsign_headers; i++) {
416 hlen = strlen(sign_headers[i]);
417 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
418 while (line[hlen] == ' ' || line[hlen] == '\t')
420 if (line[hlen] != ':')
425 if (i == nsign_headers && !force)
429 if (addheaders == 1 && !force)
430 dkim_signature_printheader(message, line);
432 if (canonheader == CANON_RELAXED) {
433 if (!message->lastheader)
435 for (r = w = 0; line[r] != '\0'; r++) {
436 if (line[r] == ':' && fieldname) {
437 if (w > 0 && line[w - 1] == ' ')
442 while (line[r + 1] == ' ' ||
447 if (line[r] == ' ' || line[r] == '\t' ||
448 line[r] == '\r' || line[r] == '\n') {
449 if (r != 0 && w != 0 && line[w - 1] == ' ')
453 } else if (fieldname) {
454 line[w++] = tolower(line[r]);
459 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
460 line[linelen] = '\0';
462 linelen = strlen(line);
464 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
466 if (!message->lastheader) {
467 mtmp = recallocarray(message->headers, lastheader + 1,
468 lastheader + 2, sizeof(*mtmp));
470 osmtpd_err(1, "reallocarray");
471 message->headers = mtmp;
473 if ((message->headers[lastheader] = strdup(line)) == NULL)
474 osmtpd_err(1, "strdup");
475 message->headers[lastheader + 1 ] = NULL;
476 message->lastheader = 1;
479 linelen += strlen(message->headers[lastheader]);
480 if (canonheader == CANON_SIMPLE)
483 htmp = reallocarray(message->headers[lastheader], linelen,
486 osmtpd_err(1, "reallocarray");
487 message->headers[lastheader] = htmp;
488 if (canonheader == CANON_SIMPLE) {
489 (void)strlcat(htmp, "\r\n", linelen);
490 } else if (canonheader == CANON_RELAXED &&
491 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
495 (void)strlcat(htmp, line, linelen);
500 dkim_parse_body(struct dkim_message *message, char *line)
505 if (canonbody == CANON_RELAXED) {
506 for (r = w = 0; line[r] != '\0'; r++) {
507 if (line[r] == ' ' || line[r] == '\t') {
508 if (r != 0 && line[w - 1] == ' ')
515 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
516 line[linelen] = '\0';
518 linelen = strlen(line);
520 if (line[0] == '\0') {
521 message->body_whitelines++;
525 while (message->body_whitelines--) {
526 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
527 osmtpd_errx(1, "EVP_DigestUpdate");
529 message->body_whitelines = 0;
530 message->has_body = 1;
532 if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
533 EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
534 osmtpd_errx(1, "EVP_DigestUpdate");
538 dkim_sign(struct osmtpd_ctx *ctx)
540 struct dkim_message *message = ctx->local_message;
541 /* Use largest hash size here */
542 unsigned char bdigest[EVP_MAX_MD_SIZE];
543 unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
545 const char *sdomain = domain[0], *tsdomain;
550 unsigned int digestsz;
552 if (addtime || addexpire)
555 dkim_signature_printf(message, "t=%lld; ", (long long)now);
557 dkim_signature_printf(message, "x=%lld; ",
558 now + addexpire < now ? LLONG_MAX : now + addexpire);
560 if (canonbody == CANON_SIMPLE && !message->has_body) {
561 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
562 osmtpd_errx(1, "EVP_DigestUpdate");
564 if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0)
565 osmtpd_errx(1, "EVP_DigestFinal_ex");
566 EVP_EncodeBlock(digest, bdigest, digestsz);
567 dkim_signature_printf(message, "bh=%s; h=", digest);
568 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
569 for (i = 0; message->headers[i] != NULL; i++)
571 EVP_MD_CTX_reset(message->dctx);
573 if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
575 osmtpd_errx(1, "EVP_DigestSignInit");
577 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1)
578 osmtpd_errx(1, "EVP_DigestInit_ex");
580 for (i--; i >= 0; i--) {
582 if (EVP_DigestSignUpdate(message->dctx,
584 strlen(message->headers[i])) != 1 ||
585 EVP_DigestSignUpdate(message->dctx, "\r\n",
587 osmtpd_errx(1, "EVP_DigestSignUpdate");
589 if (EVP_DigestUpdate(message->dctx, message->headers[i],
590 strlen(message->headers[i])) != 1 ||
591 EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
592 osmtpd_errx(1, "EVP_DigestSignUpdate");
594 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
596 /* We're done with the cached header after hashing */
597 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
598 if (tmp[0] == ' ' || tmp[0] == '\t')
600 tmp[0] = tolower(tmp[0]);
603 dkim_signature_printf(message, "%s%s",
604 message->headers[i + 1] == NULL ? "" : ":",
605 message->headers[i]);
607 dkim_signature_printf(message, "; d=%s; b=", sdomain);
608 dkim_signature_normalize(message);
609 if ((tmp = strdup(message->signature.signature)) == NULL)
610 osmtpd_err(1, "strdup");
611 dkim_parse_header(message, tmp, 1);
613 if (EVP_DigestSignUpdate(message->dctx, tmp,
615 osmtpd_errx(1, "EVP_DigestSignUpdate");
617 if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1)
618 osmtpd_errx(1, "EVP_DigestUpdate");
622 if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1)
623 osmtpd_errx(1, "EVP_DigestSignFinal");
625 if (EVP_DigestFinal_ex(message->dctx, bdigest,
627 osmtpd_errx(1, "EVP_DigestFinal_ex");
628 EVP_MD_CTX_reset(message->dctx);
629 if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
631 osmtpd_errx(1, "EVP_DigestSignInit");
632 if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
634 osmtpd_errx(1, "EVP_DigestSign");
636 if ((tmp = malloc(linelen)) == NULL)
637 osmtpd_err(1, "malloc");
639 if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1)
640 osmtpd_errx(1, "EVP_DigestSignFinal");
642 if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
644 osmtpd_errx(1, "EVP_DigestSign");
646 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL)
647 osmtpd_err(1, "malloc");
648 EVP_EncodeBlock(b, tmp, linelen);
650 dkim_signature_printf(message, "%s\r\n", b);
652 dkim_signature_normalize(message);
653 tmp = message->signature.signature;
654 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
656 osmtpd_filter_dataline(ctx, "%s", tmp);
661 rewind(message->origf);
662 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
664 osmtpd_filter_dataline(ctx, "%s", tmp);
671 dkim_signature_normalize(struct dkim_message *message)
677 size_t *headerlen = &(message->signature.len);
680 char *sig = message->signature.signature;
682 for (linelen = i = 0; sig[i] != '\0'; i++) {
683 if (sig[i] == '\r' && sig[i + 1] == '\n') {
690 linelen = (linelen + 8) & ~7;
700 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
701 for (skip = checkpoint + 1;
702 sig[skip] == ' ' || sig[skip] == '\t';
705 skip -= checkpoint + 1;
706 dkim_signature_need(message,
707 skip > 3 ? 0 : 3 - skip + 1);
708 sig = message->signature.signature;
710 memmove(sig + checkpoint + 3,
711 sig + checkpoint + skip,
712 *headerlen - skip - checkpoint + 1);
713 sig[checkpoint + 1] = '\r';
714 sig[checkpoint + 2] = '\n';
715 sig[checkpoint + 3] = '\t';
717 *headerlen = *headerlen + 3 - skip;
737 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
738 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
750 dkim_signature_printheader(struct dkim_message *message, const char *line)
755 first = message->signature.signature[message->signature.len - 1] == '=';
756 for (i = 0; line[i] != '\0'; i++) {
757 if (i == 0 && line[i] != ' ' && line[i] != '\t' && !first)
758 dkim_signature_printf(message, "|");
759 if ((line[i] >= 0x21 && line[i] <= 0x3A) ||
761 (line[i] >= 0x3E && line[i] <= 0x7B) ||
762 (line[i] >= 0x7D && line[i] <= 0x7E)) {
763 dkim_signature_printf(message, "%c", line[i]);
765 dkim_signature_printf(message, "=%02hhX", line[i]);
767 dkim_signature_printf(message, "=%02hhX=%02hhX", '\r', '\n');
771 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
773 struct dkim_signature *sig = &(message->signature);
778 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
779 fmt, ap)) >= sig->size - sig->len) {
781 dkim_signature_need(message, len + 1);
783 if ((len = vsnprintf(sig->signature + sig->len,
784 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
785 osmtpd_errx(1, "Miscalculated header size");
792 dkim_domain_select(struct dkim_message *message, char *from)
794 char *mdomain0, *mdomain;
797 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL)
800 while (mdomain != NULL && mdomain[0] != '\0') {
801 for (i = 0; i < ndomains; i++) {
802 if (strcasecmp(mdomain, domain[i]) == 0) {
807 if ((mdomain = strchr(mdomain, '.')) != NULL)
815 dkim_signature_need(struct dkim_message *message, size_t len)
817 struct dkim_signature *sig = &(message->signature);
820 if (sig->len + len < sig->size)
822 sig->size = (((len + sig->len) / 512) + 1) * 512;
823 if ((tmp = realloc(sig->signature, sig->size)) == NULL)
824 osmtpd_err(1, "malloc");
825 sig->signature = tmp;
831 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
832 "[-c canonicalization] \n [-h headerfields]"
833 "[-x seconds] -D file -d domain -k keyfile -s selector\n");