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 <sys/tree.h>
18 #include <openssl/evp.h>
19 #include <openssl/pem.h>
20 #include <openssl/sha.h>
22 #include <ctype.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <syslog.h>
30 #include <time.h>
31 #include <unistd.h>
33 #include "smtp_proc.h"
35 struct dkim_signature {
36 char *signature;
37 size_t size;
38 size_t len;
39 };
41 struct dkim_session {
42 uint64_t reqid;
43 uint64_t token;
44 FILE *origf;
45 int parsing_headers;
46 char **headers;
47 int lastheader;
48 size_t body_whitelines;
49 int has_body;
50 struct dkim_signature signature;
51 int err;
52 EVP_MD_CTX *b;
53 EVP_MD_CTX *bh;
54 RB_ENTRY(dkim_session) entry;
55 };
57 RB_HEAD(dkim_sessions, dkim_session) dkim_sessions = RB_INITIALIZER(NULL);
58 RB_PROTOTYPE(dkim_sessions, dkim_session, entry, dkim_session_cmp);
60 /* RFC 6376 section 5.4.1 */
61 static char *dsign_headers[] = {
62 "from",
63 "reply-to",
64 "subject",
65 "date",
66 "to",
67 "cc",
68 "resent-date",
69 "resent-from",
70 "resent-to",
71 "resent-cc",
72 "in-reply-to",
73 "references",
74 "list-id",
75 "list-help",
76 "list-unsubscribe",
77 "list-subscribe",
78 "list-post",
79 "list-owner",
80 "list-archive"
81 };
82 static char **sign_headers = dsign_headers;
83 static size_t nsign_headers = sizeof(dsign_headers) / sizeof(*dsign_headers);
85 static char *hashalg = "sha256";
86 static char *cryptalg = "rsa";
88 #define CANON_SIMPLE 0
89 #define CANON_RELAXED 1
90 static int canonheader = CANON_SIMPLE;
91 static int canonbody = CANON_SIMPLE;
93 static int addtime = 0;
94 static long long addexpire = 0;
95 static int addheaders = 0;
97 static char *domain = NULL;
98 static char *selector = NULL;
100 static EVP_PKEY *pkey;
101 static const EVP_MD *hash_md;
103 #define DKIM_SIGNATURE_LINELEN 78
105 void usage(void);
106 void dkim_err(struct dkim_session *, char *);
107 void dkim_errx(struct dkim_session *, char *);
108 void dkim_headers_set(char *);
109 void dkim_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
110 uint64_t, char *);
111 void dkim_commit(char *, int, struct timespec *, char *, char *, uint64_t,
112 uint64_t);
113 void dkim_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
114 struct dkim_session *dkim_session_new(uint64_t);
115 void dkim_session_free(struct dkim_session *);
116 int dkim_session_cmp(struct dkim_session *, struct dkim_session *);
117 void dkim_parse_header(struct dkim_session *, char *, int);
118 void dkim_parse_body(struct dkim_session *, char *);
119 void dkim_sign(struct dkim_session *);
120 int dkim_signature_printheader(struct dkim_session *, char *);
121 int dkim_signature_printf(struct dkim_session *, char *, ...)
122 __attribute__((__format__ (printf, 2, 3)));
123 int dkim_signature_normalize(struct dkim_session *);
124 int dkim_signature_need(struct dkim_session *, size_t);
125 int dkim_sign_init(struct dkim_session *);
127 int
128 main(int argc, char *argv[])
130 int ch;
131 int i;
132 int debug = 0;
133 FILE *keyfile;
134 const char *errstr;
136 while ((ch = getopt(argc, argv, "a:c:Dd:h:k:s:tx:zZ")) != -1) {
137 switch (ch) {
138 case 'a':
139 if (strncmp(optarg, "rsa-", 4) != 0)
140 err(1, "invalid algorithm");
141 hashalg = optarg + 4;
142 break;
143 case 'c':
144 if (strncmp(optarg, "simple", 6) == 0) {
145 canonheader = CANON_SIMPLE;
146 optarg += 6;
147 } else if (strncmp(optarg, "relaxed", 7) == 0) {
148 canonheader = CANON_RELAXED;
149 optarg += 7;
150 } else
151 err(1, "Invalid canonicalization");
152 if (optarg[0] == '/') {
153 if (strcmp(optarg + 1, "simple") == 0)
154 canonbody = CANON_SIMPLE;
155 else if (strcmp(optarg + 1, "relaxed") == 0)
156 canonbody = CANON_RELAXED;
157 else
158 err(1, "Invalid canonicalization");
159 } else if (optarg[0] == '\0')
160 canonbody = CANON_SIMPLE;
161 else
162 err(1, "Invalid canonicalization");
163 break;
164 case 'd':
165 domain = optarg;
166 break;
167 case 'h':
168 dkim_headers_set(optarg);
169 break;
170 case 'k':
171 if ((keyfile = fopen(optarg, "r")) == NULL)
172 err(1, "Can't open key file");
173 pkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
174 if (pkey == NULL)
175 errx(1, "Can't read key file");
176 if (EVP_PKEY_get0_RSA(pkey) == NULL)
177 err(1, "Key is not of type rsa");
178 fclose(keyfile);
179 break;
180 case 's':
181 selector = optarg;
182 break;
183 case 't':
184 addtime = 1;
185 break;
186 case 'x':
187 addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
188 if (addexpire == 0)
189 errx(1, "Expire offset is %s", errstr);
190 break;
191 case 'z':
192 addheaders = 1;
193 break;
194 case 'Z':
195 addheaders = 2;
196 break;
197 case 'D':
198 debug = 1;
199 break;
200 default:
201 usage();
205 OpenSSL_add_all_digests();
206 if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
207 errx(1, "Can't find hash: %s", hashalg);
209 /*
210 * fattr required for tmpfile.
211 * Can hopefully be removed in the future
212 */
213 if (pledge("fattr tmppath stdio", NULL) == -1)
214 err(1, "pledge");
216 if (domain == NULL || selector == NULL || pkey == NULL)
217 usage();
219 smtp_register_filter_dataline(dkim_dataline);
220 smtp_register_filter_commit(dkim_commit);
221 smtp_in_register_report_disconnect(dkim_disconnect);
222 smtp_run(debug);
224 return 0;
227 void
228 dkim_disconnect(char *type, int version, struct timespec *tm, char *direction,
229 char *phase, uint64_t reqid)
231 struct dkim_session *session, search;
233 search.reqid = reqid;
234 if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) != NULL)
235 dkim_session_free(session);
238 void
239 dkim_dataline(char *type, int version, struct timespec *tm, char *direction,
240 char *phase, uint64_t reqid, uint64_t token, char *line)
242 struct dkim_session *session, search;
243 size_t linelen;
245 search.reqid = reqid;
246 session = RB_FIND(dkim_sessions, &dkim_sessions, &search);
247 if (session == NULL) {
248 if ((session = dkim_session_new(reqid)) == NULL)
249 return;
250 session->token = token;
251 } else if (session->token != token)
252 errx(1, "Token incorrect");
253 if (session->err)
254 return;
256 linelen = strlen(line);
257 if (fprintf(session->origf, "%s\n", line) < linelen)
258 dkim_err(session, "Couldn't write to tempfile");
260 if (line[0] == '.' && line[1] =='\0') {
261 dkim_sign(session);
262 } else if (linelen != 0 && session->parsing_headers) {
263 if (line[0] == '.')
264 line++;
265 dkim_parse_header(session, line, 0);
266 } else if (linelen == 0 && session->parsing_headers) {
267 if (addheaders > 0 && !dkim_signature_printf(session, "; "))
268 return;
269 session->parsing_headers = 0;
270 } else {
271 if (line[0] == '.')
272 line++;
273 dkim_parse_body(session, line);
277 void
278 dkim_commit(char *type, int version, struct timespec *tm, char *direction,
279 char *phase, uint64_t reqid, uint64_t token)
281 struct dkim_session *session, search;
283 search.reqid = reqid;
284 if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) == NULL)
285 errx(1, "Commit on undefined session");
287 if (session->err)
288 smtp_filter_disconnect(session->reqid, session->token,
289 "Internal server error");
290 else
291 smtp_filter_proceed(reqid, token);
293 dkim_session_free(session);
296 struct dkim_session *
297 dkim_session_new(uint64_t reqid)
299 struct dkim_session *session;
300 struct dkim_signature *signature;
302 if ((session = calloc(1, sizeof(*session))) == NULL)
303 err(1, NULL);
305 session->reqid = reqid;
306 if ((session->origf = tmpfile()) == NULL) {
307 dkim_err(session, "Can't open tempfile");
308 return NULL;
310 session->parsing_headers = 1;
312 session->body_whitelines = 0;
313 session->headers = calloc(1, sizeof(*(session->headers)));
314 if (session->headers == NULL) {
315 dkim_err(session, "Can't save headers");
316 return NULL;
318 session->lastheader = 0;
319 session->signature.signature = NULL;
320 session->signature.size = 0;
321 session->signature.len = 0;
322 session->err = 0;
324 if (!dkim_signature_printf(session,
325 "DKIM-signature: v=%s; a=%s-%s; c=%s/%s; d=%s; s=%s; ", "1",
326 cryptalg, hashalg,
327 canonheader == CANON_SIMPLE ? "simple" : "relaxed",
328 canonbody == CANON_SIMPLE ? "simple" : "relaxed",
329 domain, selector))
330 return NULL;
331 if (addheaders > 0 && !dkim_signature_printf(session, "z="))
332 return NULL;
334 if ((session->b = EVP_MD_CTX_new()) == NULL ||
335 (session->bh = EVP_MD_CTX_new()) == NULL) {
336 dkim_errx(session, "Can't create hash context");
337 return NULL;
339 if (EVP_DigestSignInit(session->b, NULL, hash_md, NULL, pkey) <= 0 ||
340 EVP_DigestInit_ex(session->bh, hash_md, NULL) == 0) {
341 dkim_errx(session, "Failed to initialize hash context");
342 return NULL;
344 if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL)
345 errx(1, "session already registered");
346 return session;
349 void
350 dkim_session_free(struct dkim_session *session)
352 size_t i;
354 RB_REMOVE(dkim_sessions, &dkim_sessions, session);
355 fclose(session->origf);
356 EVP_MD_CTX_free(session->b);
357 EVP_MD_CTX_free(session->bh);
358 free(session->signature.signature);
359 for (i = 0; session->headers[i] != NULL; i++)
360 free(session->headers[i]);
361 free(session->headers);
362 free(session);
365 int
366 dkim_session_cmp(struct dkim_session *s1, struct dkim_session *s2)
368 return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
371 void
372 dkim_headers_set(char *headers)
374 size_t i;
375 int has_from = 0;
377 nsign_headers = 1;
379 for (i = 0; headers[i] != '\0'; i++) {
380 /* RFC 5322 field-name */
381 if (!(headers[i] >= 33 && headers[i] <= 126))
382 errx(1, "-h: invalid character");
383 if (headers[i] == ':') {
384 /* Test for empty headers */
385 if (i == 0 || headers[i - 1] == ':')
386 errx(1, "-h: header can't be empty");
387 nsign_headers++;
389 headers[i] = tolower(headers[i]);
391 if (headers[i - 1] == ':')
392 errx(1, "-h: header can't be empty");
394 sign_headers = reallocarray(NULL, nsign_headers + 1, sizeof(*sign_headers));
395 if (sign_headers == NULL)
396 errx(1, NULL);
398 for (i = 0; i < nsign_headers; i++) {
399 sign_headers[i] = headers;
400 if (i != nsign_headers - 1) {
401 headers = strchr(headers, ':');
402 headers++[0] = '\0';
404 if (strcasecmp(sign_headers[i], "from") == 0)
405 has_from = 1;
407 if (!has_from)
408 errx(1, "From header must be included");
411 void
412 dkim_err(struct dkim_session *session, char *msg)
414 session->err = 1;
415 warn("%s", msg);
418 void
419 dkim_errx(struct dkim_session *session, char *msg)
421 session->err = 1;
422 warnx("%s", msg);
425 void
426 dkim_parse_header(struct dkim_session *session, char *line, int force)
428 size_t i;
429 size_t r, w;
430 size_t linelen;
431 size_t lastheader;
432 size_t hlen;
433 int fieldname = 0;
434 char **mtmp;
435 char *htmp;
436 char *tmp;
438 if (addheaders == 2 && !force &&
439 !dkim_signature_printheader(session, line))
440 return;
442 if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader)
443 return;
444 if ((line[0] != ' ' && line[0] != '\t')) {
445 session->lastheader = 0;
446 for (i = 0; i < nsign_headers; i++) {
447 hlen = strlen(sign_headers[i]);
448 if (strncasecmp(line, sign_headers[i], hlen) == 0) {
449 while (line[hlen] == ' ' || line[hlen] == '\t')
450 hlen++;
451 if (line[hlen] != ':')
452 continue;
453 break;
456 if (i == nsign_headers && !force)
457 return;
460 if (addheaders == 1 && !force &&
461 !dkim_signature_printheader(session, line))
462 return;
464 if (canonheader == CANON_RELAXED) {
465 if (!session->lastheader)
466 fieldname = 1;
467 for (r = w = 0; line[r] != '\0'; r++) {
468 if (line[r] == ':' && fieldname) {
469 if (line[w - 1] == ' ')
470 line[w - 1] = ':';
471 else
472 line[w++] = ':';
473 fieldname = 0;
474 while (line[r + 1] == ' ' ||
475 line[r + 1] == '\t')
476 r++;
477 continue;
479 if (line[r] == ' ' || line[r] == '\t' ||
480 line[r] == '\r' || line[r] == '\n') {
481 if (r != 0 && line[w - 1] == ' ')
482 continue;
483 else
484 line[w++] = ' ';
485 } else if (fieldname) {
486 line[w++] = tolower(line[r]);
487 continue;
488 } else
489 line[w++] = line[r];
491 linelen = line[w - 1] == ' ' ? w - 1 : w;
492 line[linelen] = '\0';
493 } else
494 linelen = strlen(line);
496 for (lastheader = 0; session->headers[lastheader] != NULL; lastheader++)
497 continue;
498 if (!session->lastheader) {
499 mtmp = recallocarray(session->headers, lastheader + 1,
500 lastheader + 2, sizeof(*mtmp));
501 if (mtmp == NULL) {
502 dkim_err(session, "Can't store header");
503 return;
505 session->headers = mtmp;
507 session->headers[lastheader] = strdup(line);
508 session->headers[lastheader + 1 ] = NULL;
509 session->lastheader = 1;
510 } else {
511 lastheader--;
512 linelen += strlen(session->headers[lastheader]);
513 if (canonheader == CANON_SIMPLE)
514 linelen += 2;
515 linelen++;
516 htmp = reallocarray(session->headers[lastheader], linelen,
517 sizeof(*htmp));
518 if (htmp == NULL) {
519 dkim_err(session, "Can't store header");
520 return;
522 session->headers[lastheader] = htmp;
523 if (canonheader == CANON_SIMPLE) {
524 if (strlcat(htmp, "\r\n", linelen) >= linelen)
525 errx(1, "Missized header");
526 } else if (canonheader == CANON_RELAXED &&
527 (tmp = strchr(session->headers[lastheader], ':')) != NULL &&
528 tmp[1] == '\0')
529 line++;
531 if (strlcat(htmp, line, linelen) >= linelen)
532 errx(1, "Missized header");
536 void
537 dkim_parse_body(struct dkim_session *session, char *line)
539 size_t r, w;
540 size_t linelen;
542 if (canonbody == CANON_RELAXED) {
543 for (r = w = 0; line[r] != '\0'; r++) {
544 if (line[r] == ' ' || line[r] == '\t') {
545 if (r != 0 && line[w - 1] == ' ')
546 continue;
547 else
548 line[w++] = ' ';
549 } else
550 line[w++] = line[r];
552 linelen = line[w - 1] == ' ' ? w - 1 : w;
553 line[linelen] = '\0';
554 } else
555 linelen = strlen(line);
557 if (line[0] == '\0') {
558 session->body_whitelines++;
559 return;
562 while (session->body_whitelines--) {
563 if (EVP_DigestUpdate(session->bh, "\r\n", 2) == 0) {
564 dkim_err(session, "Can't update hash context");
565 return;
568 session->body_whitelines = 0;
569 session->has_body = 1;
571 if (EVP_DigestUpdate(session->bh, line, linelen) == 0 ||
572 EVP_DigestUpdate(session->bh, "\r\n", 2) == 0) {
573 dkim_err(session, "Can't update hash context");
574 return;
578 void
579 dkim_sign(struct dkim_session *session)
581 /* Use largest hash size here */
582 char bbh[EVP_MAX_MD_SIZE];
583 char bh[(((sizeof(bbh) + 2) / 3) * 4) + 1];
584 char *b;
585 time_t now;
586 ssize_t i, j;
587 size_t linelen;
588 char *tmp, *tmp2;
589 char tmpchar;
591 if (addtime || addexpire)
592 now = time(NULL);
593 if (addtime && !dkim_signature_printf(session, "t=%lld; ", now))
594 return;
595 if (addexpire != 0 && !dkim_signature_printf(session, "x=%lld; ",
596 now + addexpire < now ? INT64_MAX : now + addexpire))
597 return;
599 if (canonbody == CANON_SIMPLE && !session->has_body) {
600 if (EVP_DigestUpdate(session->bh, "\r\n", 2) <= 0) {
601 dkim_err(session, "Can't update hash context");
602 return;
605 if (EVP_DigestFinal_ex(session->bh, bbh, NULL) == 0) {
606 dkim_err(session, "Can't finalize hash context");
607 return;
609 EVP_EncodeBlock(bh, bbh, EVP_MD_CTX_size(session->bh));
610 if (!dkim_signature_printf(session, "bh=%s; h=", bh))
611 return;
612 /* Reverse order for ease of use of RFC6367 section 5.4.2 */
613 for (i = 0; session->headers[i] != NULL; i++)
614 continue;
615 for (i--; i >= 0; i--) {
616 if (EVP_DigestSignUpdate(session->b,
617 session->headers[i],
618 strlen(session->headers[i])) <= 0 ||
619 EVP_DigestSignUpdate(session->b, "\r\n", 2) <= 0) {
620 dkim_errx(session, "Failed to update digest context");
621 return;
623 /* We're done with the cached header after hashing */
624 for (tmp = session->headers[i]; tmp[0] != ':'; tmp++) {
625 if (tmp[0] == ' ' || tmp[0] == '\t')
626 break;
627 tmp[0] = tolower(tmp[0]);
629 tmp[0] = '\0';
630 if (!dkim_signature_printf(session, "%s%s",
631 session->headers[i + 1] == NULL ? "" : ":",
632 session->headers[i]))
633 return;
634 tmp[0] = tmpchar;
636 dkim_signature_printf(session, "; b=");
637 if (!dkim_signature_normalize(session))
638 return;
639 if ((tmp = strdup(session->signature.signature)) == NULL) {
640 dkim_err(session, "Can't create DKIM signature");
641 return;
643 dkim_parse_header(session, tmp, 1);
644 if (EVP_DigestSignUpdate(session->b, tmp, strlen(tmp)) <= 0) {
645 dkim_err(session, "Failed to update digest context");
646 return;
648 free(tmp);
649 if (EVP_DigestSignFinal(session->b, NULL, &linelen) <= 0) {
650 dkim_err(session, "Failed to finalize digest");
651 return;
653 if ((tmp = malloc(linelen)) == NULL) {
654 dkim_err(session, "Can't allocate space for digest");
655 return;
657 if (EVP_DigestSignFinal(session->b, tmp, &linelen) <= 0) {
658 dkim_err(session, "Failed to finalize digest");
659 return;
661 if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
662 dkim_err(session, "Can't create DKIM signature");
663 return;
665 EVP_EncodeBlock(b, tmp, linelen);
666 free(tmp);
667 dkim_signature_printf(session, "%s\r\n", b);
668 free(b);
669 dkim_signature_normalize(session);
670 tmp = session->signature.signature;
671 while ((tmp2 = strchr(tmp, '\r')) != NULL) {
672 tmp2[0] = '\0';
673 smtp_filter_dataline(session->reqid, session->token,
674 "%s", tmp);
675 tmp = tmp2 + 2;
677 tmp = NULL;
678 linelen = 0;
679 rewind(session->origf);
680 while ((i = getline(&tmp, &linelen, session->origf)) != -1) {
681 tmp[i - 1] = '\0';
682 smtp_filter_dataline(session->reqid, session->token, "%s", tmp);
686 int
687 dkim_signature_normalize(struct dkim_session *session)
689 size_t i;
690 size_t linelen;
691 size_t checkpoint;
692 size_t skip;
693 size_t *headerlen = &(session->signature.len);
694 int headername = 1;
695 char tag = '\0';
696 char *sig = session->signature.signature;
698 for (linelen = i = 0; sig[i] != '\0'; i++) {
699 if (sig[i] == '\r' && sig[i + 1] == '\n') {
700 i++;
701 checkpoint = 0;
702 linelen = 0;
703 continue;
705 if (sig[i] == '\t')
706 linelen = (linelen + 8) & ~7;
707 else
708 linelen++;
709 if (headername) {
710 if (sig[i] == ':') {
711 headername = 0;
712 checkpoint = i;
714 continue;
716 if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
717 for (skip = checkpoint + 1;
718 sig[skip] == ' ' || sig[skip] == '\t';
719 skip++)
720 continue;
721 skip -= checkpoint + 1;
722 if (!dkim_signature_need(session,
723 skip > 3 ? 0 : 3 - skip + 1))
724 return 0;
725 sig = session->signature.signature;
727 memmove(sig + checkpoint + 3,
728 sig + checkpoint + skip,
729 *headerlen - skip - checkpoint + 1);
730 sig[checkpoint + 1] = '\r';
731 sig[checkpoint + 2] = '\n';
732 sig[checkpoint + 3] = '\t';
733 linelen = 8;
734 *headerlen = *headerlen + 3 - skip;
735 i = checkpoint + 3;
736 checkpoint = 0;
738 if (sig[i] == ';') {
739 checkpoint = i;
740 tag = '\0';
741 continue;
743 switch (tag) {
744 case 'B':
745 case 'b':
746 case 'z':
747 checkpoint = i;
748 break;
749 case 'h':
750 if (sig[i] == ':')
751 checkpoint = i;
752 break;
754 if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
755 if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
756 sig[i + 2] == '=') {
757 tag = 'B';
758 linelen += 2;
759 i += 2;
760 } else
761 tag = sig[i];
764 return 1;
767 int
768 dkim_signature_printheader(struct dkim_session *session, char *header)
770 size_t i, j, len;
771 static char *fmtheader = NULL;
772 char *tmp;
773 static size_t size = 0;
774 int first;
776 len = strlen(header);
777 if ((len + 3) * 3 < len) {
778 errno = EOVERFLOW;
779 dkim_err(session, "Can't add z-component to header");
780 return 0;
782 if ((len + 3) * 3 > size) {
783 if ((tmp = reallocarray(fmtheader, 3, len + 3)) == NULL) {
784 dkim_err(session, "Can't add z-component to header");
785 return 0;
787 fmtheader = tmp;
788 size = (len + 1) * 3;
791 first = session->signature.signature[session->signature.len - 1] == '=';
792 for (j = i = 0; header[i] != '\0'; i++, j++) {
793 if (i == 0 && header[i] != ' ' && header[i] != '\t' && !first)
794 fmtheader[j++] = '|';
795 if ((header[i] >= 0x21 && header[i] <= 0x3A) ||
796 header[i] == 0x3C ||
797 (header[i] >= 0x3E && header[i] <= 0x7B) ||
798 (header[i] >= 0x7D && header[i] <= 0x7E))
799 fmtheader[j] = header[i];
800 else {
801 fmtheader[j++] = '=';
802 (void) sprintf(fmtheader + j, "%02hhX", header[i]);
803 j++;
806 (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
807 (unsigned char) '\n');
809 return dkim_signature_printf(session, "%s", fmtheader);
812 int
813 dkim_signature_printf(struct dkim_session *session, char *fmt, ...)
815 struct dkim_signature *sig = &(session->signature);
816 va_list ap;
817 size_t newlen;
818 char *tmp;
819 size_t len;
821 va_start(ap, fmt);
822 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
823 fmt, ap)) >= sig->size - sig->len) {
824 va_end(ap);
825 if (!dkim_signature_need(session, len + 1))
826 return 0;
827 va_start(ap, fmt);
828 if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
829 fmt, ap)) >= sig->size - sig->len)
830 errx(1, "Miscalculated header size");
832 sig->len += len;
833 va_end(ap);
834 return 1;
837 int
838 dkim_signature_need(struct dkim_session *session, size_t len)
840 struct dkim_signature *sig = &(session->signature);
841 char *tmp;
843 if (sig->len + len < sig->size)
844 return 1;
845 sig->size = (((len + sig->len) / 512) + 1) * 512;
846 if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
847 dkim_err(session, "No room for signature");
848 return 0;
850 sig->signature = tmp;
851 return 1;
854 __dead void
855 usage(void)
857 fprintf(stderr, "usage: %s [-a signalg] [-c canonicalization] [-h headerfields] -d domain -k keyfile "
858 "-s selector\n", getprogname());
859 exit(1);
862 RB_GENERATE(dkim_sessions, dkim_session, entry, dkim_session_cmp);