commit e9e8d2076ab3dc31d76955739dc0afb9f32888c0 from: Martijn van Duren date: Sat Jul 25 10:41:31 2020 UTC Import, should be basically done commit - /dev/null commit + e9e8d2076ab3dc31d76955739dc0afb9f32888c0 blob - /dev/null blob + eaa019c9175a22f22b80db9c87f1b9a4854d5407 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,23 @@ +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 blob - /dev/null blob + e419f50f5abc33db2762639e83baa6c79b509bd5 (mode 644) --- /dev/null +++ filter-admdscrub.8 @@ -0,0 +1,54 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2019 Martijn van Duren +.\" +.\" 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 @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2019 Martijn van Duren + * + * 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 +#include +#include +#include +#include +#include +#include + +#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); +}