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 "openbsd-compat.h"
26 #include "opensmtpd.h"
27 #include "mheader.h"
29 struct admd_message {
30 int foundmatch;
31 int err;
32 int inheader;
33 int parsing_headers;
34 char **cache;
35 size_t cachelen;
36 size_t headerlen;
37 };
39 void usage(void);
40 void admd_conf(const char *, const char *);
41 void *admd_message_new(struct osmtpd_ctx *);
42 void admd_message_free(struct osmtpd_ctx *, void *);
43 void admd_dataline(struct osmtpd_ctx *, const char *);
44 void admd_commit(struct osmtpd_ctx *);
45 void admd_err(struct admd_message *, const char *);
46 void admd_cache(struct admd_message *, const char *);
47 const char *admd_authservid(struct admd_message *);
48 void admd_freecache(struct admd_message *);
50 char *authservid;
51 int reject = 0;
52 int verbose = 0;
54 int
55 main(int argc, char *argv[])
56 {
57 int ch;
59 if (pledge("stdio", NULL) == -1)
60 osmtpd_err(1, "pledge");
62 while ((ch = getopt(argc, argv, "rv")) != -1) {
63 switch (ch) {
64 case 'r':
65 reject = 1;
66 break;
67 case 'v':
68 verbose++;
69 break;
70 default:
71 usage();
72 }
73 }
74 argc -= optind;
75 argv += optind;
76 if (argc > 1)
77 osmtpd_errx(1, "invalid authservid count");
78 if (argc == 1)
79 authservid = argv[0];
81 osmtpd_local_message(admd_message_new, admd_message_free);
82 osmtpd_register_filter_dataline(admd_dataline);
83 osmtpd_register_filter_commit(admd_commit);
84 osmtpd_register_conf(admd_conf);
85 osmtpd_run();
87 return 0;
88 }
90 void
91 admd_conf(const char *key, const char *value)
92 {
93 if (key == NULL) {
94 if (authservid == NULL)
95 osmtpd_errx(1, "Didn't receive admd config option");
96 return;
97 }
98 if (strcmp(key, "admd") == 0 && authservid == NULL) {
99 if ((authservid = strdup(value)) == NULL)
100 osmtpd_err(1, "malloc");
104 void *
105 admd_message_new(struct osmtpd_ctx *ctx)
107 struct admd_message *msg;
109 if ((msg = malloc(sizeof(*msg))) == NULL)
110 osmtpd_err(1, "malloc");
112 msg->foundmatch = 0;
113 msg->err = 0;
114 msg->inheader = 0;
115 msg->parsing_headers = 1;
116 msg->cache = NULL;
117 msg->cachelen = 0;
118 msg->headerlen = 0;
120 return msg;
123 void
124 admd_message_free(struct osmtpd_ctx *ctx, void *data)
126 struct admd_message *msg = data;
128 admd_freecache(msg);
129 free(msg);
132 void
133 admd_dataline(struct osmtpd_ctx *ctx, const char *orig)
135 struct admd_message *msg = ctx->local_message;
136 const char *line = orig;
137 const char *msgauthid;
138 size_t i;
140 if (msg->err) {
141 if (line[0] == '.' && line[1] =='\0')
142 osmtpd_filter_dataline(ctx, ".");
143 return;
146 if (line[0] == '\0')
147 msg->parsing_headers = 0;
148 if (line[0] == '.')
149 line++;
150 if (msg->parsing_headers) {
151 if (line[0] != ' ' && line[0] != '\t') {
152 if (msg->inheader) {
153 msgauthid = admd_authservid(msg);
154 if (msgauthid == NULL && errno != EINVAL)
155 return;
156 if (msgauthid != NULL &&
157 strcmp(msgauthid, authservid) == 0)
158 msg->foundmatch = 1;
159 else {
160 for (i = 0; i < msg->cachelen; i++)
161 osmtpd_filter_dataline(ctx,
162 "%s", msg->cache[i]);
164 admd_freecache(msg);
166 msg->inheader = 0;
168 if (strncasecmp(line, "Authentication-Results", 22) == 0) {
169 line += 22;
170 while (line[0] == ' ' || line[0] == '\t')
171 line++;
172 if (line++[0] == ':') {
173 msg->inheader = 1;
174 admd_cache(msg, orig);
175 return;
177 } else if (msg->inheader &&
178 (line[0] == ' ' || line[0] == '\t')) {
179 admd_cache(msg, orig);
180 return;
184 osmtpd_filter_dataline(ctx, "%s", orig);
185 return;
188 void
189 admd_commit(struct osmtpd_ctx *ctx)
191 struct admd_message *msg = ctx->local_message;
193 if (msg->err) {
194 osmtpd_filter_disconnect(ctx, "Internal server error");
195 return;
197 if (reject && msg->foundmatch) {
198 osmtpd_filter_disconnect(ctx, "Message contains "
199 "Authentication-Results header for authserv-id '%s'",
200 authservid);
201 fprintf(stderr, "%016"PRIx64" Message contains "
202 "Authentication-Results header for authserv-id '%s': "
203 "rejected\n", ctx->reqid, authservid);
204 return;
207 osmtpd_filter_proceed(ctx);
208 if (msg->foundmatch) {
209 fprintf(stderr, "%016"PRIx64" Message contains "
210 "Authentication-Results header for authserv-id '%s': "
211 "filtered\n", ctx->reqid, authservid);
212 } else if (verbose)
213 fprintf(stderr, "%016"PRIx64" Message contains no "
214 "Authentication-Results header for authserv-id '%s'\n",
215 ctx->reqid, authservid);
218 void
219 admd_err(struct admd_message *message, const char *msg)
221 message->err = 1;
222 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
225 void
226 admd_cache(struct admd_message *msg, const char *line)
228 char **tcache;
230 if ((tcache = reallocarray(msg->cache, msg->cachelen + 1,
231 sizeof(*(msg->cache)))) == NULL) {
232 admd_freecache(msg);
233 admd_err(msg, "malloc");
235 msg->cache = tcache;
236 msg->cache[msg->cachelen] = strdup(line);
237 if (msg->cache[msg->cachelen] == NULL) {
238 admd_freecache(msg);
239 admd_err(msg, "strdup");
241 msg->cachelen++;
242 msg->headerlen += strlen(line[0] == '.' ? line + 1 : line);
243 return;
246 const char *
247 admd_authservid(struct admd_message *msg)
249 char *header0, *header, *line, *end;
250 size_t headerlen;
251 size_t i = 0;
253 headerlen = msg->headerlen + (msg->cachelen * 2) + 1;
254 header0 = header = malloc(headerlen);
255 if (header == NULL) {
256 admd_err(msg, "malloc");
257 return NULL;
259 header[0] = '\0';
260 for (i = 0; i < msg->cachelen; i++) {
261 line = msg->cache[i];
262 if (line[0] == '.')
263 line++;
264 if (strlcat(header, line, headerlen) >= headerlen ||
265 strlcat(header, "\r\n", headerlen) >= headerlen) {
266 osmtpd_errx(1, "miscalculated header\n");
267 exit(1);
271 /* Skip key */
272 header += 22;
273 while (header[0] == ' ' || header[0] == '\t')
274 header++;
275 /* : */
276 header++;
278 header = osmtpd_mheader_skip_cfws(header, 1);
280 if ((end = osmtpd_mheader_skip_value(header, 0)) == NULL) {
281 errno = EINVAL;
282 free(header0);
283 return NULL;
285 memmove(header0, header, end - header);
286 header0[end - header] = '\0';
288 return header0;
291 void
292 admd_freecache(struct admd_message *msg)
294 while (msg->cachelen > 0) {
295 msg->cachelen--;
296 free(msg->cache[msg->cachelen]);
298 free(msg->cache);
299 msg->cache = NULL;
300 msg->cachelen = 0;
301 msg->headerlen = 0;
304 __dead void
305 usage(void)
307 fprintf(stderr, "usage: filter-admdscrub [-rv] [-a authserv-id]\n");
308 exit(1);