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/evp.h>
17 #include <openssl/pem.h>
18 #include <openssl/sha.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <syslog.h>
27 #include <time.h>
28 #include <unistd.h>
30 #include "opensmtpd.h"
31 #include "mheader.h"
33 struct dkim_signature {
34 char *signature;
35 size_t size;
36 size_t len;
37 };
39 struct dkim_message {
40 FILE *origf;
41 int parsing_headers;
42 char **headers;
43 int lastheader;
44 size_t body_whitelines;
45 int has_body;
46 struct dkim_signature signature;
47 int err;
48 EVP_MD_CTX *b;
49 EVP_MD_CTX *bh;
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;
96 #define DKIM_SIGNATURE_LINELEN 78
98 void usage(void);
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 *);
117 int
118 main(int argc, char *argv[])
120 int ch;
121 FILE *keyfile;
122 const char *errstr;
124 while ((ch = getopt(argc, argv, "a:c:d:h:k:s:tx:z")) != -1) {
125 switch (ch) {
126 case 'a':
127 if (strncmp(optarg, "rsa-", 4) != 0)
128 osmtpd_err(1, "invalid algorithm");
129 hashalg = optarg + 4;
130 break;
131 case 'c':
132 if (strncmp(optarg, "simple", 6) == 0) {
133 canonheader = CANON_SIMPLE;
134 optarg += 6;
135 } else if (strncmp(optarg, "relaxed", 7) == 0) {
136 canonheader = CANON_RELAXED;
137 optarg += 7;
138 } else
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;
145 else
146 osmtpd_err(1,
147 "Invalid canonicalization");
148 } else if (optarg[0] == '\0')
149 canonbody = CANON_SIMPLE;
150 else
151 osmtpd_err(1, "Invalid canonicalization");
152 break;
153 case 'd':
154 if ((domain = reallocarray(domain, ndomains + 1,
155 sizeof(*domain))) == NULL)
156 osmtpd_err(1, "malloc");
157 domain[ndomains++] = optarg;
158 break;
159 case 'h':
160 dkim_headers_set(optarg);
161 break;
162 case 'k':
163 if ((keyfile = fopen(optarg, "r")) == NULL)
164 osmtpd_err(1, "Can't open key file (%s)",
165 optarg);
166 pkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
167 if (pkey == 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");
171 fclose(keyfile);
172 break;
173 case 's':
174 selector = optarg;
175 break;
176 case 't':
177 addtime = 1;
178 break;
179 case 'x':
180 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
181 if (addexpire == 0)
182 osmtpd_errx(1, "Expire offset is %s", errstr);
183 break;
184 case 'z':
185 addheaders++;
186 break;
187 default:
188 usage();
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)
200 usage();
202 osmtpd_register_filter_dataline(dkim_dataline);
203 osmtpd_register_filter_commit(dkim_commit);
204 osmtpd_local_message(dkim_message_new, dkim_message_free);
205 osmtpd_run();
207 return 0;
210 void
211 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
213 struct dkim_message *message = ctx->local_message;
214 char *linedup;
215 size_t linelen;
217 if (message->err) {
218 if (line[0] == '.' && line[1] =='\0')
219 osmtpd_filter_dataline(ctx, ".");
220 return;
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') {
228 dkim_sign(ctx);
229 } else if (linelen != 0 && message->parsing_headers) {
230 if (line[0] == '.')
231 line++;
232 if ((linedup = strdup(line)) == NULL)
233 osmtpd_err(1, "strdup");
234 dkim_parse_header(message, linedup, 0);
235 free(linedup);
236 } else if (linelen == 0 && message->parsing_headers) {
237 if (addheaders > 0 && !dkim_signature_printf(message, "; "))
238 return;
239 message->parsing_headers = 0;
240 } else {
241 if (line[0] == '.')
242 line++;
243 if ((linedup = strdup(line)) == NULL)
244 osmtpd_err(1, "strdup");
245 dkim_parse_body(message, linedup);
246 free(linedup);
250 void
251 dkim_commit(struct osmtpd_ctx *ctx)
253 struct dkim_message *message = ctx->local_message;
255 if (message->err)
256 osmtpd_filter_disconnect(ctx, "Internal server error");
257 else
258 osmtpd_filter_proceed(ctx);
261 void *
262 dkim_message_new(struct osmtpd_ctx *ctx)
264 struct dkim_message *message;
266 if ((message = calloc(1, sizeof(*message))) == NULL)
267 osmtpd_err(1, NULL);
269 if ((message->origf = tmpfile()) == NULL) {
270 dkim_err(message, "Can't open tempfile");
271 return NULL;
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");
279 return NULL;
281 message->lastheader = 0;
282 message->signature.signature = NULL;
283 message->signature.size = 0;
284 message->signature.len = 0;
285 message->err = 0;
287 if (!dkim_signature_printf(message,
288 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
289 cryptalg, hashalg,
290 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
291 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
292 return NULL;
293 if (addheaders > 0 && !dkim_signature_printf(message, "z="))
294 return NULL;
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");
299 return NULL;
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");
304 return NULL;
306 return message;
309 void
310 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
312 struct dkim_message *message = data;
313 size_t i;
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);
322 free(message);
325 void
326 dkim_headers_set(char *headers)
328 size_t i;
329 int has_from = 0;
331 nsign_headers = 1;
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");
341 nsign_headers++;
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, ':');
356 headers++[0] = '\0';
358 if (strcasecmp(sign_headers[i], "from") == 0)
359 has_from = 1;
361 if (!has_from)
362 osmtpd_errx(1, "From header must be included");
365 void
366 dkim_err(struct dkim_message *message, char *msg)
368 message->err = 1;
369 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
372 void
373 dkim_errx(struct dkim_message *message, char *msg)
375 message->err = 1;
376 fprintf(stderr, "%s\n", msg);
379 void
380 dkim_parse_header(struct dkim_message *message, char *line, int force)
382 size_t i;
383 size_t r, w;
384 size_t linelen;
385 size_t lastheader;
386 size_t hlen;
387 int fieldname = 0;
388 char **mtmp;
389 char *htmp;
390 char *tmp;
392 if (addheaders == 2 && !force &&
393 !dkim_signature_printheader(message, line))
394 return;
396 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
397 return;
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')
404 hlen++;
405 if (line[hlen] != ':')
406 continue;
407 break;
410 if (i == nsign_headers && !force)
411 return;
414 if (addheaders == 1 && !force &&
415 !dkim_signature_printheader(message, line))
416 return;
418 if (canonheader == CANON_RELAXED) {
419 if (!message->lastheader)
420 fieldname = 1;
421 for (r = w = 0; line[r] != '\0'; r++) {
422 if (line[r] == ':' && fieldname) {
423 if (w > 0 && line[w - 1] == ' ')
424 line[w - 1] = ':';
425 else
426 line[w++] = ':';
427 fieldname = 0;
428 while (line[r + 1] == ' ' ||
429 line[r + 1] == '\t')
430 r++;
431 continue;
433 if (line[r] == ' ' || line[r] == '\t' ||
434 line[r] == '\r' || line[r] == '\n') {
435 if (r != 0 && w != 0 && line[w - 1] == ' ')
436 continue;
437 else
438 line[w++] = ' ';
439 } else if (fieldname) {
440 line[w++] = tolower(line[r]);
441 continue;
442 } else
443 line[w++] = line[r];
445 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
446 line[linelen] = '\0';
447 } else
448 linelen = strlen(line);
450 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
451 continue;
452 if (!message->lastheader) {
453 mtmp = recallocarray(message->headers, lastheader + 1,
454 lastheader + 2, sizeof(*mtmp));
455 if (mtmp == NULL) {
456 dkim_err(message, "Can't store header");
457 return;
459 message->headers = mtmp;
461 message->headers[lastheader] = strdup(line);
462 message->headers[lastheader + 1 ] = NULL;
463 message->lastheader = 1;
464 } else {
465 lastheader--;
466 linelen += strlen(message->headers[lastheader]);
467 if (canonheader == CANON_SIMPLE)
468 linelen += 2;
469 linelen++;
470 htmp = reallocarray(message->headers[lastheader], linelen,
471 sizeof(*htmp));
472 if (htmp == NULL) {
473 dkim_err(message, "Can't store header");
474 return;
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 &&
482 tmp[1] == '\0')
483 line++;
485 if (strlcat(htmp, line, linelen) >= linelen)
486 osmtpd_errx(1, "Missized header");
490 void
491 dkim_parse_body(struct dkim_message *message, char *line)
493 size_t r, w;
494 size_t linelen;
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] == ' ')
500 continue;
501 else
502 line[w++] = ' ';
503 } else
504 line[w++] = line[r];
506 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
507 line[linelen] = '\0';
508 } else
509 linelen = strlen(line);
511 if (line[0] == '\0') {
512 message->body_whitelines++;
513 return;
516 while (message->body_whitelines--) {
517 if (EVP_DigestUpdate(message->bh, "\r\n", 2) == 0) {
518 dkim_err(message, "Can't update hash context");
519 return;
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");
528 return;
532 void
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];
539 char *b;
540 const char *sdomain = domain[0], *tsdomain;
541 time_t now;
542 ssize_t i;
543 size_t linelen;
544 char *tmp, *tmp2;
546 if (addtime || addexpire)
547 now = time(NULL);
548 if (addtime && !dkim_signature_printf(message, "t=%lld; ", now))
549 return;
550 if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
551 now + addexpire < now ? INT64_MAX : now + addexpire))
552 return;
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");
557 return;
560 if (EVP_DigestFinal_ex(message->bh, bbh, NULL) == 0) {
561 dkim_err(message, "Can't finalize hash context");
562 return;
564 EVP_EncodeBlock(bh, bbh, EVP_MD_CTX_size(message->bh));
565 if (!dkim_signature_printf(message, "bh=%s; h=", bh))
566 return;
567 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
568 for (i = 0; message->headers[i] != NULL; i++)
569 continue;
570 for (i--; i >= 0; i--) {
571 if (EVP_DigestSignUpdate(message->b,
572 message->headers[i],
573 strlen(message->headers[i])) <= 0 ||
574 EVP_DigestSignUpdate(message->b, "\r\n", 2) <= 0) {
575 dkim_errx(message, "Failed to update digest context");
576 return;
578 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
579 sdomain = tsdomain;
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')
583 break;
584 tmp[0] = tolower(tmp[0]);
586 tmp[0] = '\0';
587 if (!dkim_signature_printf(message, "%s%s",
588 message->headers[i + 1] == NULL ? "" : ":",
589 message->headers[i]))
590 return;
592 dkim_signature_printf(message, "; d=%s; b=", sdomain);
593 if (!dkim_signature_normalize(message))
594 return;
595 if ((tmp = strdup(message->signature.signature)) == NULL) {
596 dkim_err(message, "Can't create DKIM signature");
597 return;
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");
602 return;
604 free(tmp);
605 if (EVP_DigestSignFinal(message->b, NULL, &linelen) <= 0) {
606 dkim_err(message, "Failed to finalize digest");
607 return;
609 if ((tmp = malloc(linelen)) == NULL) {
610 dkim_err(message, "Can't allocate space for digest");
611 return;
613 if (EVP_DigestSignFinal(message->b, tmp, &linelen) <= 0) {
614 dkim_err(message, "Failed to finalize digest");
615 return;
617 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
618 dkim_err(message, "Can't create DKIM signature");
619 return;
621 EVP_EncodeBlock(b, tmp, linelen);
622 free(tmp);
623 dkim_signature_printf(message, "%s\r\n", b);
624 free(b);
625 dkim_signature_normalize(message);
626 tmp = message->signature.signature;
627 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
628 tmp2[0] = '\0';
629 osmtpd_filter_dataline(ctx, "%s", tmp);
630 tmp = tmp2 + 2;
632 tmp = NULL;
633 linelen = 0;
634 rewind(message->origf);
635 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
636 tmp[i - 1] = '\0';
637 osmtpd_filter_dataline(ctx, "%s", tmp);
641 int
642 dkim_signature_normalize(struct dkim_message *message)
644 size_t i;
645 size_t linelen;
646 size_t checkpoint;
647 size_t skip;
648 size_t *headerlen = &(message->signature.len);
649 int headername = 1;
650 char tag = '\0';
651 char *sig = message->signature.signature;
653 for (linelen = i = 0; sig[i] != '\0'; i++) {
654 if (sig[i] == '\r' && sig[i + 1] == '\n') {
655 i++;
656 checkpoint = 0;
657 linelen = 0;
658 continue;
660 if (sig[i] == '\t')
661 linelen = (linelen + 8) & ~7;
662 else
663 linelen++;
664 if (headername) {
665 if (sig[i] == ':') {
666 headername = 0;
667 checkpoint = i;
669 continue;
671 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
672 for (skip = checkpoint + 1;
673 sig[skip] == ' ' || sig[skip] == '\t';
674 skip++)
675 continue;
676 skip -= checkpoint + 1;
677 if (!dkim_signature_need(message,
678 skip > 3 ? 0 : 3 - skip + 1))
679 return 0;
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';
688 linelen = 8;
689 *headerlen = *headerlen + 3 - skip;
690 i = checkpoint + 3;
691 checkpoint = 0;
693 if (sig[i] == ';') {
694 checkpoint = i;
695 tag = '\0';
696 continue;
698 switch (tag) {
699 case 'B':
700 case 'b':
701 case 'z':
702 checkpoint = i;
703 break;
704 case 'h':
705 if (sig[i] == ':')
706 checkpoint = i;
707 break;
709 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
710 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
711 sig[i + 2] == '=') {
712 tag = 'B';
713 linelen += 2;
714 i += 2;
715 } else
716 tag = sig[i];
719 return 1;
722 int
723 dkim_signature_printheader(struct dkim_message *message, const char *header)
725 size_t i, j, len;
726 static char *fmtheader = NULL;
727 char *tmp;
728 static size_t size = 0;
729 int first;
731 len = strlen(header);
732 if ((len + 3) * 3 < len) {
733 errno = EOVERFLOW;
734 dkim_err(message, "Can't add z-component to header");
735 return 0;
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");
740 return 0;
742 fmtheader = tmp;
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];
755 else {
756 fmtheader[j++] = '=';
757 (void) sprintf(fmtheader + j, "%02hhX", header[i]);
758 j++;
761 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
762 (unsigned char) '\n');
764 return dkim_signature_printf(message, "%s", fmtheader);
767 int
768 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
770 struct dkim_signature *sig = &(message->signature);
771 va_list ap;
772 size_t len;
774 va_start(ap, fmt);
775 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
776 fmt, ap)) >= sig->size - sig->len) {
777 va_end(ap);
778 if (!dkim_signature_need(message, len + 1))
779 return 0;
780 va_start(ap, fmt);
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");
785 sig->len += len;
786 va_end(ap);
787 return 1;
790 const char *
791 dkim_domain_select(struct dkim_message *message, char *from)
793 char *mdomain0, *mdomain;
794 size_t i;
796 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
797 if (errno != EINVAL) {
798 dkim_err(message, "Couldn't parse from header");
799 return NULL;
801 return NULL;
804 while (mdomain != NULL && mdomain[0] != '\0') {
805 for (i = 0; i < ndomains; i++) {
806 if (strcasecmp(mdomain, domain[i]) == 0) {
807 free(mdomain0);
808 return domain[i];
811 if ((mdomain = strchr(mdomain, '.')) != NULL)
812 mdomain++;
814 free(mdomain0);
815 return NULL;
818 int
819 dkim_signature_need(struct dkim_message *message, size_t len)
821 struct dkim_signature *sig = &(message->signature);
822 char *tmp;
824 if (sig->len + len < sig->size)
825 return 1;
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");
829 return 0;
831 sig->signature = tmp;
832 return 1;
835 __dead void
836 usage(void)
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");
841 exit(1);