Commit Diff


commit - 803cdd74ad2746c83be456e70b251067e2007c04
commit + 40cd76f4447b88dd8a31fe5452837b7b890ad1a7
blob - cd8936cf464c880415aeeae99c4c3de9758604e1
blob + 7e6d565348258244ea4df4c2258f1a93df024bd2
--- Makefile
+++ Makefile
@@ -4,7 +4,7 @@ MAN=	filter-dkimsign.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 - c1fd990650431bd9f6ff9d6e22c3b0722455f84e
blob + 9709fee64e92a81889ce55e309a7dbddb8d81ffd
--- filter-dkimsign.8
+++ filter-dkimsign.8
@@ -47,7 +47,19 @@ The canonicalization algorithm used to sign the messge
 Defaults to
 .Ar simple/simple .
 .It Fl d Ar domain
-The domain where the public key can be found.
+The
+.Ar domain
+where the public key can be found.
+This option can be specified multiple times to select the best
+.Ar domain
+during signing.
+If specified multiple times it looks at the domain component of the first
+mailbox in the from-header and tries to find a match.
+If no exact match can be found it looks for the closest parent
+.Ar domain .
+If no matches can be the first
+.Ar domain
+specified will be used.
 .It Fl h Ar headers
 The email headers which are included in the mail signature.
 Per RFC this option requires at least the from header to be included.
blob - 9e8b8070a223394e3d9d10ba129bf31f508a64df
blob + e226617f1de01bf3e6891d29fc3ce0f5a20794aa
--- main.c
+++ main.c
@@ -28,6 +28,7 @@
 #include <unistd.h>
 
 #include "opensmtpd.h"
+#include "mheader.h"
 
 struct dkim_signature {
 	char *signature;
@@ -85,7 +86,8 @@ static int addtime = 0;
 static long long addexpire = 0;
 static int addheaders = 0;
 
-static char *domain = NULL;
+static char **domain = NULL;
+static size_t ndomains = 0;
 static char *selector = NULL;
 
 static EVP_PKEY *pkey;
@@ -108,6 +110,7 @@ int dkim_signature_printheader(struct dkim_message *, 
 int dkim_signature_printf(struct dkim_message *, char *, ...)
 	__attribute__((__format__ (printf, 2, 3)));
 int dkim_signature_normalize(struct dkim_message *);
+const char *dkim_domain_select(struct dkim_message *, char *);
 int dkim_signature_need(struct dkim_message *, size_t);
 int dkim_sign_init(struct dkim_message *);
 
@@ -148,7 +151,10 @@ main(int argc, char *argv[])
 				osmtpd_err(1, "Invalid canonicalization");
 			break;
 		case 'd':
-			domain = optarg;
+			if ((domain = reallocarray(domain, ndomains + 1,
+			    sizeof(*domain))) == NULL)
+				osmtpd_err(1, "malloc");
+			domain[ndomains++] = optarg;
 			break;
 		case 'h':
 			dkim_headers_set(optarg);
@@ -279,11 +285,10 @@ dkim_message_new(struct osmtpd_ctx *ctx)
 	message->err = 0;
 
 	if (!dkim_signature_printf(message,
-	    "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; d=%s; s=%s; ", "1",
+	    "DKIM-Signature: v=%s; a=%s-%s; c=%s/%s; s=%s; ", "1",
 	    cryptalg, hashalg,
 	    canonheader == CANON_SIMPLE ? "simple" : "relaxed",
-	    canonbody == CANON_SIMPLE ? "simple" : "relaxed",
-	    domain, selector))
+	    canonbody == CANON_SIMPLE ? "simple" : "relaxed", selector))
 		return NULL;
 	if (addheaders > 0 && !dkim_signature_printf(message, "z="))
 		return NULL;
@@ -532,6 +537,7 @@ dkim_sign(struct osmtpd_ctx *ctx)
 	char bbh[EVP_MAX_MD_SIZE];
 	char bh[(((sizeof(bbh) + 2) / 3) * 4) + 1];
 	char *b;
+	const char *sdomain = domain[0], *tsdomain;
 	time_t now;
 	ssize_t i;
 	size_t linelen;
@@ -569,6 +575,8 @@ dkim_sign(struct osmtpd_ctx *ctx)
 			dkim_errx(message, "Failed to update digest context");
 			return;
 		}
+		if ((tsdomain = dkim_domain_select(message, message->headers[i])) != NULL)
+			sdomain = tsdomain;
 		/* We're done with the cached header after hashing */
 		for (tmp = message->headers[i]; tmp[0] != ':'; tmp++) {
 			if (tmp[0] == ' ' || tmp[0] == '\t')
@@ -581,7 +589,7 @@ dkim_sign(struct osmtpd_ctx *ctx)
 		    message->headers[i]))
 			return;
 	}
-	dkim_signature_printf(message, "; b=");
+	dkim_signature_printf(message, "; d=%s; b=", sdomain);
 	if (!dkim_signature_normalize(message))
 		return;
 	if ((tmp = strdup(message->signature.signature)) == NULL) {
@@ -777,6 +785,34 @@ dkim_signature_printf(struct dkim_message *message, ch
 	sig->len += len;
 	va_end(ap);
 	return 1;
+}
+
+const char *
+dkim_domain_select(struct dkim_message *message, char *from)
+{
+	char *mdomain0, *mdomain;
+	size_t i;
+
+	if ((mdomain = mdomain0 = osmtpd_mheader_from_domain(from)) == NULL) {
+		if (errno != EINVAL) {
+			dkim_err(message, "Couldn't parse from header");
+			return NULL;
+		}
+		return NULL;
+	}
+
+	while (mdomain != NULL && mdomain[0] != '\0') {
+		for (i = 0; i < ndomains; i++) {
+			if (strcasecmp(mdomain, domain[i]) == 0) {
+				free(mdomain0);
+				return domain[i];
+			}
+		}
+		if ((mdomain = strchr(mdomain, '.')) != NULL)
+			mdomain++;
+	}
+	free(mdomain0);
+	return NULL;
 }
 
 int
blob - /dev/null
blob + bf83d2c34a9425bae381902fc43fbcc08d7f1aef (mode 644)
--- /dev/null
+++ mheader.c
@@ -0,0 +1,682 @@
+/*
+ * 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_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;
+}
+
+/* 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 + 133d5dd0283a4b3a869c7aee1c189e55a74f343c (mode 644)
--- /dev/null
+++ mheader.h
@@ -0,0 +1,63 @@
+/*
+ * 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_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_from_domain(char *);