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_message_new(struct osmtpd_ctx *);
40 void admd_message_free(struct osmtpd_ctx *, void *);
41 void admd_dataline(struct osmtpd_ctx *, const char *);
42 void admd_commit(struct osmtpd_ctx *);
43 void admd_err(struct admd_message *, const char *);
44 void admd_cache(struct admd_message *, const char *);
45 const char *admd_authservid(struct admd_message *);
46 void admd_freecache(struct admd_message *);
48 char authservid[256];
49 int reject = 0;
50 int verbose = 0;
52 int
53 main(int argc, char *argv[])
54 {
55 int ch;
57 if (pledge("stdio", NULL) == -1)
58 osmtpd_err(1, "pledge");
60 while ((ch = getopt(argc, argv, "rv")) != -1) {
61 switch (ch) {
62 case 'r':
63 reject = 1;
64 break;
65 case 'v':
66 verbose++;
67 break;
68 default:
69 usage();
70 }
71 }
72 argc -= optind;
73 argv += optind;
74 if (argc > 1)
75 osmtpd_errx(1, "invalid authservid count");
76 if (argc == 1) {
77 if (strlcpy(authservid, argv[0], sizeof(authservid)) >=
78 sizeof(authservid))
79 osmtpd_errx(1, "authserv-id is too long");
80 } else {
81 if (gethostname(authservid, sizeof(authservid)) == -1)
82 osmtpd_err(1, "gethostname");
83 }
84 if (strchr(authservid, '\r') != NULL ||
85 strchr(authservid, '\n') != NULL)
86 osmtpd_errx(1, "ubsupported character in authserv-id");
88 osmtpd_local_message(admd_message_new, admd_message_free);
89 osmtpd_register_filter_dataline(admd_dataline);
90 osmtpd_register_filter_commit(admd_commit);
91 osmtpd_run();
93 return 0;
94 }
96 void *
97 admd_message_new(struct osmtpd_ctx *ctx)
98 {
99 struct admd_message *msg;
101 if ((msg = malloc(sizeof(*msg))) == NULL)
102 osmtpd_err(1, "malloc");
104 msg->foundmatch = 0;
105 msg->err = 0;
106 msg->inheader = 0;
107 msg->parsing_headers = 1;
108 msg->cache = NULL;
109 msg->cachelen = 0;
110 msg->headerlen = 0;
112 return msg;
115 void
116 admd_message_free(struct osmtpd_ctx *ctx, void *data)
118 struct admd_message *msg = data;
120 admd_freecache(msg);
121 free(msg);
124 void
125 admd_dataline(struct osmtpd_ctx *ctx, const char *orig)
127 struct admd_message *msg = ctx->local_message;
128 const char *line = orig;
129 const char *msgauthid;
130 size_t i;
132 if (msg->err) {
133 if (line[0] == '.' && line[1] =='\0')
134 osmtpd_filter_dataline(ctx, ".");
135 return;
138 if (line[0] == '\0')
139 msg->parsing_headers = 0;
140 if (line[0] == '.')
141 line++;
142 if (msg->parsing_headers) {
143 if (line[0] != ' ' && line[0] != '\t') {
144 if (msg->inheader) {
145 msgauthid = admd_authservid(msg);
146 if (msgauthid == NULL && errno != EINVAL)
147 return;
148 if (msgauthid != NULL &&
149 strcmp(msgauthid, authservid) == 0)
150 msg->foundmatch = 1;
151 else {
152 for (i = 0; i < msg->cachelen; i++)
153 osmtpd_filter_dataline(ctx,
154 "%s", msg->cache[i]);
156 admd_freecache(msg);
158 msg->inheader = 0;
160 if (strncasecmp(line, "Authentication-Results", 22) == 0) {
161 line += 22;
162 while (line[0] == ' ' || line[0] == '\t')
163 line++;
164 if (line++[0] == ':') {
165 msg->inheader = 1;
166 admd_cache(msg, orig);
167 return;
169 } else if (msg->inheader &&
170 (line[0] == ' ' || line[0] == '\t')) {
171 admd_cache(msg, orig);
172 return;
176 osmtpd_filter_dataline(ctx, "%s", orig);
177 return;
180 void
181 admd_commit(struct osmtpd_ctx *ctx)
183 struct admd_message *msg = ctx->local_message;
185 if (msg->err) {
186 osmtpd_filter_disconnect(ctx, "Internal server error");
187 return;
189 if (reject && msg->foundmatch) {
190 osmtpd_filter_disconnect(ctx, "Message contains "
191 "Authentication-Results header for authserv-id '%s'",
192 authservid);
193 fprintf(stderr, "%016"PRIx64" Message contains "
194 "Authentication-Results header for authserv-id '%s': "
195 "rejected\n", ctx->reqid, authservid);
196 return;
199 osmtpd_filter_proceed(ctx);
200 if (msg->foundmatch) {
201 fprintf(stderr, "%016"PRIx64" Message contains "
202 "Authentication-Results header for authserv-id '%s': "
203 "filtered\n", ctx->reqid, authservid);
204 } else if (verbose)
205 fprintf(stderr, "%016"PRIx64" Message contains no "
206 "Authentication-Results header for authserv-id '%s'\n",
207 ctx->reqid, authservid);
210 void
211 admd_err(struct admd_message *message, const char *msg)
213 message->err = 1;
214 fprintf(stderr, "%s: %s\n", msg, strerror(errno));
217 void
218 admd_cache(struct admd_message *msg, const char *line)
220 char **tcache;
222 if ((tcache = reallocarray(msg->cache, msg->cachelen + 1,
223 sizeof(*(msg->cache)))) == NULL) {
224 admd_freecache(msg);
225 admd_err(msg, "malloc");
227 msg->cache = tcache;
228 msg->cache[msg->cachelen] = strdup(line);
229 if (msg->cache[msg->cachelen] == NULL) {
230 admd_freecache(msg);
231 admd_err(msg, "strdup");
233 msg->cachelen++;
234 msg->headerlen += strlen(line[0] == '.' ? line + 1 : line);
235 return;
238 const char *
239 admd_authservid(struct admd_message *msg)
241 char *header0, *header, *line, *end;
242 size_t headerlen;
243 size_t i = 0;
245 headerlen = msg->headerlen + (msg->cachelen * 2) + 1;
246 header0 = header = malloc(headerlen);
247 if (header == NULL) {
248 admd_err(msg, "malloc");
249 return NULL;
251 header[0] = '\0';
252 for (i = 0; i < msg->cachelen; i++) {
253 line = msg->cache[i];
254 if (line[0] == '.')
255 line++;
256 if (strlcat(header, line, headerlen) >= headerlen ||
257 strlcat(header, "\r\n", headerlen) >= headerlen) {
258 osmtpd_errx(1, "miscalculated header\n");
259 exit(1);
263 /* Skip key */
264 header += 22;
265 while (header[0] == ' ' || header[0] == '\t')
266 header++;
267 /* : */
268 header++;
270 header = osmtpd_mheader_skip_cfws(header, 1);
272 if ((end = osmtpd_mheader_skip_value(header, 0)) == NULL) {
273 errno = EINVAL;
274 free(header0);
275 return NULL;
277 memmove(header0, header, end - header);
278 header0[end - header] = '\0';
280 return header0;
283 void
284 admd_freecache(struct admd_message *msg)
286 while (msg->cachelen > 0) {
287 msg->cachelen--;
288 free(msg->cache[msg->cachelen]);
290 free(msg->cache);
291 msg->cache = NULL;
292 msg->cachelen = 0;
293 msg->headerlen = 0;
296 __dead void
297 usage(void)
299 fprintf(stderr, "usage: filter-admdscrub [-rv] [-a authserv-id]\n");
300 exit(1);