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"
26 #include "mheader.h"
28 struct admd_message {
29 int foundmatch;
30 int err;
31 int inheader;
32 int parsing_headers;
33 char **cache;
34 size_t cachelen;
35 size_t headerlen;
36 };
38 void usage(void);
39 void admd_conf(const char *, const char *);
40 void *admd_message_new(struct osmtpd_ctx *);
41 void admd_message_free(struct osmtpd_ctx *, void *);
42 void admd_dataline(struct osmtpd_ctx *, const char *);
43 void admd_commit(struct osmtpd_ctx *);
44 void admd_err(struct admd_message *, const char *);
45 void admd_cache(struct admd_message *, const char *);
46 const char *admd_authservid(struct admd_message *);
47 void admd_freecache(struct admd_message *);
49 char *authservid;
50 int reject = 0;
51 int verbose = 0;
53 int
54 main(int argc, char *argv[])
55 {
56 int ch;
58 if (pledge("stdio", NULL) == -1)
59 osmtpd_err(1, "pledge");
61 while ((ch = getopt(argc, argv, "rv")) != -1) {
62 switch (ch) {
63 case 'r':
64 reject = 1;
65 break;
66 case 'v':
67 verbose++;
68 break;
69 default:
70 usage();
71 }
72 }
73 argc -= optind;
74 argv += optind;
75 if (argc > 1)
76 osmtpd_errx(1, "invalid authservid count");
77 if (argc == 1)
78 authservid = argv[0];
80 osmtpd_local_message(admd_message_new, admd_message_free);
81 osmtpd_register_filter_dataline(admd_dataline);
82 osmtpd_register_filter_commit(admd_commit);
83 osmtpd_register_conf(admd_conf);
84 osmtpd_run();
86 return 0;
87 }
89 void
90 admd_conf(const char *key, const char *value)
91 {
92 if (key == NULL) {
93 if (authservid == NULL)
94 osmtpd_errx(1, "Didn't receieve admd config option");
95 return;
96 }
97 if (strcmp(key, "admd") == 0 && authservid == NULL) {
98 if ((authservid = strdup(value)) == NULL)
99 osmtpd_err(1, "malloc");
103 void *
104 admd_message_new(struct osmtpd_ctx *ctx)
106 struct admd_message *msg;
108 if ((msg = malloc(sizeof(*msg))) == NULL)
109 osmtpd_err(1, "malloc");
111 msg->foundmatch = 0;
112 msg->err = 0;
113 msg->inheader = 0;
114 msg->parsing_headers = 1;
115 msg->cache = NULL;
116 msg->cachelen = 0;
117 msg->headerlen = 0;
119 return msg;
122 void
123 admd_message_free(struct osmtpd_ctx *ctx, void *data)
125 struct admd_message *msg = data;
127 admd_freecache(msg);
128 free(msg);
131 void
132 admd_dataline(struct osmtpd_ctx *ctx, const char *orig)
134 struct admd_message *msg = ctx->local_message;
135 const char *line = orig;
136 const char *msgauthid;
137 size_t i;
139 if (msg->err) {
140 if (line[0] == '.' && line[1] =='\0')
141 osmtpd_filter_dataline(ctx, ".");
142 return;
145 if (line[0] == '\0')
146 msg->parsing_headers = 0;
147 if (line[0] == '.')
148 line++;
149 if (msg->parsing_headers) {
150 if (line[0] != ' ' && line[0] != '\t') {
151 if (msg->inheader) {
152 msgauthid = admd_authservid(msg);
153 if (msgauthid == NULL && errno != EINVAL)
154 return;
155 if (msgauthid != NULL &&
156 strcmp(msgauthid, authservid) == 0)
157 msg->foundmatch = 1;
158 else {
159 for (i = 0; i < msg->cachelen; i++)
160 osmtpd_filter_dataline(ctx,
161 "%s", msg->cache[i]);
163 admd_freecache(msg);
165 msg->inheader = 0;
167 if (strncasecmp(line, "Authentication-Results", 22) == 0) {
168 line += 22;
169 while (line[0] == ' ' || line[0] == '\t')
170 line++;
171 if (line++[0] == ':') {
172 msg->inheader = 1;
173 admd_cache(msg, orig);
174 return;
176 } else if (msg->inheader &&
177 (line[0] == ' ' || line[0] == '\t')) {
178 admd_cache(msg, orig);
179 return;
183 osmtpd_filter_dataline(ctx, "%s", orig);
184 return;
187 void
188 admd_commit(struct osmtpd_ctx *ctx)
190 struct admd_message *msg = ctx->local_message;
192 if (msg->err) {
193 osmtpd_filter_disconnect(ctx, "Internal server error");
194 return;
196 if (reject && msg->foundmatch) {
197 osmtpd_filter_disconnect(ctx, "Message contains "
198 "Authentication-Results header for authserv-id '%s'",
199 authservid);
200 fprintf(stderr, "%016"PRIx64" Message contains "
201 "Authentication-Results header for authserv-id '%s': "
202 "rejected\n", ctx->reqid, authservid);
203 return;
206 osmtpd_filter_proceed(ctx);
207 if (msg->foundmatch) {
208 fprintf(stderr, "%016"PRIx64" Message contains "
209 "Authentication-Results header for authserv-id '%s': "
210 "filtered\n", ctx->reqid, authservid);
211 } else if (verbose)
212 fprintf(stderr, "%016"PRIx64" Message contains no "
213 "Authentication-Results header for authserv-id '%s'\n",
214 ctx->reqid, authservid);
217 void
218 admd_err(struct admd_message *message, const char *msg)
220 message->err = 1;
221 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
224 void
225 admd_cache(struct admd_message *msg, const char *line)
227 char **tcache;
229 if ((tcache = reallocarray(msg->cache, msg->cachelen + 1,
230 sizeof(*(msg->cache)))) == NULL) {
231 admd_freecache(msg);
232 admd_err(msg, "malloc");
234 msg->cache = tcache;
235 msg->cache[msg->cachelen] = strdup(line);
236 if (msg->cache[msg->cachelen] == NULL) {
237 admd_freecache(msg);
238 admd_err(msg, "strdup");
240 msg->cachelen++;
241 msg->headerlen += strlen(line[0] == '.' ? line + 1 : line);
242 return;
245 const char *
246 admd_authservid(struct admd_message *msg)
248 char *header0, *header, *line, *end;
249 size_t headerlen;
250 size_t i = 0;
252 headerlen = msg->headerlen + (msg->cachelen * 2) + 1;
253 header0 = header = malloc(headerlen);
254 if (header == NULL) {
255 admd_err(msg, "malloc");
256 return NULL;
258 header[0] = '\0';
259 for (i = 0; i < msg->cachelen; i++) {
260 line = msg->cache[i];
261 if (line[0] == '.')
262 line++;
263 if (strlcat(header, line, headerlen) >= headerlen ||
264 strlcat(header, "\r\n", headerlen) >= headerlen) {
265 osmtpd_errx(1, "miscalculated header\n");
266 exit(1);
270 /* Skip key */
271 header += 22;
272 while (header[0] == ' ' || header[0] == '\t')
273 header++;
274 /* : */
275 header++;
277 header = osmtpd_mheader_skip_cfws(header, 1);
279 if ((end = osmtpd_mheader_skip_value(header, 0)) == NULL) {
280 errno = EINVAL;
281 free(header0);
282 return NULL;
284 memmove(header0, header, end - header);
285 header0[end - header] = '\0';
287 return header0;
290 void
291 admd_freecache(struct admd_message *msg)
293 while (msg->cachelen > 0) {
294 msg->cachelen--;
295 free(msg->cache[msg->cachelen]);
297 free(msg->cache);
298 msg->cache = NULL;
299 msg->cachelen = 0;
300 msg->headerlen = 0;
303 __dead void
304 usage(void)
306 fprintf(stderr, "usage: filter-admdscrub [-rv] [-a authserv-id]\n");
307 exit(1);