Commit Diff


commit - /dev/null
commit + 48c4bdc1d44aac5c5fc5309b6add646c1b03f853
blob - /dev/null
blob + 7aadff031bac46191b5530f5a06610c9f867a240 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,11 @@
+#	$OpenBSD: Makefile,v 1.1 2018/04/26 13:57:13 eric Exp $
+
+PROG=	filter-dkim
+BINDIR=	/usr/libexec/smtpd/
+SRCS+=	main.c log.c smtp_proc.c
+
+CFLAGS+= -g3 -O0
+LDADD+=	-levent -lcrypto
+DPADD=	${LIBEVENT} ${LIBCRYPTO}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + e24c1ca92392875378a5a183d7c8b176c831b480 (mode 644)
--- /dev/null
+++ filter-dkim.1
@@ -0,0 +1,38 @@
+.\"	$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-DNSBL 8
+.Os
+.Sh NAME
+.Nm filter-dnsbl
+.Nd block senders based on dnsbl
+.Sh SYNOPSIS
+.Nm
+.Op Fl m
+.Ar blacklist
+.Op Ar ...
+.Sh DESCRIPTION
+.Nm
+looks up the ip-address of the sender at the
+.Ar blacklist
+and by default drops the connection if it is found.
+If the
+.Fl m
+flag is specified it will allow the message to continue, but it will be marked
+with an X-Spam and X-Spam-DNSBL header.
+.Sh SEE ALSO
+.Xr smtpd 8
blob - /dev/null
blob + 7ec8ca42e18d1c57b84e27a564c23ff352c267cf (mode 644)
--- /dev/null
+++ log.c
@@ -0,0 +1,218 @@
+/*	$OpenBSD: log.c,v 1.20 2017/03/21 12:06:56 bluhm Exp $	*/
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+static int	 debug;
+static int	 verbose;
+const char	*log_procname;
+
+void	log_init(int, int);
+void	log_procinit(const char *);
+void	log_setverbose(int);
+int	log_getverbose(void);
+void	log_warn(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_warnx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_info(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_debug(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
+void	vlog(int, const char *, va_list)
+	    __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+
+void
+log_init(int n_debug, int facility)
+{
+	extern char	*__progname;
+
+	debug = n_debug;
+	verbose = n_debug;
+	log_procinit(__progname);
+
+	if (!debug)
+		openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+	tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+	if (procname != NULL)
+		log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+	verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+	return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+	va_list	ap;
+
+	va_start(ap, fmt);
+	vlog(pri, fmt, ap);
+	va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+	char	*nfmt;
+	int	 saved_errno = errno;
+
+	if (debug) {
+		/* best effort in out of mem situations */
+		if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+			vfprintf(stderr, fmt, ap);
+			fprintf(stderr, "\n");
+		} else {
+			vfprintf(stderr, nfmt, ap);
+			free(nfmt);
+		}
+		fflush(stderr);
+	} else
+		vsyslog(pri, fmt, ap);
+
+	errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+	char		*nfmt;
+	va_list		 ap;
+	int		 saved_errno = errno;
+
+	/* best effort to even work in out of memory situations */
+	if (emsg == NULL)
+		logit(LOG_ERR, "%s", strerror(saved_errno));
+	else {
+		va_start(ap, emsg);
+
+		if (asprintf(&nfmt, "%s: %s", emsg,
+		    strerror(saved_errno)) == -1) {
+			/* we tried it... */
+			vlog(LOG_ERR, emsg, ap);
+			logit(LOG_ERR, "%s", strerror(saved_errno));
+		} else {
+			vlog(LOG_ERR, nfmt, ap);
+			free(nfmt);
+		}
+		va_end(ap);
+	}
+
+	errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_ERR, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	va_start(ap, emsg);
+	vlog(LOG_INFO, emsg, ap);
+	va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+	va_list	 ap;
+
+	if (verbose > 1) {
+		va_start(ap, emsg);
+		vlog(LOG_DEBUG, emsg, ap);
+		va_end(ap);
+	}
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+	static char	s[BUFSIZ];
+	const char	*sep;
+
+	if (emsg != NULL) {
+		(void)vsnprintf(s, sizeof(s), emsg, ap);
+		sep = ": ";
+	} else {
+		s[0] = '\0';
+		sep = "";
+	}
+	if (code)
+		logit(LOG_CRIT, "%s: %s%s%s",
+		    log_procname, s, sep, strerror(code));
+	else
+		logit(LOG_CRIT, "%s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(errno, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+	va_list	ap;
+
+	va_start(ap, emsg);
+	vfatalc(0, emsg, ap);
+	va_end(ap);
+	exit(1);
+}
blob - /dev/null
blob + 22bb416439467fde6a3c3e58209877edd7fdf468 (mode 644)
--- /dev/null
+++ log.h
@@ -0,0 +1,46 @@
+/*	$OpenBSD: log.h,v 1.8 2018/04/26 20:57:59 eric Exp $	*/
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+#include <sys/cdefs.h>
+
+void	log_init(int, int);
+void	log_procinit(const char *);
+void	log_setverbose(int);
+int	log_getverbose(void);
+void	log_warn(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_warnx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_info(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	log_debug(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
+void	vlog(int, const char *, va_list)
+	    __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+	    __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
blob - /dev/null
blob + f219be34eee7854002c2b4cb1eb0f06d5d1c075a (mode 644)
--- /dev/null
+++ main.c
@@ -0,0 +1,536 @@
+/*
+ * 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.
+ */
+#include <sys/tree.h>
+
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "smtp_proc.h"
+
+struct dkim_session {
+	uint64_t reqid;
+	uint64_t token;
+	FILE *origf;
+	int parsing_headers;
+	char **headers;
+	int lastheader;
+	union {
+		SHA_CTX sha1;
+		SHA256_CTX sha256;
+	};
+	/* Use largest hash size her */
+	char bbh[SHA256_DIGEST_LENGTH];
+	char bh[(((SHA256_DIGEST_LENGTH + 2) / 3) * 4) + 1];
+	size_t body_whitelines;
+	int has_body;
+	RB_ENTRY(dkim_session) entry;
+};
+
+RB_HEAD(dkim_sessions, dkim_session) dkim_sessions = RB_INITIALIZER(NULL);
+RB_PROTOTYPE(dkim_sessions, dkim_session, entry, dkim_session_cmp);
+
+static char **sign_headers = NULL;
+static size_t nsign_headers = 0;
+
+#define HASH_SHA1 0
+#define HASH_SHA256 1
+static int hashalg = HASH_SHA256;
+
+#define CRYPT_RSA 0
+static int cryptalg = CRYPT_RSA;
+
+#define CANON_SIMPLE 0
+#define CANON_RELAXED 1
+static int canonheader = CANON_SIMPLE;
+static int canonbody = CANON_SIMPLE;
+
+static char *domain = NULL;
+
+void usage(void);
+void dkim_err(struct dkim_session *, char *);
+void dkim_errx(struct dkim_session *, char *);
+void dkim_headers_set(char *);
+void dkim_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
+    uint64_t, char *);
+void dkim_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
+struct dkim_session *dkim_session_new(uint64_t);
+void dkim_session_free(struct dkim_session *);
+int dkim_session_cmp(struct dkim_session *, struct dkim_session *);
+void dkim_parse_header(struct dkim_session *, char *);
+void dkim_parse_body(struct dkim_session *, char *);
+void dkim_built_signature(struct dkim_session *);
+int dkim_hash_update(struct dkim_session *, char *, size_t);
+int dkim_hash_final(struct dkim_session *, char *);
+
+int
+main(int argc, char *argv[])
+{
+	int ch;
+	int i;
+	int debug = 0;
+
+	while ((ch = getopt(argc, argv, "a:c:Dd:h:")) != -1) {
+		switch (ch) {
+		case 'a':
+			if (strncmp(optarg, "rsa-", 4))
+				err(1, "invalid algorithm");
+			if (strcmp(optarg + 4, "sha256") == 0)
+				hashalg = HASH_SHA256;
+			else if (strcmp(optarg + 4, "sha1") == 0)
+				hashalg = HASH_SHA1;
+			else
+				err(1, "invalid algorithm");
+			break;
+		case 'c':
+			if (strncmp(optarg, "simple", 6) == 0) {
+				canonheader = CANON_SIMPLE;
+				optarg += 6;
+			} else if (strncmp(optarg, "relaxed", 7) == 0) {
+				canonheader = CANON_RELAXED;
+				optarg += 7;
+			} else
+				err(1, "Invalid canonicalization");
+			if (optarg[0] == '/') {
+				if (strcmp(optarg + 1, "simple") == 0)
+					canonbody = CANON_SIMPLE;
+				else if (strcmp(optarg + 1, "relaxed") == 0)
+					canonbody = CANON_RELAXED;
+				else
+					err(1, "Invalid canonicalization");
+			} else if (optarg[0] == '\0')
+				canonbody = CANON_SIMPLE;
+			else
+				err(1, "Invalid canonicalization");
+			break;
+		case 'd':
+			domain = optarg;
+			if (strlen(domain) > 255)
+				err(1, "Domain too long");
+			break;
+		case 'D':
+			debug = 1;
+			break;
+		case 'h':
+			dkim_headers_set(optarg);
+			break;
+		default:
+			usage();
+		}
+	}
+
+	log_init(debug, LOG_MAIL);
+	if (pledge("tmppath stdio", NULL) == -1)
+		fatal("pledge");
+
+	if (domain == NULL)
+		usage();
+
+	smtp_register_filter_dataline(dkim_dataline);
+	smtp_in_register_report_disconnect(dkim_disconnect);
+	smtp_run(debug);
+
+	return 0;
+}
+
+void
+dkim_disconnect(char *type, int version, struct timespec *tm, char *direction,
+    char *phase, uint64_t reqid)
+{
+	struct dkim_session *session, search;
+
+	search.reqid = reqid;
+	if ((session = RB_FIND(dkim_sessions, &dkim_sessions, &search)) != NULL)
+		dkim_session_free(session);
+}
+
+void
+dkim_dataline(char *type, int version, struct timespec *tm, char *direction,
+    char *phase, uint64_t reqid, uint64_t token, char *line)
+{
+	struct dkim_session *session, search;
+	size_t i;
+	size_t linelen;
+
+	search.reqid = reqid;
+	session = RB_FIND(dkim_sessions, &dkim_sessions, &search);
+	if (session == NULL) {
+		session = dkim_session_new(reqid);
+		session->token = token;
+	} else if (session->token != token)
+		fatalx("Token incorrect");
+
+	linelen = strlen(line);
+	if (fwrite(line, 1, linelen, session->origf) < linelen)
+		dkim_err(session, "Couldn't write to tempfile");
+
+	if (linelen !=  0 && session->parsing_headers) {
+		dkim_parse_header(session, line);
+	} else if (linelen == 0 && session->parsing_headers) {
+		session->parsing_headers = 0;
+	} else if (line[0] == '.' && line[1] =='\0') {
+		if (canonbody == CANON_SIMPLE && !session->has_body) {
+			if (dkim_hash_update(session, "\r\n", 2) == 0)
+				return;
+		}
+		if (dkim_hash_final(session, session->bbh) == 0)
+			return;
+		EVP_EncodeBlock(session->bh, session->bbh,
+		    hashalg == HASH_SHA1 ? SHA_DIGEST_LENGTH :
+		    SHA256_DIGEST_LENGTH);
+		dkim_built_signature(session);
+	} else
+		dkim_parse_body(session, line);
+}
+
+struct dkim_session *
+dkim_session_new(uint64_t reqid)
+{
+	struct dkim_session *session;
+	char origfile[] = "/tmp/filter-dkimXXXXXX";
+	int fd;
+
+	if ((session = calloc(1, sizeof(*session))) == NULL)
+		fatal(NULL);
+
+	session->reqid = reqid;
+	if ((fd = mkstemp(origfile)) == -1) {
+		dkim_err(session, "Can't open tempfile");
+		return NULL;
+	}
+	if (unlink(origfile) == -1)
+		log_warn("Failed to unlink tempfile %s", origfile);
+	if ((session->origf = fdopen(fd, "r+")) == NULL) {
+		dkim_err(session, "Can't open tempfile");
+		return NULL;
+	}
+	session->parsing_headers = 1;
+
+	if (hashalg == HASH_SHA1)
+		SHA1_Init(&(session->sha1));
+	else
+		SHA256_Init(&(session->sha256));
+	session->body_whitelines = 0;
+	session->headers = calloc(1, sizeof(*(session->headers)));
+	if (session->headers == NULL) {
+		dkim_err(session, "Can't save headers");
+		return NULL;
+	}
+	session->lastheader = 0;
+
+	if (RB_INSERT(dkim_sessions, &dkim_sessions, session) != NULL)
+		fatalx("session already registered");
+	return session;
+}
+
+void
+dkim_session_free(struct dkim_session *session)
+{
+	size_t i;
+
+	RB_REMOVE(dkim_sessions, &dkim_sessions, session);
+	fclose(session->origf);
+	for (i = 0; session->headers[i] != NULL; i++)
+		free(session->headers[i]);
+	free(session->headers);
+	free(session);
+}
+
+int
+dkim_session_cmp(struct dkim_session *s1, struct dkim_session *s2)
+{
+	return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
+}
+
+void
+dkim_headers_set(char *headers)
+{
+	size_t i;
+	int has_from = 0;
+
+	nsign_headers = 1;
+
+	/* We don't support FWS for the -h flag */
+	for (i = 0; headers[i] != '\0'; i++) {
+		/* RFC 5322 field-name */
+		if (!(headers[i] >= 33 && headers[i] <= 126))
+			errx(1, "-h: invalid character");
+		if (headers[i] == ':') {
+			/* Test for empty headers */
+			if (i == 0 || headers[i - 1] == ':')
+				errx(1, "-h: header can't be empty");
+			nsign_headers++;
+		}
+		headers[i] = tolower(headers[i]);
+	}
+	if (headers[i - 1] == ':')
+		errx(1, "-h: header can't be empty");
+
+	sign_headers = reallocarray(NULL, nsign_headers, sizeof(*sign_headers));
+	if (sign_headers == NULL)
+		errx(1, NULL);
+
+	for (i = 0; i < nsign_headers; i++) {
+		sign_headers[i] = headers;
+		if (i != nsign_headers - 1) {
+			headers = strchr(headers, ':');
+			headers++[0] = '\0';
+		}
+		if (strcasecmp(sign_headers[i], "from") == 0)
+			has_from = 1;
+	}
+	if (!has_from)
+		errx(1, "From header must be included");
+}
+
+void
+dkim_err(struct dkim_session *session, char *msg)
+{
+	smtp_filter_disconnect(session->reqid, session->token,
+	    "Internal server error");
+	log_warn("%s", msg);
+	dkim_session_free(session);
+}
+
+void
+dkim_errx(struct dkim_session *session, char *msg)
+{
+	smtp_filter_disconnect(session->reqid, session->token,
+	    "Internal server error");
+	log_warnx("%s", msg);
+	dkim_session_free(session);
+}
+
+void
+dkim_parse_header(struct dkim_session *session, char *line)
+{
+	size_t i;
+	size_t r, w;
+	size_t linelen;
+	size_t lastheader;
+	int fieldname;
+	char **mtmp;
+	char *htmp;
+
+	if ((line[0] == ' ' || line[0] == '\t') && !session->lastheader)
+		return;
+	if ((line[0] != ' ' && line[0] != '\t')) {
+		for (i = 0; i < nsign_headers; i++) {
+			if (strncasecmp(line, sign_headers[i],
+			    strlen(sign_headers[i])) == 0) {
+				break;
+			}
+		}
+		if (i == nsign_headers) {
+			session->lastheader = 0;
+			return;
+		}
+	}
+
+	if (canonheader == CANON_RELAXED) {
+		fieldname = 1;
+		for (r = w = 0; line[r] != '\0'; r++) {
+			if (line[r] == ':') {
+				if (line[w - 1] == ' ')
+					line[w - 1] = ':';
+				else
+					line[w++] = ':';
+				fieldname = 0;
+				while (line[r + 1] == ' ' ||
+				    line[r + 1] == '\t')
+					r++;
+				continue;
+			}
+			if (line[r] == ' ' || line[r] == '\t') {
+				if (r != 0 && line[w - 1] == ' ')
+					continue;
+				else
+					line[w++] = ' ';
+			} else if (fieldname) {
+				line[w++] = tolower(line[r]);
+				continue;
+			} else
+				line[w++] = line[r];
+		}
+		linelen = line[w - 1] == ' ' ? w - 1 : w;
+		line[linelen] = '\0';
+	} else
+		linelen = strlen(line);
+
+	for (lastheader = 0; session->headers[lastheader] != NULL; lastheader++)
+		continue;
+	if (!session->lastheader) {
+		mtmp = reallocarray(session->headers, lastheader + 1,
+		    sizeof(*mtmp));
+		if (mtmp == NULL) {
+			dkim_err(session, "Can't store header");
+			return;
+		}
+		session->headers = mtmp;
+		
+		session->headers[lastheader] = strdup(line);
+		session->headers[lastheader + 1 ] = NULL;
+		session->lastheader = 1;
+	} else {
+		lastheader--;
+		linelen += strlen(session->headers[lastheader]);
+		if (canonheader == CANON_SIMPLE)
+			linelen += 2;
+		linelen++;
+		htmp = reallocarray(session->headers[lastheader], linelen,
+		    sizeof(*htmp));
+		if (htmp == NULL) {
+			dkim_err(session, "Can't store header");
+			return;
+		}
+		session->headers[lastheader] = htmp;
+		if (canonheader == CANON_SIMPLE) {
+			if (strlcat(htmp, "\r\n", linelen) >= linelen)
+				fatalx("Missized header");
+		}
+		if (strlcat(htmp, line, linelen) >= linelen)
+			fatalx("Missized header");
+	}
+}
+
+void
+dkim_parse_body(struct dkim_session *session, char *line)
+{
+	size_t r, w;
+	size_t linelen;
+	if (line[0] == '\0') {
+		session->body_whitelines++;
+		return;
+	}
+
+	while (session->body_whitelines--) {
+		if (dkim_hash_update(session, "\r\n", 2) == 0)
+			return;
+	}
+	session->body_whitelines = 0;
+
+	session->has_body = 1;
+	if (canonbody == CANON_RELAXED) {
+		for (r = w = 0; line[r] != '\0'; r++) {
+			if (line[r] == ' ' || line[r] == '\t') {
+				if (r != 0 && line[w - 1] == ' ')
+					continue;
+				else
+					line[w++] = ' ';
+			} else
+				line[w++] = line[r];
+		}
+		linelen = line[w - 1] == ' ' ? w - 1 : w;
+		line[linelen] = '\0';
+	} else
+		linelen = strlen(line);
+
+	if (dkim_hash_update(session, line, linelen) == 0)
+		return;
+	if (dkim_hash_update(session, "\r\n", 2) == 0)
+		return;
+}
+
+void
+dkim_built_signature(struct dkim_session *session)
+{
+	char *signature;
+	size_t signaturesize = 1024;
+	size_t signaturelen = 0;
+	size_t linelen = 0;
+	size_t ncopied;
+
+	if ((signature = malloc(signaturesize)) == NULL) {
+		dkim_err(session, "Can't create signature");
+		return;
+	}
+
+	do {
+		ncopied = strlcpy(signature, "DKIM-Signature: v=1; a=",
+		    signaturesize);
+	if (ncopied >= signaturesize)
+	if (cryptalg == CRYPT_RSA)
+		(void) strlcat(signature, "rsa", signaturesize);
+	if (hashalg == HASH_SHA1)
+		(void) strlcat(signature, "-sha1; ", signaturesize);
+	else
+		(void) strlcat(signature, "-sha256; ", signaturesize);
+	if (canonheader != CANON_SIMPLE || canonbody != CANON_SIMPLE) {
+		if (canonheader == CANON_SIMPLE)
+			(void) strlcat(signature, "c=simple", signaturesize);
+		else
+			(void) strlcat(signature, "c=relaxed", signaturesize);
+	}
+	if (canonbody != CANON_SIMPLE)
+		(void) strlcat(signature, "/relaxed", signaturesize);
+
+	(void) strlcat(signature, "d=", signaturesize);
+	(void) strlcat(signature, domain, signaturesize);
+	(void) strlcat(signature, "; ", signaturesize);
+	printf("%s\n", signature);
+}
+
+int
+dkim_hash_update(struct dkim_session *session, char *buf, size_t len)
+{
+	if (hashalg == HASH_SHA1) {
+		if (SHA1_Update(&(session->sha1), buf, len) == 0) {
+			dkim_errx(session, "Unable to update hash");
+			return 0;
+		}
+	} else {
+		if (SHA256_Update(&(session->sha256), buf, len) == 0) {
+			dkim_errx(session, "Unable to update hash");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+int
+dkim_hash_final(struct dkim_session *session, char *dest)
+{
+	if (hashalg == HASH_SHA1) {
+		if (SHA1_Final(dest, &(session->sha1)) == 0) {
+			dkim_errx(session, "Unable to finalize hash");
+			return 0;
+		}
+	} else {
+		if (SHA256_Final(dest, &(session->sha256)) == 0) {
+			dkim_errx(session, "Unable to finalize hash");
+			return 0;
+		}
+	}
+	return 1;
+}
+
+__dead void
+usage(void)
+{
+	fprintf(stderr, "usage: %s [-a signalg] [-c canonicalization] -d domain -h headerfields\n", getprogname());
+	exit(1);
+}
+
+RB_GENERATE(dkim_sessions, dkim_session, entry, dkim_session_cmp);
blob - /dev/null
blob + 0a700a0ff66e3ff542a44f3055ed3e3100ad5e08 (mode 644)
--- /dev/null
+++ smtp_proc.c
@@ -0,0 +1,481 @@
+/*
+ * 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.
+ */
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "smtp_proc.h"
+
+#define NITEMS(x) (sizeof(x) / sizeof(*x))
+
+struct smtp_callback;
+struct smtp_request;
+
+extern struct event_base *current_base;
+
+static int smtp_register(char *, char *, char *, void *);
+static ssize_t smtp_getline(char ** restrict, size_t * restrict);
+static void smtp_newline(int, short, void *);
+static void smtp_connect(struct smtp_callback *, int, struct timespec *,
+    uint64_t, uint64_t, char *);
+static void smtp_data(struct smtp_callback *, int, struct timespec *,
+    uint64_t, uint64_t, char *);
+static void smtp_dataline(struct smtp_callback *, int, struct timespec *,
+    uint64_t, uint64_t, char *);
+static void smtp_in_link_disconnect(struct smtp_callback *, int, struct timespec *,
+    uint64_t, char *);
+static void smtp_printf(const char *, ...)
+	__attribute__((__format__ (printf, 1, 2)));
+static void smtp_vprintf(const char *, va_list);
+static void smtp_write(int, short, void *);
+
+struct smtp_writebuf {
+	char *buf;
+	size_t bufsize;
+	size_t buflen;
+};
+
+struct smtp_callback {
+	char *type;
+	char *phase;
+	char *direction;
+	union {
+		void (*smtp_filter)(struct smtp_callback *, int,
+		    struct timespec *, uint64_t, uint64_t, char *);
+		void (*smtp_report)(struct smtp_callback *, int,
+		    struct timespec *, uint64_t, char *);
+	};
+	void *cb;
+} smtp_callbacks[] = {
+        {"filter", "connect", "smtp-in", .smtp_filter = smtp_connect, NULL},
+        {"filter", "data", "smtp-in", .smtp_filter = smtp_data, NULL},
+        {"filter", "data-line", "smtp-in", .smtp_filter = smtp_dataline, NULL},
+	{"report", "link-disconnect", "smtp-in",
+	    .smtp_report = smtp_in_link_disconnect, NULL}
+};
+
+static int ready = 0;
+
+int
+smtp_register_filter_connect(void (*cb)(char *, int, struct timespec *, char *,
+    char *, uint64_t, uint64_t, char *, struct inx_addr *))
+{
+	return smtp_register("filter", "connect", "smtp-in", (void *)cb);
+}
+
+int
+smtp_register_filter_data(void (*cb)(char *, int, struct timespec *, char *,
+    char *, uint64_t, uint64_t))
+{
+	return smtp_register("filter", "data", "smtp-in", (void *)cb);
+}
+
+int
+smtp_register_filter_dataline(void (*cb)(char *, int, struct timespec *, char *,
+    char *, uint64_t, uint64_t, char *))
+{
+	return smtp_register("filter", "data-line", "smtp-in", (void *)cb);
+}
+
+int
+smtp_in_register_report_disconnect(void (*cb)(char *, int, struct timespec *,
+    char *, char *, uint64_t))
+{
+	return smtp_register("report", "link-disconnect", "smtp-in", (void *)cb);
+}
+
+void
+smtp_run(int debug)
+{
+	struct event stdinev;
+
+	smtp_printf("register|ready\n");
+	ready = 1;
+
+	log_init(debug, LOG_MAIL);
+	event_set(&stdinev, STDIN_FILENO, EV_READ | EV_PERSIST, smtp_newline,
+	    &stdinev);
+	event_add(&stdinev, NULL);
+
+	if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1)
+		fatal("fcntl");
+	event_dispatch();
+}
+
+static ssize_t
+smtp_getline(char ** restrict buf, size_t * restrict size)
+{
+	static char *rbuf = NULL;
+	static size_t rsoff = 0, reoff = 0;
+	static size_t rbsize = 0;
+	char *sep;
+	size_t sepoff;
+	ssize_t strlen, nread;
+
+	do {
+		if (rsoff != reoff) {
+			if ((sep = memchr(rbuf + rsoff, '\n', reoff - rsoff))
+			    != NULL) {
+				sepoff = sep - rbuf;
+				if (*buf == NULL)
+					*size = 0;
+				if (*size < (sepoff - rsoff + 1)) {
+					*size = sepoff - rsoff + 1;
+					*buf = realloc(*buf, sepoff - rsoff + 1);
+					if (*buf == NULL)
+						fatal(NULL);
+				}
+				sep[0] = '\0';
+				strlen = strlcpy(*buf, rbuf + rsoff, *size);
+				if (strlen >= *size)
+					fatalx("copy buffer too small");
+				rsoff = sepoff + 1;
+				return strlen;
+			}
+		}
+		/* If we can't fill at the end, move everything back. */
+		if (rbsize - reoff < 1500 && rsoff != 0) {
+			memmove(rbuf, rbuf + rsoff, reoff - rsoff);
+			reoff -= rsoff;
+			rsoff = 0;
+		}
+		/* If we still can't fill alloc some new memory. */
+		if (rbsize - reoff < 1500) {
+			if ((rbuf = realloc(rbuf, rbsize + 4096)) == NULL)
+				fatal(NULL);
+			rbsize += 4096;
+		}
+		nread = read(STDIN_FILENO, rbuf + reoff, rbsize - reoff);
+		if (nread <= 0)
+			return nread;
+		reoff += nread;
+	} while (1);
+}
+
+static void
+smtp_newline(int fd, short event, void *arg)
+{
+	struct event *stdinev = (struct event *)arg;
+	static char *line = NULL, *linedup = NULL;
+	static size_t linesize = 0;
+	static size_t dupsize = 0;
+	ssize_t linelen;
+	char *start, *end, *type, *direction, *phase, *params;
+	int version;
+	struct timespec tm;
+	uint64_t reqid, token;
+	int i;
+
+	while ((linelen = smtp_getline(&line, &linesize)) > 0) {
+		if (dupsize < linesize) {
+			if ((linedup = realloc(linedup, linesize)) == NULL)
+				fatal(NULL);
+			dupsize = linesize;
+		}
+		strlcpy(linedup, line, dupsize);
+		type = line;
+		if ((start = strchr(type, '|')) == NULL)
+			fatalx("Invalid line received: missing version: %s", linedup);
+		start++[0] = '\0';
+		if ((end = strchr(start, '|')) == NULL)
+			fatalx("Invalid line received: missing time: %s", linedup);
+		end++[0] = '\0';
+		if (strcmp(start, "1") != 0)
+			fatalx("Unsupported protocol received: %s: %s", start, linedup);
+		version = 1;
+		start = end;
+		if ((direction = strchr(start, '|')) == NULL)
+			fatalx("Invalid line received: missing direction: %s", linedup);
+		direction++[0] = '\0';
+		tm.tv_sec = (time_t) strtoull(start, &end, 10);
+		tm.tv_nsec = 0;
+		if (start[0] == '\0' || (end[0] != '\0' && end[0] != '.'))
+			fatalx("Invalid line received: invalid timestamp: %s", linedup);
+		if (end[0] == '.') {
+			start = end + 1;
+			tm.tv_nsec = strtol(start, &end, 10);
+			if (start[0] == '\0' || end[0] != '\0')
+				fatalx("Invalid line received: invalid "
+				    "timestamp: %s", linedup);
+			for (i = 9 - (end - start); i > 0; i--)
+				tm.tv_nsec *= 10;
+		}
+		if ((phase = strchr(direction, '|')) == NULL)
+			fatalx("Invalid line receieved: missing phase: %s", linedup);
+		phase++[0] = '\0';
+		if ((start = strchr(phase, '|')) == NULL)
+			fatalx("Invalid line received: missing reqid: %s", linedup);
+		start++[0] = '\0';
+		reqid = strtoull(start, &params, 16);
+		if (start[0] == '|' || (params[0] != '|' & params[0] != '\0'))
+			fatalx("Invalid line received: invalid reqid: %s", linedup);
+		params++;
+
+		for (i = 0; i < NITEMS(smtp_callbacks); i++) {
+			if (strcmp(type, smtp_callbacks[i].type) == 0 &&
+			    strcmp(phase, smtp_callbacks[i].phase) == 0 &&
+			    strcmp(direction, smtp_callbacks[i].direction) == 0)
+				break;
+		}
+		if (i == NITEMS(smtp_callbacks)) {
+			fatalx("Invalid line received: received unregistered "
+			    "%s: %s: %s", type, phase, linedup);
+		}
+		if (strcmp(type, "filter") == 0) {
+			start = params;
+			token = strtoull(start, &params, 16);
+			if (start[0] == '|' || params[0] != '|')
+				fatalx("Invalid line received: invalid token: %s", linedup);
+			params++;
+			smtp_callbacks[i].smtp_filter(&(smtp_callbacks[i]),
+			    version, &tm, reqid, token, params);
+		} else
+			smtp_callbacks[i].smtp_report(&(smtp_callbacks[i]),
+			    version, &tm, reqid, params);
+	}
+	if (linelen == 0 || errno != EAGAIN)
+		event_del(stdinev);
+}
+
+static void
+smtp_connect(struct smtp_callback *cb, int version, struct timespec *tm,
+    uint64_t reqid, uint64_t token, char *params)
+{
+	struct inx_addr addrx;
+	char *hostname;
+	char *address;
+	int ret;
+	void (*f)(char *, int, struct timespec *,char *, char *, uint64_t,
+	    uint64_t, char *, struct inx_addr *);
+
+	hostname = params;
+	if ((address = strchr(params, '|')) == NULL)
+		fatalx("Invalid line received: missing address: %s", params);
+	address++[0] = '\0';
+
+	addrx.af = AF_INET;
+	if (strncasecmp(address, "ipv6:", 5) == 0) {
+		addrx.af = AF_INET6;
+		address += 5;
+	}
+
+	ret = inet_pton(addrx.af, address, addrx.af == AF_INET ?
+	    (void *)&(addrx.addr) : (void *)&(addrx.addr6));
+	if (ret == 0)
+		fatalx("Invalid line received: Couldn't parse address: %s", params);
+	if (ret == -1)
+		fatal("Couldn't convert address: %s", params);
+
+	f = cb->cb;
+	f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
+	    hostname, &addrx);
+}
+
+static void
+smtp_data(struct smtp_callback *cb, int version, struct timespec *tm,
+    uint64_t reqid, uint64_t token, char *params)
+{
+	void (*f)(char *, int, struct timespec *, char *, char *, uint64_t,
+	    uint64_t);
+
+	f = cb->cb;
+	f(cb->type, version, tm, cb->direction, cb->phase, reqid, token);
+}
+
+static void
+smtp_dataline(struct smtp_callback *cb, int version, struct timespec *tm,
+    uint64_t reqid, uint64_t token, char *line)
+{
+	void (*f)(char *, int, struct timespec *, char *, char *, uint64_t,
+	    uint64_t, char *);
+
+	f = cb->cb;
+	f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
+	    line);
+}
+
+static void
+smtp_in_link_disconnect(struct smtp_callback *cb, int version,
+    struct timespec *tm, uint64_t reqid, char *params)
+{
+	void (*f)(char *, int, struct timespec *, char *, char *, uint64_t);
+
+	f = cb->cb;
+	f(cb->type, version, tm, cb->direction, cb->phase, reqid);
+}
+
+void
+smtp_filter_proceed(uint64_t reqid, uint64_t token)
+{
+	smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|proceed\n", token,
+	    reqid);
+}
+
+static void
+smtp_printf(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	smtp_vprintf(fmt, ap);
+	va_end(ap);
+}
+
+static void
+smtp_vprintf(const char *fmt, va_list ap)
+{
+	va_list cap;
+	static struct smtp_writebuf buf = {NULL, 0, 0};
+	int fmtlen;
+
+	va_copy(cap, ap);
+	fmtlen = vsnprintf(buf.buf + buf.buflen, buf.bufsize - buf.buflen, fmt,
+	    ap);
+	if (fmtlen == -1)
+		fatal("vsnprintf");
+	if (fmtlen >= buf.bufsize - buf.buflen) {
+		buf.bufsize = buf.buflen + fmtlen + 1;
+		buf.buf = reallocarray(buf.buf, buf.bufsize,
+		    sizeof(*(buf.buf)));
+		if (buf.buf == NULL)
+			fatalx(NULL);
+		fmtlen = vsnprintf(buf.buf + buf.buflen,
+		    buf.bufsize - buf.buflen, fmt, cap);
+		if (fmtlen == -1)
+			fatal("vsnprintf");
+	}
+	va_end(cap);
+	buf.buflen += fmtlen;
+
+	if (strchr(buf.buf, '\n') != NULL)
+		smtp_write(STDOUT_FILENO, EV_WRITE, &buf);
+}
+
+static void
+smtp_write(int fd, short event, void *arg)
+{
+	struct smtp_writebuf *buf = arg;
+	static struct event stdoutev;
+	static int evset = 0;
+	ssize_t wlen;
+
+	if (buf->buflen == 0)
+		return;
+	if (event_pending(&stdoutev, EV_WRITE, NULL))
+		return;
+	if (!evset) {
+		event_set(&stdoutev, fd, EV_WRITE, smtp_write, buf);
+		evset = 1;
+	}
+	wlen = write(fd, buf->buf, buf->buflen);
+	if (wlen == -1) {
+		if (errno != EAGAIN && errno != EINTR)
+			fatal("Failed to write to smtpd");
+		event_add(&stdoutev, NULL);
+		return;
+	}
+	if (wlen < buf->buflen) {
+		memmove(buf->buf, buf->buf + wlen, buf->buflen - wlen);
+		event_add(&stdoutev, NULL);
+	}
+	buf->buflen -= wlen;
+}
+
+void
+smtp_filter_reject(uint64_t reqid, uint64_t token, int code,
+    const char *reason, ...)
+{
+	va_list ap;
+
+	if (code < 200 || code > 599)
+		fatalx("Invalid reject code");
+
+	smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|reject|%d ", token,
+	    reqid, code);
+	va_start(ap, reason);
+	smtp_vprintf(reason, ap);
+	va_end(ap);
+	smtp_printf("\n");
+}
+
+void
+smtp_filter_disconnect(uint64_t reqid, uint64_t token, const char *reason, ...)
+{
+	va_list ap;
+
+	smtp_printf("filter-result|%016"PRIx64"|%016"PRIx64"|disconnect|421 ",
+	    token, reqid);
+	va_start(ap, reason);
+	smtp_vprintf(reason, ap);
+	va_end(ap);
+	smtp_printf("\n");
+}
+
+void
+smtp_filter_dataline(uint64_t reqid, uint64_t token, const char *line, ...)
+{
+	va_list ap;
+
+	smtp_printf("filter-dataline|%016"PRIx64"|%016"PRIx64"|", token, reqid);
+	va_start(ap, line);
+	smtp_vprintf(line, ap);
+	va_end(ap);
+	smtp_printf("\n");
+}
+
+static int
+smtp_register(char *type, char *phase, char *direction, void *cb)
+{
+	int i;
+	static int evinit = 0;
+
+	if (ready)
+		fatalx("Can't register when proc is running");
+
+	if (!evinit) {
+		event_init();
+		evinit = 1;
+	}
+
+	for (i = 0; i < NITEMS(smtp_callbacks); i++) {
+		if (strcmp(type, smtp_callbacks[i].type) == 0 &&
+		    strcmp(phase, smtp_callbacks[i].phase) == 0 &&
+		    strcmp(direction, smtp_callbacks[i].direction) == 0) {
+			if (smtp_callbacks[i].cb != NULL) {
+				errno = EALREADY;
+				return -1;
+			}
+			smtp_callbacks[i].cb = cb;
+			smtp_printf("register|%s|%s|%s\n", type, direction,
+			    phase);
+			return 0;
+		}
+	}
+	errno = EINVAL;
+	return -1;
+}
blob - /dev/null
blob + 39a43d963a2e37a9f520a2229d53d6b73694db1e (mode 644)
--- /dev/null
+++ smtp_proc.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+#include <netinet/in.h>
+
+#include <netinet/in.h>
+
+struct inx_addr {
+	int af;
+	union {
+		struct in_addr addr;
+		struct in6_addr addr6;
+	};
+};
+
+int smtp_register_filter_connect(void (*)(char *, int, struct timespec *,
+    char *, char *, uint64_t, uint64_t, char *, struct inx_addr *));
+int smtp_register_filter_data(void (*)(char *, int, struct timespec *, char *,
+    char *, uint64_t, uint64_t));
+int smtp_register_filter_dataline(void (*)(char *, int, struct timespec *, char *,
+    char *, uint64_t, uint64_t, char *));
+int smtp_in_register_report_disconnect(void (*)(char *, int, struct timespec *,
+    char *, char *, uint64_t));
+void smtp_filter_proceed(uint64_t, uint64_t);
+void smtp_filter_reject(uint64_t, uint64_t, int, const char *, ...)
+	__attribute__((__format__ (printf, 4, 5)));
+void smtp_filter_disconnect(uint64_t, uint64_t, const char *, ...)
+	__attribute__((__format__ (printf, 3, 4)));
+void smtp_filter_dataline(uint64_t, uint64_t, const char *, ...)
+	__attribute__((__format__ (printf, 3, 4)));
+void smtp_run(int);