commit - /dev/null
commit + e9e8d2076ab3dc31d76955739dc0afb9f32888c0
blob - /dev/null
blob + eaa019c9175a22f22b80db9c87f1b9a4854d5407 (mode 644)
--- /dev/null
+++ Makefile
+LOCALBASE?= /usr/local/
+
+PROG= filter-admdscrub
+MAN= filter-admdscrub.8
+BINDIR= ${LOCALBASE}/libexec/smtpd/
+MANDIR= ${LOCALBASE}/man/man
+
+SRCS+= main.c
+
+CFLAGS+=-I${LOCALBASE}/include
+CFLAGS+=-Wall -I${.CURDIR}
+CFLAGS+=-Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=-Wmissing-declarations
+CFLAGS+=-Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+=-Wsign-compare
+LDFLAGS+=-L${LOCALBASE}/lib
+LDADD+= -levent -lopensmtpd
+DPADD= ${LIBEVENT}
+
+bindir:
+ ${INSTALL} -d ${DESTDIR}${BINDIR}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + e419f50f5abc33db2762639e83baa6c79b509bd5 (mode 644)
--- /dev/null
+++ filter-admdscrub.8
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate$
+.Dt FILTER-ADMDSCRUB 8
+.Os
+.Sh NAME
+.Nm filter-admdscrub
+.Nd Administrative Management Domain scrubber.
+.Sh SYNOPSIS
+.Nm
+.Op Fl rv
+.Op Fl a Ar authserv-id
+.Sh DESCRIPTION
+.Nm
+checks the mail for
+.Ql Authentication-Results
+headers in the requested
+.Ar authserv-id
+domain and removes them.
+This filter is intended to be used as the first filter at a border MTA.
+This should prevent utilization of these headers in further filters or MTAs when
+they don't originate from within it's own trusted domain.
+The
+.Fl r
+flag can be set to reject mails containing these headers.
+If
+.Ar authserv-id
+is not specified it defaults to the system hostname.
+For more verbose logging the
+.Fl v
+flag can be used.
+.Sh SEE ALSO
+.Xr smtpd 8
+.Sh STANDARDS
+.Rs
+.%A M. Kucherawy
+.%D May 2019
+.%R RFC 8601
+.%T Message Header Field for Indicating Message Authentication Status
+.Re
blob - /dev/null
blob + 2bd64da3091101695ecc4fdf97d4c70f4546d557 (mode 644)
--- /dev/null
+++ main.c
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "opensmtpd.h"
+
+struct admd_message {
+ int foundmatch;
+ int err;
+ int inheader;
+ int parsing_headers;
+ char **cache;
+ size_t cachelen;
+};
+
+void usage(void);
+void *admd_message_new(struct osmtpd_ctx *);
+void admd_message_free(struct osmtpd_ctx *, void *);
+void admd_dataline(struct osmtpd_ctx *, const char *);
+void admd_commit(struct osmtpd_ctx *);
+void admd_err(struct admd_message *, const char *);
+void admd_cache(struct admd_message *, const char *);
+const char *admd_authservid(struct admd_message *);
+void admd_freecache(struct admd_message *);
+
+char authservid[256] = "";
+int reject = 0;
+int verbose = 0;
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+
+ while ((ch = getopt(argc, argv, "a:rv")) != -1) {
+ switch (ch) {
+ case 'a':
+ if (strlcpy(authservid, optarg, sizeof(authservid)) >=
+ sizeof(authservid))
+ osmtpd_errx(1, "authserv-id is too long");
+ break;
+ case 'r':
+ reject = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (pledge("stdio", NULL) == -1)
+ osmtpd_err(1, "pledge");
+
+ if (authservid[0] == '\0') {
+ if (gethostname(authservid, sizeof(authservid)) == -1)
+ osmtpd_err(1, "gethostname");
+ }
+ if (strchr(authservid, '\r') != NULL ||
+ strchr(authservid, '\n') != NULL)
+ osmtpd_errx(1, "ubsupported character in authserv-id");
+
+ osmtpd_local_message(admd_message_new, admd_message_free);
+ osmtpd_register_filter_dataline(admd_dataline);
+ osmtpd_register_filter_commit(admd_commit);
+ osmtpd_run();
+
+ return 0;
+}
+
+void *
+admd_message_new(struct osmtpd_ctx *ctx)
+{
+ struct admd_message *msg;
+
+ if ((msg = malloc(sizeof(*msg))) == NULL)
+ osmtpd_err(1, "malloc");
+
+ msg->foundmatch = 0;
+ msg->err = 0;
+ msg->inheader = 0;
+ msg->parsing_headers = 1;
+ msg->cache = NULL;
+ msg->cachelen = 0;
+
+ return msg;
+}
+
+void
+admd_message_free(struct osmtpd_ctx *ctx, void *data)
+{
+ struct admd_message *msg = data;
+
+ admd_freecache(msg);
+ free(msg);
+}
+
+void
+admd_dataline(struct osmtpd_ctx *ctx, const char *orig)
+{
+ struct admd_message *msg = ctx->local_message;
+ const char *line = orig;
+ const char *msgauthid;
+ size_t i;
+
+ if (msg->err) {
+ if (line[0] == '.' && line[1] =='\0')
+ osmtpd_filter_dataline(ctx, ".");
+ return;
+ }
+
+ if (line[0] == '\0')
+ msg->parsing_headers = 0;
+ if (line[0] == '.')
+ line++;
+ if (msg->parsing_headers) {
+ if (line[0] != ' ' && line[0] != '\t') {
+ if (msg->inheader) {
+ msgauthid = admd_authservid(msg);
+ if (strcmp(msgauthid, authservid) == 0)
+ msg->foundmatch = 1;
+ else {
+ for (i = 0; i < msg->cachelen; i++)
+ osmtpd_filter_dataline(ctx,
+ "%s", msg->cache[i]);
+ }
+ admd_freecache(msg);
+ }
+ msg->inheader = 0;
+ }
+ if (strncmp(line, "Authentication-Results:", 23) == 0) {
+ msg->inheader = 1;
+ admd_cache(msg, orig);
+ return;
+ }
+ if (msg->inheader && (line[0] == ' ' || line[0] == '\t')) {
+ admd_cache(msg, orig);
+ return;
+ }
+ }
+
+ osmtpd_filter_dataline(ctx, "%s", orig);
+ return;
+}
+
+void
+admd_commit(struct osmtpd_ctx *ctx)
+{
+ struct admd_message *msg = ctx->local_message;
+
+ if (msg->err) {
+ osmtpd_filter_disconnect(ctx, "Internal server error");
+ return;
+ }
+ if (reject && msg->foundmatch) {
+ osmtpd_filter_disconnect(ctx, "Message contains "
+ "Authentication-Results header for authserv-id '%s'",
+ authservid);
+ fprintf(stderr, "%016"PRIx64" Message contains "
+ "Authentication-Results header for authserv-id '%s': "
+ "rejected\n", ctx->reqid, authservid);
+ return;
+ }
+
+ osmtpd_filter_proceed(ctx);
+ if (msg->foundmatch) {
+ fprintf(stderr, "%016"PRIx64" Message contains "
+ "Authentication-Results header for authserv-id '%s': "
+ "filtered\n", ctx->reqid, authservid);
+ } else if (verbose)
+ fprintf(stderr, "%016"PRIx64" Message contains no "
+ "Authentication-Results header for authserv-id '%s'\n",
+ ctx->reqid, authservid);
+}
+
+void
+admd_err(struct admd_message *message, const char *msg)
+{
+ message->err = 1;
+ fprintf(stderr, "%s: %s\n", msg, strerror(errno));
+}
+
+void
+admd_cache(struct admd_message *msg, const char *line)
+{
+ char **tcache;
+
+ if ((tcache = reallocarray(msg->cache, msg->cachelen + 1,
+ sizeof(*(msg->cache)))) == NULL) {
+ admd_freecache(msg);
+ admd_err(msg, "malloc");
+ }
+ msg->cache = tcache;
+ msg->cache[msg->cachelen] = strdup(line);
+ if (msg->cache[msg->cachelen] == NULL) {
+ admd_freecache(msg);
+ admd_err(msg, "strdup");
+ }
+ msg->cachelen++;
+ return;
+}
+
+const char *
+admd_authservid(struct admd_message *msg)
+{
+ static char msgauthid[sizeof(authservid)];
+ const char *header;
+ size_t i = 0;
+ int depth = 0;
+
+ msgauthid[0] = '\0';
+
+ header = msg->cache[0];
+
+ if (header[0] == '.')
+ header++;
+
+ /* Skip key */
+ header += 23;
+
+ /* CFWS */
+ /*
+ * Take the extremely loose approach with both FWS and comment so we
+ * might match a non fully complient comment and still get the right
+ * authserv-id
+ */
+fws:
+ while (header[0] == ' ' || header[0] == '\t')
+ header++;
+ if (header[0] == '\0') {
+ if (++i >= msg->cachelen)
+ return msgauthid;
+ header = msg->cache[i];
+ /* For leniency allow multiple consequtive FWS */
+ goto fws;
+ }
+ /* comment */
+ if (header[0] == '(') {
+ depth++;
+ header++;
+ }
+ if (depth > 0) {
+ while (1) {
+ /*
+ * consume a full quoted-pair, which may contain
+ * parentheses
+ */
+ if (header[0] == '"') {
+ header++;
+ while (header[0] != '"') {
+ if (header[0] == '\\')
+ header++;
+ if (header[0] == '\0') {
+ if (++i >= msg->cachelen) {
+ return msgauthid;
+ }
+ header = msg->cache[i];
+ } else
+ header++;
+ }
+ header++;
+ /* End of comment */
+ } else if (header[0] == ')') {
+ header++;
+ if (--depth == 0)
+ goto fws;
+ } else if (header[0] == '(') {
+ header++;
+ depth++;
+ } else if (header[0] == '\0') {
+ if (++i >= msg->cachelen)
+ return msgauthid;
+ header = msg->cache[i];
+ } else
+ header++;
+ }
+ }
+ /* Quoted-string */
+ if (header[0] == '"') {
+ header++;
+ for (i = 0; header[0] != '"' && header[0] != '\0' &&
+ i < sizeof(msgauthid); i++, header++) {
+ if (header[0] == '\\')
+ header++;
+ /* Don't do Newline at all */
+ if (header[0] == '\0') {
+ i = 0;
+ break;
+ }
+ msgauthid[i] = header[0];
+ }
+ /* token */
+ } else {
+ /*
+ * Be more lenient towards token to hit more
+ * edgecases
+ */
+ for (i = 0; header[i] != ' ' && header[i] != '\t' &&
+ header[i] != ';' && header[i] != '\0' &&
+ i < sizeof(msgauthid); i++)
+ msgauthid[i] = header[i];
+ }
+ /* If we overflow we simply don't match */
+ if (i == sizeof(msgauthid))
+ i = 0;
+ msgauthid[i] = '\0';
+ return msgauthid;
+}
+
+void
+admd_freecache(struct admd_message *msg)
+{
+ while (msg->cachelen > 0) {
+ msg->cachelen--;
+ free(msg->cache[msg->cachelen]);
+ }
+ free(msg->cache);
+ msg->cache = NULL;
+ msg->cachelen = 0;
+}
+
+__dead void
+usage(void)
+{
+ fprintf(stderr, "usage: filter-admdscrub [-rv] [-a authserv-id]\n");
+ exit(1);
+}