Blob


1 /*
2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
3 *
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.
7 *
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.
15 */
16 #include <openssl/err.h>
17 #include <openssl/evp.h>
18 #include <openssl/pem.h>
19 #include <openssl/sha.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <syslog.h>
28 #include <time.h>
29 #include <unistd.h>
31 #include "openbsd-compat.h"
32 #include "opensmtpd.h"
33 #include "mheader.h"
35 struct dkim_signature {
36 char *signature;
37 size_t size;
38 size_t len;
39 };
41 struct dkim_message {
42 FILE *origf;
43 int parsing_headers;
44 char **headers;
45 int lastheader;
46 size_t body_whitelines;
47 int has_body;
48 struct dkim_signature signature;
49 int err;
50 EVP_MD_CTX *dctx;
51 };
53 /* RFC 6376 section 5.4.1 */
54 static char *dsign_headers[] = {
55 "from",
56 "reply-to",
57 "subject",
58 "date",
59 "to",
60 "cc",
61 "resent-date",
62 "resent-from",
63 "resent-to",
64 "resent-cc",
65 "in-reply-to",
66 "references",
67 "list-id",
68 "list-help",
69 "list-unsubscribe",
70 "list-subscribe",
71 "list-post",
72 "list-owner",
73 "list-archive"
74 };
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
101 void usage(void);
102 void dkim_err(struct dkim_message *, char *);
103 void dkim_errx(struct dkim_message *, char *);
104 void dkim_headers_set(char *);
105 void dkim_dataline(struct osmtpd_ctx *, const char *);
106 void dkim_commit(struct osmtpd_ctx *);
107 void *dkim_message_new(struct osmtpd_ctx *);
108 void dkim_message_free(struct osmtpd_ctx *, void *);
109 void dkim_parse_header(struct dkim_message *, char *, int);
110 void dkim_parse_body(struct dkim_message *, char *);
111 void dkim_sign(struct osmtpd_ctx *);
112 int dkim_signature_printheader(struct dkim_message *, const char *);
113 int dkim_signature_printf(struct dkim_message *, char *, ...)
114 __attribute__((__format__ (printf, 2, 3)));
115 int dkim_signature_normalize(struct dkim_message *);
116 const char *dkim_domain_select(struct dkim_message *, char *);
117 int dkim_signature_need(struct dkim_message *, size_t);
118 int dkim_sign_init(struct dkim_message *);
120 int
121 main(int argc, char *argv[])
123 int ch;
124 FILE *keyfile;
125 const char *errstr;
127 while ((ch = getopt(argc, argv, "a:c:d:h:k:s:tx:z")) != -1) {
128 switch (ch) {
129 case 'a':
130 if (strncmp(optarg, "rsa-", 4) == 0) {
131 cryptalg = "rsa";
132 hashalg = optarg + 4;
133 keyid = EVP_PKEY_RSA;
134 sephash = 0;
135 #ifdef HAVE_ED25519
136 } else if (strncmp(optarg, "ed25519-", 8) == 0) {
137 hashalg = optarg + 8;
138 cryptalg = "ed25519";
139 keyid = EVP_PKEY_ED25519;
140 sephash = 1;
141 #endif
142 } else
143 osmtpd_errx(1, "invalid algorithm");
144 break;
145 case 'c':
146 if (strncmp(optarg, "simple", 6) == 0) {
147 canonheader = CANON_SIMPLE;
148 optarg += 6;
149 } else if (strncmp(optarg, "relaxed", 7) == 0) {
150 canonheader = CANON_RELAXED;
151 optarg += 7;
152 } else
153 osmtpd_err(1, "Invalid canonicalization");
154 if (optarg[0] == '/') {
155 if (strcmp(optarg + 1, "simple") == 0)
156 canonbody = CANON_SIMPLE;
157 else if (strcmp(optarg + 1, "relaxed") == 0)
158 canonbody = CANON_RELAXED;
159 else
160 osmtpd_err(1,
161 "Invalid canonicalization");
162 } else if (optarg[0] == '\0')
163 canonbody = CANON_SIMPLE;
164 else
165 osmtpd_err(1, "Invalid canonicalization");
166 break;
167 case 'd':
168 if ((domain = reallocarray(domain, ndomains + 1,
169 sizeof(*domain))) == NULL)
170 osmtpd_err(1, "malloc");
171 domain[ndomains++] = optarg;
172 break;
173 case 'h':
174 dkim_headers_set(optarg);
175 break;
176 case 'k':
177 if ((keyfile = fopen(optarg, "r")) == NULL)
178 osmtpd_err(1, "Can't open key file (%s)",
179 optarg);
180 pkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
181 if (pkey == NULL)
182 osmtpd_errx(1, "Can't read key file");
183 fclose(keyfile);
184 break;
185 case 's':
186 selector = optarg;
187 break;
188 case 't':
189 addtime = 1;
190 break;
191 case 'x':
192 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
193 if (addexpire == 0)
194 osmtpd_errx(1, "Expire offset is %s", errstr);
195 break;
196 case 'z':
197 addheaders++;
198 break;
199 default:
200 usage();
204 OpenSSL_add_all_digests();
206 if (pledge("tmppath stdio", NULL) == -1)
207 osmtpd_err(1, "pledge");
209 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
210 osmtpd_errx(1, "Can't find hash: %s", hashalg);
212 if (domain == NULL || selector == NULL || pkey == NULL)
213 usage();
215 if (EVP_PKEY_id(pkey) != keyid)
216 osmtpd_errx(1, "Key is not of type %s", cryptalg);
218 osmtpd_register_filter_dataline(dkim_dataline);
219 osmtpd_register_filter_commit(dkim_commit);
220 osmtpd_local_message(dkim_message_new, dkim_message_free);
221 osmtpd_run();
223 return 0;
226 void
227 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
229 struct dkim_message *message = ctx->local_message;
230 char *linedup;
231 size_t linelen;
233 if (message->err) {
234 if (line[0] == '.' && line[1] =='\0')
235 osmtpd_filter_dataline(ctx, ".");
236 return;
239 linelen = strlen(line);
240 if (fprintf(message->origf, "%s\n", line) < (int) linelen)
241 dkim_errx(message, "Couldn't write to tempfile");
243 if (line[0] == '.' && line[1] =='\0') {
244 dkim_sign(ctx);
245 } else if (linelen != 0 && message->parsing_headers) {
246 if (line[0] == '.')
247 line++;
248 if ((linedup = strdup(line)) == NULL)
249 osmtpd_err(1, "strdup");
250 dkim_parse_header(message, linedup, 0);
251 free(linedup);
252 } else if (linelen == 0 && message->parsing_headers) {
253 if (addheaders > 0 && !dkim_signature_printf(message, "; "))
254 return;
255 message->parsing_headers = 0;
256 } else {
257 if (line[0] == '.')
258 line++;
259 if ((linedup = strdup(line)) == NULL)
260 osmtpd_err(1, "strdup");
261 dkim_parse_body(message, linedup);
262 free(linedup);
266 void
267 dkim_commit(struct osmtpd_ctx *ctx)
269 struct dkim_message *message = ctx->local_message;
271 if (message->err)
272 osmtpd_filter_disconnect(ctx, "Internal server error");
273 else
274 osmtpd_filter_proceed(ctx);
277 void *
278 dkim_message_new(struct osmtpd_ctx *ctx)
280 struct dkim_message *message;
282 if ((message = calloc(1, sizeof(*message))) == NULL) {
283 dkim_err(message, "Failed to create message context");
284 return NULL;
287 if ((message->origf = tmpfile()) == NULL) {
288 dkim_err(message, "Failed to open tempfile");
289 goto fail;
291 message->parsing_headers = 1;
293 message->body_whitelines = 0;
294 message->headers = calloc(1, sizeof(*(message->headers)));
295 if (message->headers == NULL) {
296 dkim_err(message, "Can't save headers");
297 goto fail;
299 message->lastheader = 0;
300 message->signature.signature = NULL;
301 message->signature.size = 0;
302 message->signature.len = 0;
303 message->err = 0;
305 if (!dkim_signature_printf(message,
306 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
307 cryptalg, hashalg,
308 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
309 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
310 goto fail;
311 if (addheaders > 0 && !dkim_signature_printf(message, "z="))
312 goto fail;
314 if ((message->dctx = EVP_MD_CTX_new()) == NULL) {
315 dkim_errx(message, "Failed to create hash context");
316 goto fail;
318 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0) {
319 dkim_errx(message, "Failed to initialize hash context");
320 goto fail;
322 return message;
323 fail:
324 dkim_message_free(ctx, message);
325 return NULL;
328 void
329 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
331 struct dkim_message *message = data;
332 size_t i;
334 fclose(message->origf);
335 EVP_MD_CTX_free(message->dctx);
336 free(message->signature.signature);
337 for (i = 0; message->headers != NULL &&
338 message->headers[i] != NULL; i++)
339 free(message->headers[i]);
340 free(message->headers);
341 free(message);
344 void
345 dkim_headers_set(char *headers)
347 size_t i;
348 int has_from = 0;
350 nsign_headers = 1;
352 for (i = 0; headers[i] != '\0'; i++) {
353 /* RFC 5322 field-name */
354 if (!(headers[i] >= 33 && headers[i] <= 126))
355 osmtpd_errx(1, "-h: invalid character");
356 if (headers[i] == ':') {
357 /* Test for empty headers */
358 if (i == 0 || headers[i - 1] == ':')
359 osmtpd_errx(1, "-h: header can't be empty");
360 nsign_headers++;
362 headers[i] = tolower(headers[i]);
364 if (headers[i - 1] == ':')
365 osmtpd_errx(1, "-h: header can't be empty");
367 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
368 sizeof(*sign_headers))) == NULL)
369 osmtpd_errx(1, NULL);
371 for (i = 0; i < nsign_headers; i++) {
372 sign_headers[i] = headers;
373 if (i != nsign_headers - 1) {
374 headers = strchr(headers, ':');
375 headers++[0] = '\0';
377 if (strcasecmp(sign_headers[i], "from") == 0)
378 has_from = 1;
380 if (!has_from)
381 osmtpd_errx(1, "From header must be included");
384 void
385 dkim_err(struct dkim_message *message, char *msg)
387 message->err = 1;
388 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
391 void
392 dkim_errx(struct dkim_message *message, char *msg)
394 message->err = 1;
395 fprintf(stderr, "%s\n", msg);
398 void
399 dkim_parse_header(struct dkim_message *message, char *line, int force)
401 size_t i;
402 size_t r, w;
403 size_t linelen;
404 size_t lastheader;
405 size_t hlen;
406 int fieldname = 0;
407 char **mtmp;
408 char *htmp;
409 char *tmp;
411 if (addheaders == 2 && !force &&
412 !dkim_signature_printheader(message, line))
413 return;
415 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
416 return;
417 if ((line[0] != ' ' && line[0] != '\t')) {
418 message->lastheader = 0;
419 for (i = 0; i < nsign_headers; i++) {
420 hlen = strlen(sign_headers[i]);
421 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
422 while (line[hlen] == ' ' || line[hlen] == '\t')
423 hlen++;
424 if (line[hlen] != ':')
425 continue;
426 break;
429 if (i == nsign_headers && !force)
430 return;
433 if (addheaders == 1 && !force &&
434 !dkim_signature_printheader(message, line))
435 return;
437 if (canonheader == CANON_RELAXED) {
438 if (!message->lastheader)
439 fieldname = 1;
440 for (r = w = 0; line[r] != '\0'; r++) {
441 if (line[r] == ':' && fieldname) {
442 if (w > 0 && line[w - 1] == ' ')
443 line[w - 1] = ':';
444 else
445 line[w++] = ':';
446 fieldname = 0;
447 while (line[r + 1] == ' ' ||
448 line[r + 1] == '\t')
449 r++;
450 continue;
452 if (line[r] == ' ' || line[r] == '\t' ||
453 line[r] == '\r' || line[r] == '\n') {
454 if (r != 0 && w != 0 && line[w - 1] == ' ')
455 continue;
456 else
457 line[w++] = ' ';
458 } else if (fieldname) {
459 line[w++] = tolower(line[r]);
460 continue;
461 } else
462 line[w++] = line[r];
464 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
465 line[linelen] = '\0';
466 } else
467 linelen = strlen(line);
469 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
470 continue;
471 if (!message->lastheader) {
472 mtmp = recallocarray(message->headers, lastheader + 1,
473 lastheader + 2, sizeof(*mtmp));
474 if (mtmp == NULL) {
475 dkim_err(message, "Can't store header");
476 return;
478 message->headers = mtmp;
480 if ((message->headers[lastheader] = strdup(line)) == NULL) {
481 dkim_err(message, "Can't store header");
482 return;
484 message->headers[lastheader + 1 ] = NULL;
485 message->lastheader = 1;
486 } else {
487 lastheader--;
488 linelen += strlen(message->headers[lastheader]);
489 if (canonheader == CANON_SIMPLE)
490 linelen += 2;
491 linelen++;
492 htmp = reallocarray(message->headers[lastheader], linelen,
493 sizeof(*htmp));
494 if (htmp == NULL) {
495 dkim_err(message, "Can't store header");
496 return;
498 message->headers[lastheader] = htmp;
499 if (canonheader == CANON_SIMPLE) {
500 if (strlcat(htmp, "\r\n", linelen) >= linelen)
501 osmtpd_errx(1, "Missized header");
502 } else if (canonheader == CANON_RELAXED &&
503 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
504 tmp[1] == '\0')
505 line++;
507 if (strlcat(htmp, line, linelen) >= linelen)
508 osmtpd_errx(1, "Missized header");
512 void
513 dkim_parse_body(struct dkim_message *message, char *line)
515 size_t r, w;
516 size_t linelen;
518 if (canonbody == CANON_RELAXED) {
519 for (r = w = 0; line[r] != '\0'; r++) {
520 if (line[r] == ' ' || line[r] == '\t') {
521 if (r != 0 && line[w - 1] == ' ')
522 continue;
523 else
524 line[w++] = ' ';
525 } else
526 line[w++] = line[r];
528 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
529 line[linelen] = '\0';
530 } else
531 linelen = strlen(line);
533 if (line[0] == '\0') {
534 message->body_whitelines++;
535 return;
538 while (message->body_whitelines--) {
539 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
540 dkim_errx(message, "Can't update hash context");
541 return;
544 message->body_whitelines = 0;
545 message->has_body = 1;
547 if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
548 EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
549 dkim_errx(message, "Can't update hash context");
550 return;
554 void
555 dkim_sign(struct osmtpd_ctx *ctx)
557 struct dkim_message *message = ctx->local_message;
558 /* Use largest hash size here */
559 unsigned char bdigest[EVP_MAX_MD_SIZE];
560 unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
561 unsigned char *b;
562 const char *sdomain = domain[0], *tsdomain;
563 time_t now;
564 ssize_t i;
565 size_t linelen = 0;
566 char *tmp, *tmp2;
567 unsigned int digestsz;
569 if (addtime || addexpire)
570 now = time(NULL);
571 if (addtime && !dkim_signature_printf(message, "t=%lld; ",
572 (long long)now))
573 goto fail;
574 if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
575 now + addexpire < now ? INT64_MAX : now + addexpire))
576 goto fail;
578 if (canonbody == CANON_SIMPLE && !message->has_body) {
579 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
580 dkim_errx(message, "Can't update hash context");
581 goto fail;
584 if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0) {
585 dkim_errx(message, "Can't finalize hash context");
586 goto fail;
588 EVP_EncodeBlock(digest, bdigest, digestsz);
589 if (!dkim_signature_printf(message, "bh=%s; h=", digest))
590 goto fail;
591 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
592 for (i = 0; message->headers[i] != NULL; i++)
593 continue;
594 EVP_MD_CTX_reset(message->dctx);
595 if (!sephash) {
596 if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
597 pkey) != 1) {
598 dkim_errx(message, "Failed to initialize signature "
599 "context");
600 goto fail;
602 } else {
603 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1) {
604 dkim_errx(message, "Failed to initialize hash context");
605 goto fail;
608 for (i--; i >= 0; i--) {
609 if (!sephash) {
610 if (EVP_DigestSignUpdate(message->dctx,
611 message->headers[i],
612 strlen(message->headers[i])) != 1 ||
613 EVP_DigestSignUpdate(message->dctx, "\r\n",
614 2) <= 0) {
615 dkim_errx(message, "Failed to update signature "
616 "context");
617 goto fail;
619 } else {
620 if (EVP_DigestUpdate(message->dctx, message->headers[i],
621 strlen(message->headers[i])) != 1 ||
622 EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
623 dkim_errx(message, "Failed to update digest "
624 "context");
625 goto fail;
628 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
629 sdomain = tsdomain;
630 /* We're done with the cached header after hashing */
631 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
632 if (tmp[0] == ' ' || tmp[0] == '\t')
633 break;
634 tmp[0] = tolower(tmp[0]);
636 tmp[0] = '\0';
637 if (!dkim_signature_printf(message, "%s%s",
638 message->headers[i + 1] == NULL ? "" : ":",
639 message->headers[i]))
640 goto fail;
642 dkim_signature_printf(message, "; d=%s; b=", sdomain);
643 if (!dkim_signature_normalize(message))
644 goto fail;
645 if ((tmp = strdup(message->signature.signature)) == NULL) {
646 dkim_err(message, "Can't create DKIM signature");
647 goto fail;
649 dkim_parse_header(message, tmp, 1);
650 if (!sephash) {
651 if (EVP_DigestSignUpdate(message->dctx, tmp,
652 strlen(tmp)) != 1) {
653 dkim_errx(message, "Failed to update signature "
654 "context");
655 goto fail;
657 } else {
658 if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1) {
659 dkim_errx(message, "Failed to update digest context");
660 goto fail;
663 free(tmp);
664 if (!sephash) {
665 if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1) {
666 dkim_errx(message, "Can't finalize signature context");
667 goto fail;
669 #ifdef HAVE_ED25519
670 } else {
671 if (EVP_DigestFinal_ex(message->dctx, bdigest,
672 &digestsz) != 1) {
673 dkim_errx(message, "Can't finalize hash context");
674 goto fail;
676 EVP_MD_CTX_reset(message->dctx);
677 if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
678 pkey) != 1) {
679 dkim_errx(message, "Failed to initialize signature "
680 "context");
681 goto fail;
683 if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
684 digestsz) != 1) {
685 dkim_errx(message, "Failed to finalize signature");
686 goto fail;
688 #endif
690 if ((tmp = malloc(linelen)) == NULL) {
691 dkim_err(message, "Can't allocate space for signature");
692 goto fail;
694 if (!sephash) {
695 if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1) {
696 dkim_errx(message, "Failed to finalize signature");
697 goto fail;
699 #ifdef HAVE_ED25519
700 } else {
701 if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
702 digestsz) != 1) {
703 dkim_errx(message, "Failed to finalize signature");
704 goto fail;
706 #endif
708 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
709 dkim_err(message, "Can't create DKIM signature");
710 goto fail;
712 EVP_EncodeBlock(b, tmp, linelen);
713 free(tmp);
714 dkim_signature_printf(message, "%s\r\n", b);
715 free(b);
716 dkim_signature_normalize(message);
717 tmp = message->signature.signature;
718 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
719 tmp2[0] = '\0';
720 osmtpd_filter_dataline(ctx, "%s", tmp);
721 tmp = tmp2 + 2;
723 tmp = NULL;
724 linelen = 0;
725 rewind(message->origf);
726 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
727 tmp[i - 1] = '\0';
728 osmtpd_filter_dataline(ctx, "%s", tmp);
730 free(tmp);
731 return;
732 fail:
733 osmtpd_filter_dataline(ctx, ".");
736 int
737 dkim_signature_normalize(struct dkim_message *message)
739 size_t i;
740 size_t linelen;
741 size_t checkpoint;
742 size_t skip;
743 size_t *headerlen = &(message->signature.len);
744 int headername = 1;
745 char tag = '\0';
746 char *sig = message->signature.signature;
748 for (linelen = i = 0; sig[i] != '\0'; i++) {
749 if (sig[i] == '\r' && sig[i + 1] == '\n') {
750 i++;
751 checkpoint = 0;
752 linelen = 0;
753 continue;
755 if (sig[i] == '\t')
756 linelen = (linelen + 8) & ~7;
757 else
758 linelen++;
759 if (headername) {
760 if (sig[i] == ':') {
761 headername = 0;
762 checkpoint = i;
764 continue;
766 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
767 for (skip = checkpoint + 1;
768 sig[skip] == ' ' || sig[skip] == '\t';
769 skip++)
770 continue;
771 skip -= checkpoint + 1;
772 if (!dkim_signature_need(message,
773 skip > 3 ? 0 : 3 - skip + 1))
774 return 0;
775 sig = message->signature.signature;
777 memmove(sig + checkpoint + 3,
778 sig + checkpoint + skip,
779 *headerlen - skip - checkpoint + 1);
780 sig[checkpoint + 1] = '\r';
781 sig[checkpoint + 2] = '\n';
782 sig[checkpoint + 3] = '\t';
783 linelen = 8;
784 *headerlen = *headerlen + 3 - skip;
785 i = checkpoint + 3;
786 checkpoint = 0;
788 if (sig[i] == ';') {
789 checkpoint = i;
790 tag = '\0';
791 continue;
793 switch (tag) {
794 case 'B':
795 case 'b':
796 case 'z':
797 checkpoint = i;
798 break;
799 case 'h':
800 if (sig[i] == ':')
801 checkpoint = i;
802 break;
804 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
805 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
806 sig[i + 2] == '=') {
807 tag = 'B';
808 linelen += 2;
809 i += 2;
810 } else
811 tag = sig[i];
814 return 1;
817 int
818 dkim_signature_printheader(struct dkim_message *message, const char *header)
820 size_t i, j, len;
821 static char *fmtheader = NULL;
822 char *tmp;
823 static size_t size = 0;
824 int first;
826 len = strlen(header);
827 if ((len + 3) * 3 < len) {
828 errno = EOVERFLOW;
829 dkim_err(message, "Can't add z-component to header");
830 return 0;
832 if ((len + 3) * 3 > size) {
833 if ((tmp = reallocarray(fmtheader, 3, len + 3)) == NULL) {
834 dkim_err(message, "Can't add z-component to header");
835 return 0;
837 fmtheader = tmp;
838 size = (len + 1) * 3;
841 first = message->signature.signature[message->signature.len - 1] == '=';
842 for (j = i = 0; header[i] != '\0'; i++, j++) {
843 if (i == 0 && header[i] != ' ' && header[i] != '\t' && !first)
844 fmtheader[j++] = '|';
845 if ((header[i] >= 0x21 && header[i] <= 0x3A) ||
846 (header[i] == 0x3C) ||
847 (header[i] >= 0x3E && header[i] <= 0x7B) ||
848 (header[i] >= 0x7D && header[i] <= 0x7E))
849 fmtheader[j] = header[i];
850 else {
851 fmtheader[j++] = '=';
852 (void) sprintf(fmtheader + j, "%02hhX", header[i]);
853 j++;
856 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
857 (unsigned char) '\n');
859 return dkim_signature_printf(message, "%s", fmtheader);
862 int
863 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
865 struct dkim_signature *sig = &(message->signature);
866 va_list ap;
867 size_t len;
869 va_start(ap, fmt);
870 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
871 fmt, ap)) >= sig->size - sig->len) {
872 va_end(ap);
873 if (!dkim_signature_need(message, len + 1))
874 return 0;
875 va_start(ap, fmt);
876 if ((len = vsnprintf(sig->signature + sig->len,
877 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
878 osmtpd_errx(1, "Miscalculated header size");
880 sig->len += len;
881 va_end(ap);
882 return 1;
885 const char *
886 dkim_domain_select(struct dkim_message *message, char *from)
888 char *mdomain0, *mdomain;
889 size_t i;
891 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
892 if (errno != EINVAL) {
893 dkim_errx(message, "Couldn't parse from header");
894 return NULL;
896 return NULL;
899 while (mdomain != NULL && mdomain[0] != '\0') {
900 for (i = 0; i < ndomains; i++) {
901 if (strcasecmp(mdomain, domain[i]) == 0) {
902 free(mdomain0);
903 return domain[i];
906 if ((mdomain = strchr(mdomain, '.')) != NULL)
907 mdomain++;
909 free(mdomain0);
910 return NULL;
913 int
914 dkim_signature_need(struct dkim_message *message, size_t len)
916 struct dkim_signature *sig = &(message->signature);
917 char *tmp;
919 if (sig->len + len < sig->size)
920 return 1;
921 sig->size = (((len + sig->len) / 512) + 1) * 512;
922 if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
923 dkim_err(message, "No room for signature");
924 return 0;
926 sig->signature = tmp;
927 return 1;
930 __dead void
931 usage(void)
933 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
934 "[-c canonicalization] \n [-h headerfields]"
935 "[-x seconds] -d domain -k keyfile -s selector\n");
936 exit(1);