2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
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.
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.
18 #include <openssl/evp.h>
19 #include <openssl/sha.h>
31 #include "smtp_proc.h"
44 /* Use largest hash size her */
45 char bbh[SHA256_DIGEST_LENGTH];
46 char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1];
47 size_t body_whitelines;
49 RB_ENTRY(dkim_session) entry;
52 RB_HEAD(dkim_sessions, dkim_session) dkim_sessions = RB_INITIALIZER(NULL);
53 RB_PROTOTYPE(dkim_sessions, dkim_session, entry, dkim_session_cmp);
55 static char **sign_headers = NULL;
56 static size_t nsign_headers = 0;
60 static int hashalg = HASH_SHA256;
63 static int cryptalg = CRYPT_RSA;
65 #define CANON_SIMPLE 0
66 #define CANON_RELAXED 1
67 static int canonheader = CANON_SIMPLE;
68 static int canonbody = CANON_SIMPLE;
70 static char *domain = NULL;
73 void dkim_err(struct dkim_session *, char *);
74 void dkim_errx(struct dkim_session *, char *);
75 void dkim_headers_set(char *);
76 void dkim_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
78 void dkim_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
79 struct dkim_session *dkim_session_new(uint64_t);
80 void dkim_session_free(struct dkim_session *);
81 int dkim_session_cmp(struct dkim_session *, struct dkim_session *);
82 void dkim_parse_header(struct dkim_session *, char *);
83 void dkim_parse_body(struct dkim_session *, char *);
84 void dkim_built_signature(struct dkim_session *);
85 int dkim_hash_update(struct dkim_session *, char *, size_t);
86 int dkim_hash_final(struct dkim_session *, char *);
89 main(int argc, char *argv[])
95 while ((ch = getopt(argc, argv, "a:c:Dd:h:")) != -1) {
98 if (strncmp(optarg, "rsa-", 4))
99 err(1, "invalid algorithm");
100 if (strcmp(optarg + 4, "sha256") == 0)
101 hashalg = HASH_SHA256;
102 else if (strcmp(optarg + 4, "sha1") == 0)
105 err(1, "invalid algorithm");
108 if (strncmp(optarg, "simple", 6) == 0) {
109 canonheader = CANON_SIMPLE;
111 } else if (strncmp(optarg, "relaxed", 7) == 0) {
112 canonheader = CANON_RELAXED;
115 err(1, "Invalid canonicalization");
116 if (optarg[0] == '/') {
117 if (strcmp(optarg + 1, "simple") == 0)
118 canonbody = CANON_SIMPLE;
119 else if (strcmp(optarg + 1, "relaxed") == 0)
120 canonbody = CANON_RELAXED;
122 err(1, "Invalid canonicalization");
123 } else if (optarg[0] == '\0')
124 canonbody = CANON_SIMPLE;
126 err(1, "Invalid canonicalization");
130 if (strlen(domain) > 255)
131 err(1, "Domain too long");
137 dkim_headers_set(optarg);
144 log_init(debug, LOG_MAIL);
145 if (pledge("tmppath stdio", NULL) == -1)
151 smtp_register_filter_dataline(dkim_dataline);
152 smtp_in_register_report_disconnect(dkim_disconnect);
159 dkim_disconnect(char *type, int version, struct timespec *tm, char *direction,
160 char *phase, uint64_t reqid)
162 struct dkim_session *session, search;
164 search.reqid = reqid;
165 if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) != NULL)
166 dkim_session_free(session);
170 dkim_dataline(char *type, int version, struct timespec *tm, char *direction,
171 char *phase, uint64_t reqid, uint64_t token, char *line)
173 struct dkim_session *session, search;
177 search.reqid = reqid;
178 session = RB_FIND(dkim_sessions, &dkim_sessions, &search);
179 if (session == NULL) {
180 session = dkim_session_new(reqid);
181 session->token = token;
182 } else if (session->token != token)
183 fatalx("Token incorrect");
185 linelen = strlen(line);
186 if (fwrite(line, 1, linelen, session->origf) < linelen)
187 dkim_err(session, "Couldn't write to tempfile");
189 if (linelen != 0 && session->parsing_headers) {
190 dkim_parse_header(session, line);
191 } else if (linelen == 0 && session->parsing_headers) {
192 session->parsing_headers = 0;
193 } else if (line[0] == '.' && line[1] =='\0') {
194 if (canonbody == CANON_SIMPLE && !session->has_body) {
195 if (dkim_hash_update(session, "\r\n", 2) == 0)
198 if (dkim_hash_final(session, session->bbh) == 0)
200 EVP_EncodeBlock(session->bh, session->bbh,
201 hashalg == HASH_SHA1 ? SHA_DIGEST_LENGTH :
202 SHA256_DIGEST_LENGTH);
203 dkim_built_signature(session);
205 dkim_parse_body(session, line);
208 struct dkim_session *
209 dkim_session_new(uint64_t reqid)
211 struct dkim_session *session;
212 char origfile[] = "/tmp/filter-dkimXXXXXX";
215 if ((session = calloc(1, sizeof(*session))) == NULL)
218 session->reqid = reqid;
219 if ((fd = mkstemp(origfile)) == -1) {
220 dkim_err(session, "Can't open tempfile");
223 if (unlink(origfile) == -1)
224 log_warn("Failed to unlink tempfile %s", origfile);
225 if ((session->origf = fdopen(fd, "r+")) == NULL) {
226 dkim_err(session, "Can't open tempfile");
229 session->parsing_headers = 1;
231 if (hashalg == HASH_SHA1)
232 SHA1_Init(&(session->sha1));
234 SHA256_Init(&(session->sha256));
235 session->body_whitelines = 0;
236 session->headers = calloc(1, sizeof(*(session->headers)));
237 if (session->headers == NULL) {
238 dkim_err(session, "Can't save headers");
241 session->lastheader = 0;
243 if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL)
244 fatalx("session already registered");
249 dkim_session_free(struct dkim_session *session)
253 RB_REMOVE(dkim_sessions, &dkim_sessions, session);
254 fclose(session->origf);
255 for (i = 0; session->headers[i] != NULL; i++)
256 free(session->headers[i]);
257 free(session->headers);
262 dkim_session_cmp(struct dkim_session *s1, struct dkim_session *s2)
264 return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
268 dkim_headers_set(char *headers)
275 /* We don't support FWS for the -h flag */
276 for (i = 0; headers[i] != '\0'; i++) {
277 /* RFC 5322 field-name */
278 if (!(headers[i] >= 33 && headers[i] <= 126))
279 errx(1, "-h: invalid character");
280 if (headers[i] == ':') {
281 /* Test for empty headers */
282 if (i == 0 || headers[i - 1] == ':')
283 errx(1, "-h: header can't be empty");
286 headers[i] = tolower(headers[i]);
288 if (headers[i - 1] == ':')
289 errx(1, "-h: header can't be empty");
291 sign_headers = reallocarray(NULL, nsign_headers, sizeof(*sign_headers));
292 if (sign_headers == NULL)
295 for (i = 0; i < nsign_headers; i++) {
296 sign_headers[i] = headers;
297 if (i != nsign_headers - 1) {
298 headers = strchr(headers, ':');
301 if (strcasecmp(sign_headers[i], "from") == 0)
305 errx(1, "From header must be included");
309 dkim_err(struct dkim_session *session, char *msg)
311 smtp_filter_disconnect(session->reqid, session->token,
312 "Internal server error");
314 dkim_session_free(session);
318 dkim_errx(struct dkim_session *session, char *msg)
320 smtp_filter_disconnect(session->reqid, session->token,
321 "Internal server error");
322 log_warnx("%s", msg);
323 dkim_session_free(session);
327 dkim_parse_header(struct dkim_session *session, char *line)
337 if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader)
339 if ((line[0] != ' ' && line[0] != '\t')) {
340 for (i = 0; i < nsign_headers; i++) {
341 if (strncasecmp(line, sign_headers[i],
342 strlen(sign_headers[i])) == 0) {
346 if (i == nsign_headers) {
347 session->lastheader = 0;
352 if (canonheader == CANON_RELAXED) {
354 for (r = w = 0; line[r] != '\0'; r++) {
355 if (line[r] == ':') {
356 if (line[w - 1] == ' ')
361 while (line[r + 1] == ' ' ||
366 if (line[r] == ' ' || line[r] == '\t') {
367 if (r != 0 && line[w - 1] == ' ')
371 } else if (fieldname) {
372 line[w++] = tolower(line[r]);
377 linelen = line[w - 1] == ' ' ? w - 1 : w;
378 line[linelen] = '\0';
380 linelen = strlen(line);
382 for (lastheader = 0; session->headers[lastheader] != NULL; lastheader++)
384 if (!session->lastheader) {
385 mtmp = reallocarray(session->headers, lastheader + 1,
388 dkim_err(session, "Can't store header");
391 session->headers = mtmp;
393 session->headers[lastheader] = strdup(line);
394 session->headers[lastheader + 1 ] = NULL;
395 session->lastheader = 1;
398 linelen += strlen(session->headers[lastheader]);
399 if (canonheader == CANON_SIMPLE)
402 htmp = reallocarray(session->headers[lastheader], linelen,
405 dkim_err(session, "Can't store header");
408 session->headers[lastheader] = htmp;
409 if (canonheader == CANON_SIMPLE) {
410 if (strlcat(htmp, "\r\n", linelen) >= linelen)
411 fatalx("Missized header");
413 if (strlcat(htmp, line, linelen) >= linelen)
414 fatalx("Missized header");
419 dkim_parse_body(struct dkim_session *session, char *line)
423 if (line[0] == '\0') {
424 session->body_whitelines++;
428 while (session->body_whitelines--) {
429 if (dkim_hash_update(session, "\r\n", 2) == 0)
432 session->body_whitelines = 0;
434 session->has_body = 1;
435 if (canonbody == CANON_RELAXED) {
436 for (r = w = 0; line[r] != '\0'; r++) {
437 if (line[r] == ' ' || line[r] == '\t') {
438 if (r != 0 && line[w - 1] == ' ')
445 linelen = line[w - 1] == ' ' ? w - 1 : w;
446 line[linelen] = '\0';
448 linelen = strlen(line);
450 if (dkim_hash_update(session, line, linelen) == 0)
452 if (dkim_hash_update(session, "\r\n", 2) == 0)
457 dkim_built_signature(struct dkim_session *session)
460 size_t signaturesize = 1024;
461 size_t signaturelen = 0;
465 if ((signature = malloc(signaturesize)) == NULL) {
466 dkim_err(session, "Can't create signature");
471 ncopied = strlcpy(signature, "DKIM-Signature: v=1; a=",
473 if (ncopied >= signaturesize)
474 if (cryptalg == CRYPT_RSA)
475 (void) strlcat(signature, "rsa", signaturesize);
476 if (hashalg == HASH_SHA1)
477 (void) strlcat(signature, "-sha1; ", signaturesize);
479 (void) strlcat(signature, "-sha256; ", signaturesize);
480 if (canonheader != CANON_SIMPLE || canonbody != CANON_SIMPLE) {
481 if (canonheader == CANON_SIMPLE)
482 (void) strlcat(signature, "c=simple", signaturesize);
484 (void) strlcat(signature, "c=relaxed", signaturesize);
486 if (canonbody != CANON_SIMPLE)
487 (void) strlcat(signature, "/relaxed", signaturesize);
489 (void) strlcat(signature, "d=", signaturesize);
490 (void) strlcat(signature, domain, signaturesize);
491 (void) strlcat(signature, "; ", signaturesize);
492 printf("%s\n", signature);
496 dkim_hash_update(struct dkim_session *session, char *buf, size_t len)
498 if (hashalg == HASH_SHA1) {
499 if (SHA1_Update(&(session->sha1), buf, len) == 0) {
500 dkim_errx(session, "Unable to update hash");
504 if (SHA256_Update(&(session->sha256), buf, len) == 0) {
505 dkim_errx(session, "Unable to update hash");
513 dkim_hash_final(struct dkim_session *session, char *dest)
515 if (hashalg == HASH_SHA1) {
516 if (SHA1_Final(dest, &(session->sha1)) == 0) {
517 dkim_errx(session, "Unable to finalize hash");
521 if (SHA256_Final(dest, &(session->sha256)) == 0) {
522 dkim_errx(session, "Unable to finalize hash");
532 fprintf(stderr, "usage: %s [-a signalg] [-c canonicalization] -d domain -h headerfields\n", getprogname());
536 RB_GENERATE(dkim_sessions, dkim_session, entry, dkim_session_cmp);