Commit Diff


commit - e9e8d2076ab3dc31d76955739dc0afb9f32888c0
commit + 4f29629bac17e2bc8fec298499946fd7518d249e
blob - eaa019c9175a22f22b80db9c87f1b9a4854d5407
blob + b1de9767ec30f0862521244246bb8070080eaf9c
--- Makefile
+++ Makefile
@@ -5,7 +5,7 @@ MAN=	filter-admdscrub.8
 BINDIR=	${LOCALBASE}/libexec/smtpd/
 MANDIR=	${LOCALBASE}/man/man
 
-SRCS+=	main.c
+SRCS+=	main.c mheader.c
 
 CFLAGS+=-I${LOCALBASE}/include
 CFLAGS+=-Wall -I${.CURDIR}
blob - e419f50f5abc33db2762639e83baa6c79b509bd5
blob + 3bdfa0acc2e91c50c8f38f9078d9efa9a81251d9
--- filter-admdscrub.8
+++ filter-admdscrub.8
@@ -23,7 +23,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl rv
-.Op Fl a Ar authserv-id
+.Op Ar authserv-id
 .Sh DESCRIPTION
 .Nm
 checks the mail for
blob - 2bd64da3091101695ecc4fdf97d4c70f4546d557
blob + 1f776bcb78fb44859e4b8b7ad9b91cefa9e09c8a
--- main.c
+++ main.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 
 #include "opensmtpd.h"
