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 <limits.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <time.h>
30 #include <unistd.h>
32 #include "openbsd-compat.h"
33 #include "opensmtpd.h"
34 #include "mheader.h"
36 struct dkim_signature {
37 char *signature;
38 size_t size;
39 size_t len;
40 };
42 struct dkim_message {
43 FILE *origf;
44 int parsing_headers;
45 char **headers;
46 int lastheader;
47 size_t body_whitelines;
48 int has_body;
49 struct dkim_signature signature;
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_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 *);
118 int
119 main(int argc, char *argv[])
121 int ch;
122 FILE *file;
123 char *line;
124 size_t linesz;
125 ssize_t linelen;
126 const char *errstr;
128 while ((ch = getopt(argc, argv, "a:c:D:d:h:k:s:tx:z")) != -1) {
129 switch (ch) {
130 case 'a':
131 if (strncmp(optarg, "rsa-", 4) == 0) {
132 cryptalg = "rsa";
133 hashalg = optarg + 4;
134 keyid = EVP_PKEY_RSA;
135 sephash = 0;
136 #ifdef HAVE_ED25519
137 } else if (strncmp(optarg, "ed25519-", 8) == 0) {
138 hashalg = optarg + 8;
139 cryptalg = "ed25519";
140 keyid = EVP_PKEY_ED25519;
141 sephash = 1;
142 #endif
143 } else
144 osmtpd_errx(1, "invalid algorithm");
145 break;
146 case 'c':
147 if (strncmp(optarg, "simple", 6) == 0) {
148 canonheader = CANON_SIMPLE;
149 optarg += 6;
150 } else if (strncmp(optarg, "relaxed", 7) == 0) {
151 canonheader = CANON_RELAXED;
152 optarg += 7;
153 } else
154 osmtpd_errx(1, "Invalid canonicalization");
155 if (optarg[0] == '/') {
156 if (strcmp(optarg + 1, "simple") == 0)
157 canonbody = CANON_SIMPLE;
158 else if (strcmp(optarg + 1, "relaxed") == 0)
159 canonbody = CANON_RELAXED;
160 else
161 osmtpd_errx(1,
162 "Invalid canonicalization");
163 } else if (optarg[0] == '\0')
164 canonbody = CANON_SIMPLE;
165 else
166 osmtpd_errx(1, "Invalid canonicalization");
167 break;
168 case 'D':
169 if ((file = fopen(optarg, "r")) == NULL)
170 osmtpd_err(1, "Can't open domain file (%s)",
171 optarg);
172 do {
173 line = NULL;
174 linesz = 0;
175 linelen = getline(&line, &linesz, file);
176 if (linelen > 0) {
177 if (line[linelen - 1] == '\n')
178 line[linelen - 1] = '\0';
179 if (line[0] == '#')
180 continue;
181 dkim_adddomain(line);
183 } while (linelen != -1);
184 if (ferror(file))
185 osmtpd_err(1, "Error reading domain file (%s)",
186 optarg);
187 fclose(file);
188 break;
189 case 'd':
190 dkim_adddomain(optarg);
191 break;
192 case 'h':
193 dkim_headers_set(optarg);
194 break;
195 case 'k':
196 if ((file = fopen(optarg, "r")) == NULL)
197 osmtpd_err(1, "Can't open key file (%s)",
198 optarg);
199 pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL);
200 if (pkey == NULL)
201 osmtpd_errx(1, "Can't read key file");
202 fclose(file);
203 break;
204 case 's':
205 selector = optarg;
206 break;
207 case 't':
208 addtime = 1;
209 break;
210 case 'x':
211 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
212 if (addexpire == 0)
213 osmtpd_errx(1, "Expire offset is %s", errstr);
214 break;
215 case 'z':
216 addheaders++;
217 break;
218 default:
219 usage();
223 OpenSSL_add_all_digests();
225 if (pledge("tmppath stdio", NULL) == -1)
226 osmtpd_err(1, "pledge");
228 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
229 osmtpd_errx(1, "Can't find hash: %s", hashalg);
231 if (domain == NULL || selector == NULL || pkey == NULL)
232 usage();
234 if (EVP_PKEY_id(pkey) != keyid)
235 osmtpd_errx(1, "Key is not of type %s", cryptalg);
237 osmtpd_register_filter_dataline(dkim_dataline);
238 osmtpd_local_message(dkim_message_new, dkim_message_free);
239 osmtpd_run();
241 return 0;
244 void
245 dkim_adddomain(char *d)
247 domain = reallocarray(domain, ndomains + 1, sizeof(*domain));
248 if (domain == NULL)
249 osmtpd_err(1, "reallocarray");
250 domain[ndomains++] = d;
253 int
254 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
256 struct dkim_message *message = ctx->local_message;
257 char *linedup;
258 size_t linelen;
260 linelen = strlen(line);
261 if (fprintf(message->origf, "%s\n", line) < (int) linelen) {
262 osmtpd_warnx(ctx, "Couldn't write to tempfile");
263 return -1;
266 if (line[0] == '.' && line[1] =='\0') {
267 dkim_sign(ctx);
268 } else if (linelen != 0 && message->parsing_headers) {
269 if (line[0] == '.')
270 line++;
271 if ((linedup = strdup(line)) == NULL)
272 osmtpd_err(1, "strdup");
273 dkim_parse_header(message, linedup, 0);
274 free(linedup);
275 } else if (linelen == 0 && message->parsing_headers) {
276 if (addheaders > 0)
277 dkim_signature_printf(message, "; ");
278 message->parsing_headers = 0;
279 } else {
280 if (line[0] == '.')
281 line++;
282 if ((linedup = strdup(line)) == NULL)
283 osmtpd_err(1, "strdup");
284 dkim_parse_body(message, linedup);
285 free(linedup);
288 return 0;
291 void *
292 dkim_message_new(struct osmtpd_ctx *ctx)
294 struct dkim_message *message;
296 if ((message = calloc(1, sizeof(*message))) == NULL) {
297 osmtpd_warn(ctx, "calloc");
298 return NULL;
301 if ((message->origf = tmpfile()) == NULL) {
302 osmtpd_warn(ctx, "Failed to open tempfile");
303 goto fail;
305 message->parsing_headers = 1;
307 message->body_whitelines = 0;
308 message->headers = calloc(1, sizeof(*(message->headers)));
309 if (message->headers == NULL) {
310 osmtpd_warn(ctx, "calloc");
311 goto fail;
313 message->lastheader = 0;
314 message->signature.signature = NULL;
315 message->signature.size = 0;
316 message->signature.len = 0;
318 dkim_signature_printf(message,
319 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
320 cryptalg, hashalg,
321 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
322 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector);
323 if (addheaders > 0)
324 dkim_signature_printf(message, "z=");
326 if ((message->dctx = EVP_MD_CTX_new()) == NULL) {
327 osmtpd_warnx(ctx, "EVP_MD_CTX_new");
328 goto fail;
330 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0) {
331 osmtpd_warnx(ctx, "EVP_DigestInit_ex");
332 goto fail;
335 return message;
336 fail:
337 dkim_message_free(ctx, message);
338 return NULL;
341 void
342 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
344 struct dkim_message *message = data;
345 size_t i;
347 fclose(message->origf);
348 EVP_MD_CTX_free(message->dctx);
349 free(message->signature.signature);
350 for (i = 0; message->headers != NULL &&
351 message->headers[i] != NULL; i++)
352 free(message->headers[i]);
353 free(message->headers);
354 free(message);
357 void
358 dkim_headers_set(char *headers)
360 size_t i;
361 int has_from = 0;
363 nsign_headers = 1;
365 for (i = 0; headers[i] != '\0'; i++) {
366 /* RFC 5322 field-name */
367 if (!(headers[i] >= 33 && headers[i] <= 126))
368 osmtpd_errx(1, "-h: invalid character");
369 if (headers[i] == ':') {
370 /* Test for empty headers */
371 if (i == 0 || headers[i - 1] == ':')
372 osmtpd_errx(1, "-h: header can't be empty");
373 nsign_headers++;
375 headers[i] = tolower(headers[i]);
377 if (headers[i - 1] == ':')
378 osmtpd_errx(1, "-h: header can't be empty");
380 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
381 sizeof(*sign_headers))) == NULL)
382 osmtpd_errx(1, "reallocarray");
384 for (i = 0; i < nsign_headers; i++) {
385 sign_headers[i] = headers;
386 if (i != nsign_headers - 1) {
387 headers = strchr(headers, ':');
388 headers++[0] = '\0';
390 if (strcasecmp(sign_headers[i], "from") == 0)
391 has_from = 1;
393 if (!has_from)
394 osmtpd_errx(1, "From header must be included");
397 void
398 dkim_parse_header(struct dkim_message *message, char *line, int force)
400 size_t i;
401 size_t r, w;
402 size_t linelen;
403 size_t lastheader;
404 size_t hlen;
405 int fieldname = 0;
406 char **mtmp;
407 char *htmp;
408 char *tmp;
410 if (addheaders == 2 && !force)
411 dkim_signature_printheader(message, line);
413 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
414 return;
415 if ((line[0] != ' ' && line[0] != '\t')) {
416 message->lastheader = 0;
417 for (i = 0; i < nsign_headers; i++) {
418 hlen = strlen(sign_headers[i]);
419 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
420 while (line[hlen] == ' ' || line[hlen] == '\t')
421 hlen++;
422 if (line[hlen] != ':')
423 continue;
424 break;
427 if (i == nsign_headers && !force)
428 return;
431 if (addheaders == 1 && !force)
432 dkim_signature_printheader(message, line);
434 if (canonheader == CANON_RELAXED) {
435 if (!message->lastheader)
436 fieldname = 1;
437 for (r = w = 0; line[r] != '\0'; r++) {
438 if (line[r] == ':' && fieldname) {
439 if (w > 0 && line[w - 1] == ' ')
440 line[w - 1] = ':';
441 else
442 line[w++] = ':';
443 fieldname = 0;
444 while (line[r + 1] == ' ' ||
445 line[r + 1] == '\t')
446 r++;
447 continue;
449 if (line[r] == ' ' || line[r] == '\t' ||
450 line[r] == '\r' || line[r] == '\n') {
451 if (r != 0 && w != 0 && line[w - 1] == ' ')
452 continue;
453 else
454 line[w++] = ' ';
455 } else if (fieldname) {
456 line[w++] = tolower(line[r]);
457 continue;
458 } else
459 line[w++] = line[r];
461 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
462 line[linelen] = '\0';
463 } else
464 linelen = strlen(line);
466 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
467 continue;
468 if (!message->lastheader) {
469 mtmp = recallocarray(message->headers, lastheader + 1,
470 lastheader + 2, sizeof(*mtmp));
471 if (mtmp == NULL)
472 osmtpd_err(1, "reallocarray");
473 message->headers = mtmp;
475 if ((message->headers[lastheader] = strdup(line)) == NULL)
476 osmtpd_err(1, "strdup");
477 message->headers[lastheader + 1 ] = NULL;
478 message->lastheader = 1;
479 } else {
480 lastheader--;
481 linelen += strlen(message->headers[lastheader]);
482 if (canonheader == CANON_SIMPLE)
483 linelen += 2;
484 linelen++;
485 htmp = reallocarray(message->headers[lastheader], linelen,
486 sizeof(*htmp));
487 if (htmp == NULL)
488 osmtpd_err(1, "reallocarray");
489 message->headers[lastheader] = htmp;
490 if (canonheader == CANON_SIMPLE) {
491 (void)strlcat(htmp, "\r\n", linelen);
492 } else if (canonheader == CANON_RELAXED &&
493 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
494 tmp[1] == '\0')
495 line++;
497 (void)strlcat(htmp, line, linelen);
501 void
502 dkim_parse_body(struct dkim_message *message, char *line)
504 size_t r, w;
505 size_t linelen;
507 if (canonbody == CANON_RELAXED) {
508 for (r = w = 0; line[r] != '\0'; r++) {
509 if (line[r] == ' ' || line[r] == '\t') {
510 if (r != 0 && line[w - 1] == ' ')
511 continue;
512 else
513 line[w++] = ' ';
514 } else
515 line[w++] = line[r];
517 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
518 line[linelen] = '\0';
519 } else
520 linelen = strlen(line);
522 if (line[0] == '\0') {
523 message->body_whitelines++;
524 return;
527 while (message->body_whitelines--) {
528 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
529 osmtpd_errx(1, "EVP_DigestUpdate");
531 message->body_whitelines = 0;
532 message->has_body = 1;
534 if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
535 EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0)
536 osmtpd_errx(1, "EVP_DigestUpdate");
539 void
540 dkim_sign(struct osmtpd_ctx *ctx)
542 struct dkim_message *message = ctx->local_message;
543 /* Use largest hash size here */
544 unsigned char bdigest[EVP_MAX_MD_SIZE];
545 unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
546 unsigned char *b;
547 const char *sdomain = domain[0], *tsdomain;
548 time_t now;
549 ssize_t i;
550 size_t linelen = 0;
551 char *tmp, *tmp2;
552 unsigned int digestsz;
554 if (addtime || addexpire)
555 now = time(NULL);
556 if (addtime)
557 dkim_signature_printf(message, "t=%lld; ", (long long)now);
558 if (addexpire != 0)
559 dkim_signature_printf(message, "x=%lld; ",
560 now + addexpire < now ? LLONG_MAX : now + addexpire);
562 if (canonbody == CANON_SIMPLE && !message->has_body) {
563 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
564 osmtpd_errx(1, "EVP_DigestUpdate");
566 if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0)
567 osmtpd_errx(1, "EVP_DigestFinal_ex");
568 EVP_EncodeBlock(digest, bdigest, digestsz);
569 dkim_signature_printf(message, "bh=%s; h=", digest);
570 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
571 for (i = 0; message->headers[i] != NULL; i++)
572 continue;
573 EVP_MD_CTX_reset(message->dctx);
574 if (!sephash) {
575 if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
576 pkey) != 1)
577 osmtpd_errx(1, "EVP_DigestSignInit");
578 } else {
579 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1)
580 osmtpd_errx(1, "EVP_DigestInit_ex");
582 for (i--; i >= 0; i--) {
583 if (!sephash) {
584 if (EVP_DigestSignUpdate(message->dctx,
585 message->headers[i],
586 strlen(message->headers[i])) != 1 ||
587 EVP_DigestSignUpdate(message->dctx, "\r\n",
588 2) <= 0)
589 osmtpd_errx(1, "EVP_DigestSignUpdate");
590 } else {
591 if (EVP_DigestUpdate(message->dctx, message->headers[i],
592 strlen(message->headers[i])) != 1 ||
593 EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0)
594 osmtpd_errx(1, "EVP_DigestSignUpdate");
596 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
597 sdomain = tsdomain;
598 /* We're done with the cached header after hashing */
599 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
600 if (tmp[0] == ' ' || tmp[0] == '\t')
601 break;
602 tmp[0] = tolower(tmp[0]);
604 tmp[0] = '\0';
605 dkim_signature_printf(message, "%s%s",
606 message->headers[i + 1] == NULL ? "" : ":",
607 message->headers[i]);
609 dkim_signature_printf(message, "; d=%s; b=", sdomain);
610 dkim_signature_normalize(message);
611 if ((tmp = strdup(message->signature.signature)) == NULL)
612 osmtpd_err(1, "strdup");
613 dkim_parse_header(message, tmp, 1);
614 if (!sephash) {
615 if (EVP_DigestSignUpdate(message->dctx, tmp,
616 strlen(tmp)) != 1)
617 osmtpd_errx(1, "EVP_DigestSignUpdate");
618 } else {
619 if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1)
620 osmtpd_errx(1, "EVP_DigestUpdate");
622 free(tmp);
623 if (!sephash) {
624 if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1)
625 osmtpd_errx(1, "EVP_DigestSignFinal");
626 #ifdef HAVE_ED25519
627 } else {
628 if (EVP_DigestFinal_ex(message->dctx, bdigest,
629 &digestsz) != 1)
630 osmtpd_errx(1, "EVP_DigestFinal_ex");
631 EVP_MD_CTX_reset(message->dctx);
632 if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
633 pkey) != 1)
634 osmtpd_errx(1, "EVP_DigestSignInit");
635 if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
636 digestsz) != 1)
637 osmtpd_errx(1, "EVP_DigestSign");
638 #endif
640 if ((tmp = malloc(linelen)) == NULL)
641 osmtpd_err(1, "malloc");
642 if (!sephash) {
643 if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1)
644 osmtpd_errx(1, "EVP_DigestSignFinal");
645 #ifdef HAVE_ED25519
646 } else {
647 if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
648 digestsz) != 1)
649 osmtpd_errx(1, "EVP_DigestSign");
650 #endif
652 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL)
653 osmtpd_err(1, "malloc");
654 EVP_EncodeBlock(b, tmp, linelen);
655 free(tmp);
656 dkim_signature_printf(message, "%s\r\n", b);
657 free(b);
658 dkim_signature_normalize(message);
659 tmp = message->signature.signature;
660 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
661 tmp2[0] = '\0';
662 osmtpd_filter_dataline(ctx, "%s", tmp);
663 tmp = tmp2 + 2;
665 tmp = NULL;
666 linelen = 0;
667 rewind(message->origf);
668 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
669 tmp[i - 1] = '\0';
670 osmtpd_filter_dataline(ctx, "%s", tmp);
672 free(tmp);
673 return;
676 void
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 dkim_signature_need(message,
713 skip > 3 ? 0 : 3 - skip + 1);
714 sig = message->signature.signature;
716 memmove(sig + checkpoint + 3,
717 sig + checkpoint + skip,
718 *headerlen - skip - checkpoint + 1);
719 sig[checkpoint + 1] = '\r';
720 sig[checkpoint + 2] = '\n';
721 sig[checkpoint + 3] = '\t';
722 linelen = 8;
723 *headerlen = *headerlen + 3 - skip;
724 i = checkpoint + 3;
725 checkpoint = 0;
727 if (sig[i] == ';') {
728 checkpoint = i;
729 tag = '\0';
730 continue;
732 switch (tag) {
733 case 'B':
734 case 'b':
735 case 'z':
736 checkpoint = i;
737 break;
738 case 'h':
739 if (sig[i] == ':')
740 checkpoint = i;
741 break;
743 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
744 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
745 sig[i + 2] == '=') {
746 tag = 'B';
747 linelen += 2;
748 i += 2;
749 } else
750 tag = sig[i];
755 void
756 dkim_signature_printheader(struct dkim_message *message, const char *line)
758 size_t i;
759 int first;
761 first = message->signature.signature[message->signature.len - 1] == '=';
762 for (i = 0; line[i] != '\0'; i++) {
763 if (i == 0 && line[i] != ' ' && line[i] != '\t' && !first)
764 dkim_signature_printf(message, "|");
765 if ((line[i] >= 0x21 && line[i] <= 0x3A) ||
766 (line[i] == 0x3C) ||
767 (line[i] >= 0x3E && line[i] <= 0x7B) ||
768 (line[i] >= 0x7D && line[i] <= 0x7E)) {
769 dkim_signature_printf(message, "%c", line[i]);
770 } else
771 dkim_signature_printf(message, "=%02hhX", line[i]);
773 dkim_signature_printf(message, "=%02hhX=%02hhX", '\r', '\n');
776 void
777 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
779 struct dkim_signature *sig = &(message->signature);
780 va_list ap;
781 size_t len;
783 va_start(ap, fmt);
784 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
785 fmt, ap)) >= sig->size - sig->len) {
786 va_end(ap);
787 dkim_signature_need(message, len + 1);
788 va_start(ap, fmt);
789 if ((len = vsnprintf(sig->signature + sig->len,
790 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
791 osmtpd_errx(1, "Miscalculated header size");
793 sig->len += len;
794 va_end(ap);
797 const char *
798 dkim_domain_select(struct dkim_message *message, char *from)
800 char *mdomain0, *mdomain;
801 size_t i;
803 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL)
804 return NULL;
806 while (mdomain != NULL && mdomain[0] != '\0') {
807 for (i = 0; i < ndomains; i++) {
808 if (strcasecmp(mdomain, domain[i]) == 0) {
809 free(mdomain0);
810 return domain[i];
813 if ((mdomain = strchr(mdomain, '.')) != NULL)
814 mdomain++;
816 free(mdomain0);
817 return NULL;
820 void
821 dkim_signature_need(struct dkim_message *message, size_t len)
823 struct dkim_signature *sig = &(message->signature);
824 char *tmp;
826 if (sig->len + len < sig->size)
827 return;
828 sig->size = (((len + sig->len) / 512) + 1) * 512;
829 if ((tmp = realloc(sig->signature, sig->size)) == NULL)
830 osmtpd_err(1, "malloc");
831 sig->signature = tmp;
834 __dead void
835 usage(void)
837 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
838 "[-c canonicalization] \n [-h headerfields]"
839 "[-x seconds] -D file -d domain -k keyfile -s selector\n");
840 exit(1);