Blame


1 48c4bdc1 2019-04-04 martijn /*
2 48c4bdc1 2019-04-04 martijn * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
3 48c4bdc1 2019-04-04 martijn *
4 48c4bdc1 2019-04-04 martijn * Permission to use, copy, modify, and distribute this software for any
5 48c4bdc1 2019-04-04 martijn * purpose with or without fee is hereby granted, provided that the above
6 48c4bdc1 2019-04-04 martijn * copyright notice and this permission notice appear in all copies.
7 48c4bdc1 2019-04-04 martijn *
8 48c4bdc1 2019-04-04 martijn * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 48c4bdc1 2019-04-04 martijn * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 48c4bdc1 2019-04-04 martijn * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 48c4bdc1 2019-04-04 martijn * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 48c4bdc1 2019-04-04 martijn * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 48c4bdc1 2019-04-04 martijn * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 48c4bdc1 2019-04-04 martijn * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 48c4bdc1 2019-04-04 martijn */
16 6bcbc798 2021-05-16 martijn #include <openssl/err.h>
17 48c4bdc1 2019-04-04 martijn #include <openssl/evp.h>
18 06f6b49b 2019-04-06 martijn #include <openssl/pem.h>
19 48c4bdc1 2019-04-04 martijn #include <openssl/sha.h>
20 48c4bdc1 2019-04-04 martijn
21 48c4bdc1 2019-04-04 martijn #include <ctype.h>
22 c826412d 2019-05-02 martijn #include <errno.h>
23 1308710c 2019-04-09 martijn #include <fcntl.h>
24 48c4bdc1 2019-04-04 martijn #include <stdio.h>
25 48c4bdc1 2019-04-04 martijn #include <stdlib.h>
26 48c4bdc1 2019-04-04 martijn #include <string.h>
27 48c4bdc1 2019-04-04 martijn #include <syslog.h>
28 48c4bdc1 2019-04-04 martijn #include <time.h>
29 48c4bdc1 2019-04-04 martijn #include <unistd.h>
30 48c4bdc1 2019-04-04 martijn
31 babf5d5a 2021-05-11 martijn #include "openbsd-compat.h"
32 6840f1a1 2019-08-22 martijn #include "opensmtpd.h"
33 40cd76f4 2020-08-30 martijn #include "mheader.h"
34 48c4bdc1 2019-04-04 martijn
35 06f6b49b 2019-04-06 martijn struct dkim_signature {
36 06f6b49b 2019-04-06 martijn char *signature;
37 06f6b49b 2019-04-06 martijn size_t size;
38 06f6b49b 2019-04-06 martijn size_t len;
39 06f6b49b 2019-04-06 martijn };
40 06f6b49b 2019-04-06 martijn
41 6840f1a1 2019-08-22 martijn struct dkim_message {
42 48c4bdc1 2019-04-04 martijn FILE *origf;
43 48c4bdc1 2019-04-04 martijn int parsing_headers;
44 48c4bdc1 2019-04-04 martijn char **headers;
45 48c4bdc1 2019-04-04 martijn int lastheader;
46 48c4bdc1 2019-04-04 martijn size_t body_whitelines;
47 48c4bdc1 2019-04-04 martijn int has_body;
48 06f6b49b 2019-04-06 martijn struct dkim_signature signature;
49 fa9e6602 2019-05-01 martijn int err;
50 6bcbc798 2021-05-16 martijn EVP_MD_CTX *dctx;
51 48c4bdc1 2019-04-04 martijn };
52 48c4bdc1 2019-04-04 martijn
53 37c6191b 2019-04-06 martijn /* RFC 6376 section 5.4.1 */
54 37c6191b 2019-04-06 martijn static char *dsign_headers[] = {
55 37c6191b 2019-04-06 martijn "from",
56 37c6191b 2019-04-06 martijn "reply-to",
57 37c6191b 2019-04-06 martijn "subject",
58 37c6191b 2019-04-06 martijn "date",
59 37c6191b 2019-04-06 martijn "to",
60 37c6191b 2019-04-06 martijn "cc",
61 37c6191b 2019-04-06 martijn "resent-date",
62 37c6191b 2019-04-06 martijn "resent-from",
63 37c6191b 2019-04-06 martijn "resent-to",
64 37c6191b 2019-04-06 martijn "resent-cc",
65 37c6191b 2019-04-06 martijn "in-reply-to",
66 37c6191b 2019-04-06 martijn "references",
67 37c6191b 2019-04-06 martijn "list-id",
68 37c6191b 2019-04-06 martijn "list-help",
69 37c6191b 2019-04-06 martijn "list-unsubscribe",
70 37c6191b 2019-04-06 martijn "list-subscribe",
71 37c6191b 2019-04-06 martijn "list-post",
72 37c6191b 2019-04-06 martijn "list-owner",
73 37c6191b 2019-04-06 martijn "list-archive"
74 37c6191b 2019-04-06 martijn };
75 37c6191b 2019-04-06 martijn static char **sign_headers = dsign_headers;
76 37c6191b 2019-04-06 martijn static size_t nsign_headers = sizeof(dsign_headers) / sizeof(*dsign_headers);
77 48c4bdc1 2019-04-04 martijn
78 fddd7e1e 2019-04-06 martijn static char *hashalg = "sha256";
79 fddd7e1e 2019-04-06 martijn static char *cryptalg = "rsa";
80 48c4bdc1 2019-04-04 martijn
81 48c4bdc1 2019-04-04 martijn #define CANON_SIMPLE 0
82 48c4bdc1 2019-04-04 martijn #define CANON_RELAXED 1
83 48c4bdc1 2019-04-04 martijn static int canonheader = CANON_SIMPLE;
84 48c4bdc1 2019-04-04 martijn static int canonbody = CANON_SIMPLE;
85 48c4bdc1 2019-04-04 martijn
86 e7076cef 2019-05-02 martijn static int addtime = 0;
87 e974922c 2019-05-02 martijn static long long addexpire = 0;
88 c826412d 2019-05-02 martijn static int addheaders = 0;
89 e7076cef 2019-05-02 martijn
90 40cd76f4 2020-08-30 martijn static char **domain = NULL;
91 40cd76f4 2020-08-30 martijn static size_t ndomains = 0;
92 06f6b49b 2019-04-06 martijn static char *selector = NULL;
93 48c4bdc1 2019-04-04 martijn
94 06f6b49b 2019-04-06 martijn static EVP_PKEY *pkey;
95 06f6b49b 2019-04-06 martijn static const EVP_MD *hash_md;
96 6bcbc798 2021-05-16 martijn static int keyid = EVP_PKEY_RSA;
97 6bcbc798 2021-05-16 martijn static int sephash = 0;
98 06f6b49b 2019-04-06 martijn
99 06f6b49b 2019-04-06 martijn #define DKIM_SIGNATURE_LINELEN 78
100 06f6b49b 2019-04-06 martijn
101 48c4bdc1 2019-04-04 martijn void usage(void);
102 5fdffb49 2022-01-27 martijn void dkim_adddomain(char *);
103 6840f1a1 2019-08-22 martijn void dkim_err(struct dkim_message *, char *);
104 6840f1a1 2019-08-22 martijn void dkim_errx(struct dkim_message *, char *);
105 48c4bdc1 2019-04-04 martijn void dkim_headers_set(char *);
106 6840f1a1 2019-08-22 martijn void dkim_dataline(struct osmtpd_ctx *, const char *);
107 6840f1a1 2019-08-22 martijn void dkim_commit(struct osmtpd_ctx *);
108 6840f1a1 2019-08-22 martijn void *dkim_message_new(struct osmtpd_ctx *);
109 6840f1a1 2019-08-22 martijn void dkim_message_free(struct osmtpd_ctx *, void *);
110 6840f1a1 2019-08-22 martijn void dkim_parse_header(struct dkim_message *, char *, int);
111 6840f1a1 2019-08-22 martijn void dkim_parse_body(struct dkim_message *, char *);
112 6840f1a1 2019-08-22 martijn void dkim_sign(struct osmtpd_ctx *);
113 6840f1a1 2019-08-22 martijn int dkim_signature_printheader(struct dkim_message *, const char *);
114 6840f1a1 2019-08-22 martijn int dkim_signature_printf(struct dkim_message *, char *, ...)
115 06f6b49b 2019-04-06 martijn __attribute__((__format__ (printf, 2, 3)));
116 6840f1a1 2019-08-22 martijn int dkim_signature_normalize(struct dkim_message *);
117 40cd76f4 2020-08-30 martijn const char *dkim_domain_select(struct dkim_message *, char *);
118 6840f1a1 2019-08-22 martijn int dkim_signature_need(struct dkim_message *, size_t);
119 6840f1a1 2019-08-22 martijn int dkim_sign_init(struct dkim_message *);
120 48c4bdc1 2019-04-04 martijn
121 48c4bdc1 2019-04-04 martijn int
122 48c4bdc1 2019-04-04 martijn main(int argc, char *argv[])
123 48c4bdc1 2019-04-04 martijn {
124 48c4bdc1 2019-04-04 martijn int ch;
125 5fdffb49 2022-01-27 martijn FILE *file;
126 5fdffb49 2022-01-27 martijn char *line;
127 5fdffb49 2022-01-27 martijn size_t linesz;
128 5fdffb49 2022-01-27 martijn ssize_t linelen;
129 e974922c 2019-05-02 martijn const char *errstr;
130 48c4bdc1 2019-04-04 martijn
131 5fdffb49 2022-01-27 martijn while ((ch = getopt(argc, argv, "a:c:D:d:h:k:s:tx:z")) != -1) {
132 48c4bdc1 2019-04-04 martijn switch (ch) {
133 48c4bdc1 2019-04-04 martijn case 'a':
134 6bcbc798 2021-05-16 martijn if (strncmp(optarg, "rsa-", 4) == 0) {
135 6bcbc798 2021-05-16 martijn cryptalg = "rsa";
136 6bcbc798 2021-05-16 martijn hashalg = optarg + 4;
137 6bcbc798 2021-05-16 martijn keyid = EVP_PKEY_RSA;
138 6bcbc798 2021-05-16 martijn sephash = 0;
139 33a7c297 2022-12-16 martijn #ifdef HAVE_ED25519
140 6bcbc798 2021-05-16 martijn } else if (strncmp(optarg, "ed25519-", 8) == 0) {
141 6bcbc798 2021-05-16 martijn hashalg = optarg + 8;
142 6bcbc798 2021-05-16 martijn cryptalg = "ed25519";
143 6bcbc798 2021-05-16 martijn keyid = EVP_PKEY_ED25519;
144 6bcbc798 2021-05-16 martijn sephash = 1;
145 33a7c297 2022-12-16 martijn #endif
146 6bcbc798 2021-05-16 martijn } else
147 6bcbc798 2021-05-16 martijn osmtpd_errx(1, "invalid algorithm");
148 48c4bdc1 2019-04-04 martijn break;
149 48c4bdc1 2019-04-04 martijn case 'c':
150 48c4bdc1 2019-04-04 martijn if (strncmp(optarg, "simple", 6) == 0) {
151 48c4bdc1 2019-04-04 martijn canonheader = CANON_SIMPLE;
152 48c4bdc1 2019-04-04 martijn optarg += 6;
153 48c4bdc1 2019-04-04 martijn } else if (strncmp(optarg, "relaxed", 7) == 0) {
154 48c4bdc1 2019-04-04 martijn canonheader = CANON_RELAXED;
155 48c4bdc1 2019-04-04 martijn optarg += 7;
156 48c4bdc1 2019-04-04 martijn } else
157 6840f1a1 2019-08-22 martijn osmtpd_err(1, "Invalid canonicalization");
158 48c4bdc1 2019-04-04 martijn if (optarg[0] == '/') {
159 48c4bdc1 2019-04-04 martijn if (strcmp(optarg + 1, "simple") == 0)
160 48c4bdc1 2019-04-04 martijn canonbody = CANON_SIMPLE;
161 48c4bdc1 2019-04-04 martijn else if (strcmp(optarg + 1, "relaxed") == 0)
162 48c4bdc1 2019-04-04 martijn canonbody = CANON_RELAXED;
163 48c4bdc1 2019-04-04 martijn else
164 6840f1a1 2019-08-22 martijn osmtpd_err(1,
165 6840f1a1 2019-08-22 martijn "Invalid canonicalization");
166 48c4bdc1 2019-04-04 martijn } else if (optarg[0] == '\0')
167 48c4bdc1 2019-04-04 martijn canonbody = CANON_SIMPLE;
168 48c4bdc1 2019-04-04 martijn else
169 6840f1a1 2019-08-22 martijn osmtpd_err(1, "Invalid canonicalization");
170 48c4bdc1 2019-04-04 martijn break;
171 5fdffb49 2022-01-27 martijn case 'D':
172 5fdffb49 2022-01-27 martijn if ((file = fopen(optarg, "r")) == NULL)
173 5fdffb49 2022-01-27 martijn osmtpd_err(1, "Can't open domain file (%s)",
174 5fdffb49 2022-01-27 martijn optarg);
175 5fdffb49 2022-01-27 martijn do {
176 5fdffb49 2022-01-27 martijn line = NULL;
177 5fdffb49 2022-01-27 martijn linesz = 0;
178 5fdffb49 2022-01-27 martijn linelen = getline(&line, &linesz, file);
179 5fdffb49 2022-01-27 martijn if (linelen > 0) {
180 5fdffb49 2022-01-27 martijn if (line[linelen - 1] == '\n')
181 5fdffb49 2022-01-27 martijn line[linelen - 1] = '\0';
182 3c3725bd 2025-01-27 kirill if (line[0] == '#')
183 3c3725bd 2025-01-27 kirill continue;
184 5fdffb49 2022-01-27 martijn dkim_adddomain(line);
185 5fdffb49 2022-01-27 martijn }
186 5fdffb49 2022-01-27 martijn } while (linelen != -1);
187 5fdffb49 2022-01-27 martijn if (ferror(file))
188 5fdffb49 2022-01-27 martijn osmtpd_err(1, "Error reading domain file (%s)",
189 5fdffb49 2022-01-27 martijn optarg);
190 5fdffb49 2022-01-27 martijn fclose(file);
191 5fdffb49 2022-01-27 martijn break;
192 48c4bdc1 2019-04-04 martijn case 'd':
193 5fdffb49 2022-01-27 martijn dkim_adddomain(optarg);
194 48c4bdc1 2019-04-04 martijn break;
195 06f6b49b 2019-04-06 martijn case 'h':
196 06f6b49b 2019-04-06 martijn dkim_headers_set(optarg);
197 06f6b49b 2019-04-06 martijn break;
198 06f6b49b 2019-04-06 martijn case 'k':
199 5fdffb49 2022-01-27 martijn if ((file = fopen(optarg, "r")) == NULL)
200 6840f1a1 2019-08-22 martijn osmtpd_err(1, "Can't open key file (%s)",
201 6840f1a1 2019-08-22 martijn optarg);
202 5fdffb49 2022-01-27 martijn pkey = PEM_read_PrivateKey(file, NULL, NULL, NULL);
203 06f6b49b 2019-04-06 martijn if (pkey == NULL)
204 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "Can't read key file");
205 5fdffb49 2022-01-27 martijn fclose(file);
206 06f6b49b 2019-04-06 martijn break;
207 06f6b49b 2019-04-06 martijn case 's':
208 06f6b49b 2019-04-06 martijn selector = optarg;
209 06f6b49b 2019-04-06 martijn break;
210 e7076cef 2019-05-02 martijn case 't':
211 e7076cef 2019-05-02 martijn addtime = 1;
212 e7076cef 2019-05-02 martijn break;
213 e974922c 2019-05-02 martijn case 'x':
214 e974922c 2019-05-02 martijn addexpire = strtonum(optarg, 1, INT64_MAX, &errstr);
215 e974922c 2019-05-02 martijn if (addexpire == 0)
216 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "Expire offset is %s", errstr);
217 e974922c 2019-05-02 martijn break;
218 c826412d 2019-05-02 martijn case 'z':
219 6840f1a1 2019-08-22 martijn addheaders++;
220 c826412d 2019-05-02 martijn break;
221 48c4bdc1 2019-04-04 martijn default:
222 48c4bdc1 2019-04-04 martijn usage();
223 48c4bdc1 2019-04-04 martijn }
224 48c4bdc1 2019-04-04 martijn }
225 fddd7e1e 2019-04-06 martijn
226 fddd7e1e 2019-04-06 martijn OpenSSL_add_all_digests();
227 fddd7e1e 2019-04-06 martijn
228 6840f1a1 2019-08-22 martijn if (pledge("tmppath stdio", NULL) == -1)
229 6840f1a1 2019-08-22 martijn osmtpd_err(1, "pledge");
230 48c4bdc1 2019-04-04 martijn
231 6bcbc798 2021-05-16 martijn if ((hash_md = EVP_get_digestbyname(hashalg)) == NULL)
232 6bcbc798 2021-05-16 martijn osmtpd_errx(1, "Can't find hash: %s", hashalg);
233 6bcbc798 2021-05-16 martijn
234 06f6b49b 2019-04-06 martijn if (domain == NULL || selector == NULL || pkey == NULL)
235 48c4bdc1 2019-04-04 martijn usage();
236 48c4bdc1 2019-04-04 martijn
237 6bcbc798 2021-05-16 martijn if (EVP_PKEY_id(pkey) != keyid)
238 6bcbc798 2021-05-16 martijn osmtpd_errx(1, "Key is not of type %s", cryptalg);
239 6bcbc798 2021-05-16 martijn
240 6840f1a1 2019-08-22 martijn osmtpd_register_filter_dataline(dkim_dataline);
241 6840f1a1 2019-08-22 martijn osmtpd_register_filter_commit(dkim_commit);
242 6840f1a1 2019-08-22 martijn osmtpd_local_message(dkim_message_new, dkim_message_free);
243 6840f1a1 2019-08-22 martijn osmtpd_run();
244 48c4bdc1 2019-04-04 martijn
245 48c4bdc1 2019-04-04 martijn return 0;
246 48c4bdc1 2019-04-04 martijn }
247 48c4bdc1 2019-04-04 martijn
248 48c4bdc1 2019-04-04 martijn void
249 5fdffb49 2022-01-27 martijn dkim_adddomain(char *d)
250 5fdffb49 2022-01-27 martijn {
251 5fdffb49 2022-01-27 martijn domain = reallocarray(domain, ndomains + 1, sizeof(*domain));
252 5fdffb49 2022-01-27 martijn if (domain == NULL)
253 5fdffb49 2022-01-27 martijn osmtpd_err(1, "malloc");
254 5fdffb49 2022-01-27 martijn domain[ndomains++] = d;
255 5fdffb49 2022-01-27 martijn
256 5fdffb49 2022-01-27 martijn }
257 5fdffb49 2022-01-27 martijn
258 5fdffb49 2022-01-27 martijn void
259 6840f1a1 2019-08-22 martijn dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
260 48c4bdc1 2019-04-04 martijn {
261 6840f1a1 2019-08-22 martijn struct dkim_message *message = ctx->local_message;
262 6840f1a1 2019-08-22 martijn char *linedup;
263 48c4bdc1 2019-04-04 martijn size_t linelen;
264 48c4bdc1 2019-04-04 martijn
265 d52cf19a 2020-07-25 martijn if (message->err) {
266 d52cf19a 2020-07-25 martijn if (line[0] == '.' && line[1] =='\0')
267 d52cf19a 2020-07-25 martijn osmtpd_filter_dataline(ctx, ".");
268 fa9e6602 2019-05-01 martijn return;
269 d52cf19a 2020-07-25 martijn }
270 48c4bdc1 2019-04-04 martijn
271 48c4bdc1 2019-04-04 martijn linelen = strlen(line);
272 6840f1a1 2019-08-22 martijn if (fprintf(message->origf, "%s\n", line) < (int) linelen)
273 71b92702 2021-05-14 martijn dkim_errx(message, "Couldn't write to tempfile");
274 48c4bdc1 2019-04-04 martijn
275 7ecf4491 2019-05-01 martijn if (line[0] == '.' && line[1] =='\0') {
276 6840f1a1 2019-08-22 martijn dkim_sign(ctx);
277 6840f1a1 2019-08-22 martijn } else if (linelen != 0 && message->parsing_headers) {
278 f5286ea3 2019-04-08 martijn if (line[0] == '.')
279 f5286ea3 2019-04-08 martijn line++;
280 6840f1a1 2019-08-22 martijn if ((linedup = strdup(line)) == NULL)
281 6840f1a1 2019-08-22 martijn osmtpd_err(1, "strdup");
282 6840f1a1 2019-08-22 martijn dkim_parse_header(message, linedup, 0);
283 6840f1a1 2019-08-22 martijn free(linedup);
284 6840f1a1 2019-08-22 martijn } else if (linelen == 0 && message->parsing_headers) {
285 6840f1a1 2019-08-22 martijn if (addheaders > 0 && !dkim_signature_printf(message, "; "))
286 c826412d 2019-05-02 martijn return;
287 6840f1a1 2019-08-22 martijn message->parsing_headers = 0;
288 f5286ea3 2019-04-08 martijn } else {
289 f5286ea3 2019-04-08 martijn if (line[0] == '.')
290 f5286ea3 2019-04-08 martijn line++;
291 6840f1a1 2019-08-22 martijn if ((linedup = strdup(line)) == NULL)
292 6840f1a1 2019-08-22 martijn osmtpd_err(1, "strdup");
293 6840f1a1 2019-08-22 martijn dkim_parse_body(message, linedup);
294 6840f1a1 2019-08-22 martijn free(linedup);
295 f5286ea3 2019-04-08 martijn }
296 48c4bdc1 2019-04-04 martijn }
297 48c4bdc1 2019-04-04 martijn
298 fa9e6602 2019-05-01 martijn void
299 6840f1a1 2019-08-22 martijn dkim_commit(struct osmtpd_ctx *ctx)
300 fa9e6602 2019-05-01 martijn {
301 6840f1a1 2019-08-22 martijn struct dkim_message *message = ctx->local_message;
302 fa9e6602 2019-05-01 martijn
303 6840f1a1 2019-08-22 martijn if (message->err)
304 6840f1a1 2019-08-22 martijn osmtpd_filter_disconnect(ctx, "Internal server error");
305 fa9e6602 2019-05-01 martijn else
306 6840f1a1 2019-08-22 martijn osmtpd_filter_proceed(ctx);
307 fa9e6602 2019-05-01 martijn }
308 fa9e6602 2019-05-01 martijn
309 6840f1a1 2019-08-22 martijn void *
310 6840f1a1 2019-08-22 martijn dkim_message_new(struct osmtpd_ctx *ctx)
311 48c4bdc1 2019-04-04 martijn {
312 6840f1a1 2019-08-22 martijn struct dkim_message *message;
313 48c4bdc1 2019-04-04 martijn
314 41815bcc 2021-05-14 martijn if ((message = calloc(1, sizeof(*message))) == NULL) {
315 41815bcc 2021-05-14 martijn dkim_err(message, "Failed to create message context");
316 41815bcc 2021-05-14 martijn return NULL;
317 41815bcc 2021-05-14 martijn }
318 48c4bdc1 2019-04-04 martijn
319 6840f1a1 2019-08-22 martijn if ((message->origf = tmpfile()) == NULL) {
320 41815bcc 2021-05-14 martijn dkim_err(message, "Failed to open tempfile");
321 41815bcc 2021-05-14 martijn goto fail;
322 48c4bdc1 2019-04-04 martijn }
323 6840f1a1 2019-08-22 martijn message->parsing_headers = 1;
324 48c4bdc1 2019-04-04 martijn
325 6840f1a1 2019-08-22 martijn message->body_whitelines = 0;
326 6840f1a1 2019-08-22 martijn message->headers = calloc(1, sizeof(*(message->headers)));
327 6840f1a1 2019-08-22 martijn if (message->headers == NULL) {
328 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't save headers");
329 41815bcc 2021-05-14 martijn goto fail;
330 48c4bdc1 2019-04-04 martijn }
331 6840f1a1 2019-08-22 martijn message->lastheader = 0;
332 6840f1a1 2019-08-22 martijn message->signature.signature = NULL;
333 6840f1a1 2019-08-22 martijn message->signature.size = 0;
334 6840f1a1 2019-08-22 martijn message->signature.len = 0;
335 6840f1a1 2019-08-22 martijn message->err = 0;
336 48c4bdc1 2019-04-04 martijn
337 6840f1a1 2019-08-22 martijn if (!dkim_signature_printf(message,
338 40cd76f4 2020-08-30 martijn "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
339 fa10b9e8 2019-04-06 martijn cryptalg, hashalg,
340 06f6b49b 2019-04-06 martijn canonheader == CANON_SIMPLE ? "simple" : "relaxed",
341 40cd76f4 2020-08-30 martijn canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
342 41815bcc 2021-05-14 martijn goto fail;
343 6840f1a1 2019-08-22 martijn if (addheaders > 0 && !dkim_signature_printf(message, "z="))
344 41815bcc 2021-05-14 martijn goto fail;
345 06f6b49b 2019-04-06 martijn
346 6bcbc798 2021-05-16 martijn if ((message->dctx = EVP_MD_CTX_new()) == NULL) {
347 41815bcc 2021-05-14 martijn dkim_errx(message, "Failed to create hash context");
348 41815bcc 2021-05-14 martijn goto fail;
349 06f6b49b 2019-04-06 martijn }
350 6bcbc798 2021-05-16 martijn if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) <= 0) {
351 6840f1a1 2019-08-22 martijn dkim_errx(message, "Failed to initialize hash context");
352 41815bcc 2021-05-14 martijn goto fail;
353 37df18b6 2019-04-06 martijn }
354 6840f1a1 2019-08-22 martijn return message;
355 41815bcc 2021-05-14 martijn fail:
356 500ea6d4 2022-01-27 martijn dkim_message_free(ctx, message);
357 41815bcc 2021-05-14 martijn return NULL;
358 48c4bdc1 2019-04-04 martijn }
359 48c4bdc1 2019-04-04 martijn
360 48c4bdc1 2019-04-04 martijn void
361 6840f1a1 2019-08-22 martijn dkim_message_free(struct osmtpd_ctx *ctx, void *data)
362 48c4bdc1 2019-04-04 martijn {
363 6840f1a1 2019-08-22 martijn struct dkim_message *message = data;
364 48c4bdc1 2019-04-04 martijn size_t i;
365 48c4bdc1 2019-04-04 martijn
366 6840f1a1 2019-08-22 martijn fclose(message->origf);
367 6bcbc798 2021-05-16 martijn EVP_MD_CTX_free(message->dctx);
368 6840f1a1 2019-08-22 martijn free(message->signature.signature);
369 500ea6d4 2022-01-27 martijn for (i = 0; message->headers != NULL &&
370 500ea6d4 2022-01-27 martijn message->headers[i] != NULL; i++)
371 6840f1a1 2019-08-22 martijn free(message->headers[i]);
372 6840f1a1 2019-08-22 martijn free(message->headers);
373 6840f1a1 2019-08-22 martijn free(message);
374 48c4bdc1 2019-04-04 martijn }
375 48c4bdc1 2019-04-04 martijn
376 48c4bdc1 2019-04-04 martijn void
377 48c4bdc1 2019-04-04 martijn dkim_headers_set(char *headers)
378 48c4bdc1 2019-04-04 martijn {
379 48c4bdc1 2019-04-04 martijn size_t i;
380 48c4bdc1 2019-04-04 martijn int has_from = 0;
381 48c4bdc1 2019-04-04 martijn
382 48c4bdc1 2019-04-04 martijn nsign_headers = 1;
383 48c4bdc1 2019-04-04 martijn
384 48c4bdc1 2019-04-04 martijn for (i = 0; headers[i] != '\0'; i++) {
385 48c4bdc1 2019-04-04 martijn /* RFC 5322 field-name */
386 48c4bdc1 2019-04-04 martijn if (!(headers[i] >= 33 && headers[i] <= 126))
387 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "-h: invalid character");
388 48c4bdc1 2019-04-04 martijn if (headers[i] == ':') {
389 48c4bdc1 2019-04-04 martijn /* Test for empty headers */
390 48c4bdc1 2019-04-04 martijn if (i == 0 || headers[i - 1] == ':')
391 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "-h: header can't be empty");
392 48c4bdc1 2019-04-04 martijn nsign_headers++;
393 48c4bdc1 2019-04-04 martijn }
394 48c4bdc1 2019-04-04 martijn headers[i] = tolower(headers[i]);
395 48c4bdc1 2019-04-04 martijn }
396 48c4bdc1 2019-04-04 martijn if (headers[i - 1] == ':')
397 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "-h: header can't be empty");
398 48c4bdc1 2019-04-04 martijn
399 6840f1a1 2019-08-22 martijn if ((sign_headers = reallocarray(NULL, nsign_headers + 1,
400 6840f1a1 2019-08-22 martijn sizeof(*sign_headers))) == NULL)
401 6840f1a1 2019-08-22 martijn osmtpd_errx(1, NULL);
402 48c4bdc1 2019-04-04 martijn
403 48c4bdc1 2019-04-04 martijn for (i = 0; i < nsign_headers; i++) {
404 48c4bdc1 2019-04-04 martijn sign_headers[i] = headers;
405 48c4bdc1 2019-04-04 martijn if (i != nsign_headers - 1) {
406 48c4bdc1 2019-04-04 martijn headers = strchr(headers, ':');
407 48c4bdc1 2019-04-04 martijn headers++[0] = '\0';
408 48c4bdc1 2019-04-04 martijn }
409 48c4bdc1 2019-04-04 martijn if (strcasecmp(sign_headers[i], "from") == 0)
410 48c4bdc1 2019-04-04 martijn has_from = 1;
411 48c4bdc1 2019-04-04 martijn }
412 48c4bdc1 2019-04-04 martijn if (!has_from)
413 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "From header must be included");
414 48c4bdc1 2019-04-04 martijn }
415 48c4bdc1 2019-04-04 martijn
416 48c4bdc1 2019-04-04 martijn void
417 6840f1a1 2019-08-22 martijn dkim_err(struct dkim_message *message, char *msg)
418 48c4bdc1 2019-04-04 martijn {
419 6840f1a1 2019-08-22 martijn message->err = 1;
420 803cdd74 2020-07-25 martijn fprintf(stderr, "%s: %s\n", msg, strerror(errno));
421 48c4bdc1 2019-04-04 martijn }
422 48c4bdc1 2019-04-04 martijn
423 48c4bdc1 2019-04-04 martijn void
424 6840f1a1 2019-08-22 martijn dkim_errx(struct dkim_message *message, char *msg)
425 48c4bdc1 2019-04-04 martijn {
426 6840f1a1 2019-08-22 martijn message->err = 1;
427 6840f1a1 2019-08-22 martijn fprintf(stderr, "%s\n", msg);
428 48c4bdc1 2019-04-04 martijn }
429 48c4bdc1 2019-04-04 martijn
430 48c4bdc1 2019-04-04 martijn void
431 6840f1a1 2019-08-22 martijn dkim_parse_header(struct dkim_message *message, char *line, int force)
432 48c4bdc1 2019-04-04 martijn {
433 48c4bdc1 2019-04-04 martijn size_t i;
434 48c4bdc1 2019-04-04 martijn size_t r, w;
435 48c4bdc1 2019-04-04 martijn size_t linelen;
436 48c4bdc1 2019-04-04 martijn size_t lastheader;
437 06f6b49b 2019-04-06 martijn size_t hlen;
438 b6e37a5e 2019-04-08 martijn int fieldname = 0;
439 48c4bdc1 2019-04-04 martijn char **mtmp;
440 48c4bdc1 2019-04-04 martijn char *htmp;
441 63ac36ce 2019-04-08 martijn char *tmp;
442 48c4bdc1 2019-04-04 martijn
443 c826412d 2019-05-02 martijn if (addheaders == 2 && !force &&
444 6840f1a1 2019-08-22 martijn !dkim_signature_printheader(message, line))
445 c826412d 2019-05-02 martijn return;
446 c826412d 2019-05-02 martijn
447 6840f1a1 2019-08-22 martijn if ((line[0] == ' ' || line[0] == '\t') && !message->lastheader)
448 48c4bdc1 2019-04-04 martijn return;
449 48c4bdc1 2019-04-04 martijn if ((line[0] != ' ' && line[0] != '\t')) {
450 6840f1a1 2019-08-22 martijn message->lastheader = 0;
451 48c4bdc1 2019-04-04 martijn for (i = 0; i < nsign_headers; i++) {
452 06f6b49b 2019-04-06 martijn hlen = strlen(sign_headers[i]);
453 06f6b49b 2019-04-06 martijn if (strncasecmp(line, sign_headers[i], hlen) == 0) {
454 06f6b49b 2019-04-06 martijn while (line[hlen] == ' ' || line[hlen] == '\t')
455 06f6b49b 2019-04-06 martijn hlen++;
456 06f6b49b 2019-04-06 martijn if (line[hlen] != ':')
457 06f6b49b 2019-04-06 martijn continue;
458 48c4bdc1 2019-04-04 martijn break;
459 48c4bdc1 2019-04-04 martijn }
460 48c4bdc1 2019-04-04 martijn }
461 06f6b49b 2019-04-06 martijn if (i == nsign_headers && !force)
462 48c4bdc1 2019-04-04 martijn return;
463 48c4bdc1 2019-04-04 martijn }
464 48c4bdc1 2019-04-04 martijn
465 c826412d 2019-05-02 martijn if (addheaders == 1 && !force &&
466 6840f1a1 2019-08-22 martijn !dkim_signature_printheader(message, line))
467 c826412d 2019-05-02 martijn return;
468 c826412d 2019-05-02 martijn
469 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_RELAXED) {
470 6840f1a1 2019-08-22 martijn if (!message->lastheader)
471 b6e37a5e 2019-04-08 martijn fieldname = 1;
472 48c4bdc1 2019-04-04 martijn for (r = w = 0; line[r] != '\0'; r++) {
473 5d06ccad 2019-04-08 martijn if (line[r] == ':' && fieldname) {
474 cd6f9b03 2019-10-23 martijn if (w > 0 && line[w - 1] == ' ')
475 48c4bdc1 2019-04-04 martijn line[w - 1] = ':';
476 48c4bdc1 2019-04-04 martijn else
477 48c4bdc1 2019-04-04 martijn line[w++] = ':';
478 48c4bdc1 2019-04-04 martijn fieldname = 0;
479 48c4bdc1 2019-04-04 martijn while (line[r + 1] == ' ' ||
480 48c4bdc1 2019-04-04 martijn line[r + 1] == '\t')
481 48c4bdc1 2019-04-04 martijn r++;
482 48c4bdc1 2019-04-04 martijn continue;
483 48c4bdc1 2019-04-04 martijn }
484 06f6b49b 2019-04-06 martijn if (line[r] == ' ' || line[r] == '\t' ||
485 06f6b49b 2019-04-06 martijn line[r] == '\r' || line[r] == '\n') {
486 cd6f9b03 2019-10-23 martijn if (r != 0 && w != 0 && line[w - 1] == ' ')
487 48c4bdc1 2019-04-04 martijn continue;
488 48c4bdc1 2019-04-04 martijn else
489 48c4bdc1 2019-04-04 martijn line[w++] = ' ';
490 48c4bdc1 2019-04-04 martijn } else if (fieldname) {
491 48c4bdc1 2019-04-04 martijn line[w++] = tolower(line[r]);
492 48c4bdc1 2019-04-04 martijn continue;
493 48c4bdc1 2019-04-04 martijn } else
494 48c4bdc1 2019-04-04 martijn line[w++] = line[r];
495 48c4bdc1 2019-04-04 martijn }
496 cd6f9b03 2019-10-23 martijn linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
497 48c4bdc1 2019-04-04 martijn line[linelen] = '\0';
498 48c4bdc1 2019-04-04 martijn } else
499 48c4bdc1 2019-04-04 martijn linelen = strlen(line);
500 48c4bdc1 2019-04-04 martijn
501 6840f1a1 2019-08-22 martijn for (lastheader = 0; message->headers[lastheader] != NULL; lastheader++)
502 48c4bdc1 2019-04-04 martijn continue;
503 6840f1a1 2019-08-22 martijn if (!message->lastheader) {
504 6840f1a1 2019-08-22 martijn mtmp = recallocarray(message->headers, lastheader + 1,
505 a53d74c4 2019-04-08 martijn lastheader + 2, sizeof(*mtmp));
506 48c4bdc1 2019-04-04 martijn if (mtmp == NULL) {
507 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't store header");
508 48c4bdc1 2019-04-04 martijn return;
509 48c4bdc1 2019-04-04 martijn }
510 6840f1a1 2019-08-22 martijn message->headers = mtmp;
511 6840f1a1 2019-08-22 martijn
512 500ea6d4 2022-01-27 martijn if ((message->headers[lastheader] = strdup(line)) == NULL) {
513 500ea6d4 2022-01-27 martijn dkim_err(message, "Can't store header");
514 500ea6d4 2022-01-27 martijn return;
515 500ea6d4 2022-01-27 martijn }
516 6840f1a1 2019-08-22 martijn message->headers[lastheader + 1 ] = NULL;
517 6840f1a1 2019-08-22 martijn message->lastheader = 1;
518 48c4bdc1 2019-04-04 martijn } else {
519 48c4bdc1 2019-04-04 martijn lastheader--;
520 6840f1a1 2019-08-22 martijn linelen += strlen(message->headers[lastheader]);
521 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_SIMPLE)
522 48c4bdc1 2019-04-04 martijn linelen += 2;
523 48c4bdc1 2019-04-04 martijn linelen++;
524 6840f1a1 2019-08-22 martijn htmp = reallocarray(message->headers[lastheader], linelen,
525 48c4bdc1 2019-04-04 martijn sizeof(*htmp));
526 48c4bdc1 2019-04-04 martijn if (htmp == NULL) {
527 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't store header");
528 48c4bdc1 2019-04-04 martijn return;
529 48c4bdc1 2019-04-04 martijn }
530 6840f1a1 2019-08-22 martijn message->headers[lastheader] = htmp;
531 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_SIMPLE) {
532 48c4bdc1 2019-04-04 martijn if (strlcat(htmp, "\r\n", linelen) >= linelen)
533 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "Missized header");
534 63ac36ce 2019-04-08 martijn } else if (canonheader == CANON_RELAXED &&
535 6840f1a1 2019-08-22 martijn (tmp = strchr(message->headers[lastheader], ':')) != NULL &&
536 63ac36ce 2019-04-08 martijn tmp[1] == '\0')
537 63ac36ce 2019-04-08 martijn line++;
538 63ac36ce 2019-04-08 martijn
539 48c4bdc1 2019-04-04 martijn if (strlcat(htmp, line, linelen) >= linelen)
540 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "Missized header");
541 48c4bdc1 2019-04-04 martijn }
542 48c4bdc1 2019-04-04 martijn }
543 48c4bdc1 2019-04-04 martijn
544 48c4bdc1 2019-04-04 martijn void
545 6840f1a1 2019-08-22 martijn dkim_parse_body(struct dkim_message *message, char *line)
546 48c4bdc1 2019-04-04 martijn {
547 48c4bdc1 2019-04-04 martijn size_t r, w;
548 48c4bdc1 2019-04-04 martijn size_t linelen;
549 1308710c 2019-04-09 martijn
550 1308710c 2019-04-09 martijn if (canonbody == CANON_RELAXED) {
551 1308710c 2019-04-09 martijn for (r = w = 0; line[r] != '\0'; r++) {
552 1308710c 2019-04-09 martijn if (line[r] == ' ' || line[r] == '\t') {
553 1308710c 2019-04-09 martijn if (r != 0 && line[w - 1] == ' ')
554 1308710c 2019-04-09 martijn continue;
555 1308710c 2019-04-09 martijn else
556 1308710c 2019-04-09 martijn line[w++] = ' ';
557 1308710c 2019-04-09 martijn } else
558 1308710c 2019-04-09 martijn line[w++] = line[r];
559 1308710c 2019-04-09 martijn }
560 cd6f9b03 2019-10-23 martijn linelen = (w != 0 && line[w - 1] == ' ') ? w - 1 : w;
561 1308710c 2019-04-09 martijn line[linelen] = '\0';
562 1308710c 2019-04-09 martijn } else
563 1308710c 2019-04-09 martijn linelen = strlen(line);
564 1308710c 2019-04-09 martijn
565 48c4bdc1 2019-04-04 martijn if (line[0] == '\0') {
566 6840f1a1 2019-08-22 martijn message->body_whitelines++;
567 48c4bdc1 2019-04-04 martijn return;
568 48c4bdc1 2019-04-04 martijn }
569 48c4bdc1 2019-04-04 martijn
570 6840f1a1 2019-08-22 martijn while (message->body_whitelines--) {
571 6bcbc798 2021-05-16 martijn if (EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
572 71b92702 2021-05-14 martijn dkim_errx(message, "Can't update hash context");
573 48c4bdc1 2019-04-04 martijn return;
574 37df18b6 2019-04-06 martijn }
575 48c4bdc1 2019-04-04 martijn }
576 6840f1a1 2019-08-22 martijn message->body_whitelines = 0;
577 6840f1a1 2019-08-22 martijn message->has_body = 1;
578 48c4bdc1 2019-04-04 martijn
579 6bcbc798 2021-05-16 martijn if (EVP_DigestUpdate(message->dctx, line, linelen) == 0 ||
580 6bcbc798 2021-05-16 martijn EVP_DigestUpdate(message->dctx, "\r\n", 2) == 0) {
581 71b92702 2021-05-14 martijn dkim_errx(message, "Can't update hash context");
582 48c4bdc1 2019-04-04 martijn return;
583 37df18b6 2019-04-06 martijn }
584 48c4bdc1 2019-04-04 martijn }
585 48c4bdc1 2019-04-04 martijn
586 365862b1 2019-05-02 martijn void
587 6840f1a1 2019-08-22 martijn dkim_sign(struct osmtpd_ctx *ctx)
588 365862b1 2019-05-02 martijn {
589 6840f1a1 2019-08-22 martijn struct dkim_message *message = ctx->local_message;
590 365862b1 2019-05-02 martijn /* Use largest hash size here */
591 8e76d341 2021-06-02 martijn unsigned char bdigest[EVP_MAX_MD_SIZE];
592 8e76d341 2021-06-02 martijn unsigned char digest[(((sizeof(bdigest) + 2) / 3) * 4) + 1];
593 8e76d341 2021-06-02 martijn unsigned char *b;
594 40cd76f4 2020-08-30 martijn const char *sdomain = domain[0], *tsdomain;
595 e974922c 2019-05-02 martijn time_t now;
596 6840f1a1 2019-08-22 martijn ssize_t i;
597 6bcbc798 2021-05-16 martijn size_t linelen = 0;
598 365862b1 2019-05-02 martijn char *tmp, *tmp2;
599 8e76d341 2021-06-02 martijn unsigned int digestsz;
600 365862b1 2019-05-02 martijn
601 e974922c 2019-05-02 martijn if (addtime || addexpire)
602 e974922c 2019-05-02 martijn now = time(NULL);
603 a70fd4b2 2021-05-11 martijn if (addtime && !dkim_signature_printf(message, "t=%lld; ",
604 a70fd4b2 2021-05-11 martijn (long long)now))
605 2c88ced1 2021-05-16 martijn goto fail;
606 6840f1a1 2019-08-22 martijn if (addexpire != 0 && !dkim_signature_printf(message, "x=%lld; ",
607 e974922c 2019-05-02 martijn now + addexpire < now ? INT64_MAX : now + addexpire))
608 2c88ced1 2021-05-16 martijn goto fail;
609 e974922c 2019-05-02 martijn
610 6840f1a1 2019-08-22 martijn if (canonbody == CANON_SIMPLE && !message->has_body) {
611 6bcbc798 2021-05-16 martijn if (EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
612 71b92702 2021-05-14 martijn dkim_errx(message, "Can't update hash context");
613 2c88ced1 2021-05-16 martijn goto fail;
614 365862b1 2019-05-02 martijn }
615 365862b1 2019-05-02 martijn }
616 6bcbc798 2021-05-16 martijn if (EVP_DigestFinal_ex(message->dctx, bdigest, &digestsz) == 0) {
617 71b92702 2021-05-14 martijn dkim_errx(message, "Can't finalize hash context");
618 2c88ced1 2021-05-16 martijn goto fail;
619 365862b1 2019-05-02 martijn }
620 6bcbc798 2021-05-16 martijn EVP_EncodeBlock(digest, bdigest, digestsz);
621 6bcbc798 2021-05-16 martijn if (!dkim_signature_printf(message, "bh=%s; h=", digest))
622 2c88ced1 2021-05-16 martijn goto fail;
623 365862b1 2019-05-02 martijn /* Reverse order for ease of use of RFC6367 section 5.4.2 */
624 6840f1a1 2019-08-22 martijn for (i = 0; message->headers[i] != NULL; i++)
625 365862b1 2019-05-02 martijn continue;
626 6bcbc798 2021-05-16 martijn EVP_MD_CTX_reset(message->dctx);
627 6bcbc798 2021-05-16 martijn if (!sephash) {
628 6bcbc798 2021-05-16 martijn if (EVP_DigestSignInit(message->dctx, NULL, hash_md, NULL,
629 6bcbc798 2021-05-16 martijn pkey) != 1) {
630 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to initialize signature "
631 6bcbc798 2021-05-16 martijn "context");
632 2c88ced1 2021-05-16 martijn goto fail;
633 6bcbc798 2021-05-16 martijn }
634 6bcbc798 2021-05-16 martijn } else {
635 6bcbc798 2021-05-16 martijn if (EVP_DigestInit_ex(message->dctx, hash_md, NULL) != 1) {
636 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to initialize hash context");
637 2c88ced1 2021-05-16 martijn goto fail;
638 6bcbc798 2021-05-16 martijn }
639 6bcbc798 2021-05-16 martijn }
640 365862b1 2019-05-02 martijn for (i--; i >= 0; i--) {
641 6bcbc798 2021-05-16 martijn if (!sephash) {
642 6bcbc798 2021-05-16 martijn if (EVP_DigestSignUpdate(message->dctx,
643 6bcbc798 2021-05-16 martijn message->headers[i],
644 6bcbc798 2021-05-16 martijn strlen(message->headers[i])) != 1 ||
645 6bcbc798 2021-05-16 martijn EVP_DigestSignUpdate(message->dctx, "\r\n",
646 6bcbc798 2021-05-16 martijn 2) <= 0) {
647 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to update signature "
648 6bcbc798 2021-05-16 martijn "context");
649 2c88ced1 2021-05-16 martijn goto fail;
650 6bcbc798 2021-05-16 martijn }
651 6bcbc798 2021-05-16 martijn } else {
652 6bcbc798 2021-05-16 martijn if (EVP_DigestUpdate(message->dctx, message->headers[i],
653 6bcbc798 2021-05-16 martijn strlen(message->headers[i])) != 1 ||
654 6bcbc798 2021-05-16 martijn EVP_DigestUpdate(message->dctx, "\r\n", 2) <= 0) {
655 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to update digest "
656 6bcbc798 2021-05-16 martijn "context");
657 2c88ced1 2021-05-16 martijn goto fail;
658 6bcbc798 2021-05-16 martijn }
659 365862b1 2019-05-02 martijn }
660 40cd76f4 2020-08-30 martijn if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
661 40cd76f4 2020-08-30 martijn sdomain = tsdomain;
662 365862b1 2019-05-02 martijn /* We're done with the cached header after hashing */
663 6840f1a1 2019-08-22 martijn for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
664 365862b1 2019-05-02 martijn if (tmp[0] == ' ' || tmp[0] == '\t')
665 365862b1 2019-05-02 martijn break;
666 365862b1 2019-05-02 martijn tmp[0] = tolower(tmp[0]);
667 365862b1 2019-05-02 martijn }
668 365862b1 2019-05-02 martijn tmp[0] = '\0';
669 6840f1a1 2019-08-22 martijn if (!dkim_signature_printf(message, "%s%s",
670 6840f1a1 2019-08-22 martijn message->headers[i + 1] == NULL ? "" : ":",
671 6840f1a1 2019-08-22 martijn message->headers[i]))
672 2c88ced1 2021-05-16 martijn goto fail;
673 365862b1 2019-05-02 martijn }
674 40cd76f4 2020-08-30 martijn dkim_signature_printf(message, "; d=%s; b=", sdomain);
675 6840f1a1 2019-08-22 martijn if (!dkim_signature_normalize(message))
676 2c88ced1 2021-05-16 martijn goto fail;
677 6840f1a1 2019-08-22 martijn if ((tmp = strdup(message->signature.signature)) == NULL) {
678 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't create DKIM signature");
679 2c88ced1 2021-05-16 martijn goto fail;
680 365862b1 2019-05-02 martijn }
681 6840f1a1 2019-08-22 martijn dkim_parse_header(message, tmp, 1);
682 6bcbc798 2021-05-16 martijn if (!sephash) {
683 6bcbc798 2021-05-16 martijn if (EVP_DigestSignUpdate(message->dctx, tmp,
684 6bcbc798 2021-05-16 martijn strlen(tmp)) != 1) {
685 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to update signature "
686 6bcbc798 2021-05-16 martijn "context");
687 2c88ced1 2021-05-16 martijn goto fail;
688 6bcbc798 2021-05-16 martijn }
689 6bcbc798 2021-05-16 martijn } else {
690 6bcbc798 2021-05-16 martijn if (EVP_DigestUpdate(message->dctx, tmp, strlen(tmp)) != 1) {
691 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to update digest context");
692 2c88ced1 2021-05-16 martijn goto fail;
693 6bcbc798 2021-05-16 martijn }
694 365862b1 2019-05-02 martijn }
695 365862b1 2019-05-02 martijn free(tmp);
696 6bcbc798 2021-05-16 martijn if (!sephash) {
697 6bcbc798 2021-05-16 martijn if (EVP_DigestSignFinal(message->dctx, NULL, &linelen) != 1) {
698 6bcbc798 2021-05-16 martijn dkim_errx(message, "Can't finalize signature context");
699 2c88ced1 2021-05-16 martijn goto fail;
700 6bcbc798 2021-05-16 martijn }
701 33a7c297 2022-12-16 martijn #ifdef HAVE_ED25519
702 6bcbc798 2021-05-16 martijn } else {
703 6bcbc798 2021-05-16 martijn if (EVP_DigestFinal_ex(message->dctx, bdigest,
704 6bcbc798 2021-05-16 martijn &digestsz) != 1) {
705 6bcbc798 2021-05-16 martijn dkim_errx(message, "Can't finalize hash context");
706 2c88ced1 2021-05-16 martijn goto fail;
707 6bcbc798 2021-05-16 martijn }
708 6bcbc798 2021-05-16 martijn EVP_MD_CTX_reset(message->dctx);
709 6bcbc798 2021-05-16 martijn if (EVP_DigestSignInit(message->dctx, NULL, NULL, NULL,
710 6bcbc798 2021-05-16 martijn pkey) != 1) {
711 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to initialize signature "
712 6bcbc798 2021-05-16 martijn "context");
713 2c88ced1 2021-05-16 martijn goto fail;
714 6bcbc798 2021-05-16 martijn }
715 6bcbc798 2021-05-16 martijn if (EVP_DigestSign(message->dctx, NULL, &linelen, bdigest,
716 6bcbc798 2021-05-16 martijn digestsz) != 1) {
717 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to finalize signature");
718 2c88ced1 2021-05-16 martijn goto fail;
719 6bcbc798 2021-05-16 martijn }
720 33a7c297 2022-12-16 martijn #endif
721 365862b1 2019-05-02 martijn }
722 365862b1 2019-05-02 martijn if ((tmp = malloc(linelen)) == NULL) {
723 6bcbc798 2021-05-16 martijn dkim_err(message, "Can't allocate space for signature");
724 2c88ced1 2021-05-16 martijn goto fail;
725 365862b1 2019-05-02 martijn }
726 6bcbc798 2021-05-16 martijn if (!sephash) {
727 6bcbc798 2021-05-16 martijn if (EVP_DigestSignFinal(message->dctx, tmp, &linelen) != 1) {
728 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to finalize signature");
729 2c88ced1 2021-05-16 martijn goto fail;
730 6bcbc798 2021-05-16 martijn }
731 33a7c297 2022-12-16 martijn #ifdef HAVE_ED25519
732 6bcbc798 2021-05-16 martijn } else {
733 6bcbc798 2021-05-16 martijn if (EVP_DigestSign(message->dctx, tmp, &linelen, bdigest,
734 6bcbc798 2021-05-16 martijn digestsz) != 1) {
735 6bcbc798 2021-05-16 martijn dkim_errx(message, "Failed to finalize signature");
736 2c88ced1 2021-05-16 martijn goto fail;
737 6bcbc798 2021-05-16 martijn }
738 33a7c297 2022-12-16 martijn #endif
739 365862b1 2019-05-02 martijn }
740 365862b1 2019-05-02 martijn if ((b = malloc((((linelen + 2) / 3) * 4) + 1)) == NULL) {
741 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't create DKIM signature");
742 2c88ced1 2021-05-16 martijn goto fail;
743 365862b1 2019-05-02 martijn }
744 365862b1 2019-05-02 martijn EVP_EncodeBlock(b, tmp, linelen);
745 365862b1 2019-05-02 martijn free(tmp);
746 6840f1a1 2019-08-22 martijn dkim_signature_printf(message, "%s\r\n", b);
747 365862b1 2019-05-02 martijn free(b);
748 6840f1a1 2019-08-22 martijn dkim_signature_normalize(message);
749 6840f1a1 2019-08-22 martijn tmp = message->signature.signature;
750 365862b1 2019-05-02 martijn while ((tmp2 = strchr(tmp, '\r')) != NULL) {
751 365862b1 2019-05-02 martijn tmp2[0] = '\0';
752 6840f1a1 2019-08-22 martijn osmtpd_filter_dataline(ctx, "%s", tmp);
753 365862b1 2019-05-02 martijn tmp = tmp2 + 2;
754 365862b1 2019-05-02 martijn }
755 365862b1 2019-05-02 martijn tmp = NULL;
756 365862b1 2019-05-02 martijn linelen = 0;
757 6840f1a1 2019-08-22 martijn rewind(message->origf);
758 6840f1a1 2019-08-22 martijn while ((i = getline(&tmp, &linelen, message->origf)) != -1) {
759 365862b1 2019-05-02 martijn tmp[i - 1] = '\0';
760 6840f1a1 2019-08-22 martijn osmtpd_filter_dataline(ctx, "%s", tmp);
761 365862b1 2019-05-02 martijn }
762 be6d950d 2021-03-21 martijn free(tmp);
763 2c88ced1 2021-05-16 martijn return;
764 2c88ced1 2021-05-16 martijn fail:
765 2c88ced1 2021-05-16 martijn osmtpd_filter_dataline(ctx, ".");
766 365862b1 2019-05-02 martijn }
767 365862b1 2019-05-02 martijn
768 06f6b49b 2019-04-06 martijn int
769 6840f1a1 2019-08-22 martijn dkim_signature_normalize(struct dkim_message *message)
770 48c4bdc1 2019-04-04 martijn {
771 06f6b49b 2019-04-06 martijn size_t i;
772 06f6b49b 2019-04-06 martijn size_t linelen;
773 06f6b49b 2019-04-06 martijn size_t checkpoint;
774 06f6b49b 2019-04-06 martijn size_t skip;
775 6840f1a1 2019-08-22 martijn size_t *headerlen = &(message->signature.len);
776 06f6b49b 2019-04-06 martijn int headername = 1;
777 06f6b49b 2019-04-06 martijn char tag = '\0';
778 6840f1a1 2019-08-22 martijn char *sig = message->signature.signature;
779 48c4bdc1 2019-04-04 martijn
780 06f6b49b 2019-04-06 martijn for (linelen = i = 0; sig[i] != '\0'; i++) {
781 ffa37923 2019-04-09 martijn if (sig[i] == '\r' && sig[i + 1] == '\n') {
782 ffa37923 2019-04-09 martijn i++;
783 06f6b49b 2019-04-06 martijn checkpoint = 0;
784 06f6b49b 2019-04-06 martijn linelen = 0;
785 06f6b49b 2019-04-06 martijn continue;
786 06f6b49b 2019-04-06 martijn }
787 06f6b49b 2019-04-06 martijn if (sig[i] == '\t')
788 06f6b49b 2019-04-06 martijn linelen = (linelen + 8) & ~7;
789 48c4bdc1 2019-04-04 martijn else
790 06f6b49b 2019-04-06 martijn linelen++;
791 06f6b49b 2019-04-06 martijn if (headername) {
792 06f6b49b 2019-04-06 martijn if (sig[i] == ':') {
793 06f6b49b 2019-04-06 martijn headername = 0;
794 06f6b49b 2019-04-06 martijn checkpoint = i;
795 06f6b49b 2019-04-06 martijn }
796 06f6b49b 2019-04-06 martijn continue;
797 06f6b49b 2019-04-06 martijn }
798 ffa37923 2019-04-09 martijn if (linelen > DKIM_SIGNATURE_LINELEN && checkpoint != 0) {
799 06f6b49b 2019-04-06 martijn for (skip = checkpoint + 1;
800 06f6b49b 2019-04-06 martijn sig[skip] == ' ' || sig[skip] == '\t';
801 06f6b49b 2019-04-06 martijn skip++)
802 06f6b49b 2019-04-06 martijn continue;
803 06f6b49b 2019-04-06 martijn skip -= checkpoint + 1;
804 6840f1a1 2019-08-22 martijn if (!dkim_signature_need(message,
805 226e5da8 2019-04-08 martijn skip > 3 ? 0 : 3 - skip + 1))
806 06f6b49b 2019-04-06 martijn return 0;
807 6840f1a1 2019-08-22 martijn sig = message->signature.signature;
808 6840f1a1 2019-08-22 martijn
809 06f6b49b 2019-04-06 martijn memmove(sig + checkpoint + 3,
810 06f6b49b 2019-04-06 martijn sig + checkpoint + skip,
811 540f552a 2019-04-06 martijn *headerlen - skip - checkpoint + 1);
812 06f6b49b 2019-04-06 martijn sig[checkpoint + 1] = '\r';
813 06f6b49b 2019-04-06 martijn sig[checkpoint + 2] = '\n';
814 06f6b49b 2019-04-06 martijn sig[checkpoint + 3] = '\t';
815 06f6b49b 2019-04-06 martijn linelen = 8;
816 06f6b49b 2019-04-06 martijn *headerlen = *headerlen + 3 - skip;
817 06f6b49b 2019-04-06 martijn i = checkpoint + 3;
818 06f6b49b 2019-04-06 martijn checkpoint = 0;
819 06f6b49b 2019-04-06 martijn }
820 06f6b49b 2019-04-06 martijn if (sig[i] == ';') {
821 06f6b49b 2019-04-06 martijn checkpoint = i;
822 06f6b49b 2019-04-06 martijn tag = '\0';
823 06f6b49b 2019-04-06 martijn continue;
824 06f6b49b 2019-04-06 martijn }
825 06f6b49b 2019-04-06 martijn switch (tag) {
826 06f6b49b 2019-04-06 martijn case 'B':
827 06f6b49b 2019-04-06 martijn case 'b':
828 c826412d 2019-05-02 martijn case 'z':
829 06f6b49b 2019-04-06 martijn checkpoint = i;
830 06f6b49b 2019-04-06 martijn break;
831 06f6b49b 2019-04-06 martijn case 'h':
832 06f6b49b 2019-04-06 martijn if (sig[i] == ':')
833 06f6b49b 2019-04-06 martijn checkpoint = i;
834 c826412d 2019-05-02 martijn break;
835 06f6b49b 2019-04-06 martijn }
836 06f6b49b 2019-04-06 martijn if (tag == '\0' && sig[i] != ' ' && sig[i] != '\t') {
837 06f6b49b 2019-04-06 martijn if ((tag = sig[i]) == 'b' && sig[i + 1] == 'h' &&
838 06f6b49b 2019-04-06 martijn sig[i + 2] == '=') {
839 06f6b49b 2019-04-06 martijn tag = 'B';
840 06f6b49b 2019-04-06 martijn linelen += 2;
841 06f6b49b 2019-04-06 martijn i += 2;
842 06f6b49b 2019-04-06 martijn } else
843 06f6b49b 2019-04-06 martijn tag = sig[i];
844 06f6b49b 2019-04-06 martijn }
845 48c4bdc1 2019-04-04 martijn }
846 06f6b49b 2019-04-06 martijn return 1;
847 06f6b49b 2019-04-06 martijn }
848 48c4bdc1 2019-04-04 martijn
849 06f6b49b 2019-04-06 martijn int
850 6840f1a1 2019-08-22 martijn dkim_signature_printheader(struct dkim_message *message, const char *header)
851 c826412d 2019-05-02 martijn {
852 c826412d 2019-05-02 martijn size_t i, j, len;
853 c826412d 2019-05-02 martijn static char *fmtheader = NULL;
854 c826412d 2019-05-02 martijn char *tmp;
855 c826412d 2019-05-02 martijn static size_t size = 0;
856 c826412d 2019-05-02 martijn int first;
857 c826412d 2019-05-02 martijn
858 c826412d 2019-05-02 martijn len = strlen(header);
859 c826412d 2019-05-02 martijn if ((len + 3) * 3 < len) {
860 c826412d 2019-05-02 martijn errno = EOVERFLOW;
861 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't add z-component to header");
862 c826412d 2019-05-02 martijn return 0;
863 c826412d 2019-05-02 martijn }
864 c826412d 2019-05-02 martijn if ((len + 3) * 3 > size) {
865 c826412d 2019-05-02 martijn if ((tmp = reallocarray(fmtheader, 3, len + 3)) == NULL) {
866 6840f1a1 2019-08-22 martijn dkim_err(message, "Can't add z-component to header");
867 c826412d 2019-05-02 martijn return 0;
868 c826412d 2019-05-02 martijn }
869 c826412d 2019-05-02 martijn fmtheader = tmp;
870 c826412d 2019-05-02 martijn size = (len + 1) * 3;
871 c826412d 2019-05-02 martijn }
872 c826412d 2019-05-02 martijn
873 6840f1a1 2019-08-22 martijn first = message->signature.signature[message->signature.len - 1] == '=';
874 c826412d 2019-05-02 martijn for (j = i = 0; header[i] != '\0'; i++, j++) {
875 c826412d 2019-05-02 martijn if (i == 0 && header[i] != ' ' && header[i] != '\t' && !first)
876 c826412d 2019-05-02 martijn fmtheader[j++] = '|';
877 c826412d 2019-05-02 martijn if ((header[i] >= 0x21 && header[i] <= 0x3A) ||
878 6840f1a1 2019-08-22 martijn (header[i] == 0x3C) ||
879 c826412d 2019-05-02 martijn (header[i] >= 0x3E && header[i] <= 0x7B) ||
880 c826412d 2019-05-02 martijn (header[i] >= 0x7D && header[i] <= 0x7E))
881 c826412d 2019-05-02 martijn fmtheader[j] = header[i];
882 c826412d 2019-05-02 martijn else {
883 c826412d 2019-05-02 martijn fmtheader[j++] = '=';
884 c826412d 2019-05-02 martijn (void) sprintf(fmtheader + j, "%02hhX", header[i]);
885 c826412d 2019-05-02 martijn j++;
886 c826412d 2019-05-02 martijn }
887 c826412d 2019-05-02 martijn }
888 c826412d 2019-05-02 martijn (void) sprintf(fmtheader + j, "=%02hhX=%02hhX", (unsigned char) '\r',
889 c826412d 2019-05-02 martijn (unsigned char) '\n');
890 c826412d 2019-05-02 martijn
891 6840f1a1 2019-08-22 martijn return dkim_signature_printf(message, "%s", fmtheader);
892 c826412d 2019-05-02 martijn }
893 c826412d 2019-05-02 martijn
894 c826412d 2019-05-02 martijn int
895 6840f1a1 2019-08-22 martijn dkim_signature_printf(struct dkim_message *message, char *fmt, ...)
896 06f6b49b 2019-04-06 martijn {
897 6840f1a1 2019-08-22 martijn struct dkim_signature *sig = &(message->signature);
898 06f6b49b 2019-04-06 martijn va_list ap;
899 06f6b49b 2019-04-06 martijn size_t len;
900 06f6b49b 2019-04-06 martijn
901 06f6b49b 2019-04-06 martijn va_start(ap, fmt);
902 06f6b49b 2019-04-06 martijn if ((len = vsnprintf(sig->signature + sig->len, sig->size - sig->len,
903 06f6b49b 2019-04-06 martijn fmt, ap)) >= sig->size - sig->len) {
904 06f6b49b 2019-04-06 martijn va_end(ap);
905 6840f1a1 2019-08-22 martijn if (!dkim_signature_need(message, len + 1))
906 06f6b49b 2019-04-06 martijn return 0;
907 06f6b49b 2019-04-06 martijn va_start(ap, fmt);
908 6840f1a1 2019-08-22 martijn if ((len = vsnprintf(sig->signature + sig->len,
909 6840f1a1 2019-08-22 martijn sig->size - sig->len, fmt, ap)) >= sig->size - sig->len)
910 6840f1a1 2019-08-22 martijn osmtpd_errx(1, "Miscalculated header size");
911 06f6b49b 2019-04-06 martijn }
912 06f6b49b 2019-04-06 martijn sig->len += len;
913 06f6b49b 2019-04-06 martijn va_end(ap);
914 06f6b49b 2019-04-06 martijn return 1;
915 06f6b49b 2019-04-06 martijn }
916 06f6b49b 2019-04-06 martijn
917 40cd76f4 2020-08-30 martijn const char *
918 40cd76f4 2020-08-30 martijn dkim_domain_select(struct dkim_message *message, char *from)
919 40cd76f4 2020-08-30 martijn {
920 40cd76f4 2020-08-30 martijn char *mdomain0, *mdomain;
921 40cd76f4 2020-08-30 martijn size_t i;
922 40cd76f4 2020-08-30 martijn
923 40cd76f4 2020-08-30 martijn if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
924 40cd76f4 2020-08-30 martijn if (errno != EINVAL) {
925 71b92702 2021-05-14 martijn dkim_errx(message, "Couldn't parse from header");
926 40cd76f4 2020-08-30 martijn return NULL;
927 40cd76f4 2020-08-30 martijn }
928 40cd76f4 2020-08-30 martijn return NULL;
929 40cd76f4 2020-08-30 martijn }
930 40cd76f4 2020-08-30 martijn
931 40cd76f4 2020-08-30 martijn while (mdomain != NULL && mdomain[0] != '\0') {
932 40cd76f4 2020-08-30 martijn for (i = 0; i < ndomains; i++) {
933 40cd76f4 2020-08-30 martijn if (strcasecmp(mdomain, domain[i]) == 0) {
934 40cd76f4 2020-08-30 martijn free(mdomain0);
935 40cd76f4 2020-08-30 martijn return domain[i];
936 40cd76f4 2020-08-30 martijn }
937 40cd76f4 2020-08-30 martijn }
938 40cd76f4 2020-08-30 martijn if ((mdomain = strchr(mdomain, '.')) != NULL)
939 40cd76f4 2020-08-30 martijn mdomain++;
940 40cd76f4 2020-08-30 martijn }
941 40cd76f4 2020-08-30 martijn free(mdomain0);
942 40cd76f4 2020-08-30 martijn return NULL;
943 40cd76f4 2020-08-30 martijn }
944 40cd76f4 2020-08-30 martijn
945 06f6b49b 2019-04-06 martijn int
946 6840f1a1 2019-08-22 martijn dkim_signature_need(struct dkim_message *message, size_t len)
947 06f6b49b 2019-04-06 martijn {
948 6840f1a1 2019-08-22 martijn struct dkim_signature *sig = &(message->signature);
949 06f6b49b 2019-04-06 martijn char *tmp;
950 06f6b49b 2019-04-06 martijn
951 a27b5ff7 2019-04-08 martijn if (sig->len + len < sig->size)
952 06f6b49b 2019-04-06 martijn return 1;
953 503597cc 2019-04-08 martijn sig->size = (((len + sig->len) / 512) + 1) * 512;
954 06f6b49b 2019-04-06 martijn if ((tmp = realloc(sig->signature, sig->size)) == NULL) {
955 6840f1a1 2019-08-22 martijn dkim_err(message, "No room for signature");
956 06f6b49b 2019-04-06 martijn return 0;
957 06f6b49b 2019-04-06 martijn }
958 06f6b49b 2019-04-06 martijn sig->signature = tmp;
959 06f6b49b 2019-04-06 martijn return 1;
960 06f6b49b 2019-04-06 martijn }
961 06f6b49b 2019-04-06 martijn
962 48c4bdc1 2019-04-04 martijn __dead void
963 48c4bdc1 2019-04-04 martijn usage(void)
964 48c4bdc1 2019-04-04 martijn {
965 fe371fc9 2019-09-05 martijn fprintf(stderr, "usage: filter-dkimsign [-tz] [-a signalg] "
966 fe371fc9 2019-09-05 martijn "[-c canonicalization] \n [-h headerfields]"
967 5fdffb49 2022-01-27 martijn "[-x seconds] -D file -d domain -k keyfile -s selector\n");
968 48c4bdc1 2019-04-04 martijn exit(1);
969 48c4bdc1 2019-04-04 martijn }