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_adddomain(char *);
103 void dkim_err(struct dkim_message *, char *);
104 void dkim_errx(struct dkim_message *, char *);
105 void dkim_headers_set(char *);
106 void dkim_dataline(struct osmtpd_ctx *, const char *);
107 void dkim_commit(struct osmtpd_ctx *);
108 void *dkim_message_new(struct osmtpd_ctx *);
109 void dkim_message_free(struct osmtpd_ctx *, void *);
110 void dkim_parse_header(struct dkim_message *, char *, int);
111 void dkim_parse_body(struct dkim_message *, char *);
112 void dkim_sign(struct osmtpd_ctx *);
113 int dkim_signature_printheader(struct dkim_message *, const char *);
114 int dkim_signature_printf(struct dkim_message *, char *, ...)
115 __attribute__((__format__ (printf, 2, 3)));
116 int dkim_signature_normalize(struct dkim_message *);
117 const char *dkim_domain_select(struct dkim_message *, char *);
118 int dkim_signature_need(struct dkim_message *, size_t);
119 int dkim_sign_init(struct dkim_message *);
121 int
122 main(int argc, char *argv[])
124 int ch;
125 FILE *file;
126 char *line;
127 size_t linesz;
128 ssize_t linelen;
129 const char *errstr;
131 while ((ch = getopt(argc, argv, "a:c:D:d:h:k:s:tx:z")) != -1) {
132 switch (ch) {
133 case 'a':
134 if (strncmp(optarg, "rsa-", 4) == 0) {
135 cryptalg = "rsa";
136 hashalg = optarg + 4;
137 keyid = EVP_PKEY_RSA;
138 sephash = 0;
139 #ifdef HAVE_ED25519
140 } else if (strncmp(optarg, "ed25519-", 8) == 0) {
141 hashalg = optarg + 8;
142 cryptalg = "ed25519";
143 keyid = EVP_PKEY_ED25519;
144 sephash = 1;
145 #endif
146 } else
147 osmtpd_errx(1, "invalid algorithm");
148 break;
149 case 'c':
150 if (strncmp(optarg, "simple", 6) == 0) {
151 canonheader = CANON_SIMPLE;
152 optarg += 6;
153 } else if (strncmp(optarg, "relaxed", 7) == 0) {
154 canonheader = CANON_RELAXED;
155 optarg += 7;
156 } else
157 osmtpd_err(1, "Invalid canonicalization");
158 if (optarg[0] == '/') {
159 if (strcmp(optarg + 1, "simple") == 0)
160 canonbody = CANON_SIMPLE;
161 else if (strcmp(optarg + 1, "relaxed") == 0)
162 canonbody = CANON_RELAXED;
163 else
164 osmtpd_err(1,
165 "Invalid canonicalization");
166 } else if (optarg[0] == '\0')
167 canonbody = CANON_SIMPLE;
168 else
169 osmtpd_err(1, "Invalid canonicalization");
170 break;
171 case 'D':
172 if ((file = fopen(optarg, "r")) == NULL)
173 osmtpd_err(1, "Can't open domain file (%s)",
174 optarg);
175 do {
176 line = NULL;
177 linesz = 0;
178 linelen = getline(&line, &linesz, file);
179 if (linelen > 0) {
180 if (line[linelen - 1] == '\n')
181 line[linelen - 1] = '\0';
182 if (line[0] == '#')
183 continue;
184 dkim_adddomain(line);
186 } while (linelen != -1);
187 if (ferror(file))
188 osmtpd_err(1, "Error reading domain file (%s)",
189 optarg);
190 fclose(file);
191 break;
192 case 'd':
193 dkim_adddomain(optarg);
194 break;
195 case 'h':
196 dkim_headers_set(optarg);
197 break;
198 case 'k':
199 if ((file = fopen(optarg, "r")) == NULL)
200 osmtpd_err(1, "Can't open key file (%s)",
201 optarg);
202 pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL);
203 if (pkey == NULL)
204 osmtpd_errx(1, "Can't read key file");
205 fclose(file);
206 break;
207 case 's':
208 selector = optarg;
209 break;
210 case 't':
211 addtime = 1;
212 break;
213 case 'x':
214 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
215 if (addexpire == 0)
216 osmtpd_errx(1, "Expire offset is %s", errstr);
217 break;
218 case 'z':
219 addheaders++;
220 break;
221 default:
222 usage();
226 OpenSSL_add_all_digests();
228 if (pledge("tmppath stdio", NULL) == -1)
229 osmtpd_err(1, "pledge");
231 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
232 osmtpd_errx(1, "Can't find hash: %s", hashalg);
234 if (domain == NULL || selector == NULL || pkey == NULL)
235 usage();
237 if (EVP_PKEY_id(pkey) != keyid)
238 osmtpd_errx(1, "Key is not of type %s", cryptalg);
240 osmtpd_register_filter_dataline(dkim_dataline);
241 osmtpd_register_filter_commit(dkim_commit);
242 osmtpd_local_message(dkim_message_new, dkim_message_free);
243 osmtpd_run();
245 return 0;
248 void
249 dkim_adddomain(char *d)
251 domain = reallocarray(domain, ndomains + 1, sizeof(*domain));
252 if (domain == NULL)
253 osmtpd_err(1, "malloc");
254 domain[ndomains++] = d;
258 void
259 dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
261 struct dkim_message *message = ctx->local_message;
262 char *linedup;
263 size_t linelen;
265 if (message->err) {
266 if (line[0] == '.' && line[1] =='\0')
267 osmtpd_filter_dataline(ctx, ".");
268 return;
271 linelen = strlen(line);
272 if (fprintf(message->origf, "%s\n", line) < (int) linelen)
273 dkim_errx(message, "Couldn't write to tempfile");
275 if (line[0] == '.' && line[1] =='\0') {
276 dkim_sign(ctx);
277 } else if (linelen != 0 && message->parsing_headers) {
278 if (line[0] == '.')
279 line++;
280 if ((linedup = strdup(line)) == NULL)
281 osmtpd_err(1, "strdup");
282 dkim_parse_header(message, linedup, 0);
283 free(linedup);
284 } else if (linelen == 0 && message->parsing_headers) {
285 if (addheaders > 0 && !dkim_signature_printf(message, "; "))
286 return;
287 message->parsing_headers = 0;
288 } else {
289 if (line[0] == '.')
290 line++;
291 if ((linedup = strdup(line)) == NULL)
292 osmtpd_err(1, "strdup");
293 dkim_parse_body(message, linedup);
294 free(linedup);
298 void
299 dkim_commit(struct osmtpd_ctx *ctx)
301 struct dkim_message *message = ctx->local_message;
303 if (message->err)
304 osmtpd_filter_disconnect(ctx, "Internal server error");
305 else
306 osmtpd_filter_proceed(ctx);
309 void *
310 dkim_message_new(struct osmtpd_ctx *ctx)
312 struct dkim_message *message;
314 if ((message = calloc(1, sizeof(*message))) == NULL) {
315 dkim_err(message, "Failed to create message context");
316 return NULL;
319 if ((message->origf = tmpfile()) == NULL) {
320 dkim_err(message, "Failed to open tempfile");
321 goto fail;
323 message->parsing_headers = 1;
325 message->body_whitelines = 0;
326 message->headers = calloc(1, sizeof(*(message->headers)));
327 if (message->headers == NULL) {
328 dkim_err(message, "Can't save headers");
329 goto fail;
331 message->lastheader = 0;
332 message->signature.signature = NULL;
333 message->signature.size = 0;
334 message->signature.len = 0;
335 message->err = 0;
337 if (!dkim_signature_printf(message,
338 "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
339 cryptalg, hashalg,
340 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
341 canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
342 goto fail;
343 if (addheaders > 0 && !dkim_signature_printf(message, "z="))
344 goto fail;
346 if ((message->dctx = EVP_MD_CTX_new()) == NULL) {
347 dkim_errx(message, "Failed to create hash context");
348 goto fail;
350 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0) {
351 dkim_errx(message, "Failed to initialize hash context");
352 goto fail;
354 return message;
355 fail:
356 dkim_message_free(ctx, message);
357 return NULL;
360 void
361 dkim_message_free(struct osmtpd_ctx *ctx, void *data)
363 struct dkim_message *message = data;
364 size_t i;
366 fclose(message->origf);
367 EVP_MD_CTX_free(message->dctx);
368 free(message->signature.signature);
369 for (i = 0; message->headers != NULL &&
370 message->headers[i] != NULL; i++)
371 free(message->headers[i]);
372 free(message->headers);
373 free(message);
376 void
377 dkim_headers_set(char *headers)
379 size_t i;
380 int has_from = 0;
382 nsign_headers = 1;
384 for (i = 0; headers[i] != '\0'; i++) {
385 /* RFC 5322 field-name */
386 if (!(headers[i] >= 33 && headers[i] <= 126))
387 osmtpd_errx(1, "-h: invalid character");
388 if (headers[i] == ':') {
389 /* Test for empty headers */
390 if (i == 0 || headers[i - 1] == ':')
391 osmtpd_errx(1, "-h: header can't be empty");
392 nsign_headers++;
394 headers[i] = tolower(headers[i]);
396 if (headers[i - 1] == ':')
397 osmtpd_errx(1, "-h: header can't be empty");
399 if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
400 sizeof(*sign_headers))) == NULL)
401 osmtpd_errx(1, NULL);
403 for (i = 0; i < nsign_headers; i++) {
404 sign_headers[i] = headers;
405 if (i != nsign_headers - 1) {
406 headers = strchr(headers, ':');
407 headers++[0] = '\0';
409 if (strcasecmp(sign_headers[i], "from") == 0)
410 has_from = 1;
412 if (!has_from)
413 osmtpd_errx(1, "From header must be included");
416 void
417 dkim_err(struct dkim_message *message, char *msg)
419 message->err = 1;
420 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
423 void
424 dkim_errx(struct dkim_message *message, char *msg)
426 message->err = 1;
427 fprintf(stderr, "%s\n", msg);
430 void
431 dkim_parse_header(struct dkim_message *message, char *line, int force)
433 size_t i;
434 size_t r, w;
435 size_t linelen;
436 size_t lastheader;
437 size_t hlen;
438 int fieldname = 0;
439 char **mtmp;
440 char *htmp;
441 char *tmp;
443 if (addheaders == 2 && !force &&
444 !dkim_signature_printheader(message, line))
445 return;
447 if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
448 return;
449 if ((line[0] != ' ' && line[0] != '\t')) {
450 message->lastheader = 0;
451 for (i = 0; i < nsign_headers; i++) {
452 hlen = strlen(sign_headers[i]);
453 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
454 while (line[hlen] == ' ' || line[hlen] == '\t')
455 hlen++;
456 if (line[hlen] != ':')
457 continue;
458 break;
461 if (i == nsign_headers && !force)
462 return;
465 if (addheaders == 1 && !force &&
466 !dkim_signature_printheader(message, line))
467 return;
469 if (canonheader == CANON_RELAXED) {
470 if (!message->lastheader)
471 fieldname = 1;
472 for (r = w = 0; line[r] != '\0'; r++) {
473 if (line[r] == ':' && fieldname) {
474 if (w > 0 && line[w - 1] == ' ')
475 line[w - 1] = ':';
476 else
477 line[w++] = ':';
478 fieldname = 0;
479 while (line[r + 1] == ' ' ||
480 line[r + 1] == '\t')
481 r++;
482 continue;
484 if (line[r] == ' ' || line[r] == '\t' ||
485 line[r] == '\r' || line[r] == '\n') {
486 if (r != 0 && w != 0 && line[w - 1] == ' ')
487 continue;
488 else
489 line[w++] = ' ';
490 } else if (fieldname) {
491 line[w++] = tolower(line[r]);
492 continue;
493 } else
494 line[w++] = line[r];
496 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
497 line[linelen] = '\0';
498 } else
499 linelen = strlen(line);
501 for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
502 continue;
503 if (!message->lastheader) {
504 mtmp = recallocarray(message->headers, lastheader + 1,
505 lastheader + 2, sizeof(*mtmp));
506 if (mtmp == NULL) {
507 dkim_err(message, "Can't store header");
508 return;
510 message->headers = mtmp;
512 if ((message->headers[lastheader] = strdup(line)) == NULL) {
513 dkim_err(message, "Can't store header");
514 return;
516 message->headers[lastheader + 1 ] = NULL;
517 message->lastheader = 1;
518 } else {
519 lastheader--;
520 linelen += strlen(message->headers[lastheader]);
521 if (canonheader == CANON_SIMPLE)
522 linelen += 2;
523 linelen++;
524 htmp = reallocarray(message->headers[lastheader], linelen,
525 sizeof(*htmp));
526 if (htmp == NULL) {
527 dkim_err(message, "Can't store header");
528 return;
530 message->headers[lastheader] = htmp;
531 if (canonheader == CANON_SIMPLE) {
532 if (strlcat(htmp, "\r\n", linelen) >= linelen)
533 osmtpd_errx(1, "Missized header");
534 } else if (canonheader == CANON_RELAXED &&
535 (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
536 tmp[1] == '\0')
537 line++;
539 if (strlcat(htmp, line, linelen) >= linelen)
540 osmtpd_errx(1, "Missized header");
544 void
545 dkim_parse_body(struct dkim_message *message, char *line)
547 size_t r, w;
548 size_t linelen;
550 if (canonbody == CANON_RELAXED) {
551 for (r = w = 0; line[r] != '\0'; r++) {
552 if (line[r] == ' ' || line[r] == '\t') {
553 if (r != 0 && line[w - 1] == ' ')
554 continue;
555 else
556 line[w++] = ' ';
557 } else
558 line[w++] = line[r];
560 linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
561 line[linelen] = '\0';
562 } else
563 linelen = strlen(line);
565 if (line[0] == '\0') {
566 message->body_whitelines++;
567 return;
570 while (message->body_whitelines--) {
571 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
572 dkim_errx(message, "Can't update hash context");
573 return;
576 message->body_whitelines = 0;
577 message->has_body = 1;
579 if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
580 EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
581 dkim_errx(message, "Can't update hash context");
582 return;
586 void
587 dkim_sign(struct osmtpd_ctx *ctx)
589 struct dkim_message *message = ctx->local_message;
590 /* Use largest hash size here */
591 unsigned char bdigest[EVP_MAX_MD_SIZE];
592 unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
593 unsigned char *b;
594 const char *sdomain = domain[0], *tsdomain;
595 time_t now;
596 ssize_t i;
597 size_t linelen = 0;
598 char *tmp, *tmp2;
599 unsigned int digestsz;
601 if (addtime || addexpire)
602 now = time(NULL);
603 if (addtime && !dkim_signature_printf(message, "t=%lld; ",
604 (long long)now))
605 goto fail;
606 if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
607 now + addexpire < now ? INT64_MAX : now + addexpire))
608 goto fail;
610 if (canonbody == CANON_SIMPLE && !message->has_body) {
611 if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
612 dkim_errx(message, "Can't update hash context");
613 goto fail;
616 if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0) {
617 dkim_errx(message, "Can't finalize hash context");
618 goto fail;
620 EVP_EncodeBlock(digest, bdigest, digestsz);
621 if (!dkim_signature_printf(message, "bh=%s; h=", digest))
622 goto fail;
623 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
624 for (i = 0; message->headers[i] != NULL; i++)
625 continue;
626 EVP_MD_CTX_reset(message->dctx);
627 if (!sephash) {
628 if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
629 pkey) != 1) {
630 dkim_errx(message, "Failed to initialize signature "
631 "context");
632 goto fail;
634 } else {
635 if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1) {
636 dkim_errx(message, "Failed to initialize hash context");
637 goto fail;
640 for (i--; i >= 0; i--) {
641 if (!sephash) {
642 if (EVP_DigestSignUpdate(message->dctx,
643 message->headers[i],
644 strlen(message->headers[i])) != 1 ||
645 EVP_DigestSignUpdate(message->dctx, "\r\n",
646 2) <= 0) {
647 dkim_errx(message, "Failed to update signature "
648 "context");
649 goto fail;
651 } else {
652 if (EVP_DigestUpdate(message->dctx, message->headers[i],
653 strlen(message->headers[i])) != 1 ||
654 EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
655 dkim_errx(message, "Failed to update digest "
656 "context");
657 goto fail;
660 if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
661 sdomain = tsdomain;
662 /* We're done with the cached header after hashing */
663 for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
664 if (tmp[0] == ' ' || tmp[0] == '\t')
665 break;
666 tmp[0] = tolower(tmp[0]);
668 tmp[0] = '\0';
669 if (!dkim_signature_printf(message, "%s%s",
670 message->headers[i + 1] == NULL ? "" : ":",
671 message->headers[i]))
672 goto fail;
674 dkim_signature_printf(message, "; d=%s; b=", sdomain);
675 if (!dkim_signature_normalize(message))
676 goto fail;
677 if ((tmp = strdup(message->signature.signature)) == NULL) {
678 dkim_err(message, "Can't create DKIM signature");
679 goto fail;
681 dkim_parse_header(message, tmp, 1);
682 if (!sephash) {
683 if (EVP_DigestSignUpdate(message->dctx, tmp,
684 strlen(tmp)) != 1) {
685 dkim_errx(message, "Failed to update signature "
686 "context");
687 goto fail;
689 } else {
690 if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1) {
691 dkim_errx(message, "Failed to update digest context");
692 goto fail;
695 free(tmp);
696 if (!sephash) {
697 if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1) {
698 dkim_errx(message, "Can't finalize signature context");
699 goto fail;
701 #ifdef HAVE_ED25519
702 } else {
703 if (EVP_DigestFinal_ex(message->dctx, bdigest,
704 &digestsz) != 1) {
705 dkim_errx(message, "Can't finalize hash context");
706 goto fail;
708 EVP_MD_CTX_reset(message->dctx);
709 if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
710 pkey) != 1) {
711 dkim_errx(message, "Failed to initialize signature "
712 "context");
713 goto fail;
715 if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
716 digestsz) != 1) {
717 dkim_errx(message, "Failed to finalize signature");
718 goto fail;
720 #endif
722 if ((tmp = malloc(linelen)) == NULL) {
723 dkim_err(message, "Can't allocate space for signature");
724 goto fail;
726 if (!sephash) {
727 if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1) {
728 dkim_errx(message, "Failed to finalize signature");
729 goto fail;
731 #ifdef HAVE_ED25519
732 } else {
733 if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
734 digestsz) != 1) {
735 dkim_errx(message, "Failed to finalize signature");
736 goto fail;
738 #endif
740 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
741 dkim_err(message, "Can't create DKIM signature");
742 goto fail;
744 EVP_EncodeBlock(b, tmp, linelen);
745 free(tmp);
746 dkim_signature_printf(message, "%s\r\n", b);
747 free(b);
748 dkim_signature_normalize(message);
749 tmp = message->signature.signature;
750 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
751 tmp2[0] = '\0';
752 osmtpd_filter_dataline(ctx, "%s", tmp);
753 tmp = tmp2 + 2;
755 tmp = NULL;
756 linelen = 0;
757 rewind(message->origf);
758 while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
759 tmp[i - 1] = '\0';
760 osmtpd_filter_dataline(ctx, "%s", tmp);
762 free(tmp);
763 return;
764 fail:
765 osmtpd_filter_dataline(ctx, ".");
768 int
769 dkim_signature_normalize(struct dkim_message *message)
771 size_t i;
772 size_t linelen;
773 size_t checkpoint;
774 size_t skip;
775 size_t *headerlen = &(message->signature.len);
776 int headername = 1;
777 char tag = '\0';
778 char *sig = message->signature.signature;
780 for (linelen = i = 0; sig[i] != '\0'; i++) {
781 if (sig[i] == '\r' && sig[i + 1] == '\n') {
782 i++;
783 checkpoint = 0;
784 linelen = 0;
785 continue;
787 if (sig[i] == '\t')
788 linelen = (linelen + 8) & ~7;
789 else
790 linelen++;
791 if (headername) {
792 if (sig[i] == ':') {
793 headername = 0;
794 checkpoint = i;
796 continue;
798 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
799 for (skip = checkpoint + 1;
800 sig[skip] == ' ' || sig[skip] == '\t';
801 skip++)
802 continue;
803 skip -= checkpoint + 1;
804 if (!dkim_signature_need(message,
805 skip > 3 ? 0 : 3 - skip + 1))
806 return 0;
807 sig = message->signature.signature;
809 memmove(sig + checkpoint + 3,
810 sig + checkpoint + skip,
811 *headerlen - skip - checkpoint + 1);
812 sig[checkpoint + 1] = '\r';
813 sig[checkpoint + 2] = '\n';
814 sig[checkpoint + 3] = '\t';
815 linelen = 8;
816 *headerlen = *headerlen + 3 - skip;
817 i = checkpoint + 3;
818 checkpoint = 0;
820 if (sig[i] == ';') {
821 checkpoint = i;
822 tag = '\0';
823 continue;
825 switch (tag) {
826 case 'B':
827 case 'b':
828 case 'z':
829 checkpoint = i;
830 break;
831 case 'h':
832 if (sig[i] == ':')
833 checkpoint = i;
834 break;
836 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
837 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
838 sig[i + 2] == '=') {
839 tag = 'B';
840 linelen += 2;
841 i += 2;
842 } else
843 tag = sig[i];
846 return 1;
849 int
850 dkim_signature_printheader(struct dkim_message *message, const char *header)
852 size_t i, j, len;
853 static char *fmtheader = NULL;
854 char *tmp;
855 static size_t size = 0;
856 int first;
858 len = strlen(header);
859 if ((len + 3) * 3 < len) {
860 errno = EOVERFLOW;
861 dkim_err(message, "Can't add z-component to header");
862 return 0;
864 if ((len + 3) * 3 > size) {
865 if ((tmp = reallocarray(fmtheader, 3, len + 3)) == NULL) {
866 dkim_err(message, "Can't add z-component to header");
867 return 0;
869 fmtheader = tmp;
870 size = (len + 1) * 3;
873 first = message->signature.signature[message->signature.len - 1] == '=';
874 for (j = i = 0; header[i] != '\0'; i++, j++) {
875 if (i == 0 && header[i] != ' ' && header[i] != '\t' && !first)
876 fmtheader[j++] = '|';
877 if ((header[i] >= 0x21 && header[i] <= 0x3A) ||
878 (header[i] == 0x3C) ||
879 (header[i] >= 0x3E && header[i] <= 0x7B) ||
880 (header[i] >= 0x7D && header[i] <= 0x7E))
881 fmtheader[j] = header[i];
882 else {
883 fmtheader[j++] = '=';
884 (void) sprintf(fmtheader + j, "%02hhX", header[i]);
885 j++;
888 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
889 (unsigned char) '\n');
891 return dkim_signature_printf(message, "%s", fmtheader);
894 int
895 dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
897 struct dkim_signature *sig = &(message->signature);
898 va_list ap;
899 size_t len;
901 va_start(ap, fmt);
902 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
903 fmt, ap)) >= sig->size - sig->len) {
904 va_end(ap);
905 if (!dkim_signature_need(message, len + 1))
906 return 0;
907 va_start(ap, fmt);
908 if ((len = vsnprintf(sig->signature + sig->len,
909 sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
910 osmtpd_errx(1, "Miscalculated header size");
912 sig->len += len;
913 va_end(ap);
914 return 1;
917 const char *
918 dkim_domain_select(struct dkim_message *message, char *from)
920 char *mdomain0, *mdomain;
921 size_t i;
923 if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
924 if (errno != EINVAL) {
925 dkim_errx(message, "Couldn't parse from header");
926 return NULL;
928 return NULL;
931 while (mdomain != NULL && mdomain[0] != '\0') {
932 for (i = 0; i < ndomains; i++) {
933 if (strcasecmp(mdomain, domain[i]) == 0) {
934 free(mdomain0);
935 return domain[i];
938 if ((mdomain = strchr(mdomain, '.')) != NULL)
939 mdomain++;
941 free(mdomain0);
942 return NULL;
945 int
946 dkim_signature_need(struct dkim_message *message, size_t len)
948 struct dkim_signature *sig = &(message->signature);
949 char *tmp;
951 if (sig->len + len < sig->size)
952 return 1;
953 sig->size = (((len + sig->len) / 512) + 1) * 512;
954 if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
955 dkim_err(message, "No room for signature");
956 return 0;
958 sig->signature = tmp;
959 return 1;
962 __dead void
963 usage(void)
965 fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
966 "[-c canonicalization] \n [-h headerfields]"
967 "[-x seconds] -D file -d domain -k keyfile -s selector\n");
968 exit(1);