commit - /dev/null
commit + f15094901545c7a69c0989c80bc8715829c34db4
blob - /dev/null
blob + 5a6d7c794505e09b9725140560776db03b94882f (mode 644)
--- /dev/null
+++ Makefile
+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
+.\" $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
+/*
+ * 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
+/*
+ * 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
+/*
+ * 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
+/* $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
+/* $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);
+