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/evp.h>
17 #include <openssl/pem.h>
18 #include <openssl/sha.h>
30 #include "opensmtpd.h"
33 struct dkim_signature {
44 size_t body_whitelines;
46 struct dkim_signature signature;
52 /* RFC 6376 section 5.4.1 */
53 static char *dsign_headers[] = {
74 static char **sign_headers = dsign_headers;
75 static size_t nsign_headers = sizeof(dsign_headers) / sizeof(*dsign_headers);
77 static char *hashalg = "sha256";
78 static char *cryptalg = "rsa";
80 #define CANON_SIMPLE 0
81 #define CANON_RELAXED 1
82 static int canonheader = CANON_SIMPLE;
83 static int canonbody = CANON_SIMPLE;
85 static int addtime = 0;
86 static long long addexpire = 0;
87 static int addheaders = 0;
89 static char **domain = NULL;
90 static size_t ndomains = 0;
91 static char *selector = NULL;
93 static EVP_PKEY *pkey;
94 static const EVP_MD *hash_md;
96 #define DKIM_SIGNATURE_LINELEN 78
99 void dkim_err(struct dkim_message *, char *);
100 void dkim_errx(struct dkim_message *, char *);
101 void dkim_headers_set(char *);
102 void dkim_dataline(struct osmtpd_ctx *, const char *);
103 void dkim_commit(struct osmtpd_ctx *);
104 void *dkim_message_new(struct osmtpd_ctx *);
105 void dkim_message_free(struct osmtpd_ctx *, void *);
106 void dkim_parse_header(struct dkim_message *, char *, int);
107 void dkim_parse_body(struct dkim_message *, char *);
108 void dkim_sign(struct osmtpd_ctx *);
109 int dkim_signature_printheader(struct dkim_message *, const char *);
110 int dkim_signature_printf(struct dkim_message *, char *, ...)
111 __attribute__((__format__ (printf, 2, 3)));
112 int dkim_signature_normalize(struct dkim_message *);
113 const char *dkim_domain_select(struct dkim_message *, char *);
114 int dkim_signature_need(struct dkim_message *, size_t);
115 int dkim_sign_init(struct dkim_message *);
118 main(int argc, char *argv[])
124 while ((ch = getopt(argc, argv, "a:c:d:h:k:s:tx:z")) != -1) {
127 if (strncmp(optarg, "rsa-", 4) != 0)
128 osmtpd_err(1, "invalid algorithm");
129 hashalg = optarg + 4;
132 if (strncmp(optarg, "simple", 6) == 0) {
133 canonheader = CANON_SIMPLE;
135 } else if (strncmp(optarg, "relaxed", 7) == 0) {
136 canonheader = CANON_RELAXED;
139 osmtpd_err(1, "Invalid canonicalization");
140 if (optarg[0] == '/') {
141 if (strcmp(optarg + 1, "simple") == 0)
142 canonbody = CANON_SIMPLE;
143 else if (strcmp(optarg + 1, "relaxed") == 0)
144 canonbody = CANON_RELAXED;
147 "Invalid canonicalization");
148 } else if (optarg[0] == '\0')
149 canonbody = CANON_SIMPLE;
151 osmtpd_err(1, "Invalid canonicalization");
154 if ((domain = reallocarray(domain, ndomains + 1,
155 sizeof(*domain))) == NULL)
156 osmtpd_err(1, "malloc");
157 domain[ndomains++] = optarg;
160 dkim_headers_set(optarg);
163 if ((keyfile = fopen(optarg, "r")) == NULL)
164 osmtpd_err(1, "Can't open key file (%s)",
166 pkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
168 osmtpd_errx(1, "Can't read key file");
169 if (EVP_PKEY_get0_RSA(pkey) == NULL)
170 osmtpd_err(1, "Key is not of type rsa");
180 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
182 osmtpd_errx(1, "Expire offset is %s", errstr);
192 OpenSSL_add_all_digests();
193 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
194 osmtpd_errx(1, "Can't find hash: %s", hashalg);
196 if (pledge("tmppath stdio", NULL) == -1)
197 osmtpd_err(1, "pledge");
199 if (domain == NULL || selector == NULL || pkey == NULL)
202 osmtpd_register_filter_dataline(dkim_dataline);
203 osmtpd_register_filter_commit(dkim_commit);
204 osmtpd_local_message(dkim_message_new, dkim_message_free);
211 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
213 struct dkim_message *message = ctx->local_message;
218 if (line[0] == '.' && line[1] =='\0')
219 osmtpd_filter_dataline(ctx, ".");
223 linelen = strlen(line);
224 if (fprintf(message->origf, "%s\n", line) < (int) linelen)
225 dkim_err(message, "Couldn't write to tempfile");
227 if (line[0] == '.' && line[1] =='\0') {
229 } else if (linelen != 0 && message->parsing_headers) {
232 if ((linedup = strdup(line)) == NULL)
233 osmtpd_err(1, "strdup");
234 dkim_parse_header(message, linedup, 0);
236 } else if (linelen == 0 && message->parsing_headers) {
237 if (addheaders > 0 && !dkim_signature_printf(message, "; "))
239 message->parsing_headers = 0;
243 if ((linedup = strdup(line)) == NULL)
244 osmtpd_err(1, "strdup");
245 dkim_parse_body(message, linedup);
251 dkim_commit(struct osmtpd_ctx *ctx)
253 struct dkim_message *message = ctx->local_message;
256 osmtpd_filter_disconnect(ctx, "Internal server error");
258 osmtpd_filter_proceed(ctx);
262 dkim_message_new(struct osmtpd_ctx *ctx)
264 struct dkim_message *message;
266 if ((message = calloc(1, sizeof(*message))) == NULL)
269 if ((message->origf = tmpfile()) == NULL) {
270 dkim_err(message, "Can't open tempfile");
273 message->parsing_headers = 1;
275 message->body_whitelines = 0;
276 message->headers = calloc(1, sizeof(*(message->headers)));
277 if (message->headers == NULL) {
278 dkim_err(message, "Can't save headers");
281 message->lastheader = 0;
282 message->signature.signature = NULL;
283 message->signature.size = 0;
284 message->signature.len = 0;
287 if (!dkim_signature_printf(message,
288 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
290 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
291 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
293 if (addheaders > 0 && !dkim_signature_printf(message, "z="))
296 if ((message->b = EVP_MD_CTX_new()) == NULL ||
297 (message->bh = EVP_MD_CTX_new()) == NULL) {
298 dkim_errx(message, "Can't create hash context");
301 if (EVP_DigestSignInit(message->b, NULL, hash_md, NULL, pkey) <= 0 ||
302 EVP_DigestInit_ex(message->bh, hash_md, NULL) == 0) {
303 dkim_errx(message, "Failed to initialize hash context");
310 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
312 struct dkim_message *message = data;
315 fclose(message->origf);
316 EVP_MD_CTX_free(message->b);
317 EVP_MD_CTX_free(message->bh);
318 free(message->signature.signature);
319 for (i = 0; message->headers[i] != NULL; i++)
320 free(message->headers[i]);
321 free(message->headers);
326 dkim_headers_set(char *headers)
333 for (i = 0; headers[i] != '\0'; i++) {
334 /* RFC 5322 field-name */
335 if (!(headers[i] >= 33 && headers[i] <= 126))
336 osmtpd_errx(1, "-h: invalid character");
337 if (headers[i] == ':') {
338 /* Test for empty headers */
339 if (i == 0 || headers[i - 1] == ':')
340 osmtpd_errx(1, "-h: header can't be empty");
343 headers[i] = tolower(headers[i]);
345 if (headers[i - 1] == ':')
346 osmtpd_errx(1, "-h: header can't be empty");
348 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
349 sizeof(*sign_headers))) == NULL)
350 osmtpd_errx(1, NULL);
352 for (i = 0; i < nsign_headers; i++) {
353 sign_headers[i] = headers;
354 if (i != nsign_headers - 1) {
355 headers = strchr(headers, ':');
358 if (strcasecmp(sign_headers[i], "from") == 0)
362 osmtpd_errx(1, "From header must be included");
366 dkim_err(struct dkim_message *message, char *msg)
369 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
373 dkim_errx(struct dkim_message *message, char *msg)
376 fprintf(stderr, "%s\n", msg);
380 dkim_parse_header(struct dkim_message *message, char *line, int force)
392 if (addheaders == 2 && !force &&
393 !dkim_signature_printheader(message, line))
396 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
398 if ((line[0] != ' ' && line[0] != '\t')) {
399 message->lastheader = 0;
400 for (i = 0; i < nsign_headers; i++) {
401 hlen = strlen(sign_headers[i]);
402 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
403 while (line[hlen] == ' ' || line[hlen] == '\t')
405 if (line[hlen] != ':')
410 if (i == nsign_headers && !force)
414 if (addheaders == 1 && !force &&
415 !dkim_signature_printheader(message, line))
418 if (canonheader == CANON_RELAXED) {
419 if (!message->lastheader)
421 for (r = w = 0; line[r] != '\0'; r++) {
422 if (line[r] == ':' && fieldname) {
423 if (w > 0 && line[w - 1] == ' ')
428 while (line[r + 1] == ' ' ||
433 if (line[r] == ' ' || line[r] == '\t' ||
434 line[r] == '\r' || line[r] == '\n') {
435 if (r != 0 && w != 0 && line[w - 1] == ' ')
439 } else if (fieldname) {
440 line[w++] = tolower(line[r]);
445 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
446 line[linelen] = '\0';
448 linelen = strlen(line);
450 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
452 if (!message->lastheader) {
453 mtmp = recallocarray(message->headers, lastheader + 1,
454 lastheader + 2, sizeof(*mtmp));
456 dkim_err(message, "Can't store header");
459 message->headers = mtmp;
461 message->headers[lastheader] = strdup(line);
462 message->headers[lastheader + 1 ] = NULL;
463 message->lastheader = 1;
466 linelen += strlen(message->headers[lastheader]);
467 if (canonheader == CANON_SIMPLE)
470 htmp = reallocarray(message->headers[lastheader], linelen,
473 dkim_err(message, "Can't store header");
476 message->headers[lastheader] = htmp;
477 if (canonheader == CANON_SIMPLE) {
478 if (strlcat(htmp, "\r\n", linelen) >= linelen)
479 osmtpd_errx(1, "Missized header");
480 } else if (canonheader == CANON_RELAXED &&
481 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
485 if (strlcat(htmp, line, linelen) >= linelen)
486 osmtpd_errx(1, "Missized header");
491 dkim_parse_body(struct dkim_message *message, char *line)
496 if (canonbody == CANON_RELAXED) {
497 for (r = w = 0; line[r] != '\0'; r++) {
498 if (line[r] == ' ' || line[r] == '\t') {
499 if (r != 0 && line[w - 1] == ' ')
506 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
507 line[linelen] = '\0';
509 linelen = strlen(line);
511 if (line[0] == '\0') {
512 message->body_whitelines++;
516 while (message->body_whitelines--) {
517 if (EVP_DigestUpdate(message->bh, "\r\n", 2) == 0) {
518 dkim_err(message, "Can't update hash context");
522 message->body_whitelines = 0;
523 message->has_body = 1;
525 if (EVP_DigestUpdate(message->bh, line, linelen) == 0 ||
526 EVP_DigestUpdate(message->bh, "\r\n", 2) == 0) {
527 dkim_err(message, "Can't update hash context");
533 dkim_sign(struct osmtpd_ctx *ctx)
535 struct dkim_message *message = ctx->local_message;
536 /* Use largest hash size here */
537 char bbh[EVP_MAX_MD_SIZE];
538 char bh[(((sizeof(bbh) + 2) / 3) * 4) + 1];
540 const char *sdomain = domain[0], *tsdomain;
546 if (addtime || addexpire)
548 if (addtime && !dkim_signature_printf(message, "t=%lld; ", now))
550 if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
551 now + addexpire < now ? INT64_MAX : now + addexpire))
554 if (canonbody == CANON_SIMPLE && !message->has_body) {
555 if (EVP_DigestUpdate(message->bh, "\r\n", 2) <= 0) {
556 dkim_err(message, "Can't update hash context");
560 if (EVP_DigestFinal_ex(message->bh, bbh, NULL) == 0) {
561 dkim_err(message, "Can't finalize hash context");
564 EVP_EncodeBlock(bh, bbh, EVP_MD_CTX_size(message->bh));
565 if (!dkim_signature_printf(message, "bh=%s; h=", bh))
567 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
568 for (i = 0; message->headers[i] != NULL; i++)
570 for (i--; i >= 0; i--) {
571 if (EVP_DigestSignUpdate(message->b,
573 strlen(message->headers[i])) <= 0 ||
574 EVP_DigestSignUpdate(message->b, "\r\n", 2) <= 0) {
575 dkim_errx(message, "Failed to update digest context");
578 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
580 /* We're done with the cached header after hashing */
581 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
582 if (tmp[0] == ' ' || tmp[0] == '\t')
584 tmp[0] = tolower(tmp[0]);
587 if (!dkim_signature_printf(message, "%s%s",
588 message->headers[i + 1] == NULL ? "" : ":",
589 message->headers[i]))
592 dkim_signature_printf(message, "; d=%s; b=", sdomain);
593 if (!dkim_signature_normalize(message))
595 if ((tmp = strdup(message->signature.signature)) == NULL) {
596 dkim_err(message, "Can't create DKIM signature");
599 dkim_parse_header(message, tmp, 1);
600 if (EVP_DigestSignUpdate(message->b, tmp, strlen(tmp)) <= 0) {
601 dkim_err(message, "Failed to update digest context");
605 if (EVP_DigestSignFinal(message->b, NULL, &linelen) <= 0) {
606 dkim_err(message, "Failed to finalize digest");
609 if ((tmp = malloc(linelen)) == NULL) {
610 dkim_err(message, "Can't allocate space for digest");
613 if (EVP_DigestSignFinal(message->b, tmp, &linelen) <= 0) {
614 dkim_err(message, "Failed to finalize digest");
617 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
618 dkim_err(message, "Can't create DKIM signature");
621 EVP_EncodeBlock(b, tmp, linelen);
623 dkim_signature_printf(message, "%s\r\n", b);
625 dkim_signature_normalize(message);
626 tmp = message->signature.signature;
627 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
629 osmtpd_filter_dataline(ctx, "%s", tmp);
634 rewind(message->origf);
635 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
637 osmtpd_filter_dataline(ctx, "%s", tmp);
642 dkim_signature_normalize(struct dkim_message *message)
648 size_t *headerlen = &(message->signature.len);
651 char *sig = message->signature.signature;
653 for (linelen = i = 0; sig[i] != '\0'; i++) {
654 if (sig[i] == '\r' && sig[i + 1] == '\n') {
661 linelen = (linelen + 8) & ~7;
671 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
672 for (skip = checkpoint + 1;
673 sig[skip] == ' ' || sig[skip] == '\t';
676 skip -= checkpoint + 1;
677 if (!dkim_signature_need(message,
678 skip > 3 ? 0 : 3 - skip + 1))
680 sig = message->signature.signature;
682 memmove(sig + checkpoint + 3,
683 sig + checkpoint + skip,
684 *headerlen - skip - checkpoint + 1);
685 sig[checkpoint + 1] = '\r';
686 sig[checkpoint + 2] = '\n';
687 sig[checkpoint + 3] = '\t';
689 *headerlen = *headerlen + 3 - skip;
709 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
710 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
723 dkim_signature_printheader(struct dkim_message *message, const char *header)
726 static char *fmtheader = NULL;
728 static size_t size = 0;
731 len = strlen(header);
732 if ((len + 3) * 3 < len) {
734 dkim_err(message, "Can't add z-component to header");
737 if ((len + 3) * 3 > size) {
738 if ((tmp = reallocarray(fmtheader, 3, len + 3)) == NULL) {
739 dkim_err(message, "Can't add z-component to header");
743 size = (len + 1) * 3;
746 first = message->signature.signature[message->signature.len - 1] == '=';
747 for (j = i = 0; header[i] != '\0'; i++, j++) {
748 if (i == 0 && header[i] != ' ' && header[i] != '\t' && !first)
749 fmtheader[j++] = '|';
750 if ((header[i] >= 0x21 && header[i] <= 0x3A) ||
751 (header[i] == 0x3C) ||
752 (header[i] >= 0x3E && header[i] <= 0x7B) ||
753 (header[i] >= 0x7D && header[i] <= 0x7E))
754 fmtheader[j] = header[i];
756 fmtheader[j++] = '=';
757 (void) sprintf(fmtheader + j, "%02hhX", header[i]);
761 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
762 (unsigned char) '\n');
764 return dkim_signature_printf(message, "%s", fmtheader);
768 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
770 struct dkim_signature *sig = &(message->signature);
775 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
776 fmt, ap)) >= sig->size - sig->len) {
778 if (!dkim_signature_need(message, len + 1))
781 if ((len = vsnprintf(sig->signature + sig->len,
782 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
783 osmtpd_errx(1, "Miscalculated header size");
791 dkim_domain_select(struct dkim_message *message, char *from)
793 char *mdomain0, *mdomain;
796 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
797 if (errno != EINVAL) {
798 dkim_err(message, "Couldn't parse from header");
804 while (mdomain != NULL && mdomain[0] != '\0') {
805 for (i = 0; i < ndomains; i++) {
806 if (strcasecmp(mdomain, domain[i]) == 0) {
811 if ((mdomain = strchr(mdomain, '.')) != NULL)
819 dkim_signature_need(struct dkim_message *message, size_t len)
821 struct dkim_signature *sig = &(message->signature);
824 if (sig->len + len < sig->size)
826 sig->size = (((len + sig->len) / 512) + 1) * 512;
827 if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
828 dkim_err(message, "No room for signature");
831 sig->signature = tmp;
838 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
839 "[-c canonicalization] \n [-h headerfields]"
840 "[-x seconds] -d domain -k keyfile -s selector\n");