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 48c4bdc1 2019-04-04 martijn #include <sys/tree.h>
17 48c4bdc1 2019-04-04 martijn
18 48c4bdc1 2019-04-04 martijn #include <openssl/evp.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 48c4bdc1 2019-04-04 martijn #include <err.h>
23 48c4bdc1 2019-04-04 martijn #include <stdio.h>
24 48c4bdc1 2019-04-04 martijn #include <stdlib.h>
25 48c4bdc1 2019-04-04 martijn #include <string.h>
26 48c4bdc1 2019-04-04 martijn #include <syslog.h>
27 48c4bdc1 2019-04-04 martijn #include <time.h>
28 48c4bdc1 2019-04-04 martijn #include <unistd.h>
29 48c4bdc1 2019-04-04 martijn
30 48c4bdc1 2019-04-04 martijn #include "log.h"
31 48c4bdc1 2019-04-04 martijn #include "smtp_proc.h"
32 48c4bdc1 2019-04-04 martijn
33 48c4bdc1 2019-04-04 martijn struct dkim_session {
34 48c4bdc1 2019-04-04 martijn uint64_t reqid;
35 48c4bdc1 2019-04-04 martijn uint64_t token;
36 48c4bdc1 2019-04-04 martijn FILE *origf;
37 48c4bdc1 2019-04-04 martijn int parsing_headers;
38 48c4bdc1 2019-04-04 martijn char **headers;
39 48c4bdc1 2019-04-04 martijn int lastheader;
40 48c4bdc1 2019-04-04 martijn union {
41 48c4bdc1 2019-04-04 martijn SHA_CTX sha1;
42 48c4bdc1 2019-04-04 martijn SHA256_CTX sha256;
43 48c4bdc1 2019-04-04 martijn };
44 48c4bdc1 2019-04-04 martijn /* Use largest hash size her */
45 48c4bdc1 2019-04-04 martijn char bbh[SHA256_DIGEST_LENGTH];
46 48c4bdc1 2019-04-04 martijn char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1];
47 48c4bdc1 2019-04-04 martijn size_t body_whitelines;
48 48c4bdc1 2019-04-04 martijn int has_body;
49 48c4bdc1 2019-04-04 martijn RB_ENTRY(dkim_session) entry;
50 48c4bdc1 2019-04-04 martijn };
51 48c4bdc1 2019-04-04 martijn
52 48c4bdc1 2019-04-04 martijn RB_HEAD(dkim_sessions, dkim_session) dkim_sessions = RB_INITIALIZER(NULL);
53 48c4bdc1 2019-04-04 martijn RB_PROTOTYPE(dkim_sessions, dkim_session, entry, dkim_session_cmp);
54 48c4bdc1 2019-04-04 martijn
55 48c4bdc1 2019-04-04 martijn static char **sign_headers = NULL;
56 48c4bdc1 2019-04-04 martijn static size_t nsign_headers = 0;
57 48c4bdc1 2019-04-04 martijn
58 48c4bdc1 2019-04-04 martijn #define HASH_SHA1 0
59 48c4bdc1 2019-04-04 martijn #define HASH_SHA256 1
60 48c4bdc1 2019-04-04 martijn static int hashalg = HASH_SHA256;
61 48c4bdc1 2019-04-04 martijn
62 48c4bdc1 2019-04-04 martijn #define CRYPT_RSA 0
63 48c4bdc1 2019-04-04 martijn static int cryptalg = CRYPT_RSA;
64 48c4bdc1 2019-04-04 martijn
65 48c4bdc1 2019-04-04 martijn #define CANON_SIMPLE 0
66 48c4bdc1 2019-04-04 martijn #define CANON_RELAXED 1
67 48c4bdc1 2019-04-04 martijn static int canonheader = CANON_SIMPLE;
68 48c4bdc1 2019-04-04 martijn static int canonbody = CANON_SIMPLE;
69 48c4bdc1 2019-04-04 martijn
70 48c4bdc1 2019-04-04 martijn static char *domain = NULL;
71 48c4bdc1 2019-04-04 martijn
72 48c4bdc1 2019-04-04 martijn void usage(void);
73 48c4bdc1 2019-04-04 martijn void dkim_err(struct dkim_session *, char *);
74 48c4bdc1 2019-04-04 martijn void dkim_errx(struct dkim_session *, char *);
75 48c4bdc1 2019-04-04 martijn void dkim_headers_set(char *);
76 48c4bdc1 2019-04-04 martijn void dkim_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
77 48c4bdc1 2019-04-04 martijn uint64_t, char *);
78 48c4bdc1 2019-04-04 martijn void dkim_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
79 48c4bdc1 2019-04-04 martijn struct dkim_session *dkim_session_new(uint64_t);
80 48c4bdc1 2019-04-04 martijn void dkim_session_free(struct dkim_session *);
81 48c4bdc1 2019-04-04 martijn int dkim_session_cmp(struct dkim_session *, struct dkim_session *);
82 48c4bdc1 2019-04-04 martijn void dkim_parse_header(struct dkim_session *, char *);
83 48c4bdc1 2019-04-04 martijn void dkim_parse_body(struct dkim_session *, char *);
84 48c4bdc1 2019-04-04 martijn void dkim_built_signature(struct dkim_session *);
85 48c4bdc1 2019-04-04 martijn int dkim_hash_update(struct dkim_session *, char *, size_t);
86 48c4bdc1 2019-04-04 martijn int dkim_hash_final(struct dkim_session *, char *);
87 48c4bdc1 2019-04-04 martijn
88 48c4bdc1 2019-04-04 martijn int
89 48c4bdc1 2019-04-04 martijn main(int argc, char *argv[])
90 48c4bdc1 2019-04-04 martijn {
91 48c4bdc1 2019-04-04 martijn int ch;
92 48c4bdc1 2019-04-04 martijn int i;
93 48c4bdc1 2019-04-04 martijn int debug = 0;
94 48c4bdc1 2019-04-04 martijn
95 48c4bdc1 2019-04-04 martijn while ((ch = getopt(argc, argv, "a:c:Dd:h:")) != -1) {
96 48c4bdc1 2019-04-04 martijn switch (ch) {
97 48c4bdc1 2019-04-04 martijn case 'a':
98 48c4bdc1 2019-04-04 martijn if (strncmp(optarg, "rsa-", 4))
99 48c4bdc1 2019-04-04 martijn err(1, "invalid algorithm");
100 48c4bdc1 2019-04-04 martijn if (strcmp(optarg + 4, "sha256") == 0)
101 48c4bdc1 2019-04-04 martijn hashalg = HASH_SHA256;
102 48c4bdc1 2019-04-04 martijn else if (strcmp(optarg + 4, "sha1") == 0)
103 48c4bdc1 2019-04-04 martijn hashalg = HASH_SHA1;
104 48c4bdc1 2019-04-04 martijn else
105 48c4bdc1 2019-04-04 martijn err(1, "invalid algorithm");
106 48c4bdc1 2019-04-04 martijn break;
107 48c4bdc1 2019-04-04 martijn case 'c':
108 48c4bdc1 2019-04-04 martijn if (strncmp(optarg, "simple", 6) == 0) {
109 48c4bdc1 2019-04-04 martijn canonheader = CANON_SIMPLE;
110 48c4bdc1 2019-04-04 martijn optarg += 6;
111 48c4bdc1 2019-04-04 martijn } else if (strncmp(optarg, "relaxed", 7) == 0) {
112 48c4bdc1 2019-04-04 martijn canonheader = CANON_RELAXED;
113 48c4bdc1 2019-04-04 martijn optarg += 7;
114 48c4bdc1 2019-04-04 martijn } else
115 48c4bdc1 2019-04-04 martijn err(1, "Invalid canonicalization");
116 48c4bdc1 2019-04-04 martijn if (optarg[0] == '/') {
117 48c4bdc1 2019-04-04 martijn if (strcmp(optarg + 1, "simple") == 0)
118 48c4bdc1 2019-04-04 martijn canonbody = CANON_SIMPLE;
119 48c4bdc1 2019-04-04 martijn else if (strcmp(optarg + 1, "relaxed") == 0)
120 48c4bdc1 2019-04-04 martijn canonbody = CANON_RELAXED;
121 48c4bdc1 2019-04-04 martijn else
122 48c4bdc1 2019-04-04 martijn err(1, "Invalid canonicalization");
123 48c4bdc1 2019-04-04 martijn } else if (optarg[0] == '\0')
124 48c4bdc1 2019-04-04 martijn canonbody = CANON_SIMPLE;
125 48c4bdc1 2019-04-04 martijn else
126 48c4bdc1 2019-04-04 martijn err(1, "Invalid canonicalization");
127 48c4bdc1 2019-04-04 martijn break;
128 48c4bdc1 2019-04-04 martijn case 'd':
129 48c4bdc1 2019-04-04 martijn domain = optarg;
130 48c4bdc1 2019-04-04 martijn if (strlen(domain) > 255)
131 48c4bdc1 2019-04-04 martijn err(1, "Domain too long");
132 48c4bdc1 2019-04-04 martijn break;
133 48c4bdc1 2019-04-04 martijn case 'D':
134 48c4bdc1 2019-04-04 martijn debug = 1;
135 48c4bdc1 2019-04-04 martijn break;
136 48c4bdc1 2019-04-04 martijn case 'h':
137 48c4bdc1 2019-04-04 martijn dkim_headers_set(optarg);
138 48c4bdc1 2019-04-04 martijn break;
139 48c4bdc1 2019-04-04 martijn default:
140 48c4bdc1 2019-04-04 martijn usage();
141 48c4bdc1 2019-04-04 martijn }
142 48c4bdc1 2019-04-04 martijn }
143 48c4bdc1 2019-04-04 martijn
144 48c4bdc1 2019-04-04 martijn log_init(debug, LOG_MAIL);
145 48c4bdc1 2019-04-04 martijn if (pledge("tmppath stdio", NULL) == -1)
146 48c4bdc1 2019-04-04 martijn fatal("pledge");
147 48c4bdc1 2019-04-04 martijn
148 48c4bdc1 2019-04-04 martijn if (domain == NULL)
149 48c4bdc1 2019-04-04 martijn usage();
150 48c4bdc1 2019-04-04 martijn
151 48c4bdc1 2019-04-04 martijn smtp_register_filter_dataline(dkim_dataline);
152 48c4bdc1 2019-04-04 martijn smtp_in_register_report_disconnect(dkim_disconnect);
153 48c4bdc1 2019-04-04 martijn smtp_run(debug);
154 48c4bdc1 2019-04-04 martijn
155 48c4bdc1 2019-04-04 martijn return 0;
156 48c4bdc1 2019-04-04 martijn }
157 48c4bdc1 2019-04-04 martijn
158 48c4bdc1 2019-04-04 martijn void
159 48c4bdc1 2019-04-04 martijn dkim_disconnect(char *type, int version, struct timespec *tm, char *direction,
160 48c4bdc1 2019-04-04 martijn char *phase, uint64_t reqid)
161 48c4bdc1 2019-04-04 martijn {
162 48c4bdc1 2019-04-04 martijn struct dkim_session *session, search;
163 48c4bdc1 2019-04-04 martijn
164 48c4bdc1 2019-04-04 martijn search.reqid = reqid;
165 48c4bdc1 2019-04-04 martijn if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) != NULL)
166 48c4bdc1 2019-04-04 martijn dkim_session_free(session);
167 48c4bdc1 2019-04-04 martijn }
168 48c4bdc1 2019-04-04 martijn
169 48c4bdc1 2019-04-04 martijn void
170 48c4bdc1 2019-04-04 martijn dkim_dataline(char *type, int version, struct timespec *tm, char *direction,
171 48c4bdc1 2019-04-04 martijn char *phase, uint64_t reqid, uint64_t token, char *line)
172 48c4bdc1 2019-04-04 martijn {
173 48c4bdc1 2019-04-04 martijn struct dkim_session *session, search;
174 48c4bdc1 2019-04-04 martijn size_t i;
175 48c4bdc1 2019-04-04 martijn size_t linelen;
176 48c4bdc1 2019-04-04 martijn
177 48c4bdc1 2019-04-04 martijn search.reqid = reqid;
178 48c4bdc1 2019-04-04 martijn session = RB_FIND(dkim_sessions, &dkim_sessions, &search);
179 48c4bdc1 2019-04-04 martijn if (session == NULL) {
180 48c4bdc1 2019-04-04 martijn session = dkim_session_new(reqid);
181 48c4bdc1 2019-04-04 martijn session->token = token;
182 48c4bdc1 2019-04-04 martijn } else if (session->token != token)
183 48c4bdc1 2019-04-04 martijn fatalx("Token incorrect");
184 48c4bdc1 2019-04-04 martijn
185 48c4bdc1 2019-04-04 martijn linelen = strlen(line);
186 48c4bdc1 2019-04-04 martijn if (fwrite(line, 1, linelen, session->origf) < linelen)
187 48c4bdc1 2019-04-04 martijn dkim_err(session, "Couldn't write to tempfile");
188 48c4bdc1 2019-04-04 martijn
189 48c4bdc1 2019-04-04 martijn if (linelen != 0 && session->parsing_headers) {
190 48c4bdc1 2019-04-04 martijn dkim_parse_header(session, line);
191 48c4bdc1 2019-04-04 martijn } else if (linelen == 0 && session->parsing_headers) {
192 48c4bdc1 2019-04-04 martijn session->parsing_headers = 0;
193 48c4bdc1 2019-04-04 martijn } else if (line[0] == '.' && line[1] =='\0') {
194 48c4bdc1 2019-04-04 martijn if (canonbody == CANON_SIMPLE && !session->has_body) {
195 48c4bdc1 2019-04-04 martijn if (dkim_hash_update(session, "\r\n", 2) == 0)
196 48c4bdc1 2019-04-04 martijn return;
197 48c4bdc1 2019-04-04 martijn }
198 48c4bdc1 2019-04-04 martijn if (dkim_hash_final(session, session->bbh) == 0)
199 48c4bdc1 2019-04-04 martijn return;
200 48c4bdc1 2019-04-04 martijn EVP_EncodeBlock(session->bh, session->bbh,
201 48c4bdc1 2019-04-04 martijn hashalg == HASH_SHA1 ? SHA_DIGEST_LENGTH :
202 48c4bdc1 2019-04-04 martijn SHA256_DIGEST_LENGTH);
203 48c4bdc1 2019-04-04 martijn dkim_built_signature(session);
204 48c4bdc1 2019-04-04 martijn } else
205 48c4bdc1 2019-04-04 martijn dkim_parse_body(session, line);
206 48c4bdc1 2019-04-04 martijn }
207 48c4bdc1 2019-04-04 martijn
208 48c4bdc1 2019-04-04 martijn struct dkim_session *
209 48c4bdc1 2019-04-04 martijn dkim_session_new(uint64_t reqid)
210 48c4bdc1 2019-04-04 martijn {
211 48c4bdc1 2019-04-04 martijn struct dkim_session *session;
212 48c4bdc1 2019-04-04 martijn char origfile[] = "/tmp/filter-dkimXXXXXX";
213 48c4bdc1 2019-04-04 martijn int fd;
214 48c4bdc1 2019-04-04 martijn
215 48c4bdc1 2019-04-04 martijn if ((session = calloc(1, sizeof(*session))) == NULL)
216 48c4bdc1 2019-04-04 martijn fatal(NULL);
217 48c4bdc1 2019-04-04 martijn
218 48c4bdc1 2019-04-04 martijn session->reqid = reqid;
219 48c4bdc1 2019-04-04 martijn if ((fd = mkstemp(origfile)) == -1) {
220 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't open tempfile");
221 48c4bdc1 2019-04-04 martijn return NULL;
222 48c4bdc1 2019-04-04 martijn }
223 48c4bdc1 2019-04-04 martijn if (unlink(origfile) == -1)
224 48c4bdc1 2019-04-04 martijn log_warn("Failed to unlink tempfile %s", origfile);
225 48c4bdc1 2019-04-04 martijn if ((session->origf = fdopen(fd, "r+")) == NULL) {
226 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't open tempfile");
227 48c4bdc1 2019-04-04 martijn return NULL;
228 48c4bdc1 2019-04-04 martijn }
229 48c4bdc1 2019-04-04 martijn session->parsing_headers = 1;
230 48c4bdc1 2019-04-04 martijn
231 48c4bdc1 2019-04-04 martijn if (hashalg == HASH_SHA1)
232 48c4bdc1 2019-04-04 martijn SHA1_Init(&(session->sha1));
233 48c4bdc1 2019-04-04 martijn else
234 48c4bdc1 2019-04-04 martijn SHA256_Init(&(session->sha256));
235 48c4bdc1 2019-04-04 martijn session->body_whitelines = 0;
236 48c4bdc1 2019-04-04 martijn session->headers = calloc(1, sizeof(*(session->headers)));
237 48c4bdc1 2019-04-04 martijn if (session->headers == NULL) {
238 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't save headers");
239 48c4bdc1 2019-04-04 martijn return NULL;
240 48c4bdc1 2019-04-04 martijn }
241 48c4bdc1 2019-04-04 martijn session->lastheader = 0;
242 48c4bdc1 2019-04-04 martijn
243 48c4bdc1 2019-04-04 martijn if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL)
244 48c4bdc1 2019-04-04 martijn fatalx("session already registered");
245 48c4bdc1 2019-04-04 martijn return session;
246 48c4bdc1 2019-04-04 martijn }
247 48c4bdc1 2019-04-04 martijn
248 48c4bdc1 2019-04-04 martijn void
249 48c4bdc1 2019-04-04 martijn dkim_session_free(struct dkim_session *session)
250 48c4bdc1 2019-04-04 martijn {
251 48c4bdc1 2019-04-04 martijn size_t i;
252 48c4bdc1 2019-04-04 martijn
253 48c4bdc1 2019-04-04 martijn RB_REMOVE(dkim_sessions, &dkim_sessions, session);
254 48c4bdc1 2019-04-04 martijn fclose(session->origf);
255 48c4bdc1 2019-04-04 martijn for (i = 0; session->headers[i] != NULL; i++)
256 48c4bdc1 2019-04-04 martijn free(session->headers[i]);
257 48c4bdc1 2019-04-04 martijn free(session->headers);
258 48c4bdc1 2019-04-04 martijn free(session);
259 48c4bdc1 2019-04-04 martijn }
260 48c4bdc1 2019-04-04 martijn
261 48c4bdc1 2019-04-04 martijn int
262 48c4bdc1 2019-04-04 martijn dkim_session_cmp(struct dkim_session *s1, struct dkim_session *s2)
263 48c4bdc1 2019-04-04 martijn {
264 48c4bdc1 2019-04-04 martijn return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
265 48c4bdc1 2019-04-04 martijn }
266 48c4bdc1 2019-04-04 martijn
267 48c4bdc1 2019-04-04 martijn void
268 48c4bdc1 2019-04-04 martijn dkim_headers_set(char *headers)
269 48c4bdc1 2019-04-04 martijn {
270 48c4bdc1 2019-04-04 martijn size_t i;
271 48c4bdc1 2019-04-04 martijn int has_from = 0;
272 48c4bdc1 2019-04-04 martijn
273 48c4bdc1 2019-04-04 martijn nsign_headers = 1;
274 48c4bdc1 2019-04-04 martijn
275 48c4bdc1 2019-04-04 martijn /* We don't support FWS for the -h flag */
276 48c4bdc1 2019-04-04 martijn for (i = 0; headers[i] != '\0'; i++) {
277 48c4bdc1 2019-04-04 martijn /* RFC 5322 field-name */
278 48c4bdc1 2019-04-04 martijn if (!(headers[i] >= 33 && headers[i] <= 126))
279 48c4bdc1 2019-04-04 martijn errx(1, "-h: invalid character");
280 48c4bdc1 2019-04-04 martijn if (headers[i] == ':') {
281 48c4bdc1 2019-04-04 martijn /* Test for empty headers */
282 48c4bdc1 2019-04-04 martijn if (i == 0 || headers[i - 1] == ':')
283 48c4bdc1 2019-04-04 martijn errx(1, "-h: header can't be empty");
284 48c4bdc1 2019-04-04 martijn nsign_headers++;
285 48c4bdc1 2019-04-04 martijn }
286 48c4bdc1 2019-04-04 martijn headers[i] = tolower(headers[i]);
287 48c4bdc1 2019-04-04 martijn }
288 48c4bdc1 2019-04-04 martijn if (headers[i - 1] == ':')
289 48c4bdc1 2019-04-04 martijn errx(1, "-h: header can't be empty");
290 48c4bdc1 2019-04-04 martijn
291 48c4bdc1 2019-04-04 martijn sign_headers = reallocarray(NULL, nsign_headers, sizeof(*sign_headers));
292 48c4bdc1 2019-04-04 martijn if (sign_headers == NULL)
293 48c4bdc1 2019-04-04 martijn errx(1, NULL);
294 48c4bdc1 2019-04-04 martijn
295 48c4bdc1 2019-04-04 martijn for (i = 0; i < nsign_headers; i++) {
296 48c4bdc1 2019-04-04 martijn sign_headers[i] = headers;
297 48c4bdc1 2019-04-04 martijn if (i != nsign_headers - 1) {
298 48c4bdc1 2019-04-04 martijn headers = strchr(headers, ':');
299 48c4bdc1 2019-04-04 martijn headers++[0] = '\0';
300 48c4bdc1 2019-04-04 martijn }
301 48c4bdc1 2019-04-04 martijn if (strcasecmp(sign_headers[i], "from") == 0)
302 48c4bdc1 2019-04-04 martijn has_from = 1;
303 48c4bdc1 2019-04-04 martijn }
304 48c4bdc1 2019-04-04 martijn if (!has_from)
305 48c4bdc1 2019-04-04 martijn errx(1, "From header must be included");
306 48c4bdc1 2019-04-04 martijn }
307 48c4bdc1 2019-04-04 martijn
308 48c4bdc1 2019-04-04 martijn void
309 48c4bdc1 2019-04-04 martijn dkim_err(struct dkim_session *session, char *msg)
310 48c4bdc1 2019-04-04 martijn {
311 48c4bdc1 2019-04-04 martijn smtp_filter_disconnect(session->reqid, session->token,
312 48c4bdc1 2019-04-04 martijn "Internal server error");
313 48c4bdc1 2019-04-04 martijn log_warn("%s", msg);
314 48c4bdc1 2019-04-04 martijn dkim_session_free(session);
315 48c4bdc1 2019-04-04 martijn }
316 48c4bdc1 2019-04-04 martijn
317 48c4bdc1 2019-04-04 martijn void
318 48c4bdc1 2019-04-04 martijn dkim_errx(struct dkim_session *session, char *msg)
319 48c4bdc1 2019-04-04 martijn {
320 48c4bdc1 2019-04-04 martijn smtp_filter_disconnect(session->reqid, session->token,
321 48c4bdc1 2019-04-04 martijn "Internal server error");
322 48c4bdc1 2019-04-04 martijn log_warnx("%s", msg);
323 48c4bdc1 2019-04-04 martijn dkim_session_free(session);
324 48c4bdc1 2019-04-04 martijn }
325 48c4bdc1 2019-04-04 martijn
326 48c4bdc1 2019-04-04 martijn void
327 48c4bdc1 2019-04-04 martijn dkim_parse_header(struct dkim_session *session, char *line)
328 48c4bdc1 2019-04-04 martijn {
329 48c4bdc1 2019-04-04 martijn size_t i;
330 48c4bdc1 2019-04-04 martijn size_t r, w;
331 48c4bdc1 2019-04-04 martijn size_t linelen;
332 48c4bdc1 2019-04-04 martijn size_t lastheader;
333 48c4bdc1 2019-04-04 martijn int fieldname;
334 48c4bdc1 2019-04-04 martijn char **mtmp;
335 48c4bdc1 2019-04-04 martijn char *htmp;
336 48c4bdc1 2019-04-04 martijn
337 48c4bdc1 2019-04-04 martijn if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader)
338 48c4bdc1 2019-04-04 martijn return;
339 48c4bdc1 2019-04-04 martijn if ((line[0] != ' ' && line[0] != '\t')) {
340 48c4bdc1 2019-04-04 martijn for (i = 0; i < nsign_headers; i++) {
341 48c4bdc1 2019-04-04 martijn if (strncasecmp(line, sign_headers[i],
342 48c4bdc1 2019-04-04 martijn strlen(sign_headers[i])) == 0) {
343 48c4bdc1 2019-04-04 martijn break;
344 48c4bdc1 2019-04-04 martijn }
345 48c4bdc1 2019-04-04 martijn }
346 48c4bdc1 2019-04-04 martijn if (i == nsign_headers) {
347 48c4bdc1 2019-04-04 martijn session->lastheader = 0;
348 48c4bdc1 2019-04-04 martijn return;
349 48c4bdc1 2019-04-04 martijn }
350 48c4bdc1 2019-04-04 martijn }
351 48c4bdc1 2019-04-04 martijn
352 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_RELAXED) {
353 48c4bdc1 2019-04-04 martijn fieldname = 1;
354 48c4bdc1 2019-04-04 martijn for (r = w = 0; line[r] != '\0'; r++) {
355 48c4bdc1 2019-04-04 martijn if (line[r] == ':') {
356 48c4bdc1 2019-04-04 martijn if (line[w - 1] == ' ')
357 48c4bdc1 2019-04-04 martijn line[w - 1] = ':';
358 48c4bdc1 2019-04-04 martijn else
359 48c4bdc1 2019-04-04 martijn line[w++] = ':';
360 48c4bdc1 2019-04-04 martijn fieldname = 0;
361 48c4bdc1 2019-04-04 martijn while (line[r + 1] == ' ' ||
362 48c4bdc1 2019-04-04 martijn line[r + 1] == '\t')
363 48c4bdc1 2019-04-04 martijn r++;
364 48c4bdc1 2019-04-04 martijn continue;
365 48c4bdc1 2019-04-04 martijn }
366 48c4bdc1 2019-04-04 martijn if (line[r] == ' ' || line[r] == '\t') {
367 48c4bdc1 2019-04-04 martijn if (r != 0 && line[w - 1] == ' ')
368 48c4bdc1 2019-04-04 martijn continue;
369 48c4bdc1 2019-04-04 martijn else
370 48c4bdc1 2019-04-04 martijn line[w++] = ' ';
371 48c4bdc1 2019-04-04 martijn } else if (fieldname) {
372 48c4bdc1 2019-04-04 martijn line[w++] = tolower(line[r]);
373 48c4bdc1 2019-04-04 martijn continue;
374 48c4bdc1 2019-04-04 martijn } else
375 48c4bdc1 2019-04-04 martijn line[w++] = line[r];
376 48c4bdc1 2019-04-04 martijn }
377 48c4bdc1 2019-04-04 martijn linelen = line[w - 1] == ' ' ? w - 1 : w;
378 48c4bdc1 2019-04-04 martijn line[linelen] = '\0';
379 48c4bdc1 2019-04-04 martijn } else
380 48c4bdc1 2019-04-04 martijn linelen = strlen(line);
381 48c4bdc1 2019-04-04 martijn
382 48c4bdc1 2019-04-04 martijn for (lastheader = 0; session->headers[lastheader] != NULL; lastheader++)
383 48c4bdc1 2019-04-04 martijn continue;
384 48c4bdc1 2019-04-04 martijn if (!session->lastheader) {
385 48c4bdc1 2019-04-04 martijn mtmp = reallocarray(session->headers, lastheader + 1,
386 48c4bdc1 2019-04-04 martijn sizeof(*mtmp));
387 48c4bdc1 2019-04-04 martijn if (mtmp == NULL) {
388 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't store header");
389 48c4bdc1 2019-04-04 martijn return;
390 48c4bdc1 2019-04-04 martijn }
391 48c4bdc1 2019-04-04 martijn session->headers = mtmp;
392 48c4bdc1 2019-04-04 martijn
393 48c4bdc1 2019-04-04 martijn session->headers[lastheader] = strdup(line);
394 48c4bdc1 2019-04-04 martijn session->headers[lastheader + 1 ] = NULL;
395 48c4bdc1 2019-04-04 martijn session->lastheader = 1;
396 48c4bdc1 2019-04-04 martijn } else {
397 48c4bdc1 2019-04-04 martijn lastheader--;
398 48c4bdc1 2019-04-04 martijn linelen += strlen(session->headers[lastheader]);
399 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_SIMPLE)
400 48c4bdc1 2019-04-04 martijn linelen += 2;
401 48c4bdc1 2019-04-04 martijn linelen++;
402 48c4bdc1 2019-04-04 martijn htmp = reallocarray(session->headers[lastheader], linelen,
403 48c4bdc1 2019-04-04 martijn sizeof(*htmp));
404 48c4bdc1 2019-04-04 martijn if (htmp == NULL) {
405 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't store header");
406 48c4bdc1 2019-04-04 martijn return;
407 48c4bdc1 2019-04-04 martijn }
408 48c4bdc1 2019-04-04 martijn session->headers[lastheader] = htmp;
409 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_SIMPLE) {
410 48c4bdc1 2019-04-04 martijn if (strlcat(htmp, "\r\n", linelen) >= linelen)
411 48c4bdc1 2019-04-04 martijn fatalx("Missized header");
412 48c4bdc1 2019-04-04 martijn }
413 48c4bdc1 2019-04-04 martijn if (strlcat(htmp, line, linelen) >= linelen)
414 48c4bdc1 2019-04-04 martijn fatalx("Missized header");
415 48c4bdc1 2019-04-04 martijn }
416 48c4bdc1 2019-04-04 martijn }
417 48c4bdc1 2019-04-04 martijn
418 48c4bdc1 2019-04-04 martijn void
419 48c4bdc1 2019-04-04 martijn dkim_parse_body(struct dkim_session *session, char *line)
420 48c4bdc1 2019-04-04 martijn {
421 48c4bdc1 2019-04-04 martijn size_t r, w;
422 48c4bdc1 2019-04-04 martijn size_t linelen;
423 48c4bdc1 2019-04-04 martijn if (line[0] == '\0') {
424 48c4bdc1 2019-04-04 martijn session->body_whitelines++;
425 48c4bdc1 2019-04-04 martijn return;
426 48c4bdc1 2019-04-04 martijn }
427 48c4bdc1 2019-04-04 martijn
428 48c4bdc1 2019-04-04 martijn while (session->body_whitelines--) {
429 48c4bdc1 2019-04-04 martijn if (dkim_hash_update(session, "\r\n", 2) == 0)
430 48c4bdc1 2019-04-04 martijn return;
431 48c4bdc1 2019-04-04 martijn }
432 48c4bdc1 2019-04-04 martijn session->body_whitelines = 0;
433 48c4bdc1 2019-04-04 martijn
434 48c4bdc1 2019-04-04 martijn session->has_body = 1;
435 48c4bdc1 2019-04-04 martijn if (canonbody == CANON_RELAXED) {
436 48c4bdc1 2019-04-04 martijn for (r = w = 0; line[r] != '\0'; r++) {
437 48c4bdc1 2019-04-04 martijn if (line[r] == ' ' || line[r] == '\t') {
438 48c4bdc1 2019-04-04 martijn if (r != 0 && line[w - 1] == ' ')
439 48c4bdc1 2019-04-04 martijn continue;
440 48c4bdc1 2019-04-04 martijn else
441 48c4bdc1 2019-04-04 martijn line[w++] = ' ';
442 48c4bdc1 2019-04-04 martijn } else
443 48c4bdc1 2019-04-04 martijn line[w++] = line[r];
444 48c4bdc1 2019-04-04 martijn }
445 48c4bdc1 2019-04-04 martijn linelen = line[w - 1] == ' ' ? w - 1 : w;
446 48c4bdc1 2019-04-04 martijn line[linelen] = '\0';
447 48c4bdc1 2019-04-04 martijn } else
448 48c4bdc1 2019-04-04 martijn linelen = strlen(line);
449 48c4bdc1 2019-04-04 martijn
450 48c4bdc1 2019-04-04 martijn if (dkim_hash_update(session, line, linelen) == 0)
451 48c4bdc1 2019-04-04 martijn return;
452 48c4bdc1 2019-04-04 martijn if (dkim_hash_update(session, "\r\n", 2) == 0)
453 48c4bdc1 2019-04-04 martijn return;
454 48c4bdc1 2019-04-04 martijn }
455 48c4bdc1 2019-04-04 martijn
456 48c4bdc1 2019-04-04 martijn void
457 48c4bdc1 2019-04-04 martijn dkim_built_signature(struct dkim_session *session)
458 48c4bdc1 2019-04-04 martijn {
459 48c4bdc1 2019-04-04 martijn char *signature;
460 48c4bdc1 2019-04-04 martijn size_t signaturesize = 1024;
461 48c4bdc1 2019-04-04 martijn size_t signaturelen = 0;
462 48c4bdc1 2019-04-04 martijn size_t linelen = 0;
463 48c4bdc1 2019-04-04 martijn size_t ncopied;
464 48c4bdc1 2019-04-04 martijn
465 48c4bdc1 2019-04-04 martijn if ((signature = malloc(signaturesize)) == NULL) {
466 48c4bdc1 2019-04-04 martijn dkim_err(session, "Can't create signature");
467 48c4bdc1 2019-04-04 martijn return;
468 48c4bdc1 2019-04-04 martijn }
469 48c4bdc1 2019-04-04 martijn
470 48c4bdc1 2019-04-04 martijn do {
471 48c4bdc1 2019-04-04 martijn ncopied = strlcpy(signature, "DKIM-Signature: v=1; a=",
472 48c4bdc1 2019-04-04 martijn signaturesize);
473 48c4bdc1 2019-04-04 martijn if (ncopied >= signaturesize)
474 48c4bdc1 2019-04-04 martijn if (cryptalg == CRYPT_RSA)
475 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "rsa", signaturesize);
476 48c4bdc1 2019-04-04 martijn if (hashalg == HASH_SHA1)
477 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "-sha1; ", signaturesize);
478 48c4bdc1 2019-04-04 martijn else
479 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "-sha256; ", signaturesize);
480 48c4bdc1 2019-04-04 martijn if (canonheader != CANON_SIMPLE || canonbody != CANON_SIMPLE) {
481 48c4bdc1 2019-04-04 martijn if (canonheader == CANON_SIMPLE)
482 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "c=simple", signaturesize);
483 48c4bdc1 2019-04-04 martijn else
484 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "c=relaxed", signaturesize);
485 48c4bdc1 2019-04-04 martijn }
486 48c4bdc1 2019-04-04 martijn if (canonbody != CANON_SIMPLE)
487 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "/relaxed", signaturesize);
488 48c4bdc1 2019-04-04 martijn
489 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "d=", signaturesize);
490 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, domain, signaturesize);
491 48c4bdc1 2019-04-04 martijn (void) strlcat(signature, "; ", signaturesize);
492 48c4bdc1 2019-04-04 martijn printf("%s\n", signature);
493 48c4bdc1 2019-04-04 martijn }
494 48c4bdc1 2019-04-04 martijn
495 48c4bdc1 2019-04-04 martijn int
496 48c4bdc1 2019-04-04 martijn dkim_hash_update(struct dkim_session *session, char *buf, size_t len)
497 48c4bdc1 2019-04-04 martijn {
498 48c4bdc1 2019-04-04 martijn if (hashalg == HASH_SHA1) {
499 48c4bdc1 2019-04-04 martijn if (SHA1_Update(&(session->sha1), buf, len) == 0) {
500 48c4bdc1 2019-04-04 martijn dkim_errx(session, "Unable to update hash");
501 48c4bdc1 2019-04-04 martijn return 0;
502 48c4bdc1 2019-04-04 martijn }
503 48c4bdc1 2019-04-04 martijn } else {
504 48c4bdc1 2019-04-04 martijn if (SHA256_Update(&(session->sha256), buf, len) == 0) {
505 48c4bdc1 2019-04-04 martijn dkim_errx(session, "Unable to update hash");
506 48c4bdc1 2019-04-04 martijn return 0;
507 48c4bdc1 2019-04-04 martijn }
508 48c4bdc1 2019-04-04 martijn }
509 48c4bdc1 2019-04-04 martijn return 1;
510 48c4bdc1 2019-04-04 martijn }
511 48c4bdc1 2019-04-04 martijn
512 48c4bdc1 2019-04-04 martijn int
513 48c4bdc1 2019-04-04 martijn dkim_hash_final(struct dkim_session *session, char *dest)
514 48c4bdc1 2019-04-04 martijn {
515 48c4bdc1 2019-04-04 martijn if (hashalg == HASH_SHA1) {
516 48c4bdc1 2019-04-04 martijn if (SHA1_Final(dest, &(session->sha1)) == 0) {
517 48c4bdc1 2019-04-04 martijn dkim_errx(session, "Unable to finalize hash");
518 48c4bdc1 2019-04-04 martijn return 0;
519 48c4bdc1 2019-04-04 martijn }
520 48c4bdc1 2019-04-04 martijn } else {
521 48c4bdc1 2019-04-04 martijn if (SHA256_Final(dest, &(session->sha256)) == 0) {
522 48c4bdc1 2019-04-04 martijn dkim_errx(session, "Unable to finalize hash");
523 48c4bdc1 2019-04-04 martijn return 0;
524 48c4bdc1 2019-04-04 martijn }
525 48c4bdc1 2019-04-04 martijn }
526 48c4bdc1 2019-04-04 martijn return 1;
527 48c4bdc1 2019-04-04 martijn }
528 48c4bdc1 2019-04-04 martijn
529 48c4bdc1 2019-04-04 martijn __dead void
530 48c4bdc1 2019-04-04 martijn usage(void)
531 48c4bdc1 2019-04-04 martijn {
532 48c4bdc1 2019-04-04 martijn fprintf(stderr, "usage: %s [-a signalg] [-c canonicalization] -d domain -h headerfields\n", getprogname());
533 48c4bdc1 2019-04-04 martijn exit(1);
534 48c4bdc1 2019-04-04 martijn }
535 48c4bdc1 2019-04-04 martijn
536 48c4bdc1 2019-04-04 martijn RB_GENERATE(dkim_sessions, dkim_session, entry, dkim_session_cmp);