Blame


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