Commit Diff


commit - /dev/null
commit + f15094901545c7a69c0989c80bc8715829c34db4
blob - /dev/null
blob + 5a6d7c794505e09b9725140560776db03b94882f (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,22 @@
+LOCALBASE?=	/usr/local/
+PROG=	filter-dkimverify
+MAN=	filter-dkimverify.8
+BINDIR=	${LOCALBASE}/libexec/smtpd/
+MANDIR=	${LOCALBASE}/man/man
+
+SRCS+=	main.c mheader.c unpack_dns.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+=	-lcrypto -lopensmtpd -levent
+DPADD=	${LIBCRYPTO} ${LIBEVENT}
+
+bindir:
+	${INSTALL} -d ${DESTDIR}${BINDIR}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + b49771b61776484246483a4eb8e34bf5287f2f58 (mode 644)
--- /dev/null
+++ filter-dkimverify.8
@@ -0,0 +1,32 @@
+.\"	$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-DKIMVERIFY 8
+.Os
+.Sh NAME
+.Nm filter-dkimverify
+.Nd verifies dkim signature of messages
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+verifies the dkim signatures of messages and adds an Authentication-Results
+header to the message.
+.Sh SEE ALSO
+.Xr filter-admdscrub 8
+.Xr filter-dkimsign 8
+.Xr smtpd 8
blob - /dev/null
blob + c4bf6a31b31e82fe7371c37322d6baed44bc5a8e (mode 644)
--- /dev/null
+++ main.c
@@ -0,0 +1,1646 @@
+/*
+ * Copyright (c) 2022 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 <sys/types.h>
+#include <sys/socket.h>
+
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/sha.h>
+#include <openssl/err.h>
+
+#include <arpa/nameser.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <asr.h>
+
+#include "opensmtpd.h"
+#include "unpack_dns.h"
+#include "mheader.h"
+
+/*
+ * Use RFC8601 (Authentication-Results) codes instead of RFC6376 codes,
+ * since they're more expressive.
+ */
+enum state {
+	DKIM_UNKNOWN,
+	DKIM_PASS,
+	DKIM_FAIL,
+	DKIM_POLICY,
+	DKIM_NEUTRAL,
+	DKIM_TEMPERROR,
+	DKIM_PERMERROR
+};
+
+struct signature {
+	struct header *header;
+	enum state state;
+	const char *state_reason;
+	int v;
+	char *a;
+	size_t asz;
+	int ak;
+	const EVP_MD *ah;
+	char *b;
+	size_t bsz;
+	char *bheader;
+	/* Make sure padding bits for base64 decoding fit */
+	char bh[EVP_MAX_MD_SIZE + (3 - (EVP_MAX_MD_SIZE % 3))];
+	size_t bhsz;
+	EVP_MD_CTX *bhctx;
+	int c;
+#define CANON_HEADER_SIMPLE	0
+#define CANON_HEADER_RELAXED	1
+#define CANON_HEADER		1
+#define CANON_BODY_SIMPLE	0
+#define CANON_BODY_RELAXED	1 << 1
+#define CANON_BODY		1 << 1
+#define CANON_DONE		1 << 2
+	char d[HOST_NAME_MAX + 1];
+	char **h;
+	char *i;
+	size_t isz;
+	ssize_t l;
+	int q;
+	char s[HOST_NAME_MAX + 1];
+	time_t t;	/* Signature t=/timestamp */
+#define KT_Y			1
+#define KT_S			1 << 1
+	int kt;		/* Key t=/Flags */
+	time_t x;
+	int z;
+	struct event_asr *query;
+	EVP_PKEY *p;
+};
+
+struct header {
+	struct message *msg;
+	uint8_t readdone;
+	uint8_t parsed;
+	char *buf;
+	size_t buflen;
+	struct signature *sig;
+};
+
+#define AUTHENTICATION_RESULTS_LINELEN 78
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+struct message {
+	struct osmtpd_ctx *ctx;
+	FILE *origf;
+	int parsing_headers;
+	size_t body_whitelines;
+	int has_body;
+	struct header *header;
+	size_t nheaders;
+	int err;
+	int readdone;
+};
+
+void usage(void);
+void dkim_err(struct message *, char *);
+void dkim_errx(struct message *, char *);
+void dkim_conf(const char *, const char *);
+void dkim_dataline(struct osmtpd_ctx *, const char *);
+void dkim_commit(struct osmtpd_ctx *);
+void *dkim_message_new(struct osmtpd_ctx *);
+void dkim_message_free(struct osmtpd_ctx *, void *);
+void dkim_header_add(struct osmtpd_ctx *, const char *);
+void dkim_signature_parse(struct header *);
+void dkim_signature_parse_v(struct signature *, char *, char *);
+void dkim_signature_parse_a(struct signature *, char *, char *);
+void dkim_signature_parse_b(struct signature *, char *, char *);
+void dkim_signature_parse_bh(struct signature *, char *, char *);
+void dkim_signature_parse_c(struct signature *, char *, char *);
+void dkim_signature_parse_d(struct signature *, char *, char *);
+void dkim_signature_parse_h(struct signature *, char *, char *);
+void dkim_signature_parse_i(struct signature *, char *, char *);
+void dkim_signature_parse_l(struct signature *, char *, char *);
+void dkim_signature_parse_q(struct signature *, char *, char *);
+void dkim_signature_parse_s(struct signature *, char *, char *);
+void dkim_signature_parse_t(struct signature *, char *, char *);
+void dkim_signature_parse_x(struct signature *, char *, char *);
+void dkim_signature_parse_z(struct signature *, char *, char *);
+void dkim_signature_verify(struct signature *);
+void dkim_signature_header(EVP_MD_CTX *, struct signature *, struct header *);
+void dkim_signature_state(struct signature *, enum state, const char *);
+const char *dkim_state2str(enum state);
+void dkim_header_cat(struct osmtpd_ctx *, const char *);
+void dkim_body_parse(struct message *, const char *);
+void dkim_body_verify(struct signature *);
+void dkim_rr_resolve(struct asr_result *, void *);
+void dkim_message_verify(struct message *);
+ssize_t dkim_ar_cat(char **ar, size_t *n, size_t aroff, const char *fmt, ...)
+    __attribute__((__format__ (printf, 4, 5)));
+void dkim_ar_print(struct osmtpd_ctx *, char *);
+int dkim_key_text_parse(struct signature *, char *);
+
+char *authservid;
+EVP_ENCODE_CTX *ectx = NULL;
+
+int
+main(int argc, char *argv[])
+{
+	if (argc != 1)
+		osmtpd_errx(1, "Invalid argument count");
+
+	OpenSSL_add_all_digests();
+
+	if (pledge("tmppath stdio dns", NULL) == -1)
+		osmtpd_err(1, "pledge");
+
+	if ((ectx = EVP_ENCODE_CTX_new()) == NULL)
+		osmtpd_err(1, "EVP_ENCODE_CTX_new");
+
+	osmtpd_register_conf(dkim_conf);
+	osmtpd_register_filter_dataline(dkim_dataline);
+	osmtpd_register_filter_commit(dkim_commit);
+	osmtpd_local_message(dkim_message_new, dkim_message_free);
+	osmtpd_run();
+
+	return 0;
+}
+
+void
+dkim_conf(const char *key, const char *value)
+{
+	char *end;
+
+	if (key == NULL) {
+		if (authservid == NULL)
+			osmtpd_errx(1, "Didn't receive admd config option");
+		return;
+	}
+	if (strcmp(key, "admd") == 0 && authservid == NULL) {
+		if ((authservid = strdup(value)) == NULL)
+			osmtpd_err(1, "malloc");
+		end = osmtpd_mheader_skip_value(authservid, 0);
+		if (authservid + strlen(authservid) != end)
+			osmtpd_errx(1, "Invalid authservid");
+		osmtpd_mheader_quoted_string_normalize(authservid);
+	}
+}
+
+void
+dkim_dataline(struct osmtpd_ctx *ctx, const char *line)
+{
+	struct message *msg = ctx->local_message;
+	size_t i;
+
+	if (msg->err) {
+		if (line[0] == '.' && line[1] =='\0') {
+			msg->readdone = 1;
+			osmtpd_filter_dataline(ctx, ".");
+		}
+		return;
+	}
+
+	if (fprintf(msg->origf, "%s\n", line) < 0) {
+		dkim_err(msg, "Couldn't write to tempfile");
+		return;
+	}
+	if (line[0] == '.') {
+		line++;
+		if (line[0] == '\0') {
+			msg->readdone = 1;
+			for (i = 0; i < msg->nheaders; i++) {
+				if (msg->header[i].sig == NULL)
+					continue;
+				dkim_body_verify(msg->header[i].sig);
+			}
+			dkim_message_verify(msg);
+			return;
+		}
+	}
+	if (msg->parsing_headers) {
+		dkim_header_add(ctx, line);
+		if (line[0] == '\0') {
+			msg->parsing_headers = 0;
+			for (i = 0; i < msg->nheaders; i++) {
+				if (msg->header[i].sig == NULL)
+					continue;
+				if (msg->header[i].sig->query == NULL)
+					dkim_signature_verify(
+					    msg->header[i].sig);
+			}
+		}
+		return;
+	} else {
+		dkim_body_parse(msg, line);
+	}
+}
+
+void
+dkim_commit(struct osmtpd_ctx *ctx)
+{
+	struct message *msg = ctx->local_message;
+
+	if (msg->err)
+		osmtpd_filter_disconnect(ctx, "Internal server error");
+	else
+		osmtpd_filter_proceed(ctx);
+}
+
+void *
+dkim_message_new(struct osmtpd_ctx *ctx)
+{
+	struct message *msg;
+
+	if ((msg = malloc(sizeof(*msg))) == NULL)
+		osmtpd_err(1, NULL);
+
+	if ((msg->origf = tmpfile()) == NULL) {
+		dkim_err(msg, "Can't open tempfile");
+		return NULL;
+	}
+	msg->ctx = ctx;
+	msg->parsing_headers = 1;
+	msg->body_whitelines = 0;
+	msg->has_body = 0;
+	msg->header = NULL;
+	msg->nheaders = 0;
+	msg->err = 0;
+	msg->readdone = 0;
+
+	return msg;
+}
+
+void
+dkim_message_free(struct osmtpd_ctx *ctx, void *data)
+{
+	struct message *msg = data;
+	size_t i, j;
+
+	fclose(msg->origf);
+	for (i = 0; i < msg->nheaders; i++) {
+		if (msg->header[i].sig != NULL) {
+			free(msg->header[i].sig->b);
+			EVP_MD_CTX_free(msg->header[i].sig->bhctx);
+			for (j = 0; msg->header[i].sig->h != NULL &&
+			    msg->header[i].sig->h[j] != NULL; j++)
+				free(msg->header[i].sig->h[j]);
+			free(msg->header[i].sig->h);
+		}
+		free(msg->header[i].sig);
+	}
+	free(msg->header);
+	free(msg);
+}
+
+void
+dkim_header_add(struct osmtpd_ctx *ctx, const char *line)
+{
+	struct message *msg = ctx->local_message;
+	char *start, *end, *verify;
+	struct header *headers;
+	size_t i;
+
+	if (msg->nheaders > 0 &&
+	    msg->header[msg->nheaders - 1].readdone == 0) {
+		if (line[0] != ' ' && line[0] != '\t') {
+			msg->header[msg->nheaders - 1].readdone = 1;
+			start = msg->header[msg->nheaders - 1].buf;
+			end = osmtpd_mheader_skip_fieldname(start, 0);
+			/* In case someone uses an obs-optional */
+			verify = osmtpd_mheader_skip_wsp(end, 1);
+			if (strncasecmp(
+			    start, "DKIM-Signature", end - start) == 0 &&
+			    verify[0] == ':')
+				dkim_signature_parse(
+				    &msg->header[msg->nheaders - 1]);
+			if (line[0] == '\0')
+				return;
+		} else {
+			dkim_header_cat(ctx, line);
+			return;
+		}
+	}
+	if (msg->nheaders % 10 == 0) {
+		if ((headers = recallocarray(msg->header, msg->nheaders,
+		    msg->nheaders + 10, sizeof(*msg->header))) == NULL) {
+			dkim_err(msg, "malloc");
+			return;
+		}
+		msg->header = headers;
+		for (i = 0; i < msg->nheaders; i++) {
+			if (msg->header[i].sig == NULL)
+				continue;
+			msg->header[i].sig->header = &msg->header[i];
+		}
+	}
+	msg->header[msg->nheaders].msg = msg;
+	msg->nheaders++;
+	dkim_header_cat(ctx, line);
+}
+
+void
+dkim_header_cat(struct osmtpd_ctx *ctx, const char *line)
+{
+	struct message *msg = ctx->local_message;
+	struct header *header = &msg->header[msg->nheaders - 1];
+	char *buf;
+
+	size_t needed = header->buflen + strlen(line) + 2;
+
+	if (needed > (header->buflen / 1024) + 1) {
+		buf = reallocarray(header->buf, (needed / 1024) + 1, 1024);
+		if (buf == NULL) {
+			dkim_err(msg, "malloc");
+			return;
+		}
+		header->buf = buf;
+	}
+	header->buflen += snprintf(header->buf + header->buflen,
+	    (((needed / 1024) + 1) * 1024) - header->buflen, "%s%s",
+	    header->buflen == 0 ? "" : "\r\n", line);
+}
+
+void
+dkim_signature_parse(struct header *header)
+{
+	char *buf;
+	char *end, tagname[3];
+	char subdomain[HOST_NAME_MAX + 1];
+	struct signature *sig;
+	struct asr_query *query;
+
+	/* Format checked by dkim_header_add */
+	buf = osmtpd_mheader_skip_fieldname(header->buf, 0);
+	buf = osmtpd_mheader_skip_wsp(buf, 1) + 1;
+
+	if ((header->sig = calloc(1, sizeof(*header->sig))) == NULL) {
+		dkim_err(header->msg, "malloc");
+		return;
+	}
+	sig = header->sig;
+	sig->header = header;
+	sig->l = -1;
+	sig->t = -1;
+	sig->x = -1;
+
+	end = osmtpd_mheader_skip_dkimsig_taglist(buf, 0);
+	if (end == NULL || end[0] != '\0') {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid taglist");
+		return;
+	}
+
+	while (buf[0] != '\0') {
+		buf = osmtpd_mheader_skip_fws(buf, 1);
+		end = osmtpd_mheader_skip_dkimsig_tagname(buf, 0);
+
+		/* Unknown tag-name */
+		if ((size_t)(end - buf) >= sizeof(tagname))
+			tagname[0] = '\0';
+		else
+			strlcpy(tagname, buf, (end - buf) + 1);
+		buf = osmtpd_mheader_skip_fws(end, 1);
+		/* '=' */
+		buf = osmtpd_mheader_skip_fws(buf + 1, 1);
+		end = osmtpd_mheader_skip_dkimsig_tagvalue(buf, 1);
+		if (strcmp(tagname, "v") == 0)
+			dkim_signature_parse_v(sig, buf, end);
+		else if (strcmp(tagname, "a") == 0)
+			dkim_signature_parse_a(sig, buf, end);
+		else if (strcmp(tagname, "b") == 0)
+			dkim_signature_parse_b(sig, buf, end);
+		else if (strcmp(tagname, "bh") == 0)
+			dkim_signature_parse_bh(sig, buf, end);
+		else if (strcmp(tagname, "c") == 0)
+			dkim_signature_parse_c(sig, buf, end);
+		else if (strcmp(tagname, "d") == 0)
+			dkim_signature_parse_d(sig, buf, end);
+		else if (strcmp(tagname, "h") == 0)
+			dkim_signature_parse_h(sig, buf, end);
+		else if (strcmp(tagname, "i") == 0)
+			dkim_signature_parse_i(sig, buf, end);
+		else if (strcmp(tagname, "l") == 0)
+			dkim_signature_parse_l(sig, buf, end);
+		else if (strcmp(tagname, "q") == 0)
+			dkim_signature_parse_q(sig, buf, end);
+		else if (strcmp(tagname, "s") == 0)
+			dkim_signature_parse_s(sig, buf, end);
+		else if (strcmp(tagname, "t") == 0)
+			dkim_signature_parse_t(sig, buf, end);
+		else if (strcmp(tagname, "x") == 0)
+			dkim_signature_parse_x(sig, buf, end);
+		else if (strcmp(tagname, "z") == 0)
+			dkim_signature_parse_z(sig, buf, end);
+
+		buf = osmtpd_mheader_skip_fws(end, 1);
+		if (buf[0] == ';')
+			buf++;
+		else if (buf[0] != '\0') {
+			dkim_signature_state(sig, DKIM_PERMERROR,
+			    "Invalid taglist");
+			return;
+		}
+	}
+	if (sig->state != DKIM_UNKNOWN)
+		return;
+
+	if (sig->v != 1)
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing v tag");
+	else if (sig->ah == NULL)
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing a tag");
+	else if (sig->b == NULL)
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing b tag");
+	else if (sig->bhsz == 0)
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing bh tag");
+	else if (sig->d[0] == '\0')
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing d tag");
+	else if (sig->h == NULL)
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing h tag");
+	else if (sig->s[0] == '\0')
+		dkim_signature_state(sig, DKIM_PERMERROR, "Missing s tag");
+
+	if ((size_t)snprintf(subdomain, sizeof(subdomain), "%s._domainkey.%s",
+	    sig->s, sig->d) >= sizeof(subdomain)) {
+		dkim_signature_state(sig, DKIM_PERMERROR,
+		    "dns/txt query too long");
+		return;
+	}
+
+	if ((query = res_query_async(subdomain, C_IN, T_TXT, NULL)) == NULL) {
+		dkim_err(header->msg, "res_query_async");
+		return;
+	}
+	if ((sig->query = event_asr_run(query, dkim_rr_resolve, sig)) == NULL) {
+		dkim_err(header->msg, "event_asr_run");
+		asr_abort(query);
+		return;
+	}
+}
+
+void
+dkim_signature_parse_v(struct signature *sig, char *start, char *end)
+{
+	if (sig->v != 0) {	/* Duplicate tag */
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate v tag");
+		return;
+	}
+	/* Unsupported version */
+	if (start[0] != '1' || start + 1 != end)
+		dkim_signature_state(sig, DKIM_NEUTRAL, "Unsupported v tag");
+	else
+		sig->v = 1;
+}
+
+void
+dkim_signature_parse_a(struct signature *sig, char *start, char *end)
+{
+	char ah[sizeof("sha256")];
+
+	if (sig->ah != NULL) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate a tag");
+		return;
+	}
+
+	if (osmtpd_mheader_skip_dkimsig_sigatagalg(start, 0) != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid a tag");
+		return;
+	}
+	sig->a = start;
+	sig->asz = (size_t)(end - start);
+	if (strncmp(start, "rsa-", 4) == 0) {
+		start += 4;
+		sig->ak = EVP_PKEY_RSA;
+	} else {
+		dkim_signature_state(sig, DKIM_NEUTRAL, "Unsuppored a tag k");
+		return;
+	}
+	if ((size_t)(end - start) >= sizeof(ah)) {
+		dkim_signature_state(sig, DKIM_NEUTRAL, "Unsuppored a tag h");
+		return;
+	}
+	strlcpy(ah, start, sizeof(ah));
+	ah[end - start] = '\0';
+	if ((sig->ah = EVP_get_digestbyname(ah)) == NULL) {
+		dkim_signature_state(sig, DKIM_NEUTRAL, "Unsuppored a tag h");
+		return;
+	}
+	if ((sig->bhctx = EVP_MD_CTX_new()) == NULL) {
+		dkim_err(sig->header->msg, "EVP_MD_CTX_new");
+		return;
+	}
+	if (EVP_DigestInit_ex(sig->bhctx, sig->ah, NULL) <= 0) {
+		dkim_err(sig->header->msg, "EVP_DigestInit_ex");
+		return;
+	}
+}
+
+void
+dkim_signature_parse_b(struct signature *sig, char *start, char *end)
+{
+	int decodesz;
+
+	if (sig->b != NULL) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate b tag");
+		return;
+	}
+	sig->bheader = start;
+	if ((sig->b = malloc((((end - start) / 4) + 1) * 3)) == NULL) {
+		dkim_err(sig->header->msg, "malloc");
+		return;
+	}
+	/* EVP_DecodeBlock doesn't handle internal whitespace */
+	EVP_DecodeInit(ectx);
+	if (EVP_DecodeUpdate(ectx, sig->b, &decodesz, start,
+	    (int)(end - start)) == -1) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid b tag");
+		return;
+	}
+	sig->bsz = decodesz;
+	if (EVP_DecodeFinal(ectx, sig->b + sig->bsz,
+	    &decodesz) == -1) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid b tag");
+		return;
+	}
+	sig->bsz += decodesz;
+}
+
+void
+dkim_signature_parse_bh(struct signature *sig, char *start, char *end)
+{
+	char *b64;
+	size_t n;
+	int decodesz;
+
+	if (sig->bhsz != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate bh tag");
+		return;
+	}
+	/*
+	 * EVP_Decode* expects sig->bh to be large enough,
+	 * so count the actual b64 characters.
+	 */
+	b64 = start;
+	n = 0;
+	while (1) {
+		b64 = osmtpd_mheader_skip_fws(b64, 1);
+		if (osmtpd_mheader_skip_alphadigitps(b64, 0) == NULL)
+			break;
+		n++;
+		b64++;
+	}
+	if (b64[0] == '=') {
+		n++;
+		b64 = osmtpd_mheader_skip_fws(b64 + 1, 1);
+		if (b64[0] == '=') {
+			n++;
+			b64++;
+		}
+	}
+	/* Invalid tag value */
+	if (b64 != end || n % 4 != 0 || (n / 4) * 3 > sizeof(sig->bh)) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid bh tag");
+		return;
+	}
+	/* EVP_DecodeBlock doesn't handle internal whitespace */
+	EVP_DecodeInit(ectx);
+	if (EVP_DecodeUpdate(ectx, sig->bh, &decodesz, start,
+	    (int)(end - start)) == -1) {
+		/* Paranoia check */
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid bh tag");
+		return;
+	}
+	sig->bhsz = decodesz;
+	if (EVP_DecodeFinal(ectx, sig->bh + sig->bhsz, &decodesz) == -1) {
+		/* Paranoia check */
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid bh tag");
+		return;
+	}
+	sig->bhsz += decodesz;
+}
+
+void
+dkim_signature_parse_c(struct signature *sig, char *start, char *end)
+{
+	if (sig->c != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate c tag");
+		return;
+	}
+	if (strncmp(start, "simple", 6) == 0) {
+		sig->c = CANON_HEADER_SIMPLE;
+		start += 6;
+	} else if (strncmp(start, "relaxed", 7) == 0) {
+		sig->c = CANON_HEADER_RELAXED;
+		start += 7;
+	} else {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid c tag");
+		return;
+	}
+	if (start[0] == '/') {
+		start++;
+		if (strncmp(start, "simple", 6) == 0) {
+			sig->c |= CANON_BODY_SIMPLE;
+			start += 6;
+		} else if (strncmp(start, "relaxed", 7) == 0) {
+			sig->c |= CANON_BODY_RELAXED;
+			start += 7;
+		} else {
+			dkim_signature_state(sig, DKIM_PERMERROR,
+			    "Invalid c tag");
+			return;
+		}
+	}
+
+	if (start != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid c tag");
+		return;
+	}
+	sig->c |= CANON_DONE;
+}
+
+void
+dkim_signature_parse_d(struct signature *sig, char *start, char *end)
+{
+	if (sig->d[0] != '\0') {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate d tag");
+		return;
+	}
+	if (osmtpd_mheader_skip_domain(start, 0) != end ||
+	    (size_t)(end - start) >= sizeof(sig->d)) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid d tag");
+		return;
+	}
+	strlcpy(sig->d, start, end - start + 1);
+}
+
+void
+dkim_signature_parse_h(struct signature *sig, char *start, char *end)
+{
+	char *h;
+	size_t n = 0;
+
+	if (sig->h != NULL) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate h tag");
+		return;
+	}
+	h = start;
+	while (1) {
+		if ((h = osmtpd_mheader_skip_hdrname(h, 0)) == NULL) {
+			dkim_signature_state(sig, DKIM_PERMERROR,
+			    "Invalid h tag");
+			return;
+		}
+		n++;
+		/* ';' is part of hdr-name */
+		if (h > end) {
+			h = end;
+			break;
+		}
+		h = osmtpd_mheader_skip_fws(h, 1);
+		if (h[0] != ':')
+			break;
+		h = osmtpd_mheader_skip_fws(h + 1, 1);
+	}
+	if (h != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid h tag");
+		return;
+	}
+	if ((sig->h = calloc(n + 1, sizeof(*sig->h))) == NULL) {
+		dkim_err(sig->header->msg, "malloc");
+		return;
+	}
+	n = 0;
+	h = start;
+	while (1) {
+		h = osmtpd_mheader_skip_hdrname(start, 0);
+		/* ';' is part of hdr-name */
+		if (h > end) {
+			sig->h[n] = strndup(start, end - start);
+			break;
+		}
+		if ((sig->h[n++] = strndup(start, h - start)) == NULL) {
+			dkim_err(sig->header->msg, "malloc");
+			return;
+		}
+		start = osmtpd_mheader_skip_fws(h, 1);
+		if (start[0] != ':')
+			break;
+		start = osmtpd_mheader_skip_fws(start + 1, 1);
+	}
+}
+
+void
+dkim_signature_parse_i(struct signature *sig, char *start, char *end)
+{
+	char *i;
+
+	if (sig->i != NULL) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate i tag");
+		return;
+	}
+	i = osmtpd_mheader_skip_local_part(start, 1);
+	if (i[0] != '@') {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid i tag");
+		return;
+	}
+	if (osmtpd_mheader_skip_domain(i + 1, 0) != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid i tag");
+		return;
+	}
+	sig->i = start;
+	sig->isz = (size_t)(end - start);
+}
+
+void
+dkim_signature_parse_l(struct signature *sig, char *start, char *end)
+{
+	long long l;
+	char *lend;
+
+	if (sig->l != -1) {	/* Duplicate tag */
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate l tag");
+		return;
+	}
+	errno = 0;
+	l = strtoll(start, &lend, 10);
+	/* > 76 digits in stroll is an overflow */
+	if (osmtpd_mheader_skip_digit(start, 0) == NULL ||
+	    lend != end || errno != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid l tag");
+		return;
+	}
+	if (l > SSIZE_MAX) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "l tag too large");
+		return;
+	}
+	sig->l = (ssize_t)l;
+}
+
+void
+dkim_signature_parse_q(struct signature *sig, char *start, char *end)
+{
+	char *qend;
+
+	if (sig->q != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate q tag");
+		return;
+	}
+
+	while (1) {
+		start = osmtpd_mheader_skip_fws(start, 1);
+		qend = osmtpd_mheader_skip_dkimsig_sigqtagmethod(start, 0);
+		if (qend == NULL) {
+			dkim_signature_state(sig, DKIM_PERMERROR, "Invalid q tag");
+			return;
+		}
+		if (strncmp(start, "dns/txt", qend - start) == 0)
+			sig->q = 1;
+		start = osmtpd_mheader_skip_fws(qend, 1);
+		if (start[0] != ':')
+			break;
+	}
+	if (start != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid q tag");
+		return;
+	}
+	if (sig->q != 1) {
+		sig->q = 1;
+		dkim_signature_state(sig, DKIM_NEUTRAL, "No useable q found");
+		return;
+	}
+}
+
+void
+dkim_signature_parse_s(struct signature *sig, char *start, char *end)
+{
+	if (sig->s[0] != '\0') {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate s tag");
+		return;
+	}
+	if (osmtpd_mheader_skip_dkimsig_selector(start, 0) != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid s tag");
+		return;
+	}
+	strlcpy(sig->s, start, end - start + 1);
+}
+
+void
+dkim_signature_parse_t(struct signature *sig, char *start, char *end)
+{
+	char *tend;
+
+	if (sig->t != -1) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate t tag");
+		return;
+	}
+	errno = 0;
+	sig->t = strtoll(start, &tend, 10);
+	if (osmtpd_mheader_skip_digit(start, 0) == NULL || tend != end ||
+	    tend - start > 12 || errno != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid t tag");
+		return;
+	}
+}
+
+void
+dkim_signature_parse_x(struct signature *sig, char *start, char *end)
+{
+	char *xend;
+
+	if (sig->x != -1) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate x tag");
+		return;
+	}
+	errno = 0;
+	sig->x = strtoll(start, &xend, 10);
+	if (osmtpd_mheader_skip_digit(start, 0) == NULL || xend != end ||
+	    xend - start > 12 || errno != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid x tag");
+		return;
+	}
+}
+
+void
+dkim_signature_parse_z(struct signature *sig, char *start, char *end)
+{
+	if (sig->z != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Duplicate z tag");
+		return;
+	}
+
+	sig->z = 1;
+	if (osmtpd_mheader_skip_dkimsig_sigztagvalue(start, 0) != end) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid z tag");
+		return;
+	}
+}
+
+void
+dkim_signature_verify(struct signature *sig)
+{
+	struct message *msg = sig->header->msg;
+	static EVP_MD_CTX *bctx = NULL;
+	char *end;
+	size_t i, header;
+
+	if (sig->state != DKIM_UNKNOWN)
+		return;
+
+	if (bctx == NULL) {
+		if ((bctx = EVP_MD_CTX_new()) == NULL) {
+			dkim_errx(msg, "EVP_MD_CTX_new");
+			return;
+		}
+	}
+	if (EVP_DigestVerifyInit(bctx, NULL, sig->ah, NULL, sig->p) != 1) {
+		dkim_errx(msg, "EVP_DigestVerifyInit");
+		return;
+	}
+
+	for (i = 0; i < msg->nheaders; i++)
+		msg->header[i].parsed = 0;
+
+	for (header = 0; sig->h[header] != NULL; header++) {
+		for (i = msg->nheaders; i > 0; ) {
+			i--;
+			if (msg->header[i].parsed ||
+			    strncasecmp(msg->header[i].buf, sig->h[header],
+			    strlen(sig->h[header])) != 0 ||
+			    msg->header[i].sig == sig)
+				continue;
+			end = osmtpd_mheader_skip_fws(
+			    msg->header[i].buf + strlen(sig->h[header]), 1);
+			if (end[0] != ':')
+				continue;
+			dkim_signature_header(bctx, sig, &(msg->header[i]));
+			msg->header[i].parsed = 1;
+		}
+	}
+	dkim_signature_header(bctx, sig, sig->header);
+	if (EVP_DigestVerifyFinal(bctx, sig->b, sig->bsz) != 1)
+		dkim_signature_state(sig, DKIM_FAIL, "b mismatch");
+}
+
+void
+dkim_signature_header(EVP_MD_CTX *bctx, struct signature *sig,
+    struct header *header)
+{
+	char c, *ptr = header->buf, *end;
+	int inhdrname = 1;
+	int canon = sig->c & CANON_HEADER;
+
+	for (ptr = header->buf; ptr[0] != '\0'; ptr++) {
+		if (inhdrname) {
+			if (canon == CANON_HEADER_RELAXED) {
+				ptr = osmtpd_mheader_skip_fws(ptr, 1);
+				c = tolower(ptr[0]);
+			} else
+				c = ptr[0];
+			if (c == ':') {
+				inhdrname = 0;
+				if (canon == CANON_HEADER_RELAXED)
+					ptr = osmtpd_mheader_skip_fws(
+					    ptr + 1, 1) - 1;
+			}
+			if (EVP_DigestVerifyUpdate(bctx, &c, 1) == 0) {
+				dkim_errx(sig->header->msg,
+				    "EVP_DigestVerifyUpdate");
+				return;
+			}
+			continue;
+		}
+		end = osmtpd_mheader_skip_fws(ptr, 1);
+		if (end == ptr) {
+			if (sig->header == header && ptr == sig->bheader) {
+				ptr = osmtpd_mheader_skip_dkimsig_tagvalue(
+				    ptr, 0) - 1;
+				continue;
+			}
+			if (EVP_DigestVerifyUpdate(bctx, ptr, 1) == 0) {
+				dkim_errx(sig->header->msg,
+				    "EVP_DigestVerifyUpdate");
+				return;
+			}
+		} else {
+			if (canon == CANON_HEADER_RELAXED) {
+				if (end[0] == '\0')
+					continue;
+				if (EVP_DigestVerifyUpdate(bctx, " ", 1) == 0) {
+					dkim_errx(sig->header->msg,
+					    "EVP_DigestVerifyUpdate");
+					return;
+				}
+				ptr = end - 1;
+			} else {
+				if (EVP_DigestVerifyUpdate(bctx, ptr,
+				    end - ptr) == 0) {
+					dkim_errx(sig->header->msg,
+					    "EVP_DigestVerifyUpdate");
+					return;
+				}
+			}
+		}
+			
+	}
+	if (sig->header != header) {
+		if (EVP_DigestVerifyUpdate(bctx, "\r\n", 2) == 0) {
+			dkim_errx(sig->header->msg, "EVP_DigestVerifyUpdate");
+			return;
+		}
+	}
+}
+
+void
+dkim_signature_state(struct signature *sig, enum state state,
+    const char *reason)
+{
+	if (sig->query != NULL) {
+		event_asr_abort(sig->query);
+		sig->query = NULL;
+	}
+	switch (sig->state) {
+	case DKIM_UNKNOWN:
+		break;
+	case DKIM_PASS:
+	case DKIM_FAIL:
+		osmtpd_errx(1, "Unexpected transition");
+	case DKIM_POLICY:
+		if (state == DKIM_PASS)
+			return;
+		break;
+	case DKIM_NEUTRAL:
+		if (state == DKIM_PASS)
+			return;
+		if (state == DKIM_TEMPERROR || state == DKIM_PERMERROR)
+			break;
+		osmtpd_errx(1, "Unexpected transition");
+	case DKIM_TEMPERROR:
+		if (state == DKIM_PERMERROR)
+			break;
+		return;
+	case DKIM_PERMERROR:
+		return;
+	}
+	sig->state = state;
+	sig->state_reason = reason;
+}
+
+const char *
+dkim_state2str(enum state state)
+{
+	switch (state)
+	{
+	case DKIM_UNKNOWN:
+		return "unknown";
+	case DKIM_PASS:
+		return "pass";
+	case DKIM_FAIL:
+		return "fail";
+	case DKIM_POLICY:
+		return "policy";
+	case DKIM_NEUTRAL:
+		return "neutral";
+	case DKIM_TEMPERROR:
+		return "temperror";
+	case DKIM_PERMERROR:
+		return "permerror";
+	}
+}
+
+void
+dkim_rr_resolve(struct asr_result *ar, void *arg)
+{
+	struct signature *sig = arg;
+	char key[UINT16_MAX + 1];
+	const char *rr_txt;
+	size_t keylen, cstrlen;
+	struct unpack pack;
+	struct dns_header h;
+	struct dns_query q;
+	struct dns_rr rr;
+
+	sig->query = NULL;
+
+	if (ar->ar_h_errno == TRY_AGAIN || ar->ar_h_errno == NO_RECOVERY) {
+		dkim_signature_state(sig, DKIM_TEMPERROR,
+		    hstrerror(ar->ar_h_errno));
+		return;
+	}
+	if (ar->ar_h_errno != NETDB_SUCCESS) {
+		dkim_signature_state(sig, DKIM_PERMERROR,
+		    hstrerror(ar->ar_h_errno));
+		return;
+	}
+
+	unpack_init(&pack, ar->ar_data, ar->ar_datalen);
+	if (unpack_header(&pack, &h) != 0 ||
+	    unpack_query(&pack, &q) != 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Invalid dns/txt");
+		return;
+	}
+	for (; h.ancount > 0; h.ancount--) {
+		unpack_rr(&pack, &rr);
+		if (rr.rr_type != T_TXT)
+			continue;
+
+		keylen = 0;
+		rr_txt = rr.rr.other.rdata;
+		while (rr.rr.other.rdlen > 0) {
+			cstrlen = ((const unsigned char *)rr_txt)[0];
+			if (cstrlen >= rr.rr.other.rdlen ||
+			    keylen + cstrlen >= sizeof(key))
+				break;
+			/*
+			 * RFC 6376 Section 3.6.2.2
+			 * Strings in a TXT RR MUST be concatenated together
+			 * before use with no intervening whitespace.
+			 */
+			strlcpy(key + keylen, rr_txt + 1, cstrlen + 1);
+			rr.rr.other.rdlen -= (cstrlen + 1);
+			rr_txt += (cstrlen + 1);
+			keylen += cstrlen;
+		}
+		if (rr.rr.other.rdlen > 0)	/* Invalid TXT RDATA */
+			continue;
+
+		if (dkim_key_text_parse(sig, key))
+			break;
+	}
+
+	if (h.ancount == 0) {
+		dkim_signature_state(sig, DKIM_PERMERROR,
+		    "No matching key found");
+	} else {
+		/* Only verify if all headers have been read */
+		if (!sig->header->msg->parsing_headers)
+			dkim_signature_verify(sig);
+	}
+	dkim_message_verify(sig->header->msg);
+}
+
+int
+dkim_key_text_parse(struct signature *sig, char *key)
+{
+	char *end, *tagvend, tagname;
+	char tmp, pkraw[UINT16_MAX] = "", pkimp[UINT16_MAX];
+	size_t pkoff, linelen;
+	int h = 0, k = 0, n = 0, p = 0, s = 0, t = 0;
+	BIO *bio;
+
+	key = osmtpd_mheader_skip_fws(key, 1);
+	/* Validate syntax early */
+	if ((end = osmtpd_mheader_skip_dkimsig_taglist(key, 0)) == NULL)
+		return 0;
+
+	/*
+	 * RFC 6376 section 3.6.1, v=:
+	 * RECOMMENDED...This tag MUST be the first tag in the record.
+	 */
+	if (osmtpd_mheader_skip_dkimsig_tagname(key, 0) - key == 1 &&
+	    key[0] == 'v') {
+		key = osmtpd_mheader_skip_dkimsig_tagname(key, 0);
+		key = osmtpd_mheader_skip_fws(key, 1);
+		key++;	/* = */
+		key = osmtpd_mheader_skip_fws(key, 1);
+		end = osmtpd_mheader_skip_dkimsig_tagvalue(key, 0);
+		if (end - key != 5 || strncmp(key, "DKIM1", 5) != 0)
+			return 0;
+		key += 5;
+		key = osmtpd_mheader_skip_fws(key, 1);
+		if (key[0] == ';')
+			key++;
+	}
+	while (key[0] != '\0') {
+		key = osmtpd_mheader_skip_fws(key, 1);
+		end = osmtpd_mheader_skip_dkimsig_tagname(key, 0);
+
+		if ((size_t)(end - key) != 1) {
+			key = osmtpd_mheader_skip_fws(end, 1);
+			/* '=' */
+			key++;
+			key = osmtpd_mheader_skip_fws(key, 1);
+			key = osmtpd_mheader_skip_dkimsig_tagvalue(key, 0);
+			key = osmtpd_mheader_skip_fws(key, 1);
+			if (key[0] == ';')
+				key++;
+			continue;
+		}
+		tagname = key[0];
+		key = osmtpd_mheader_skip_fws(end, 1);
+		/* '=' */
+		key++;
+		key = osmtpd_mheader_skip_fws(key, 1);
+		end = osmtpd_mheader_skip_dkimsig_tagvalue(key, 0);
+		switch (tagname) {
+		case 'v':
+			/* tagname in wrong position */
+			return 0;
+		case 'h':
+			if (h != 0)	/* Duplicate tag */
+				return 0;
+			/* Invalid tag value */
+			if (osmtpd_mheader_skip_dkimsig_keyhtagvalue(
+			    key, 0) != end)
+				return 0;
+			while (1) {
+				if ((tagvend =
+				    osmtpd_mheader_skip_dkimsig_keyhtagvalue(
+				    key, 0)) != NULL)
+					break;
+				tmp = tagvend[0];
+				tagvend[0] = '\0';
+				if (EVP_get_digestbyname(key) == sig->ah) {
+					h = 1;
+					break;
+				}
+				tagvend[0] = tmp;
+				key = osmtpd_mheader_skip_fws(tagvend, 1);
+				if (key[0] != ':')
+					break;
+				key = osmtpd_mheader_skip_fws(key + 1, 1);
+			}
+			if (h != 1)
+				return 0;
+			key = end;
+			break;
+		case 'k':
+			if (k != 0)	/* Duplicate tag */
+				return 0;
+			k = 1;
+			if (strncmp(key, "rsa", end - key) != 0)
+				return 0;
+			key = end;
+			break;
+		case 'n':
+			if (n != 0)	/* Duplicate tag */
+				return 0;
+			n = 1;
+			key = end;
+			break;
+		case 'p':
+			if (p != 0)	/* Duplicate tag */
+				return 0;
+			p = 1;
+			tagvend = osmtpd_mheader_skip_base64string(key, 1);
+			/* Invalid tag value */
+			if (tagvend != end ||
+			    (size_t)(end - key) >= sizeof(pkraw))
+				return 0;
+			strlcpy(pkraw, key, tagvend - key + 1);
+			key = end;
+			break;
+		case 's':
+			if (s != 0)	/* Duplicate tag */
+				return 0;
+			/* Invalid tag value */
+			if (osmtpd_mheader_skip_dkimsig_keystagvalue(key, 0) !=
+			    end)
+				return 0;
+			while (1) {
+				if ((tagvend =
+				    osmtpd_mheader_skip_dkimsig_keystagtype(
+				    key, 0)) != NULL)
+					break;
+				if (strncmp(key, "*", tagvend - key) == 0 ||
+				    strncmp(key, "email", tagvend - key) == 0) {
+					s = 1;
+					break;
+				}
+				key = osmtpd_mheader_skip_fws(tagvend, 1);
+				if (key[0] != ':')
+					break;
+				key = osmtpd_mheader_skip_fws(key + 1, 1);
+			}
+			if (s != 1)
+				return 0;
+			key = end;
+			break;
+		case 't':
+			if (t != 0)	/* Duplicate tag */
+				return 0;
+			t = 1;
+			if (osmtpd_mheader_skip_dkimsig_keystagtype(key, 0) !=
+			    end)
+				return 0;
+			while (1) {
+				tagvend =
+				    osmtpd_mheader_skip_dkimsig_keyttagflag(
+				    key, 0);
+				if (strncmp(key, "y", tagvend - key) == 0)
+					sig->kt |= KT_Y;
+				else if (strncmp(key, "s", tagvend - key) == 0)
+					sig->kt |= KT_S;
+				key = osmtpd_mheader_skip_fws(tagvend, 1);
+				if (key[0] != ':')
+					break;
+				key = osmtpd_mheader_skip_fws(key + 1, 1);
+			}
+			break;
+		default:
+			key = end;
+			break;
+		}
+
+		key = osmtpd_mheader_skip_fws(key, 1);
+		if (key[0] == ';')
+			key++;
+		else if (key[0] != '\0')
+			return 0;
+	}
+
+	if (p == 0)		/* Missing tag */
+		return 0;
+	if (k == 0 && sig->ak != EVP_PKEY_RSA)	/* Default to RSA */
+		return 0;
+
+	if (pkraw[0] == '\0') {
+		dkim_signature_state(sig, DKIM_PERMERROR, "Key is revoked");
+		return 1;
+	}
+
+	switch (sig->ak) {
+	case EVP_PKEY_RSA:
+		pkoff = strlcpy(pkimp, "-----BEGIN PUBLIC KEY-----\n",
+		    sizeof(pkimp));
+		linelen = 0;
+		for (key = pkraw; key[0] != '\0';) {
+			if (pkoff + 2 >= sizeof(pkimp))
+				return 0;
+			pkimp[pkoff++] = key[0];
+			if (++linelen == 64) {
+				pkimp[pkoff++] = '\n';
+				linelen = 0;
+			}
+			key = osmtpd_mheader_skip_fws(key + 1, 1);
+		}
+		/* Leverage pkoff check in loop */
+		if (linelen != 0)
+			pkimp[pkoff++] = '\n';
+		/* PEM_read_bio_PUBKEY will catch truncated keys */
+		pkoff += strlcpy(pkimp + pkoff, "-----END PUBLIC KEY-----\n",
+		    sizeof(pkimp) - pkoff);
+		if ((bio = BIO_new_mem_buf(pkimp, pkoff)) == NULL) {
+			dkim_err(sig->header->msg, "BIO_new_mem_buf");
+			return 1;
+		}
+		sig->p = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
+		BIO_free(bio);
+		if (sig->p == NULL) {
+			/*
+			 * XXX No clue how to differentiate between invalid key
+			 * and temporary failure like *alloc.
+			 * Assume invalid key, because it's more likely.
+			 */
+			return 0;
+		}
+		break;
+	}
+	return 1;
+}
+
+void
+dkim_body_parse(struct message *msg, const char *line)
+{
+	char buf[999];	/* Line limit + 1 */
+	struct signature *sig;
+	size_t r, w;
+	ssize_t linelen;
+	size_t hashn, hashlf;
+	size_t i;
+	int canon = CANON_BODY_SIMPLE;
+
+	linelen = (ssize_t)strlcpy(buf, line, sizeof(buf));
+	if (linelen >= (ssize_t)sizeof(buf)) {
+		dkim_errx(msg, "Line too long");
+		return;
+	}
+	if (buf[0] == '\0') {
+		msg->body_whitelines++;
+		return;
+	}
+
+	while (msg->body_whitelines-- > 0) {
+		for (i = 0; i < msg->nheaders; i++) {
+			if ((sig = msg->header[i].sig) == NULL)
+				continue;
+			hashlf = sig->l == -1 ? 2 : MIN(2, sig->l);
+			sig->l -= sig->l == -1 ? 0 : hashlf;
+			if (EVP_DigestUpdate(sig->bhctx, "\r\n", hashlf) == 0) {
+				dkim_errx(msg, "Can't update hash context");
+				return;
+			}
+		}
+	}
+	msg->body_whitelines = 0;
+	msg->has_body = 1;
+
+ hash:
+	for (i = 0; i < msg->nheaders; i++) {
+		sig = msg->header[i].sig;
+		if (sig == NULL || ((sig->c & CANON_BODY) != canon) ||
+		    sig->state != DKIM_UNKNOWN)
+			continue;
+		hashn = sig->l == -1 ? linelen : MIN(linelen, sig->l);
+		sig->l -= sig->l == -1 ? 0 : hashn;
+		hashlf = sig->l == -1 ? 2 : MIN(2, sig->l);
+		sig->l -= sig->l == -1 ? 0 : hashlf;
+		if (EVP_DigestUpdate(sig->bhctx, buf, hashn) == 0 ||
+		    EVP_DigestUpdate(sig->bhctx, "\r\n", hashlf) == 0) {
+			dkim_errx(msg, "Can't update hash context");
+			return;
+		}
+	}
+	if (canon == CANON_BODY_RELAXED)
+		return;
+	canon = CANON_BODY_RELAXED;
+	for (r = w = 0; buf[r] != '\0'; r++) {
+		if (buf[r] == ' ' || buf[r] == '\t') {
+			if (r != 0 && buf[w - 1] == ' ')
+				continue;
+			else
+				buf[w++] = ' ';
+		} else
+			buf[w++] = buf[r];
+	}
+	linelen = (w != 0 && buf[w - 1] == ' ') ? w - 1 : w;
+	buf[linelen] = '\0';
+
+	goto hash;
+}
+
+void
+dkim_body_verify(struct signature *sig)
+{
+	unsigned char digest[EVP_MAX_MD_SIZE];
+	unsigned int digestsz;
+
+	if (sig->state != DKIM_UNKNOWN)
+		return;
+
+	if ((sig->c & CANON_BODY) == CANON_BODY_SIMPLE &&
+	    !sig->header->msg->has_body) {
+		if (EVP_DigestUpdate(sig->bhctx, "\r\n",
+		    sig->l == -1 ? 2 : MIN(2, sig->l)) <= 0) {
+			dkim_errx(sig->header->msg,
+			    "Can't update hash context");
+			return;
+		}
+	}
+
+	if (EVP_DigestFinal_ex(sig->bhctx, digest, &digestsz) == 0) {
+		dkim_errx(sig->header->msg, "Can't finalize hash context");
+		return;
+	}
+
+	if (digestsz != sig->bhsz || memcmp(digest, sig->bh, digestsz) != 0)
+		dkim_signature_state(sig, DKIM_FAIL, "bh mismatch");
+}
+
+void
+dkim_message_verify(struct message *msg)
+{
+	struct signature *sig;
+	size_t i;
+	ssize_t n, aroff = 0;
+	int found = 0;
+	char *line = NULL;
+	size_t linelen = 0;
+
+	if (!msg->readdone)
+		return;
+
+	for (i = 0; i < msg->nheaders; i++) {
+		if (msg->header[i].sig == NULL)
+			continue;
+		if (msg->header[i].sig->query != NULL)
+			return;
+		if (msg->header[i].sig->state != DKIM_UNKNOWN)
+			continue;
+		dkim_signature_state(msg->header[i].sig, DKIM_PASS, NULL);
+	}
+	
+	if ((aroff = dkim_ar_cat(&line, &linelen, aroff,
+	    "Authentication-Results: %s", authservid)) == -1) {
+		dkim_err(msg, "malloc");
+		goto fail;
+	}
+	for (i = 0; i < msg->nheaders; i++) {
+		sig = msg->header[i].sig;
+		if (sig == NULL)
+			continue;
+		found = 1;
+		if ((aroff = dkim_ar_cat(&line, &linelen, aroff, "; dkim=%s",
+		    dkim_state2str(sig->state))) == -1) {
+			dkim_err(msg, "malloc");
+			goto fail;
+		}
+		if (sig->state_reason != NULL) {
+			if ((aroff = dkim_ar_cat(&line, &linelen, aroff,
+			    " reason=\"%s\"", sig->state_reason)) == -1) {
+				dkim_err(msg, "malloc");
+				goto fail;
+			}
+		}
+		if (sig->s[0] != '\0') {
+			if ((aroff = dkim_ar_cat(&line, &linelen, aroff,
+			    " header.s=%s", sig->s)) == -1) {
+				dkim_err(msg, "malloc");
+				goto fail;
+			}
+		}
+		if (sig->d[0] != '\0') {
+			if ((aroff = dkim_ar_cat(&line, &linelen, aroff,
+			    " header.d=%s", sig->d)) == -1) {
+				dkim_err(msg, "malloc");
+				goto fail;
+			}
+		}
+		/*
+		 * Don't print i-tag, since localpart can be a quoted-string,
+		 * which can contain FWS and CFWS.
+		 */
+		if (sig->a != NULL) {
+			if ((aroff = dkim_ar_cat(&line, &linelen, aroff,
+			    " header.a=%.*s", (int)sig->asz, sig->a)) == -1) {
+				dkim_err(msg, "malloc");
+				goto fail;
+			}
+		}
+	}
+	if (!found) {
+		aroff = dkim_ar_cat(&line, &linelen, aroff, "; dkim=none");
+		if (aroff == -1) {
+			dkim_err(msg, "malloc");
+			goto fail;
+		}
+	}
+	dkim_ar_print(msg->ctx, line);
+
+	rewind(msg->origf);
+	while ((n = getline(&line, &linelen, msg->origf)) != -1) {
+		line[n - 1] = '\0';
+		osmtpd_filter_dataline(msg->ctx, "%s", line);
+	}
+	if (ferror(msg->origf))
+		dkim_err(msg, "getline");
+ fail:
+	free(line);
+	return;
+}
+
+void
+dkim_ar_print(struct osmtpd_ctx *ctx, char *start)
+{
+	char *scan, *checkpoint, *ncheckpoint;
+	size_t arlen = 0;
+	int first = 1, arid = 1;
+
+	checkpoint = start;
+	ncheckpoint = osmtpd_mheader_skip_hdrname(start, 0) + 1;
+	for (scan = start; scan[0] != '\0'; scan++) {
+		if (scan[0] == '\t')
+			arlen = (arlen + 8) & ~7;
+		else
+			arlen++;
+		if (arlen >= AUTHENTICATION_RESULTS_LINELEN) {
+			osmtpd_filter_dataline(ctx, "%s%.*s", first ? "" : "\t",
+			    (int)((checkpoint == start ?
+			    ncheckpoint : checkpoint) - start), start);
+			start = osmtpd_mheader_skip_cfws(checkpoint, 1);
+			scan = start;
+			arlen = 8;
+			first = 0;
+		}
+		if (scan == ncheckpoint) {
+			checkpoint = ncheckpoint;
+			ncheckpoint = osmtpd_mheader_skip_cfws(ncheckpoint, 1);
+			/* authserv-id */
+			if (arid) {
+				ncheckpoint = osmtpd_mheader_skip_value(
+				    ncheckpoint, 0);
+				arid = 0;
+			/* methodspec */
+			} else if (strncmp(ncheckpoint, "dkim",
+			    sizeof("dkim") - 1) == 0) {
+				ncheckpoint = osmtpd_mheader_skip_keyword(
+				    ncheckpoint + sizeof("dkim"), 0);
+			/* reasonspec */
+			} else if (strncmp(ncheckpoint, "reason",
+			    sizeof("reason") - 1) == 0) {
+				ncheckpoint = osmtpd_mheader_skip_value(
+				    ncheckpoint + sizeof("reason"), 0);
+			/* propspec */
+			} else {
+				ncheckpoint += sizeof("header.x=") - 1;
+				ncheckpoint = osmtpd_mheader_skip_ar_pvalue(
+				    ncheckpoint, 0);
+				if (ncheckpoint[0] == ';')
+					ncheckpoint++;
+			}
+		}
+	}
+	osmtpd_filter_dataline(ctx, "%s%s", first ? "" : "\t", start);
+}
+
+ssize_t
+dkim_ar_cat(char **ar, size_t *n, size_t aroff, const char *fmt, ...)
+{
+	va_list ap;
+	char *artmp;
+	int size;
+	size_t nn;
+
+	assert(*n >= aroff);
+	va_start(ap, fmt);
+	size = vsnprintf(*ar + aroff, *n - aroff, fmt, ap);
+	va_end(ap);
+	if (size + aroff <= *n)
+		return (ssize_t)size + aroff;
+	nn = (((aroff + size)  / 256) + 1) * 256;
+	artmp = realloc(*ar, nn);
+	if (artmp == NULL)
+		return -1;
+	*ar = artmp;
+	*n = nn;
+	va_start(ap, fmt);
+	size = vsnprintf(*ar + aroff, *n - aroff, fmt, ap);
+	va_end(ap);
+	return (ssize_t)size + aroff;
+}
+
+void
+dkim_err(struct message *msg, char *text)
+{
+	msg->err = 1;
+	fprintf(stderr, "%s: %s\n", text, strerror(errno));
+}
+
+void
+dkim_errx(struct message *msg, char *text)
+{
+	msg->err = 1;
+	fprintf(stderr, "%s\n", text);
+}
+
+__dead void
+usage(void)
+{
+	fprintf(stderr, "usage: filter-dkimverify\n");
+	exit(1);
+}
blob - /dev/null
blob + a673215a42df4f74a3c93a4b16ba0b6292a5f430 (mode 644)
--- /dev/null
+++ mheader.c
@@ -0,0 +1,1395 @@
+/*
+ * 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"
+
+#include <stdio.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_char(char *ptr, int optional)
+{
+	if (ptr[0] >= 0x01 && ptr[0] <= 0x7f)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_ctl(char *ptr, int optional)
+{
+	if ((ptr[0] >= 0x00 && ptr[0] <= 0x1f) || ptr[0] == 0x7f)
+		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 ? start : 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_letdig(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 optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_ldhstring(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+	int letdig = 0;
+
+	while (1) {
+		prev = ptr;
+		if ((ptr = osmtpd_mheader_skip_alpha(prev, 0)) != NULL ||
+		    (ptr = osmtpd_mheader_skip_digit(prev, 0)) != NULL) {
+			letdig = 1;
+			continue;
+		}
+		if (prev[0] == '-') {
+			letdig = 0;
+			ptr = prev + 1;
+			continue;
+		}
+		ptr = prev;
+		break;
+	}
+	if (letdig)
+		return ptr;
+	if (ptr == start)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_dquote(char *ptr, int optional)
+{
+	if (ptr[0] == 0x22)
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_hexoctet(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if (ptr[0] != '=')
+		return optional ? ptr : NULL;
+	ptr++;
+	if (ptr[0] == 'A' || ptr[0] == 'B' || ptr[0] == 'C' || ptr[0] == 'D' ||
+	    ptr[0] == 'E' || ptr[0] == 'F')
+		ptr++;
+	else if ((ptr = osmtpd_mheader_skip_digit(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if (ptr[0] == 'A' || ptr[0] == 'B' || ptr[0] == 'C' || ptr[0] == 'D' ||
+	    ptr[0] == 'E' || ptr[0] == 'F')
+		ptr++;
+	else if ((ptr = osmtpd_mheader_skip_digit(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+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_keyword(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_ldhstring(ptr, optional);
+}
+
+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] == '.') {
+			ptr++;
+			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_subdomain(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_letdig(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_ldhstring(ptr, 1);
+}
+
+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;
+	ptr++;
+	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 = ptr;
+
+	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;
+}
+
+char *
+osmtpd_mheader_skip_alphadigitps(char *ptr, int optional)
+{
+	char *end;
+
+	if ((end = osmtpd_mheader_skip_alpha(ptr, 0)) == NULL &&
+	    (end = osmtpd_mheader_skip_digit(ptr, 0)) == NULL &&
+	    ptr[0] != '+' && ptr[0] != '/')
+		return optional ? ptr : NULL;
+	return end == NULL ? ptr + 1 : end;
+}
+
+char *
+osmtpd_mheader_skip_base64string(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_alphadigitps(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if ((ptr = osmtpd_mheader_skip_alphadigitps(ptr, 0)) == NULL)
+			break;
+	}
+	ptr = start;
+	ptr = osmtpd_mheader_skip_fws(ptr, 1);
+	if (ptr[0] == '=') {
+		ptr++;
+		start = ptr;
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if (ptr[0] == '=')
+			ptr++;
+		else
+			ptr = start;
+	} else
+		ptr = start;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_hyphenatedword(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+	int ishyp = 0;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+
+	prev = ptr;
+	while (1) {
+		if (ptr[0] == '-') {
+			ishyp = 1;
+			ptr++;
+			continue;
+		}
+		start = ptr;
+		if ((ptr = osmtpd_mheader_skip_alpha(start, 0)) == NULL &&
+		    (ptr = osmtpd_mheader_skip_digit(start, 0)) == NULL)
+			break;
+		ishyp = 0;
+		prev = ptr;
+		
+	}
+	return ishyp ? prev : ptr;
+}
+
+char *
+osmtpd_mheader_skip_ftext(char *ptr, int optional)
+{
+	if ((ptr[0] >= 33 && ptr[0] <= 57) ||
+	    (ptr[0] >= 59 && ptr[0] <= 126))
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_fieldname(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_ftext(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		if ((ptr = osmtpd_mheader_skip_ftext(ptr, 0)) == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_hdrname(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_fieldname(ptr, optional);
+}
+
+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)
+{
+	char *start;
+	int first = 1;
+
+	while (1) {
+		start = ptr;
+		if ((ptr = osmtpd_mheader_skip_char(start, 0)) != NULL &&
+		    osmtpd_mheader_skip_sp(start, 0) == NULL &&
+		    osmtpd_mheader_skip_ctl(start, 0) == NULL &&
+		    osmtpd_mheader_skip_tspecials(start, 0) == NULL) {
+			first = 0;
+			continue;
+		}
+		return optional || !first ? start : NULL;
+	}
+}
+
+char *
+osmtpd_mheader_skip_value(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_token(start, 0)) != NULL)
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_quoted_string(start, 0)) != NULL)
+		return ptr;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_dkim_safe_char(char *ptr, int optional)
+{
+	if ((ptr[0] >= 0x21 && ptr[0] <= 0x3a) || ptr[0] == 0x3c ||
+	    (ptr[0] >= 0x3e && ptr[0] <= 0x7e))
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_dkim_quoted_printable(char *ptr, int optional)
+{
+	char *start;
+
+	while (1) {
+		start = ptr;
+		if ((ptr = osmtpd_mheader_skip_fws(start, 0)) != NULL)
+			continue;
+		if ((ptr = osmtpd_mheader_skip_hexoctet(start, 0)) != NULL)
+			continue;
+		ptr = osmtpd_mheader_skip_dkim_safe_char(start, 0);
+		if (ptr == NULL)
+			break;
+	}
+	return start;
+}
+
+char *
+osmtpd_mheader_skip_dkim_qp_hdr_value(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_dkim_quoted_printable(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_alnumpunc(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(start, 0)) != NULL)
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_digit(start, 0)) != NULL)
+		return ptr;
+	if (start[0] == '_')
+		return start + 1;
+	return optional ? start : NULL;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_valchar(char *ptr, int optional)
+{
+	if ((ptr[0] >= 0x21 && ptr[0] <= 0x3A) ||
+	    (ptr[0] >= 0x3C && ptr[0] <= 0x7E))
+		return ptr + 1;
+	return optional ? ptr : NULL;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_tval(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_valchar(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while ((ptr = osmtpd_mheader_skip_dkimsig_valchar(ptr, 0)) != NULL)
+		prev = ptr;
+	return prev;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_tagvalue(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_tval(ptr, 0)) == NULL)
+		return start;
+
+	while (1) {
+		start = ptr;
+		/* FWS contains WSP */
+		if ((ptr = osmtpd_mheader_skip_fws(ptr, 0)) == NULL)
+			return start;
+		prev = ptr;
+		while ((ptr = osmtpd_mheader_skip_fws(ptr, 0)) != NULL)
+			prev = ptr;
+		ptr = prev;
+		if ((ptr = osmtpd_mheader_skip_dkimsig_tval(ptr, 0)) == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_tagname(char *ptr, int optional)
+{
+	char *start = ptr, *prev;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while ((ptr = osmtpd_mheader_skip_dkimsig_alnumpunc(ptr, 0)) != NULL)
+		prev = ptr;
+	return prev;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_tagspec(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	ptr = osmtpd_mheader_skip_fws(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_dkimsig_tagname(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	ptr = osmtpd_mheader_skip_fws(ptr, 1);
+	if (ptr[0] != '=')
+		return optional ? start : NULL;
+	ptr++;
+	ptr = osmtpd_mheader_skip_fws(ptr, 1);
+	if ((ptr = osmtpd_mheader_skip_dkimsig_tagvalue(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return osmtpd_mheader_skip_fws(ptr, 1);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_taglist(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_tagspec(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		/* Starting or trailing ';' */
+		if (ptr[0] != ';')
+			return ptr;
+		ptr++;
+		start = ptr;
+		if ((ptr = osmtpd_mheader_skip_dkimsig_tagspec(ptr, 0)) == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xsigatagh(char *ptr, int optional)
+{
+	char *start = ptr, *prev, *end;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while ((end = osmtpd_mheader_skip_alpha(ptr, 0)) != NULL ||
+	    (end = osmtpd_mheader_skip_digit(ptr, 0)) != NULL) {
+		ptr = end;
+		prev = end;
+	}
+	return prev;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xsigatagk(char *ptr, int optional)
+{
+	char *start = ptr, *prev, *end;
+
+	if ((ptr = osmtpd_mheader_skip_alpha(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	prev = ptr;
+	while ((end = osmtpd_mheader_skip_alpha(ptr, 0)) != NULL ||
+	    (end = osmtpd_mheader_skip_digit(ptr, 0)) != NULL) {
+		ptr = end;
+		prev = end;
+	}
+	return prev;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigatagh(char *ptr, int optional)
+{
+	/* rsa / ed25519 covered by x-sig-a-tag-h */
+	return osmtpd_mheader_skip_dkimsig_xsigatagh(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigatagk(char *ptr, int optional)
+{
+	/* sha1 / sha256 covered by x-sig-a-tag-k */
+	return osmtpd_mheader_skip_dkimsig_xsigatagk(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigatagalg(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_sigatagk(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	if (ptr[0] != '-')
+		return optional ? start : NULL;
+	ptr++;
+	if ((ptr = osmtpd_mheader_skip_dkimsig_sigatagh(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xsigctagalg(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_hyphenatedword(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigctagalg(char *ptr, int optional)
+{
+	/* simple / relaxed covered by x-sig-c-tag-alga */
+	return osmtpd_mheader_skip_dkimsig_xsigctagalg(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xkeyhtagalg(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_hyphenatedword(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keyhtagalg(char *ptr, int optional)
+{
+	/* sha1 / sha256 covered by x-key-h-tag-alg */
+	return osmtpd_mheader_skip_dkimsig_xkeyhtagalg(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keyhtagvalue(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_keyhtagalg(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if (ptr[0] != ':')
+			return start;
+		ptr = osmtpd_mheader_skip_fws(ptr + 1, 1);
+		ptr = osmtpd_mheader_skip_dkimsig_keyhtagalg(ptr, 0);
+		if (ptr == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xsigqtagargs(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_dkim_qp_hdr_value(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xsigqtagtype(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_hyphenatedword(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigqtagmethod(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	/* dns/txt covered by x-sig-q-tag-type ["/" x-sig-q-tag-args] */
+	if ((ptr = osmtpd_mheader_skip_dkimsig_xsigqtagtype(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	start = ptr;
+	if (ptr[0] != '/')
+		return ptr;
+	if ((ptr = osmtpd_mheader_skip_dkimsig_xsigqtagargs(ptr, 0)) == NULL)
+		return start;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigztagcopy(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_hdrname(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	ptr = osmtpd_mheader_skip_fws(ptr, 1);
+	if (ptr[0] != ':')
+		return optional ? start : NULL;
+	if ((ptr = osmtpd_mheader_skip_dkim_qp_hdr_value(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_sigztagvalue(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_sigztagcopy(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		if (ptr[0] != '|')
+			return start;
+		osmtpd_mheader_skip_fws(ptr + 1, 1);
+		ptr = osmtpd_mheader_skip_dkimsig_sigztagcopy(ptr, 0);
+		if (ptr == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xkeystagtype(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_hyphenatedword(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keystagtype(char *ptr, int optional)
+{
+	if (ptr[0] == '*')
+		return ptr + 1;
+	/* email covered by x-key-s-tag-type */
+	return osmtpd_mheader_skip_dkimsig_xkeystagtype(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keystagvalue(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_keystagtype(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if (ptr[0] != ':')
+			return start;
+		ptr = osmtpd_mheader_skip_fws(ptr + 1, 1);
+		ptr = osmtpd_mheader_skip_dkimsig_keystagtype(ptr, 0);
+		if (ptr == NULL)
+			return start;
+	}
+	
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_xkeyttagflag(char *ptr, int optional)
+{
+	return osmtpd_mheader_skip_hyphenatedword(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keyttagflag(char *ptr, int optional)
+{
+	/* y / s covered by x-key-t-tag-flag */
+	return osmtpd_mheader_skip_dkimsig_xkeyttagflag(ptr, optional);
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_keyttagvalue(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_dkimsig_keyttagflag(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		ptr = osmtpd_mheader_skip_fws(ptr, 1);
+		if (ptr[0] != ':')
+			return start;
+		ptr = osmtpd_mheader_skip_fws(ptr + 1, 1);
+		ptr = osmtpd_mheader_skip_dkimsig_keyttagflag(ptr, 0);
+		if (ptr == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_dkimsig_selector(char *ptr, int optional)
+{
+	char *start = ptr;
+
+	if ((ptr = osmtpd_mheader_skip_subdomain(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	while (1) {
+		start = ptr;
+		if (ptr[0] != '.')
+			return start;
+		ptr++;
+		if ((ptr = osmtpd_mheader_skip_subdomain(ptr, 0)) == NULL)
+			return start;
+	}
+}
+
+char *
+osmtpd_mheader_skip_ar_pvalue(char *ptr, int optional)
+{
+	char *start = ptr, *tmp;
+
+	ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+	if ((tmp = osmtpd_mheader_skip_value(ptr, 0)) != NULL)
+		return tmp;
+	ptr = osmtpd_mheader_skip_local_part(ptr, 1);
+	if (ptr[0] == '@')
+		ptr++;
+	if ((ptr = osmtpd_mheader_skip_domain(ptr, 0)) == NULL)
+		return optional ? start : NULL;
+	return ptr;
+}
+
+char *
+osmtpd_mheader_domain_uncomment(char *ptr)
+{
+	char *domain0, *domain, *tmp, *end;
+
+	if (osmtpd_mheader_skip_dot_atom(ptr, 0) != NULL) {
+		ptr = osmtpd_mheader_skip_cfws(ptr, 1);
+		return strndup(ptr,
+		    osmtpd_mheader_skip_dot_atom_text(ptr, 0) - ptr);
+	}
+	if ((tmp = osmtpd_mheader_skip_domain_literal(ptr, 0)) != NULL) {
+		ptr = osmtpd_mheader_skip_cfws(ptr, 1) + 1;
+		domain0 = domain = strndup(ptr, (size_t)(tmp - ptr));
+		if (domain0 == NULL)
+			return NULL;
+		end = domain0 + (tmp - ptr) + 1;
+		domain++;
+		while (1) {
+			tmp = osmtpd_mheader_skip_fws(domain, 1);
+			if (tmp != domain) {
+				memmove(domain, tmp, end - tmp);
+				end -= (tmp - domain);
+			}
+			tmp = osmtpd_mheader_skip_dtext(domain, 0);
+			if (tmp == NULL)
+				break;
+			domain = tmp;
+		}
+		/* domain[0] ==  ']' */
+		domain[0] = '\0';
+		return domain0;
+	}
+	return strndup(ptr, osmtpd_mheader_skip_obs_domain(ptr, 1) - 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++;
+		return osmtpd_mheader_domain_uncomment(ptr);
+	}
+	if (osmtpd_mheader_skip_addr_spec(ptr, 0) != NULL) {
+		ptr = osmtpd_mheader_skip_local_part(ptr, 0);
+		/* @ */
+		ptr++;
+		return osmtpd_mheader_domain_uncomment(ptr);
+	}
+	errno = EINVAL;
+	return NULL;
+}
+
+char *
+osmtpd_mheader_quoted_string_normalize(char *ptr)
+{
+	char *end;
+	size_t d = 0, s;
+
+	end = osmtpd_mheader_skip_cfws(ptr, 1);
+	s = end - ptr;
+	if (osmtpd_mheader_skip_dquote(end, 0) == NULL)
+		return NULL;
+	ptr[d++] = ptr[s++];
+	while (ptr[s] != '\0') {
+		if (osmtpd_mheader_skip_quoted_pair(ptr + s, 0) != NULL) {
+			end = osmtpd_mheader_skip_qtext(ptr + s + 1, 0);
+			if (end != NULL)
+				s++;
+			else
+				ptr[d++] = ptr[s++];
+			ptr[d++] = ptr[s++];
+			continue;
+		} else if (osmtpd_mheader_skip_qtext(ptr + s, 0) != NULL) {
+			ptr[d++] = ptr[s++];
+		} else if ((end = osmtpd_mheader_skip_fws(
+		    ptr + s, 0)) != NULL) {
+			ptr[d++] = ' ';
+			s = end - ptr;
+		} else
+			return NULL;
+	}
+	if (osmtpd_mheader_skip_dquote(end, 0) == NULL)
+		return NULL;
+	ptr[d++] = ptr[s++];
+	end = osmtpd_mheader_skip_cfws(ptr + s, 1);
+	if (end[0] != '\0')
+		return NULL;
+	ptr[d] = '\0';
+	return ptr;
+}
blob - /dev/null
blob + 7a68b1f66b7e086387bc60bc77076b4a36d5f149 (mode 644)
--- /dev/null
+++ mheader.h
@@ -0,0 +1,119 @@
+/*
+ * 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_char(char *, int);
+char *osmtpd_mheader_skip_ctl(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_letdig(char *, int);
+char *osmtpd_mheader_skip_ldhstring(char *, int);
+char *osmtpd_mheader_skip_dquote(char *, int);
+char *osmtpd_mheader_skip_hexoctet(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_keyword(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_subdomain(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_alphadigitps(char *, int);
+char *osmtpd_mheader_skip_base64string(char *, int);
+char *osmtpd_mheader_skip_hyphenatedword(char *, int);
+char *osmtpd_mheader_skip_ftext(char *, int);
+char *osmtpd_mheader_skip_fieldname(char *, int);
+char *osmtpd_mheader_skip_hdrname(char *, int);
+char *osmtpd_mheader_skip_tspecials(char *, int);
+char *osmtpd_mheader_skip_token(char *, int);
+char *osmtpd_mheader_skip_value(char *, int);
+
+/* DKIM-Signature */
+char *osmtpd_mheader_skip_dkim_safe_char(char *, int);
+char *osmtpd_mheader_skip_dkim_quoted_printable(char *, int);
+char *osmtpd_mheader_skip_dkim_qp_hdr_value(char *, int);
+char *osmtpd_mheader_skip_dkimsig_alnumpunc(char *, int);
+char *osmtpd_mheader_skip_dkimsig_valchar(char *, int);
+char *osmtpd_mheader_skip_dkimsig_tval(char *, int);
+char *osmtpd_mheader_skip_dkimsig_tagvalue(char *, int);
+char *osmtpd_mheader_skip_dkimsig_tagname(char *, int);
+char *osmtpd_mheader_skip_dkimsig_tagspec(char *, int);
+char *osmtpd_mheader_skip_dkimsig_taglist(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xsigatagh(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xsigatagk(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigatagh(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigatagk(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigatagalg(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xsigctagalg(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigctagalg(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xkeyhtagalg(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xsigqtagargs(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xsigqtagtype(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigqtagmethod(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigztagcopy(char *, int);
+char *osmtpd_mheader_skip_dkimsig_sigztagvalue(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keyhtagalg(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keyhtagvalue(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xkeystagtype(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keystagtype(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keystagvalue(char *, int);
+char *osmtpd_mheader_skip_dkimsig_xkeyttagflag(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keyttagflag(char *, int);
+char *osmtpd_mheader_skip_dkimsig_keyttagvalue(char *, int);
+char *osmtpd_mheader_skip_dkimsig_selector(char *, int);
+
+/* Authentication-Results */
+char *osmtpd_mheader_skip_ar_pvalue(char *, int);
+
+char *osmtpd_mheader_domain_uncomment(char *);
+char *osmtpd_mheader_from_domain(char *);
+
+char *osmtpd_mheader_quoted_string_normalize(char *);
blob - /dev/null
blob + 8108226582c2bcd1ac27577fcb0ffc0dc2f318a7 (mode 644)
--- /dev/null
+++ unpack_dns.c
@@ -0,0 +1,295 @@
+/*	$OpenBSD: unpack_dns.c,v 1.1 2018/01/06 07:57:53 sunil Exp $	*/
+
+/*
+ * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net>
+ *
+ * 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 <arpa/inet.h>
+
+#include <string.h>
+
+#include "unpack_dns.h"
+
+static int unpack_data(struct unpack *, void *, size_t);
+static int unpack_u16(struct unpack *, uint16_t *);
+static int unpack_u32(struct unpack *, uint32_t *);
+static int unpack_inaddr(struct unpack *, struct in_addr *);
+static int unpack_in6addr(struct unpack *, struct in6_addr *);
+static int unpack_dname(struct unpack *, char *, size_t);
+
+void
+unpack_init(struct unpack *unpack, const char *buf, size_t len)
+{
+	unpack->buf = buf;
+	unpack->len = len;
+	unpack->offset = 0;
+	unpack->err = NULL;
+}
+
+int
+unpack_header(struct unpack *p, struct dns_header *h)
+{
+	if (unpack_data(p, h, HFIXEDSZ) == -1)
+		return (-1);
+
+	h->flags = ntohs(h->flags);
+	h->qdcount = ntohs(h->qdcount);
+	h->ancount = ntohs(h->ancount);
+	h->nscount = ntohs(h->nscount);
+	h->arcount = ntohs(h->arcount);
+
+	return (0);
+}
+
+int
+unpack_query(struct unpack *p, struct dns_query *q)
+{
+	unpack_dname(p, q->q_dname, sizeof(q->q_dname));
+	unpack_u16(p, &q->q_type);
+	unpack_u16(p, &q->q_class);
+
+	return (p->err) ? (-1) : (0);
+}
+
+int
+unpack_rr(struct unpack *p, struct dns_rr *rr)
+{
+	uint16_t	rdlen;
+	size_t		save_offset;
+
+	unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname));
+	unpack_u16(p, &rr->rr_type);
+	unpack_u16(p, &rr->rr_class);
+	unpack_u32(p, &rr->rr_ttl);
+	unpack_u16(p, &rdlen);
+
+	if (p->err)
+		return (-1);
+
+	if (p->len - p->offset < rdlen) {
+		p->err = "too short";
+		return (-1);
+	}
+
+	save_offset = p->offset;
+
+	switch (rr->rr_type) {
+
+	case T_CNAME:
+		unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname));
+		break;
+
+	case T_MX:
+		unpack_u16(p, &rr->rr.mx.preference);
+		unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange));
+		break;
+
+	case T_NS:
+		unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname));
+		break;
+
+	case T_PTR:
+		unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname));
+		break;
+
+	case T_SOA:
+		unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname));
+		unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname));
+		unpack_u32(p, &rr->rr.soa.serial);
+		unpack_u32(p, &rr->rr.soa.refresh);
+		unpack_u32(p, &rr->rr.soa.retry);
+		unpack_u32(p, &rr->rr.soa.expire);
+		unpack_u32(p, &rr->rr.soa.minimum);
+		break;
+
+	case T_A:
+		if (rr->rr_class != C_IN)
+			goto other;
+		unpack_inaddr(p, &rr->rr.in_a.addr);
+		break;
+
+	case T_AAAA:
+		if (rr->rr_class != C_IN)
+			goto other;
+		unpack_in6addr(p, &rr->rr.in_aaaa.addr6);
+		break;
+	default:
+	other:
+		rr->rr.other.rdata = p->buf + p->offset;
+		rr->rr.other.rdlen = rdlen;
+		p->offset += rdlen;
+	}
+
+	if (p->err)
+		return (-1);
+
+	/* make sure that the advertised rdlen is really ok */
+	if (p->offset - save_offset != rdlen)
+		p->err = "bad dlen";
+
+	return (p->err) ? (-1) : (0);
+}
+
+ssize_t
+dname_expand(const unsigned char *data, size_t len, size_t offset,
+    size_t *newoffset, char *dst, size_t max)
+{
+	size_t		 n, count, end, ptr, start;
+	ssize_t		 res;
+
+	if (offset >= len)
+		return (-1);
+
+	res = 0;
+	end = start = offset;
+
+	for (; (n = data[offset]); ) {
+		if ((n & 0xc0) == 0xc0) {
+			if (offset + 2 > len)
+				return (-1);
+			ptr = 256 * (n & ~0xc0) + data[offset + 1];
+			if (ptr >= start)
+				return (-1);
+			if (end < offset + 2)
+				end = offset + 2;
+			offset = start = ptr;
+			continue;
+		}
+		if (offset + n + 1 > len)
+			return (-1);
+
+		/* copy n + at offset+1 */
+		if (dst != NULL && max != 0) {
+			count = (max < n + 1) ? (max) : (n + 1);
+			memmove(dst, data + offset, count);
+			dst += count;
+			max -= count;
+		}
+		res += n + 1;
+		offset += n + 1;
+		if (end < offset)
+			end = offset;
+	}
+	if (end < offset + 1)
+		end = offset + 1;
+
+	if (dst != NULL && max != 0)
+		dst[0] = 0;
+	if (newoffset)
+		*newoffset = end;
+	return (res + 1);
+}
+
+char *
+print_dname(const char *_dname, char *buf, size_t max)
+{
+	const unsigned char *dname = _dname;
+	char    *res;
+	size_t   left, count;
+
+	if (_dname[0] == 0) {
+		(void)strlcpy(buf, ".", max);
+		return buf;
+	}
+
+	res = buf;
+	left = max - 1;
+	for (; dname[0] && left;) {
+		count = (dname[0] < (left - 1)) ? dname[0] : (left - 1);
+		memmove(buf, dname + 1, count);
+		dname += dname[0] + 1;
+		left -= count;
+		buf += count;
+		if (left) {
+			left -= 1;
+			*buf++ = '.';
+		}
+	}
+	buf[0] = 0;
+
+	return (res);
+}
+
+static int
+unpack_data(struct unpack *p, void *data, size_t len)
+{
+	if (p->err)
+		return (-1);
+
+	if (p->len - p->offset < len) {
+		p->err = "too short";
+		return (-1);
+	}
+
+	memmove(data, p->buf + p->offset, len);
+	p->offset += len;
+
+	return (0);
+}
+
+static int
+unpack_u16(struct unpack *p, uint16_t *u16)
+{
+	if (unpack_data(p, u16, 2) == -1)
+		return (-1);
+
+	*u16 = ntohs(*u16);
+
+	return (0);
+}
+
+static int
+unpack_u32(struct unpack *p, uint32_t *u32)
+{
+	if (unpack_data(p, u32, 4) == -1)
+		return (-1);
+
+	*u32 = ntohl(*u32);
+
+	return (0);
+}
+
+static int
+unpack_inaddr(struct unpack *p, struct in_addr *a)
+{
+	return (unpack_data(p, a, 4));
+}
+
+static int
+unpack_in6addr(struct unpack *p, struct in6_addr *a6)
+{
+	return (unpack_data(p, a6, 16));
+}
+
+static int
+unpack_dname(struct unpack *p, char *dst, size_t max)
+{
+	ssize_t e;
+
+	if (p->err)
+		return (-1);
+
+	e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max);
+	if (e == -1) {
+		p->err = "bad domain name";
+		return (-1);
+	}
+	if (e < 0 || e > MAXDNAME) {
+		p->err = "domain name too long";
+		return (-1);
+	}
+
+	return (0);
+}
blob - /dev/null
blob + 2318a0c51e2e2ac1dc9b326c10652d3b05d8899c (mode 644)
--- /dev/null
+++ unpack_dns.h
@@ -0,0 +1,96 @@
+/*	$OpenBSD: unpack_dns.h,v 1.1 2018/01/06 07:57:53 sunil Exp $	*/
+
+/*
+ * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net>
+ *
+ * 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 <sys/types.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+struct unpack {
+	const char	*buf;
+	size_t		 len;
+	size_t		 offset;
+	const char	*err;
+};
+
+struct dns_header {
+	uint16_t	id;
+	uint16_t	flags;
+	uint16_t	qdcount;
+	uint16_t	ancount;
+	uint16_t	nscount;
+	uint16_t	arcount;
+};
+
+struct dns_query {
+	char		q_dname[MAXDNAME];
+	uint16_t	q_type;
+	uint16_t	q_class;
+};
+
+struct dns_rr {
+	char		rr_dname[MAXDNAME];
+	uint16_t	rr_type;
+	uint16_t	rr_class;
+	uint32_t	rr_ttl;
+	union {
+		struct {
+			char	cname[MAXDNAME];
+		} cname;
+		struct {
+			uint16_t	preference;
+			char		exchange[MAXDNAME];
+		} mx;
+		struct {
+			char	nsname[MAXDNAME];
+		} ns;
+		struct {
+			char	ptrname[MAXDNAME];
+		} ptr;
+		struct {
+			char		mname[MAXDNAME];
+			char		rname[MAXDNAME];
+			uint32_t	serial;
+			uint32_t	refresh;
+			uint32_t	retry;
+			uint32_t	expire;
+			uint32_t	minimum;
+		} soa;
+		struct {
+			struct in_addr	addr;
+		} in_a;
+		struct {
+			struct in6_addr	addr6;
+		} in_aaaa;
+		struct {
+			uint16_t	 rdlen;
+			const void	*rdata;
+		} other;
+	} rr;
+};
+
+void	 unpack_init(struct unpack *, const char *, size_t);
+int	 unpack_header(struct unpack *, struct dns_header *);
+int	 unpack_rr(struct unpack *, struct dns_rr *);
+int	 unpack_query(struct unpack *, struct dns_query *);
+char    *print_dname(const char *, char *, size_t);
+ssize_t	 dname_expand(const unsigned char *, size_t, size_t, size_t *,
+	    char *, size_t);
+