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 EVP_MD_CTX *dctx;
50 };
52 /* RFC 6376 section 5.4.1 */
53 static char *dsign_headers[] = {
54 "from",
55 "reply-to",
56 "subject",
57 "date",
58 "to",
59 "cc",
60 "resent-date",
61 "resent-from",
62 "resent-to",
63 "resent-cc",
64 "in-reply-to",
65 "references",
66 "list-id",
67 "list-help",
68 "list-unsubscribe",
69 "list-subscribe",
70 "list-post",
71 "list-owner",
72 "list-archive"
73 };
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;
95 static int keyid = EVP_PKEY_RSA;
96 static int sephash = 0;
98 #define DKIM_SIGNATURE_LINELEN 78
100 void usage(void);
101 void dkim_adddomain(char *);
102 void dkim_headers_set(char *);
103 void dkim_dataline(struct osmtpd_ctx *, const char *);
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 *);
117 int
118 main(int argc, char *argv[])
120 int ch;
121 FILE *file;
122 char *line;
123 size_t linesz;
124 ssize_t linelen;
125 const char *errstr;
127 while ((ch = getopt(argc, argv, "a:c:D: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_errx(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_errx(1,
161 "Invalid canonicalization");
162 } else if (optarg[0] == '\0')
163 canonbody = CANON_SIMPLE;
164 else
165 osmtpd_errx(1, "Invalid canonicalization");
166 break;
167 case 'D':
168 if ((file = fopen(optarg, "r")) == NULL)
169 osmtpd_err(1, "Can't open domain file (%s)",
170 optarg);
171 do {
172 line = NULL;
173 linesz = 0;
174 linelen = getline(&line, &linesz, file);
175 if (linelen > 0) {
176 if (line[linelen - 1] == '\n')
177 line[linelen - 1] = '\0';
178 if (line[0] == '#')
179 continue;
180 dkim_adddomain(line);
182 } while (linelen != -1);
183 if (ferror(file))
184 osmtpd_err(1, "Error reading domain file (%s)",
185 optarg);
186 fclose(file);
187 break;
188 case 'd':
189 dkim_adddomain(optarg);
190 break;
191 case 'h':
192 dkim_headers_set(optarg);
193 break;
194 case 'k':
195 if ((file = fopen(optarg, "r")) == NULL)
196 osmtpd_err(1, "Can't open key file (%s)",
197 optarg);
198 pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL);
199 if (pkey == NULL)
200 osmtpd_errx(1, "Can't read key file");
201 fclose(file);
202 break;
203 case 's':
204 selector = optarg;
205 break;
206 case 't':
207 addtime = 1;
208 break;
209 case 'x':
210 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
211 if (addexpire == 0)
212 osmtpd_errx(1, "Expire offset is %s", errstr);
213 break;
214 case 'z':
215 addheaders++;
216 break;
217 default:
218 usage();
222 OpenSSL_add_all_digests();
224 if (pledge("tmppath stdio", NULL) == -1)
225 osmtpd_err(1, "pledge");
227 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
228 osmtpd_errx(1, "Can't find hash: %s", hashalg);
230 if (domain == NULL || selector == NULL || pkey == NULL)
231 usage();
233 if (EVP_PKEY_id(pkey) != keyid)
234 osmtpd_errx(1, "Key is not of type %s", cryptalg);
236 osmtpd_register_filter_dataline(dkim_dataline);
237 osmtpd_local_message(dkim_message_new, dkim_message_free);
238 osmtpd_run();
240 return 0;
243 void
244 dkim_adddomain(char *d)
246 domain = reallocarray(domain, ndomains + 1, sizeof(*domain));
247 if (domain == NULL)
248 osmtpd_err(1, "%s: reallocarray", __func__);
249 domain[ndomains++] = d;
252 void
253 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
255 struct dkim_message *message = ctx->local_message;
256 char *linedup;
257 size_t linelen;
259 linelen = strlen(line);
260 if (fprintf(message->origf, "%s\n", line) < (int) linelen)
261 osmtpd_errx(1, "Couldn't write to tempfile");
263 if (line[0] == '.' && line[1] =='\0') {
264 dkim_sign(ctx);
265 } else if (linelen != 0 && message->parsing_headers) {
266 if (line[0] == '.')
267 line++;
268 if ((linedup = strdup(line)) == NULL)
269 osmtpd_err(1, "%s: strdup", __func__);
270 dkim_parse_header(message, linedup, 0);
271 free(linedup);
272 } else if (linelen == 0 && message->parsing_headers) {
273 if (addheaders > 0 && !dkim_signature_printf(message, "; "))
274 return;
275 message->parsing_headers = 0;
276 } else {
277 if (line[0] == '.')
278 line++;
279 if ((linedup = strdup(line)) == NULL)
280 osmtpd_err(1, "%s: strdup", __func__);
281 dkim_parse_body(message, linedup);
282 free(linedup);
286 void *
287 dkim_message_new(struct osmtpd_ctx *ctx)
289 struct dkim_message *message;
291 if ((message = calloc(1, sizeof(*message))) == NULL) {
292 osmtpd_err(1, "%s: calloc", __func__);
293 return NULL;
296 if ((message->origf = tmpfile()) == NULL) {
297 osmtpd_warn(NULL, "Failed to open tempfile");
298 goto fail;
300 message->parsing_headers = 1;
302 message->body_whitelines = 0;
303 message->headers = calloc(1, sizeof(*(message->headers)));
304 if (message->headers == NULL)
305 osmtpd_err(1, "%s: calloc", __func__);
306 message->lastheader = 0;
307 message->signature.signature = NULL;
308 message->signature.size = 0;
309 message->signature.len = 0;
311 if (!dkim_signature_printf(message,
312 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
313 cryptalg, hashalg,
314 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
315 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
316 goto fail;
317 if (addheaders > 0 && !dkim_signature_printf(message, "z="))
318 goto fail;
320 if ((message->dctx = EVP_MD_CTX_new()) == NULL)
321 osmtpd_errx(1, "EVP_MD_CTX_new");
322 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0)
323 osmtpd_errx(1, "EVP_DigestInit_ex");
325 return message;
326 fail:
327 dkim_message_free(ctx, message);
328 return NULL;
331 void
332 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
334 struct dkim_message *message = data;
335 size_t i;
337 fclose(message->origf);
338 EVP_MD_CTX_free(message->dctx);
339 free(message->signature.signature);
340 for (i = 0; message->headers != NULL &&
341 message->headers[i] != NULL; i++)
342 free(message->headers[i]);
343 free(message->headers);
344 free(message);
347 void
348 dkim_headers_set(char *headers)
350 size_t i;
351 int has_from = 0;
353 nsign_headers = 1;
355 for (i = 0; headers[i] != '\0'; i++) {
356 /* RFC 5322 field-name */
357 if (!(headers[i] >= 33 && headers[i] <= 126))
358 osmtpd_errx(1, "-h: invalid character");
359 if (headers[i] == ':') {
360 /* Test for empty headers */
361 if (i == 0 || headers[i - 1] == ':')
362 osmtpd_errx(1, "-h: header can't be empty");
363 nsign_headers++;
365 headers[i] = tolower(headers[i]);
367 if (headers[i - 1] == ':')
368 osmtpd_errx(1, "-h: header can't be empty");
370 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
371 sizeof(*sign_headers))) == NULL)
372 osmtpd_errx(1, "%s: reallocarray", __func__);
374 for (i = 0; i < nsign_headers; i++) {
375 sign_headers[i] = headers;
376 if (i != nsign_headers - 1) {
377 headers = strchr(headers, ':');
378 headers++[0] = '\0';
380 if (strcasecmp(sign_headers[i], "from") == 0)
381 has_from = 1;
383 if (!has_from)
384 osmtpd_errx(1, "From header must be included");
387 void
388 dkim_parse_header(struct dkim_message *message, char *line, int force)
390 size_t i;
391 size_t r, w;
392 size_t linelen;
393 size_t lastheader;
394 size_t hlen;
395 int fieldname = 0;
396 char **mtmp;
397 char *htmp;
398 char *tmp;
400 if (addheaders == 2 && !force &&
401 !dkim_signature_printheader(message, line))
402 return;
404 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
405 return;
406 if ((line[0] != ' ' && line[0] != '\t')) {
407 message->lastheader = 0;
408 for (i = 0; i < nsign_headers; i++) {
409 hlen = strlen(sign_headers[i]);
410 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
411 while (line[hlen] == ' ' || line[hlen] == '\t')
412 hlen++;
413 if (line[hlen] != ':')
414 continue;
415 break;
418 if (i == nsign_headers && !force)
419 return;
422 if (addheaders == 1 && !force &&
423 !dkim_signature_printheader(message, line))
424 return;
426 if (canonheader == CANON_RELAXED) {
427 if (!message->lastheader)
428 fieldname = 1;
429 for (r = w = 0; line[r] != '\0'; r++) {
430 if (line[r] == ':' && fieldname) {
431 if (w > 0 && line[w - 1] == ' ')
432 line[w - 1] = ':';
433 else
434 line[w++] = ':';
435 fieldname = 0;
436 while (line[r + 1] == ' ' ||
437 line[r + 1] == '\t')
438 r++;
439 continue;
441 if (line[r] == ' ' || line[r] == '\t' ||
442 line[r] == '\r' || line[r] == '\n') {
443 if (r != 0 && w != 0 && line[w - 1] == ' ')
444 continue;
445 else
446 line[w++] = ' ';
447 } else if (fieldname) {
448 line[w++] = tolower(line[r]);
449 continue;
450 } else
451 line[w++] = line[r];
453 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
454 line[linelen] = '\0';
455 } else
456 linelen = strlen(line);
458 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
459 continue;
460 if (!message->lastheader) {
461 mtmp = recallocarray(message->headers, lastheader + 1,
462 lastheader + 2, sizeof(*mtmp));
463 if (mtmp == NULL)
464 osmtpd_err(1, "%s: reallocarray", __func__);
465 message->headers = mtmp;
467 if ((message->headers[lastheader] = strdup(line)) == NULL)
468 osmtpd_err(1, "%s: strdup", __func__);
469 message->headers[lastheader + 1 ] = NULL;
470 message->lastheader = 1;
471 } else {
472 lastheader--;
473 linelen += strlen(message->headers[lastheader]);
474 if (canonheader == CANON_SIMPLE)
475 linelen += 2;
476 linelen++;
477 htmp = reallocarray(message->headers[lastheader], linelen,
478 sizeof(*htmp));
479 if (htmp == NULL)
480 osmtpd_err(1, "%s: reallocarray", __func__);
481 message->headers[lastheader] = htmp;
482 if (canonheader == CANON_SIMPLE) {
483 if (strlcat(htmp, "\r\n", linelen) >= linelen)
484 osmtpd_warnx(NULL, "Missized header");
485 } else if (canonheader == CANON_RELAXED &&
486 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
487 tmp[1] == '\0')
488 line++;
490 if (strlcat(htmp, line, linelen) >= linelen)
491 osmtpd_warnx(NULL, "Missized header");
495 void
496 dkim_parse_body(struct dkim_message *message, char *line)
498 size_t r, w;
499 size_t linelen;
501 if (canonbody == CANON_RELAXED) {
502 for (r = w = 0; line[r] != '\0'; r++) {
503 if (line[r] == ' ' || line[r] == '\t') {
504 if (r != 0 && line[w - 1] == ' ')
505 continue;
506 else
507 line[w++] = ' ';
508 } else
509 line[w++] = line[r];
511 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
512 line[linelen] = '\0';
513 } else
514 linelen = strlen(line);
516 if (line[0] == '\0') {
517 message->body_whitelines++;
518 return;
521 while (message->body_whitelines--) {
522 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
523 osmtpd_errx(1, "EVP_DigestUpdate");
525 message->body_whitelines = 0;
526 message->has_body = 1;
528 if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
529 EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
530 osmtpd_errx(1, "EVP_DigestUpdate");
533 void
534 dkim_sign(struct osmtpd_ctx *ctx)
536 struct dkim_message *message = ctx->local_message;
537 /* Use largest hash size here */
538 unsigned char bdigest[EVP_MAX_MD_SIZE];
539 unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
540 unsigned char *b;
541 const char *sdomain = domain[0], *tsdomain;
542 time_t now;
543 ssize_t i;
544 size_t linelen = 0;
545 char *tmp, *tmp2;
546 unsigned int digestsz;
548 if (addtime || addexpire)
549 now = time(NULL);
550 if (addtime && !dkim_signature_printf(message, "t=%lld; ",
551 (long long)now))
552 goto fail;
553 if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
554 now + addexpire < now ? INT64_MAX : now + addexpire))
555 goto fail;
557 if (canonbody == CANON_SIMPLE && !message->has_body) {
558 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
559 osmtpd_errx(1, "EVP_DigestUpdate");
561 if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0)
562 osmtpd_errx(1, "EVP_DigestFinal_ex");
563 EVP_EncodeBlock(digest, bdigest, digestsz);
564 if (!dkim_signature_printf(message, "bh=%s; h=", digest))
565 goto fail;
566 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
567 for (i = 0; message->headers[i] != NULL; i++)
568 continue;
569 EVP_MD_CTX_reset(message->dctx);
570 if (!sephash) {
571 if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
572 pkey) != 1)
573 osmtpd_errx(1, "EVP_DigestSignInit");
574 } else {
575 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1)
576 osmtpd_errx(1, "EVP_DigestInit_ex");
578 for (i--; i >= 0; i--) {
579 if (!sephash) {
580 if (EVP_DigestSignUpdate(message->dctx,
581 message->headers[i],
582 strlen(message->headers[i])) != 1 ||
583 EVP_DigestSignUpdate(message->dctx, "\r\n",
584 2) <= 0)
585 osmtpd_errx(1, "EVP_DigestSignUpdate");
586 } else {
587 if (EVP_DigestUpdate(message->dctx, message->headers[i],
588 strlen(message->headers[i])) != 1 ||
589 EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
590 osmtpd_errx(1, "EVP_DigestSignUpdate");
592 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
593 sdomain = tsdomain;
594 /* We're done with the cached header after hashing */
595 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
596 if (tmp[0] == ' ' || tmp[0] == '\t')
597 break;
598 tmp[0] = tolower(tmp[0]);
600 tmp[0] = '\0';
601 if (!dkim_signature_printf(message, "%s%s",
602 message->headers[i + 1] == NULL ? "" : ":",
603 message->headers[i]))
604 goto fail;
606 dkim_signature_printf(message, "; d=%s; b=", sdomain);
607 if (!dkim_signature_normalize(message))
608 goto fail;
609 if ((tmp = strdup(message->signature.signature)) == NULL)
610 osmtpd_err(1, "%s: strdup", __func__);
611 dkim_parse_header(message, tmp, 1);
612 if (!sephash) {
613 if (EVP_DigestSignUpdate(message->dctx, tmp,
614 strlen(tmp)) != 1)
615 osmtpd_errx(1, "EVP_DigestSignUpdate");
616 } else {
617 if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1)
618 osmtpd_errx(1, "EVP_DigestUpdate");
620 free(tmp);
621 if (!sephash) {
622 if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1)
623 osmtpd_errx(1, "EVP_DigestSignFinal");
624 #ifdef HAVE_ED25519
625 } else {
626 if (EVP_DigestFinal_ex(message->dctx, bdigest,
627 &digestsz) != 1)
628 osmtpd_errx(1, "EVP_DigestFinal_ex");
629 EVP_MD_CTX_reset(message->dctx);
630 if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
631 pkey) != 1)
632 osmtpd_errx(1, "EVP_DigestSignInit");
633 if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
634 digestsz) != 1)
635 osmtpd_errx(1, "EVP_DigestSign");
636 #endif
638 if ((tmp = malloc(linelen)) == NULL)
639 osmtpd_err(1, "%s: malloc", __func__);
640 if (!sephash) {
641 if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1)
642 osmtpd_errx(1, "EVP_DigestSignFinal");
643 #ifdef HAVE_ED25519
644 } else {
645 if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
646 digestsz) != 1)
647 osmtpd_errx(1, "EVP_DigestSign");
648 #endif
650 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL)
651 osmtpd_err(1, "%s: malloc", __func__);
652 EVP_EncodeBlock(b, tmp, linelen);
653 free(tmp);
654 dkim_signature_printf(message, "%s\r\n", b);
655 free(b);
656 dkim_signature_normalize(message);
657 tmp = message->signature.signature;
658 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
659 tmp2[0] = '\0';
660 osmtpd_filter_dataline(ctx, "%s", tmp);
661 tmp = tmp2 + 2;
663 tmp = NULL;
664 linelen = 0;
665 rewind(message->origf);
666 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
667 tmp[i - 1] = '\0';
668 osmtpd_filter_dataline(ctx, "%s", tmp);
670 free(tmp);
671 return;
672 fail:
673 osmtpd_filter_dataline(ctx, ".");
676 int
677 dkim_signature_normalize(struct dkim_message *message)
679 size_t i;
680 size_t linelen;
681 size_t checkpoint;
682 size_t skip;
683 size_t *headerlen = &(message->signature.len);
684 int headername = 1;
685 char tag = '\0';
686 char *sig = message->signature.signature;
688 for (linelen = i = 0; sig[i] != '\0'; i++) {
689 if (sig[i] == '\r' && sig[i + 1] == '\n') {
690 i++;
691 checkpoint = 0;
692 linelen = 0;
693 continue;
695 if (sig[i] == '\t')
696 linelen = (linelen + 8) & ~7;
697 else
698 linelen++;
699 if (headername) {
700 if (sig[i] == ':') {
701 headername = 0;
702 checkpoint = i;
704 continue;
706 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
707 for (skip = checkpoint + 1;
708 sig[skip] == ' ' || sig[skip] == '\t';
709 skip++)
710 continue;
711 skip -= checkpoint + 1;
712 if (!dkim_signature_need(message,
713 skip > 3 ? 0 : 3 - skip + 1))
714 return 0;
715 sig = message->signature.signature;
717 memmove(sig + checkpoint + 3,
718 sig + checkpoint + skip,
719 *headerlen - skip - checkpoint + 1);
720 sig[checkpoint + 1] = '\r';
721 sig[checkpoint + 2] = '\n';
722 sig[checkpoint + 3] = '\t';
723 linelen = 8;
724 *headerlen = *headerlen + 3 - skip;
725 i = checkpoint + 3;
726 checkpoint = 0;
728 if (sig[i] == ';') {
729 checkpoint = i;
730 tag = '\0';
731 continue;
733 switch (tag) {
734 case 'B':
735 case 'b':
736 case 'z':
737 checkpoint = i;
738 break;
739 case 'h':
740 if (sig[i] == ':')
741 checkpoint = i;
742 break;
744 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
745 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
746 sig[i + 2] == '=') {
747 tag = 'B';
748 linelen += 2;
749 i += 2;
750 } else
751 tag = sig[i];
754 return 1;
757 int
758 dkim_signature_printheader(struct dkim_message *message, const char *line)
760 size_t i, j, len;
761 int r;
762 char *fmtheader;
763 int first;
765 len = strlen(line);
766 if ((fmtheader = reallocarray(NULL, 3, len + 3)) == NULL)
767 osmtpd_err(1, "malloc");
769 first = message->signature.signature[message->signature.len - 1] == '=';
770 for (j = i = 0; line[i] != '\0'; i++, j++) {
771 if (i == 0 && line[i] != ' ' && line[i] != '\t' && !first)
772 fmtheader[j++] = '|';
773 if ((line[i] >= 0x21 && line[i] <= 0x3A) ||
774 (line[i] == 0x3C) ||
775 (line[i] >= 0x3E && line[i] <= 0x7B) ||
776 (line[i] >= 0x7D && line[i] <= 0x7E))
777 fmtheader[j] = line[i];
778 else {
779 fmtheader[j++] = '=';
780 (void) sprintf(fmtheader + j, "%02hhX", line[i]);
781 j++;
784 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
785 (unsigned char) '\n');
787 r = dkim_signature_printf(message, "%s", fmtheader);
788 free(fmtheader);
789 return r;
792 int
793 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
795 struct dkim_signature *sig = &(message->signature);
796 va_list ap;
797 size_t len;
799 va_start(ap, fmt);
800 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
801 fmt, ap)) >= sig->size - sig->len) {
802 va_end(ap);
803 if (!dkim_signature_need(message, len + 1))
804 return 0;
805 va_start(ap, fmt);
806 if ((len = vsnprintf(sig->signature + sig->len,
807 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
808 osmtpd_errx(1, "Miscalculated header size");
810 sig->len += len;
811 va_end(ap);
812 return 1;
815 const char *
816 dkim_domain_select(struct dkim_message *message, char *from)
818 char *mdomain0, *mdomain;
819 size_t i;
821 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL)
822 return NULL;
824 while (mdomain != NULL && mdomain[0] != '\0') {
825 for (i = 0; i < ndomains; i++) {
826 if (strcasecmp(mdomain, domain[i]) == 0) {
827 free(mdomain0);
828 return domain[i];
831 if ((mdomain = strchr(mdomain, '.')) != NULL)
832 mdomain++;
834 free(mdomain0);
835 return NULL;
838 int
839 dkim_signature_need(struct dkim_message *message, size_t len)
841 struct dkim_signature *sig = &(message->signature);
842 char *tmp;
844 if (sig->len + len < sig->size)
845 return 1;
846 sig->size = (((len + sig->len) / 512) + 1) * 512;
847 if ((tmp = realloc(sig->signature, sig->size)) == NULL)
848 osmtpd_err(1, "%s: malloc", __func__);
849 sig->signature = tmp;
850 return 1;
853 __dead void
854 usage(void)
856 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
857 "[-c canonicalization] \n [-h headerfields]"
858 "[-x seconds] -D file -d domain -k keyfile -s selector\n");
859 exit(1);