commit f15094901545c7a69c0989c80bc8715829c34db4 from: Martijn van Duren date: Sun Mar 20 15:20:14 2022 UTC Initial implementation for filter-dkimverify. Probably still contains quite a few bugs and does't yet support ed25519. 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 blob - /dev/null blob + b49771b61776484246483a4eb8e34bf5287f2f58 (mode 644) --- /dev/null +++ filter-dkimverify.8 @@ -0,0 +1,32 @@ +.\" $OpenBSD$ +.\" +.\" Copyright (c) 2019 Martijn van Duren +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate$ +.Dt FILTER-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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "mheader.h" + +#include + +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 + * + * 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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include "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 + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include +#include + +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); +