Blob


1 /*
2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <errno.h>
18 #include <inttypes.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23 #include <unistd.h>
25 #include "opensmtpd.h"
27 struct admd_message {
28 int foundmatch;
29 int err;
30 int inheader;
31 int parsing_headers;
32 char **cache;
33 size_t cachelen;
34 };
36 void usage(void);
37 void *admd_message_new(struct osmtpd_ctx *);
38 void admd_message_free(struct osmtpd_ctx *, void *);
39 void admd_dataline(struct osmtpd_ctx *, const char *);
40 void admd_commit(struct osmtpd_ctx *);
41 void admd_err(struct admd_message *, const char *);
42 void admd_cache(struct admd_message *, const char *);
43 const char *admd_authservid(struct admd_message *);
44 void admd_freecache(struct admd_message *);
46 char authservid[256] = "";
47 int reject = 0;
48 int verbose = 0;
50 int
51 main(int argc, char *argv[])
52 {
53 int ch;
55 while ((ch = getopt(argc, argv, "a:rv")) != -1) {
56 switch (ch) {
57 case 'a':
58 if (strlcpy(authservid, optarg, sizeof(authservid)) >=
59 sizeof(authservid))
60 osmtpd_errx(1, "authserv-id is too long");
61 break;
62 case 'r':
63 reject = 1;
64 break;
65 case 'v':
66 verbose++;
67 break;
68 default:
69 usage();
70 }
71 }
73 if (pledge("stdio", NULL) == -1)
74 osmtpd_err(1, "pledge");
76 if (authservid[0] == '\0') {
77 if (gethostname(authservid, sizeof(authservid)) == -1)
78 osmtpd_err(1, "gethostname");
79 }
80 if (strchr(authservid, '\r') != NULL ||
81 strchr(authservid, '\n') != NULL)
82 osmtpd_errx(1, "ubsupported character in authserv-id");
84 osmtpd_local_message(admd_message_new, admd_message_free);
85 osmtpd_register_filter_dataline(admd_dataline);
86 osmtpd_register_filter_commit(admd_commit);
87 osmtpd_run();
89 return 0;
90 }
92 void *
93 admd_message_new(struct osmtpd_ctx *ctx)
94 {
95 struct admd_message *msg;
97 if ((msg = malloc(sizeof(*msg))) == NULL)
98 osmtpd_err(1, "malloc");
100 msg->foundmatch = 0;
101 msg->err = 0;
102 msg->inheader = 0;
103 msg->parsing_headers = 1;
104 msg->cache = NULL;
105 msg->cachelen = 0;
107 return msg;
110 void
111 admd_message_free(struct osmtpd_ctx *ctx, void *data)
113 struct admd_message *msg = data;
115 admd_freecache(msg);
116 free(msg);
119 void
120 admd_dataline(struct osmtpd_ctx *ctx, const char *orig)
122 struct admd_message *msg = ctx->local_message;
123 const char *line = orig;
124 const char *msgauthid;
125 size_t i;
127 if (msg->err) {
128 if (line[0] == '.' && line[1] =='\0')
129 osmtpd_filter_dataline(ctx, ".");
130 return;
133 if (line[0] == '\0')
134 msg->parsing_headers = 0;
135 if (line[0] == '.')
136 line++;
137 if (msg->parsing_headers) {
138 if (line[0] != ' ' && line[0] != '\t') {
139 if (msg->inheader) {
140 msgauthid = admd_authservid(msg);
141 if (strcmp(msgauthid, authservid) == 0)
142 msg->foundmatch = 1;
143 else {
144 for (i = 0; i < msg->cachelen; i++)
145 osmtpd_filter_dataline(ctx,
146 "%s", msg->cache[i]);
148 admd_freecache(msg);
150 msg->inheader = 0;
152 if (strncmp(line, "Authentication-Results:", 23) == 0) {
153 msg->inheader = 1;
154 admd_cache(msg, orig);
155 return;
157 if (msg->inheader && (line[0] == ' ' || line[0] == '\t')) {
158 admd_cache(msg, orig);
159 return;
163 osmtpd_filter_dataline(ctx, "%s", orig);
164 return;
167 void
168 admd_commit(struct osmtpd_ctx *ctx)
170 struct admd_message *msg = ctx->local_message;
172 if (msg->err) {
173 osmtpd_filter_disconnect(ctx, "Internal server error");
174 return;
176 if (reject && msg->foundmatch) {
177 osmtpd_filter_disconnect(ctx, "Message contains "
178 "Authentication-Results header for authserv-id '%s'",
179 authservid);
180 fprintf(stderr, "%016"PRIx64" Message contains "
181 "Authentication-Results header for authserv-id '%s': "
182 "rejected\n", ctx->reqid, authservid);
183 return;
186 osmtpd_filter_proceed(ctx);
187 if (msg->foundmatch) {
188 fprintf(stderr, "%016"PRIx64" Message contains "
189 "Authentication-Results header for authserv-id '%s': "
190 "filtered\n", ctx->reqid, authservid);
191 } else if (verbose)
192 fprintf(stderr, "%016"PRIx64" Message contains no "
193 "Authentication-Results header for authserv-id '%s'\n",
194 ctx->reqid, authservid);
197 void
198 admd_err(struct admd_message *message, const char *msg)
200 message->err = 1;
201 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
204 void
205 admd_cache(struct admd_message *msg, const char *line)
207 char **tcache;
209 if ((tcache = reallocarray(msg->cache, msg->cachelen + 1,
210 sizeof(*(msg->cache)))) == NULL) {
211 admd_freecache(msg);
212 admd_err(msg, "malloc");
214 msg->cache = tcache;
215 msg->cache[msg->cachelen] = strdup(line);
216 if (msg->cache[msg->cachelen] == NULL) {
217 admd_freecache(msg);
218 admd_err(msg, "strdup");
220 msg->cachelen++;
221 return;
224 const char *
225 admd_authservid(struct admd_message *msg)
227 static char msgauthid[sizeof(authservid)];
228 const char *header;
229 size_t i = 0;
230 int depth = 0;
232 msgauthid[0] = '\0';
234 header = msg->cache[0];
236 if (header[0] == '.')
237 header++;
239 /* Skip key */
240 header += 23;
242 /* CFWS */
243 /*
244 * Take the extremely loose approach with both FWS and comment so we
245 * might match a non fully complient comment and still get the right
246 * authserv-id
247 */
248 fws:
249 while (header[0] == ' ' || header[0] == '\t')
250 header++;
251 if (header[0] == '\0') {
252 if (++i >= msg->cachelen)
253 return msgauthid;
254 header = msg->cache[i];
255 /* For leniency allow multiple consequtive FWS */
256 goto fws;
258 /* comment */
259 if (header[0] == '(') {
260 depth++;
261 header++;
263 if (depth > 0) {
264 while (1) {
265 /*
266 * consume a full quoted-pair, which may contain
267 * parentheses
268 */
269 if (header[0] == '"') {
270 header++;
271 while (header[0] != '"') {
272 if (header[0] == '\\')
273 header++;
274 if (header[0] == '\0') {
275 if (++i >= msg->cachelen) {
276 return msgauthid;
278 header = msg->cache[i];
279 } else
280 header++;
282 header++;
283 /* End of comment */
284 } else if (header[0] == ')') {
285 header++;
286 if (--depth == 0)
287 goto fws;
288 } else if (header[0] == '(') {
289 header++;
290 depth++;
291 } else if (header[0] == '\0') {
292 if (++i >= msg->cachelen)
293 return msgauthid;
294 header = msg->cache[i];
295 } else
296 header++;
299 /* Quoted-string */
300 if (header[0] == '"') {
301 header++;
302 for (i = 0; header[0] != '"' && header[0] != '\0' &&
303 i < sizeof(msgauthid); i++, header++) {
304 if (header[0] == '\\')
305 header++;
306 /* Don't do Newline at all */
307 if (header[0] == '\0') {
308 i = 0;
309 break;
311 msgauthid[i] = header[0];
313 /* token */
314 } else {
315 /*
316 * Be more lenient towards token to hit more
317 * edgecases
318 */
319 for (i = 0; header[i] != ' ' && header[i] != '\t' &&
320 header[i] != ';' && header[i] != '\0' &&
321 i < sizeof(msgauthid); i++)
322 msgauthid[i] = header[i];
324 /* If we overflow we simply don't match */
325 if (i == sizeof(msgauthid))
326 i = 0;
327 msgauthid[i] = '\0';
328 return msgauthid;
331 void
332 admd_freecache(struct admd_message *msg)
334 while (msg->cachelen > 0) {
335 msg->cachelen--;
336 free(msg->cache[msg->cachelen]);
338 free(msg->cache);
339 msg->cache = NULL;
340 msg->cachelen = 0;
343 __dead void
344 usage(void)
346 fprintf(stderr, "usage: filter-admdscrub [-rv] [-a authserv-id]\n");
347 exit(1);