+#include "mheader.h"
 
 struct admd_message {
 	int foundmatch;
@@ -31,6 +32,7 @@ struct admd_message {
 	int parsing_headers;
 	char **cache;
 	size_t cachelen;
+	size_t headerlen;
 };
 
 void usage(void);
@@ -43,7 +45,7 @@ void admd_cache(struct admd_message *, const char *);
 const char *admd_authservid(struct admd_message *);
 void admd_freecache(struct admd_message *);
 
-char authservid[256] = "";
+char authservid[256];
 int reject = 0;
 int verbose = 0;
 
@@ -52,13 +54,11 @@ main(int argc, char *argv[])
 {
 	int ch;
 
-	while ((ch = getopt(argc, argv, "a:rv")) != -1) {
+	if (pledge("stdio", NULL) == -1)
+		osmtpd_err(1, "pledge");
+
+	while ((ch = getopt(argc, argv, "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;
@@ -69,11 +69,15 @@ main(int argc, char *argv[])
 			usage();
 		}
 	}
-
-	if (pledge("stdio", NULL) == -1)
-		osmtpd_err(1, "pledge");
-
-	if (authservid[0] == '\0') {
+	argc -= optind;
+	argv += optind;
+	if (argc > 1)
+		osmtpd_errx(1, "invalid authservid count");
+	if (argc == 1) {
+		if (strlcpy(authservid, argv[0], sizeof(authservid)) >=
+		    sizeof(authservid))
+			osmtpd_errx(1, "authserv-id is too long");
+	} else {
 		if (gethostname(authservid, sizeof(authservid)) == -1)
 			osmtpd_err(1, "gethostname");
 	}
@@ -103,6 +107,7 @@ admd_message_new(struct osmtpd_ctx *ctx)
 	msg->parsing_headers = 1;
 	msg->cache = NULL;
 	msg->cachelen = 0;
+	msg->headerlen = 0;
 
 	return msg;
 }
@@ -138,7 +143,10 @@ admd_dataline(struct osmtpd_ctx *ctx, const char *orig
 		if (line[0] != ' ' && line[0] != '\t') {
 			if (msg->inheader) {
 				msgauthid = admd_authservid(msg);
-				if (strcmp(msgauthid, authservid) == 0)
+				if (msgauthid == NULL && errno != EINVAL)
+					return;
+				if (msgauthid != NULL &&
+				    strcmp(msgauthid, authservid) == 0)
 					msg->foundmatch = 1;
 				else {
 					for (i = 0; i < msg->cachelen; i++)
@@ -149,12 +157,17 @@ admd_dataline(struct osmtpd_ctx *ctx, const char *orig
 			}
 			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')) {
+		if (strncasecmp(line, "Authentication-Results", 22) == 0) {
+			line += 22;
+			while (line[0] == ' ' || line[0] == '\t')
+				line++;
+			if (line++[0] == ':') {
+				msg->inheader = 1;
+				admd_cache(msg, orig);
+				return;
+			}
+		} else if (msg->inheader &&
+		    (line[0] == ' ' || line[0] == '\t')) {
 			admd_cache(msg, orig);
 			return;
 		}
@@ -218,114 +231,53 @@ admd_cache(struct admd_message *msg, const char *line)
 		admd_err(msg, "strdup");
 	}
 	msg->cachelen++;
+	msg->headerlen += strlen(line[0] == '.' ? line + 1 : line);
 	return;
 }
 
 const char *
 admd_authservid(struct admd_message *msg)
 {
-	static char msgauthid[sizeof(authservid)];
-	const char *header;
+	char *header0, *header, *line, *end;
+	size_t headerlen;
 	size_t i = 0;
-	int depth = 0;
 	
-	msgauthid[0] = '\0';
+	headerlen = msg->headerlen + (msg->cachelen * 2) + 1;
+	header0 = header = malloc(headerlen);
+	if (header == NULL) {
+		admd_err(msg, "malloc");
+		return NULL;
+	}
+	header[0] = '\0';
+	for (i = 0; i < msg->cachelen; i++) {
+		line = msg->cache[i];
+		if (line[0] == '.')
+			line++;
+		if (strlcat(header, line, headerlen) >= headerlen ||
+		    strlcat(header, "\r\n", headerlen) >= headerlen) {
+			osmtpd_errx(1, "miscalculated header\n");
+			exit(1);
+		}
+	}
 
-	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:
+	header += 22;
 	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;
+	/* : */
+	header++;
+
+	header = osmtpd_mheader_skip_cfws(header, 1);
+
+	if ((end = osmtpd_mheader_skip_value(header, 0)) == NULL) {
+		errno = EINVAL;
+		free(header0);
+		return NULL;
 	}
-	/* 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;
+	memmove(header0, header, end - header);
+	header0[end - header] = '\0';
+
+	return header0;
 }
 
 void
@@ -338,6 +290,7 @@ admd_freecache(struct admd_message *msg)
 	free(msg->cache);
 	msg->cache = NULL;
 	msg->cachelen = 0;
+	msg->headerlen = 0;
 }
 
 __dead void
blob - /dev/null
blob + 8831ab8037ae6aa8869bb840ff8f92295b0ee930 (mode 644)
--- /dev/null
+++ mheader.c
@@ -0,0 +1,747 @@
+/*
+ * Copyright (c) 2020 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 <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+#include <strings.h>
+
+#include "mheader.h"
+
+char *
+osmtpd_mheader_skip_sp(char *ptr, int optional)
+{
+	if (ptr[0] == 0x20)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_htab(char *ptr, int optional)
+{
+	if (ptr[0] == 0x9)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_wsp(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_sp(start, 0)) != NULL ||
+	    (ptr = osmtpd_mheader_skip_htab(start, 0)) != NULL)
+		return ptr;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_crlf(char *ptr, int optional)
+{
+	if (ptr[0] == 13 && ptr[1] == 10)
+		return ptr + 2;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_vchar(char *ptr, int optional)
+{
+	if (ptr[0] >= 0x21 && ptr[0] <= 0x7e)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_lf(char *ptr, int optional)
+{
+	if (ptr[0] == 0xa)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_cr(char *ptr, int optional)
+{
+	if (ptr[0] == 0xd)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_alpha(char *ptr, int optional)
+{
+	if ((ptr[0] >= 0x41 && ptr[0] <= 0x5a) ||
+	    (ptr[0] >= 0x61 && ptr[0] <= 0x7a))
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_digit(char *ptr, int optional)
+{
+	if (ptr[0] >= 0x30 && ptr[0] <= 0x39)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_dquote(char *ptr, int optional)
+{
+	if (ptr[0] == 0x22)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_char(char *ptr, int optional)
+{
+	if (ptr[0] >= 0x1 && ptr[0] <= 0x7f)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_ctl(char *ptr, int optional)
+{
+	if ((ptr[0] >= 0x0 && ptr[0] <= 0x1f) || ptr[0] == 0x7f)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_obs_fws(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) != NULL)
+		prev = ptr;
+
+	ptr = prev;
+	while (1) {
+		if ((ptr = osmtpd_mheader_skip_crlf(ptr, 0)) == NULL)
+			return prev;
+		if ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) == NULL)
+			return prev;
+		prev = ptr;
+		while ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) != NULL)
+			prev = ptr;
+		ptr = prev;
+	}
+}
+
+char *
+osmtpd_mheader_skip_fws(char *ptr, int optional)
+{
+	char *start = ptr, *prev = ptr;
+
+	while ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) != NULL)
+		prev = ptr;
+	if ((ptr = osmtpd_mheader_skip_crlf(prev, 1)) == prev)
+		ptr = start;
+	if ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) == NULL)
+		return osmtpd_mheader_skip_obs_fws(start, optional);
+	prev = ptr;
+	while ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) != NULL)
+		prev = ptr;
+	return prev;
+}
+
+char *
+osmtpd_mheader_skip_obs_no_ws_ctl(char *ptr, int optional)
+{
+	if ((ptr[0] >= 1 && ptr[0] <= 8) || ptr[0] == 11 || ptr[0] == 12 ||
+	    (ptr[0] >= 14 && ptr[0] <= 31) || ptr[0] == 127)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_obs_ctext(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_obs_no_ws_ctl(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_ctext(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr[0] >= 33 && ptr[0] <= 39) || (ptr[0] >= 42 && ptr[0] <= 91) ||
+	    (ptr[0] >= 93 && ptr[0] <= 126))
+		return ptr + 1;
+	if ((ptr = osmtpd_mheader_skip_obs_ctext(ptr, 0)) != NULL)
+		return ptr;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_obs_qp(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if (ptr[0] == '\\' && (
+	    (ptr = osmtpd_mheader_skip_obs_no_ws_ctl(start + 1, 0)) != NULL ||
+	    (ptr = osmtpd_mheader_skip_lf(start + 1, 0)) != NULL ||
+	    (ptr = osmtpd_mheader_skip_cr(start + 1, 0)) != NULL))
+		return ptr;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_quoted_pair(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if (ptr[0] == '\\' && (
+	    (ptr = osmtpd_mheader_skip_vchar(start + 1, 0)) != NULL ||
+	    (ptr = osmtpd_mheader_skip_wsp(start + 1, 0)) != NULL))
+		return ptr;
+	return osmtpd_mheader_skip_obs_qp(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_ccontent(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_ctext(ptr, 0)) != NULL)
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_quoted_pair(start, 0)) != NULL)
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_comment(start, 0)) != NULL)
+		return ptr;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_comment(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if (ptr++[0] != '(')
+		return optional ? start : NULL;
+	while (1) {
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if (ptr[0] == ')')
+			return ptr + 1;
+		if ((ptr = osmtpd_mheader_skip_ccontent(ptr, 0)) == NULL)
+			return optional ? start : NULL;
+	}
+}
+
+char *
+osmtpd_mheader_skip_cfws(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	while (1) {
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_comment(ptr, 0)) == NULL) {
+			ptr = prev;
+			break;
+		}
+	}
+	return ptr == start && !optional ? NULL : ptr;
+}
+
+char *
+osmtpd_mheader_skip_atext(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(start, 0)) != NULL ||
+	    (ptr = osmtpd_mheader_skip_digit(start, 0)) != NULL)
+		return ptr;
+	ptr = start;
+	if (ptr[0] == '!' || ptr[0] == '#' || ptr[0] == '$' || ptr[0] == '%' ||
+	    ptr[0] == '&' || ptr[0] == '\'' || ptr[0] == '*' || ptr[0] == '+' ||
+	    ptr[0] == '-' || ptr[0] == '/' || ptr[0] == '=' || ptr[0] == '?' ||
+	    ptr[0] == '^' || ptr[0] == '_' || ptr[0] == '`' || ptr[0] == '{' ||
+	    ptr[0] == '|' || ptr[0] == '}' || ptr[0] == '~')
+		return ptr + 1;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_atom(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_atext(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	do {
+		prev = ptr;
+		ptr = osmtpd_mheader_skip_atext(ptr, 1);
+	} while (prev != ptr);
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_dot_atom_text(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_atext(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	do {
+		prev = ptr;
+		ptr = osmtpd_mheader_skip_atext(ptr, 1);
+	} while (ptr != prev);
+
+	while (ptr[0] == '.') {
+		ptr++;
+		if ((ptr = osmtpd_mheader_skip_atext(ptr, 0)) == NULL)
+			return prev;
+		do {
+			prev = ptr;
+			ptr = osmtpd_mheader_skip_atext(ptr, 1);
+		} while (ptr != prev);
+	}
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_dot_atom(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_dot_atom_text(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+
+char *
+osmtpd_mheader_skip_obs_qtext(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_obs_no_ws_ctl(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_qtext(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if (ptr[0] == 33 || (ptr[0] >= 35 && ptr[0] <= 91) ||
+	    (ptr[0] >= 93 && ptr[0] <= 126))
+		return ptr + 1;
+	if ((ptr = osmtpd_mheader_skip_obs_qtext(ptr, 0)) != NULL)
+		return ptr;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_qcontent(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_qtext(ptr, 0)) != NULL)
+		return ptr;
+	return osmtpd_mheader_skip_quoted_pair(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_quoted_string(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_dquote(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while (1) {
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if ((ptr = osmtpd_mheader_skip_qcontent(ptr, 0)) == NULL)
+			break;
+		prev = ptr;
+	}
+	if ((ptr = osmtpd_mheader_skip_dquote(prev, 0)) == NULL)
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_word(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_atom(ptr, 0)) != NULL)
+		return ptr;
+	return osmtpd_mheader_skip_quoted_string(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_obs_phrase(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) != NULL)
+			continue;
+		ptr = prev;
+		if (ptr[0] == '.')
+			continue;
+		if ((ptr = osmtpd_mheader_skip_cfws(ptr, 0)) != NULL)
+			continue;
+		return prev;
+	}
+}
+
+char *
+osmtpd_mheader_skip_phrase(char *ptr, int optional)
+{
+	/* obs-phrase is a superset of phrae */
+	return osmtpd_mheader_skip_obs_phrase(ptr, optional);
+#if 0
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) == NULL)
+			return prev;
+	}
+#endif
+}
+
+char *
+osmtpd_mheader_skip_obs_local_part(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while (ptr[0] == '.') {
+		ptr++;
+		if ((ptr = osmtpd_mheader_skip_word(ptr, 0)) == NULL)
+			return prev;
+		prev = ptr;
+	}
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_local_part(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dot_atom(ptr, 0)) != NULL)
+		return ptr;
+	ptr = start;
+	if ((ptr = osmtpd_mheader_skip_quoted_string(ptr, 0)) != NULL)
+		return ptr;
+	return osmtpd_mheader_skip_obs_local_part(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_obs_dtext(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_obs_no_ws_ctl(ptr, 0)) != NULL)
+		return ptr;
+	return osmtpd_mheader_skip_quoted_pair(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_dtext(char *ptr, int optional)
+{
+	if ((ptr[0] >= 33 && ptr[0] <= 90) || (ptr[0] >= 94 && ptr[0] <= 126))
+		return ptr + 1;
+	return osmtpd_mheader_skip_obs_dtext(ptr, optional);
+
+}
+
+char *
+osmtpd_mheader_skip_domain_literal(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if (ptr++[0] != '[')
+		return optional ? start : NULL;
+	while (1) {
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_dtext(ptr, 0)) == NULL) {
+			ptr = prev;
+			break;
+		}
+	}
+	if (ptr[0] != ']')
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_obs_domain(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_atom(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while (1) {
+		if (ptr++[0] != '.')
+			return prev;
+		if ((ptr = osmtpd_mheader_skip_atom(ptr, 0)) == NULL)
+			return prev;
+		prev = ptr;
+	}
+}
+
+char *
+osmtpd_mheader_skip_domain(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dot_atom(start, 0)) != NULL)
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_domain_literal(start, 0)) != NULL)
+		return ptr;
+	return osmtpd_mheader_skip_obs_domain(start, optional);
+}
+
+char *
+osmtpd_mheader_skip_display_name(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_phrase(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_obs_domain_list(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	while (1) {
+		if (ptr[0] == ',') {
+			ptr++;
+			prev = ptr;
+			continue;
+		} else if ((ptr = osmtpd_mheader_skip_cfws(ptr, 0)) != NULL) {
+			prev = ptr;
+			continue;
+		}
+		break;
+	}
+	ptr = prev;
+
+	if (ptr++[0] != '@')
+		return optional ? start : NULL;
+	if ((ptr = osmtpd_mheader_skip_domain(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		if (ptr[0] != ',')
+			break;
+		ptr++;
+		ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+		if (ptr[0] != '@')
+			continue;
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_domain(ptr + 1, 0)) == NULL) {
+			ptr = prev;
+			break;
+		}
+	}
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_obs_route(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_obs_domain_list(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if (ptr++[0] != ':')
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_addr_spec(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_local_part(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if (ptr++[0] != '@')
+		return optional ? start : NULL;
+	if ((ptr = osmtpd_mheader_skip_domain(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_obs_angle_addr(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if (ptr++[0] != '<')
+		return optional ? start : NULL;
+	if ((ptr = osmtpd_mheader_skip_obs_route(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if ((ptr = osmtpd_mheader_skip_addr_spec(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if (ptr++[0] != '>')
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_angle_addr(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if (ptr++[0] != '<')
+		return osmtpd_mheader_skip_obs_angle_addr(start, optional);
+	if ((ptr = osmtpd_mheader_skip_addr_spec(ptr, 0)) == NULL)
+		return osmtpd_mheader_skip_obs_angle_addr(start, optional);
+	if (ptr++[0] != '>')
+		return osmtpd_mheader_skip_obs_angle_addr(start, optional);
+	return osmtpd_mheader_skip_cfws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_name_addr(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	ptr = osmtpd_mheader_skip_display_name(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_angle_addr(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+/* RFC 2045 */
+char *
+osmtpd_mheader_skip_tspecials(char *ptr, int optional)
+{
+	if (ptr[0] == '(' || ptr[0] == ')' || ptr[0] == '<' || ptr[0] == '>' ||
+	    ptr[0] == '@' || ptr[0] == ',' || ptr[0] == ';' || ptr[0] == ':' ||
+	    ptr[0] == '\\' || ptr[0] == '\'' || ptr[0] == '/' ||
+	    ptr[0] == '[' || ptr[0] == ']' || ptr[0] == '?' || ptr[0] == '=')
+		return ptr + 1;
+	return optional ? ptr : NULL;
+
+}
+
+char *
+osmtpd_mheader_skip_token(char *ptr, int optional)
+{
+	if (osmtpd_mheader_skip_char(ptr, 0) == NULL)
+		return optional ? ptr : NULL;
+	/* Can't find the official definition for SPACE, so use WSP */
+	if (osmtpd_mheader_skip_wsp(ptr, 0) != NULL)
+		return optional ? ptr : NULL;
+	if (osmtpd_mheader_skip_ctl(ptr, 0) != NULL)
+		return optional ? ptr : NULL;
+	if (osmtpd_mheader_skip_tspecials(ptr, 0) != NULL)
+		return optional ? ptr : NULL;
+	ptr++;
+	while (1) {
+		if (osmtpd_mheader_skip_char(ptr, 0) == NULL)
+			return ptr;
+		if (osmtpd_mheader_skip_wsp(ptr, 0) != NULL)
+			return ptr;
+		if (osmtpd_mheader_skip_ctl(ptr, 0) != NULL)
+			return ptr;
+		if (osmtpd_mheader_skip_tspecials(ptr, 0) != NULL)
+			return ptr;
+		ptr++;
+	}
+}
+
+char *
+osmtpd_mheader_skip_value(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_token(ptr, 0)) == NULL)
+		return osmtpd_mheader_skip_quoted_string(start, optional);
+	return ptr;
+}
+
+/* Return the domain component of the first mailbox */
+char *
+osmtpd_mheader_from_domain(char *ptr)
+{
+	char *tmp;
+
+	/* from */
+	if (strncasecmp(ptr, "from:", 5) == 0) {
+		ptr += 5;
+	/* obs-from */
+	} else if (strncasecmp(ptr, "from", 4) == 0) {
+		ptr += 4;
+		do {
+			tmp = ptr;
+		} while ((ptr = osmtpd_mheader_skip_wsp(ptr, 0)) != NULL);
+		ptr = tmp;
+		if (ptr++[0] != ':')
+			return NULL;
+	} else {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	/* Both from and obs-from use Mailbox-list CRLF */
+	/* obs-mbox-list has just a prefix compared to mailbox-list */
+	while (1) {
+		tmp = ptr;
+		ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+		if (ptr++[0] != ',') {
+			ptr = tmp;
+			break;
+		}
+	}
+	/* We're only interested in the first mailbox */
+	if (osmtpd_mheader_skip_name_addr(ptr, 0) != NULL) {
+		ptr = osmtpd_mheader_skip_display_name(ptr, 1);
+		ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+		/* < */
+		ptr++;
+		/* addr-spec */
+		ptr = osmtpd_mheader_skip_local_part(ptr, 0);
+		/* @ */
+		ptr++;
+		tmp = osmtpd_mheader_skip_domain(ptr, 0);
+		return strndup(ptr, tmp - ptr);
+	}
+	if (osmtpd_mheader_skip_addr_spec(ptr, 0) != NULL) {
+		ptr = osmtpd_mheader_skip_local_part(ptr, 0);
+		/* @ */
+		ptr++;
+		tmp = osmtpd_mheader_skip_domain(ptr, 0);
+		return strndup(ptr, tmp - ptr);
+	}
+	errno = EINVAL;
+	return NULL;
+}
blob - /dev/null
blob + afc337958a16e8ba85c5144bf9edeacd225e18b5 (mode 644)
--- /dev/null
+++ mheader.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 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.
+ */
+
+char *osmtpd_mheader_skip_sp(char *, int);
+char *osmtpd_mheader_skip_htab(char *, int);
+char *osmtpd_mheader_skip_wsp(char *, int);
+char *osmtpd_mheader_skip_crlf(char *, int);
+char *osmtpd_mheader_skip_vchar(char *, int);
+char *osmtpd_mheader_skip_lf(char *, int);
+char *osmtpd_mheader_skip_cr(char *, int);
+char *osmtpd_mheader_skip_alpha(char *, int);
+char *osmtpd_mheader_skip_digit(char *, int);
+char *osmtpd_mheader_skip_dquote(char *, int);
+char *osmtpd_mheader_skip_char(char *, int);
+char *osmtpd_mheader_skip_ctl(char *, int);
+char *osmtpd_mheader_skip_obs_fws(char *, int);
+char *osmtpd_mheader_skip_fws(char *, int);
+char *osmtpd_mheader_skip_obs_no_ws_ctl(char *, int);
+char *osmtpd_mheader_skip_obs_ctext(char *, int);
+char *osmtpd_mheader_skip_obs_qp(char *, int);
+char *osmtpd_mheader_skip_quoted_pair(char *, int);
+char *osmtpd_mheader_skip_ctext(char *, int);
+char *osmtpd_mheader_skip_ccontent(char *, int);
+char *osmtpd_mheader_skip_comment(char *, int);
+char *osmtpd_mheader_skip_cfws(char *, int);
+char *osmtpd_mheader_skip_atext(char *, int);
+char *osmtpd_mheader_skip_atom(char *, int);
+char *osmtpd_mheader_skip_dot_atom_text(char *, int);
+char *osmtpd_mheader_skip_dot_atom(char *, int);
+char *osmtpd_mheader_skip_obs_qtext(char *, int);
+char *osmtpd_mheader_skip_qtext(char *, int);
+char *osmtpd_mheader_skip_qcontent(char *, int);
+char *osmtpd_mheader_skip_quoted_string(char *, int);
+char *osmtpd_mheader_skip_word(char *, int);
+char *osmtpd_mheader_skip_obs_phrase(char *, int);
+char *osmtpd_mheader_skip_phrase(char *, int);
+char *osmtpd_mheader_skip_obs_local_part(char *, int);
+char *osmtpd_mheader_skip_local_part(char *, int);
+char *osmtpd_mheader_skip_obs_dtext(char *, int);
+char *osmtpd_mheader_skip_dtext(char *, int);
+char *osmtpd_mheader_skip_domain_literal(char *, int);
+char *osmtpd_mheader_skip_obs_domain(char *, int);
+char *osmtpd_mheader_skip_domain(char *, int);
+char *osmtpd_mheader_skip_display_name(char *, int);
+char *osmtpd_mheader_skip_obs_domain_list(char *, int);
+char *osmtpd_mheader_skip_obs_route(char *, int);
+char *osmtpd_mheader_skip_addr_spec(char *, int);
+char *osmtpd_mheader_skip_obs_angle_addr(char *, int);
+char *osmtpd_mheader_skip_angle_addr(char *, int);
+char *osmtpd_mheader_skip_name_addr(char *, int);
+char *osmtpd_mheader_skip_tspecials(char *, int);
+char *osmtpd_mheader_skip_token(char *, int);
+char *osmtpd_mheader_skip_value(char *, int);
+
+char *osmtpd_mheader_from_domain(char *);