xref: /openbsd-src/usr.sbin/smtpd/lka_filter.c (revision d49c07c786cfbc18357be57b78b5771525d1b8bb)
1*d49c07c7Sop /*	$OpenBSD: lka_filter.c,v 1.78 2024/08/12 09:32:44 op Exp $	*/
2d7b0dc3bSgilles 
3d7b0dc3bSgilles /*
4d7b0dc3bSgilles  * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org>
5d7b0dc3bSgilles  *
6d7b0dc3bSgilles  * Permission to use, copy, modify, and distribute this software for any
7d7b0dc3bSgilles  * purpose with or without fee is hereby granted, provided that the above
8d7b0dc3bSgilles  * copyright notice and this permission notice appear in all copies.
9d7b0dc3bSgilles  *
10d7b0dc3bSgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11d7b0dc3bSgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d7b0dc3bSgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13d7b0dc3bSgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14d7b0dc3bSgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15d7b0dc3bSgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16d7b0dc3bSgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17d7b0dc3bSgilles  */
18d7b0dc3bSgilles 
19d7b0dc3bSgilles #include <errno.h>
20d7b0dc3bSgilles #include <inttypes.h>
21d7b0dc3bSgilles #include <stdlib.h>
22d7b0dc3bSgilles #include <string.h>
23d7b0dc3bSgilles 
24d7b0dc3bSgilles #include "smtpd.h"
25d7b0dc3bSgilles #include "log.h"
26d7b0dc3bSgilles 
2751ecec67Sop #define	PROTOCOL_VERSION	"0.7"
28522448a1Sgilles 
29d53cf4e2Sgilles struct filter;
30d53cf4e2Sgilles struct filter_session;
316c654ff6Sgilles static void	filter_protocol_internal(struct filter_session *, uint64_t *, uint64_t, enum filter_phase, const char *);
32ec69ed85Sgilles static void	filter_protocol(uint64_t, enum filter_phase, const char *);
33aefb8623Smartijn static void	filter_protocol_next(uint64_t, uint64_t, enum filter_phase);
34ec69ed85Sgilles static void	filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *);
354faa8b13Sgilles 
366c654ff6Sgilles static void	filter_data_internal(struct filter_session *, uint64_t, uint64_t, const char *);
37ec69ed85Sgilles static void	filter_data(uint64_t, const char *);
38ec69ed85Sgilles static void	filter_data_next(uint64_t, uint64_t, const char *);
39ec69ed85Sgilles static void	filter_data_query(struct filter *, uint64_t, uint64_t, const char *);
40d7b0dc3bSgilles 
41d53cf4e2Sgilles static int	filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *);
42d53cf4e2Sgilles static int	filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *);
43d53cf4e2Sgilles static int	filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *);
44d53cf4e2Sgilles static int	filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *);
45d53cf4e2Sgilles static int	filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *);
46d85cc38eSgilles static int	filter_builtins_data(struct filter_session *, struct filter *, uint64_t, const char *);
4780acd33cSgilles static int	filter_builtins_commit(struct filter_session *, struct filter *, uint64_t, const char *);
48ec69ed85Sgilles 
49ec69ed85Sgilles static void	filter_result_proceed(uint64_t);
50*d49c07c7Sop static void	filter_result_report(uint64_t, const char *);
51f6b0bf9aSgilles static void	filter_result_junk(uint64_t);
52ec69ed85Sgilles static void	filter_result_rewrite(uint64_t, const char *);
53ec69ed85Sgilles static void	filter_result_reject(uint64_t, const char *);
54ec69ed85Sgilles static void	filter_result_disconnect(uint64_t, const char *);
55d7b0dc3bSgilles 
56590a6142Sgilles static void	filter_session_io(struct io *, int, void *);
571d89356fSmartijn void		lka_filter_process_response(const char *, const char *);
58ec69ed85Sgilles 
59590a6142Sgilles 
60d53cf4e2Sgilles struct filter_session {
61d53cf4e2Sgilles 	uint64_t	id;
62d53cf4e2Sgilles 	struct io	*io;
63d53cf4e2Sgilles 
64aefb8623Smartijn 	char *lastparam;
65aefb8623Smartijn 
66d53cf4e2Sgilles 	char *filter_name;
67d53cf4e2Sgilles 	struct sockaddr_storage ss_src;
68d53cf4e2Sgilles 	struct sockaddr_storage ss_dest;
69d53cf4e2Sgilles 	char *rdns;
70d53cf4e2Sgilles 	int fcrdns;
71d53cf4e2Sgilles 
72fc55f345Sgilles 	char *helo;
73bdf9247dSgilles 	char *username;
74fc55f345Sgilles 	char *mail_from;
756c654ff6Sgilles 
76d53cf4e2Sgilles 	enum filter_phase	phase;
77d53cf4e2Sgilles };
78d7b0dc3bSgilles 
79d7b0dc3bSgilles static struct filter_exec {
80d7b0dc3bSgilles 	enum filter_phase	phase;
81d7b0dc3bSgilles 	const char	       *phase_name;
82d53cf4e2Sgilles 	int		       (*func)(struct filter_session *, struct filter *, uint64_t, const char *);
83ec69ed85Sgilles } filter_execs[FILTER_PHASES_COUNT] = {
84ec69ed85Sgilles 	{ FILTER_CONNECT,	"connect",	filter_builtins_connect },
85ec69ed85Sgilles 	{ FILTER_HELO,		"helo",		filter_builtins_helo },
86ec69ed85Sgilles 	{ FILTER_EHLO,		"ehlo",		filter_builtins_helo },
87ec69ed85Sgilles 	{ FILTER_STARTTLS,     	"starttls",	filter_builtins_notimpl },
88ec69ed85Sgilles 	{ FILTER_AUTH,     	"auth",		filter_builtins_notimpl },
89ec69ed85Sgilles 	{ FILTER_MAIL_FROM,    	"mail-from",	filter_builtins_mail_from },
90ec69ed85Sgilles 	{ FILTER_RCPT_TO,    	"rcpt-to",	filter_builtins_rcpt_to },
91d85cc38eSgilles 	{ FILTER_DATA,    	"data",		filter_builtins_data },
92ec69ed85Sgilles 	{ FILTER_DATA_LINE,    	"data-line",   	filter_builtins_notimpl },
93ec69ed85Sgilles 	{ FILTER_RSET,    	"rset",		filter_builtins_notimpl },
94ec69ed85Sgilles 	{ FILTER_QUIT,    	"quit",		filter_builtins_notimpl },
95ec69ed85Sgilles 	{ FILTER_NOOP,    	"noop",		filter_builtins_notimpl },
96ec69ed85Sgilles 	{ FILTER_HELP,    	"help",		filter_builtins_notimpl },
97ec69ed85Sgilles 	{ FILTER_WIZ,    	"wiz",		filter_builtins_notimpl },
9880acd33cSgilles 	{ FILTER_COMMIT,    	"commit",      	filter_builtins_commit },
99d7b0dc3bSgilles };
100d7b0dc3bSgilles 
101ec69ed85Sgilles struct filter {
102ec69ed85Sgilles 	uint64_t		id;
103ec69ed85Sgilles 	uint32_t		phases;
104ec69ed85Sgilles 	const char	       *name;
105ec69ed85Sgilles 	const char	       *proc;
106ec69ed85Sgilles 	struct filter  	      **chain;
107ec69ed85Sgilles 	size_t 			chain_size;
108ec69ed85Sgilles 	struct filter_config   *config;
109ec69ed85Sgilles };
110ec69ed85Sgilles static struct dict filters;
111ec69ed85Sgilles 
112ec69ed85Sgilles struct filter_entry {
113ec69ed85Sgilles 	TAILQ_ENTRY(filter_entry)	entries;
114ec69ed85Sgilles 	uint64_t			id;
115ec69ed85Sgilles 	const char		       *name;
116ec69ed85Sgilles };
117ec69ed85Sgilles 
118ec69ed85Sgilles struct filter_chain {
119ec69ed85Sgilles 	TAILQ_HEAD(, filter_entry)		chain[nitems(filter_execs)];
120ec69ed85Sgilles };
121ec69ed85Sgilles 
122ec69ed85Sgilles static struct tree	sessions;
123a59ac10cSgilles static int		filters_inited;
124ec69ed85Sgilles 
125ec69ed85Sgilles static struct dict	filter_chains;
126ec69ed85Sgilles 
1270c2dad68Sgilles struct reporter_proc {
1280c2dad68Sgilles 	TAILQ_ENTRY(reporter_proc)	entries;
1290c2dad68Sgilles 	const char		       *name;
1300c2dad68Sgilles };
1310c2dad68Sgilles TAILQ_HEAD(reporters, reporter_proc);
1320c2dad68Sgilles 
1330c2dad68Sgilles static struct dict	report_smtp_in;
1340c2dad68Sgilles static struct dict	report_smtp_out;
1350c2dad68Sgilles 
1360c2dad68Sgilles static struct smtp_events {
1370c2dad68Sgilles 	const char     *event;
1380c2dad68Sgilles } smtp_events[] = {
1390c2dad68Sgilles 	{ "link-connect" },
1400c2dad68Sgilles 	{ "link-disconnect" },
1410c2dad68Sgilles 	{ "link-greeting" },
1420c2dad68Sgilles 	{ "link-identify" },
1430c2dad68Sgilles 	{ "link-tls" },
1440c2dad68Sgilles 	{ "link-auth" },
1450c2dad68Sgilles 
1460c2dad68Sgilles 	{ "tx-reset" },
1470c2dad68Sgilles 	{ "tx-begin" },
1480c2dad68Sgilles 	{ "tx-mail" },
1490c2dad68Sgilles 	{ "tx-rcpt" },
1500c2dad68Sgilles 	{ "tx-envelope" },
1510c2dad68Sgilles 	{ "tx-data" },
1520c2dad68Sgilles 	{ "tx-commit" },
1530c2dad68Sgilles 	{ "tx-rollback" },
1540c2dad68Sgilles 
1550c2dad68Sgilles 	{ "protocol-client" },
1560c2dad68Sgilles 	{ "protocol-server" },
1570c2dad68Sgilles 
1580c2dad68Sgilles 	{ "filter-report" },
1590c2dad68Sgilles 	{ "filter-response" },
1600c2dad68Sgilles 
1610c2dad68Sgilles 	{ "timeout" },
1620c2dad68Sgilles };
1630c2dad68Sgilles 
164a59ac10cSgilles static int			processors_inited = 0;
165a59ac10cSgilles static struct dict		processors;
166a59ac10cSgilles 
167a59ac10cSgilles struct processor_instance {
168a59ac10cSgilles 	char			*name;
169a59ac10cSgilles 	struct io		*io;
170a59ac10cSgilles 	struct io		*errfd;
171a59ac10cSgilles 	int			 ready;
172a59ac10cSgilles 	uint32_t		 subsystems;
173a59ac10cSgilles };
174a59ac10cSgilles 
175a59ac10cSgilles static void	processor_io(struct io *, int, void *);
176a59ac10cSgilles static void	processor_errfd(struct io *, int, void *);
177a59ac10cSgilles void		lka_filter_process_response(const char *, const char *);
178a59ac10cSgilles 
179a59ac10cSgilles int
180a59ac10cSgilles lka_proc_ready(void)
181a59ac10cSgilles {
182a59ac10cSgilles 	void	*iter;
183a59ac10cSgilles 	struct processor_instance	*pi;
184a59ac10cSgilles 
185a59ac10cSgilles 	iter = NULL;
186a59ac10cSgilles 	while (dict_iter(&processors, &iter, NULL, (void **)&pi))
187a59ac10cSgilles 		if (!pi->ready)
188a59ac10cSgilles 			return 0;
189a59ac10cSgilles 	return 1;
190a59ac10cSgilles }
191a59ac10cSgilles 
192a59ac10cSgilles static void
193a59ac10cSgilles lka_proc_config(struct processor_instance *pi)
194a59ac10cSgilles {
195a59ac10cSgilles 	io_printf(pi->io, "config|smtpd-version|%s\n", SMTPD_VERSION);
196806da1f1Sop 	io_printf(pi->io, "config|protocol|%s\n", PROTOCOL_VERSION);
197a59ac10cSgilles 	io_printf(pi->io, "config|smtp-session-timeout|%d\n", SMTPD_SESSION_TIMEOUT);
198a59ac10cSgilles 	if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_IN)
199a59ac10cSgilles 		io_printf(pi->io, "config|subsystem|smtp-in\n");
2002beeb4dbSgilles 	if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_OUT)
2012beeb4dbSgilles 		io_printf(pi->io, "config|subsystem|smtp-out\n");
202f2bf7361Smartijn 	io_printf(pi->io, "config|admd|%s\n",
203f2bf7361Smartijn 	    env->sc_admd != NULL ? env->sc_admd : env->sc_hostname);
204a59ac10cSgilles 	io_printf(pi->io, "config|ready\n");
205a59ac10cSgilles }
206a59ac10cSgilles 
207a59ac10cSgilles void
208a59ac10cSgilles lka_proc_forked(const char *name, uint32_t subsystems, int fd)
209a59ac10cSgilles {
210a59ac10cSgilles 	struct processor_instance	*processor;
211a59ac10cSgilles 
212a59ac10cSgilles 	if (!processors_inited) {
213a59ac10cSgilles 		dict_init(&processors);
214a59ac10cSgilles 		processors_inited = 1;
215a59ac10cSgilles 	}
216a59ac10cSgilles 
217a59ac10cSgilles 	processor = xcalloc(1, sizeof *processor);
218a59ac10cSgilles 	processor->name = xstrdup(name);
219a59ac10cSgilles 	processor->io = io_new();
220a59ac10cSgilles 	processor->subsystems = subsystems;
221a59ac10cSgilles 
222a59ac10cSgilles 	io_set_nonblocking(fd);
223a59ac10cSgilles 
224a59ac10cSgilles 	io_set_fd(processor->io, fd);
225a59ac10cSgilles 	io_set_callback(processor->io, processor_io, processor->name);
226a59ac10cSgilles 	dict_xset(&processors, name, processor);
227a59ac10cSgilles }
228a59ac10cSgilles 
229a59ac10cSgilles void
230a59ac10cSgilles lka_proc_errfd(const char *name, int fd)
231a59ac10cSgilles {
232a59ac10cSgilles 	struct processor_instance	*processor;
233a59ac10cSgilles 
234a59ac10cSgilles 	processor = dict_xget(&processors, name);
235a59ac10cSgilles 
236a59ac10cSgilles 	io_set_nonblocking(fd);
237a59ac10cSgilles 
238a59ac10cSgilles 	processor->errfd = io_new();
239a59ac10cSgilles 	io_set_fd(processor->errfd, fd);
240a59ac10cSgilles 	io_set_callback(processor->errfd, processor_errfd, processor->name);
241a59ac10cSgilles 
242a59ac10cSgilles 	lka_proc_config(processor);
243a59ac10cSgilles }
244a59ac10cSgilles 
245a59ac10cSgilles struct io *
246a59ac10cSgilles lka_proc_get_io(const char *name)
247a59ac10cSgilles {
248a59ac10cSgilles 	struct processor_instance *processor;
249a59ac10cSgilles 
250a59ac10cSgilles 	processor = dict_xget(&processors, name);
251a59ac10cSgilles 
252a59ac10cSgilles 	return processor->io;
253a59ac10cSgilles }
254a59ac10cSgilles 
255a59ac10cSgilles static void
256a59ac10cSgilles processor_register(const char *name, const char *line)
257a59ac10cSgilles {
258a59ac10cSgilles 	struct processor_instance *processor;
259a59ac10cSgilles 
260a59ac10cSgilles 	processor = dict_xget(&processors, name);
261a59ac10cSgilles 
262a59ac10cSgilles 	if (strcmp(line, "register|ready") == 0) {
263a59ac10cSgilles 		processor->ready = 1;
264a59ac10cSgilles 		return;
265a59ac10cSgilles 	}
266a59ac10cSgilles 
267a59ac10cSgilles 	if (strncmp(line, "register|report|", 16) == 0) {
268a59ac10cSgilles 		lka_report_register_hook(name, line+16);
269a59ac10cSgilles 		return;
270a59ac10cSgilles 	}
271a59ac10cSgilles 
272a59ac10cSgilles 	if (strncmp(line, "register|filter|", 16) == 0) {
273a59ac10cSgilles 		lka_filter_register_hook(name, line+16);
274a59ac10cSgilles 		return;
275a59ac10cSgilles 	}
276a59ac10cSgilles 
277a59ac10cSgilles 	fatalx("Invalid register line received: %s", line);
278a59ac10cSgilles }
279a59ac10cSgilles 
280a59ac10cSgilles static void
281a59ac10cSgilles processor_io(struct io *io, int evt, void *arg)
282a59ac10cSgilles {
283a59ac10cSgilles 	struct processor_instance *processor;
284a59ac10cSgilles 	const char		*name = arg;
285a59ac10cSgilles 	char			*line = NULL;
286a59ac10cSgilles 	ssize_t			 len;
287a59ac10cSgilles 
288a59ac10cSgilles 	switch (evt) {
289a59ac10cSgilles 	case IO_DATAIN:
290a59ac10cSgilles 		while ((line = io_getline(io, &len)) != NULL) {
291a59ac10cSgilles 			if (strncmp("register|", line, 9) == 0) {
292a59ac10cSgilles 				processor_register(name, line);
293a59ac10cSgilles 				continue;
294a59ac10cSgilles 			}
295a59ac10cSgilles 
296a59ac10cSgilles 			processor = dict_xget(&processors, name);
297a59ac10cSgilles 			if (!processor->ready)
298a59ac10cSgilles 				fatalx("Non-register message before register|"
299a59ac10cSgilles 				    "ready: %s", line);
300a59ac10cSgilles 			else if (strncmp(line, "filter-result|", 14) == 0 ||
301a59ac10cSgilles 			    strncmp(line, "filter-dataline|", 16) == 0)
302a59ac10cSgilles 				lka_filter_process_response(name, line);
303a59ac10cSgilles 			else if (strncmp(line, "report|", 7) == 0)
304a59ac10cSgilles 				lka_report_proc(name, line);
305a59ac10cSgilles 			else
306a59ac10cSgilles 				fatalx("Invalid filter message type: %s", line);
307a59ac10cSgilles 		}
308a59ac10cSgilles 	}
309a59ac10cSgilles }
310a59ac10cSgilles 
311a59ac10cSgilles static void
312a59ac10cSgilles processor_errfd(struct io *io, int evt, void *arg)
313a59ac10cSgilles {
314a59ac10cSgilles 	const char	*name = arg;
315a59ac10cSgilles 	char		*line = NULL;
316a59ac10cSgilles 	ssize_t		 len;
317a59ac10cSgilles 
318a59ac10cSgilles 	switch (evt) {
319a59ac10cSgilles 	case IO_DATAIN:
320a59ac10cSgilles 		while ((line = io_getline(io, &len)) != NULL)
321a59ac10cSgilles 			log_warnx("%s: %s", name, line);
322a59ac10cSgilles 	}
323a59ac10cSgilles }
324a59ac10cSgilles 
325ec69ed85Sgilles void
326ec69ed85Sgilles lka_filter_init(void)
327ec69ed85Sgilles {
328ec69ed85Sgilles 	void		*iter;
329ec69ed85Sgilles 	const char	*name;
330ec69ed85Sgilles 	struct filter  	*filter;
331ec69ed85Sgilles 	struct filter_config	*filter_config;
332ec69ed85Sgilles 	size_t		i;
333417409c3Sgilles 	char		 buffer[LINE_MAX];	/* for traces */
334ec69ed85Sgilles 
335ec69ed85Sgilles 	dict_init(&filters);
336ec69ed85Sgilles 	dict_init(&filter_chains);
337ec69ed85Sgilles 
3386c654ff6Sgilles 	/* first pass, allocate and init individual filters */
339ec69ed85Sgilles 	iter = NULL;
340ec69ed85Sgilles 	while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) {
341ec69ed85Sgilles 		switch (filter_config->filter_type) {
342ec69ed85Sgilles 		case FILTER_TYPE_BUILTIN:
343ec69ed85Sgilles 			filter = xcalloc(1, sizeof(*filter));
344ec69ed85Sgilles 			filter->name = name;
345ec69ed85Sgilles 			filter->phases |= (1<<filter_config->phase);
346ec69ed85Sgilles 			filter->config = filter_config;
347ec69ed85Sgilles 			dict_set(&filters, name, filter);
348417409c3Sgilles 			log_trace(TRACE_FILTERS, "filters init type=builtin, name=%s, hooks=%08x",
349417409c3Sgilles 			    name, filter->phases);
350ec69ed85Sgilles 			break;
351ec69ed85Sgilles 
352ec69ed85Sgilles 		case FILTER_TYPE_PROC:
353ec69ed85Sgilles 			filter = xcalloc(1, sizeof(*filter));
354ec69ed85Sgilles 			filter->name = name;
355ec69ed85Sgilles 			filter->proc = filter_config->proc;
356ec69ed85Sgilles 			filter->config = filter_config;
357ec69ed85Sgilles 			dict_set(&filters, name, filter);
358417409c3Sgilles 			log_trace(TRACE_FILTERS, "filters init type=proc, name=%s, proc=%s",
359417409c3Sgilles 			    name, filter_config->proc);
360ec69ed85Sgilles 			break;
361ec69ed85Sgilles 
362ec69ed85Sgilles 		case FILTER_TYPE_CHAIN:
36300312206Sgilles 			break;
36400312206Sgilles 		}
36500312206Sgilles 	}
36600312206Sgilles 
3676c654ff6Sgilles 	/* second pass, allocate and init filter chains but don't build yet */
36800312206Sgilles 	iter = NULL;
36900312206Sgilles 	while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) {
37000312206Sgilles 		switch (filter_config->filter_type) {
37100312206Sgilles 		case FILTER_TYPE_CHAIN:
372ec69ed85Sgilles 			filter = xcalloc(1, sizeof(*filter));
373ec69ed85Sgilles 			filter->name = name;
374ec69ed85Sgilles 			filter->chain = xcalloc(filter_config->chain_size, sizeof(void **));
375ec69ed85Sgilles 			filter->chain_size = filter_config->chain_size;
376ec69ed85Sgilles 			filter->config = filter_config;
377417409c3Sgilles 
378417409c3Sgilles 			buffer[0] = '\0';
379417409c3Sgilles 			for (i = 0; i < filter->chain_size; ++i) {
38000312206Sgilles 				filter->chain[i] = dict_xget(&filters, filter_config->chain[i]);
381417409c3Sgilles 				if (i)
382417409c3Sgilles 					(void)strlcat(buffer, ", ", sizeof buffer);
383417409c3Sgilles 				(void)strlcat(buffer, filter->chain[i]->name, sizeof buffer);
384417409c3Sgilles 			}
385417409c3Sgilles 			log_trace(TRACE_FILTERS, "filters init type=chain, name=%s { %s }", name, buffer);
386417409c3Sgilles 
387ec69ed85Sgilles 			dict_set(&filters, name, filter);
388ec69ed85Sgilles 			break;
38900312206Sgilles 
39000312206Sgilles 		case FILTER_TYPE_BUILTIN:
39100312206Sgilles 		case FILTER_TYPE_PROC:
39200312206Sgilles 			break;
393ec69ed85Sgilles 		}
394ec69ed85Sgilles 	}
395ec69ed85Sgilles }
396ec69ed85Sgilles 
397ec69ed85Sgilles void
398ec69ed85Sgilles lka_filter_register_hook(const char *name, const char *hook)
399ec69ed85Sgilles {
400ec69ed85Sgilles 	struct filter		*filter;
401ec69ed85Sgilles 	const char	*filter_name;
402ec69ed85Sgilles 	void		*iter;
403ec69ed85Sgilles 	size_t	i;
404ec69ed85Sgilles 
405ec69ed85Sgilles 	if (strncasecmp(hook, "smtp-in|", 8) == 0) {
406ec69ed85Sgilles 		hook += 8;
407ec69ed85Sgilles 	}
408ec69ed85Sgilles 	else
4091d89356fSmartijn 		fatalx("Invalid message direction: %s", hook);
410ec69ed85Sgilles 
411ec69ed85Sgilles 	for (i = 0; i < nitems(filter_execs); i++)
412ec69ed85Sgilles 		if (strcmp(hook, filter_execs[i].phase_name) == 0)
413ec69ed85Sgilles 			break;
414ec69ed85Sgilles 	if (i == nitems(filter_execs))
4151d89356fSmartijn 		fatalx("Unrecognized report name: %s", hook);
416ec69ed85Sgilles 
417ec69ed85Sgilles 	iter = NULL;
418ec69ed85Sgilles 	while (dict_iter(&filters, &iter, &filter_name, (void **)&filter))
419ec69ed85Sgilles 		if (filter->proc && strcmp(name, filter->proc) == 0)
420ec69ed85Sgilles 			filter->phases |= (1<<filter_execs[i].phase);
421ec69ed85Sgilles }
422ec69ed85Sgilles 
423ec69ed85Sgilles void
424ec69ed85Sgilles lka_filter_ready(void)
425ec69ed85Sgilles {
426ec69ed85Sgilles 	struct filter  	*filter;
427ec69ed85Sgilles 	struct filter  	*subfilter;
428ec69ed85Sgilles 	const char	*filter_name;
429ec69ed85Sgilles 	struct filter_entry	*filter_entry;
430ec69ed85Sgilles 	struct filter_chain	*filter_chain;
431ec69ed85Sgilles 	void		*iter;
432ec69ed85Sgilles 	size_t		i;
433ec69ed85Sgilles 	size_t		j;
434ec69ed85Sgilles 
4356c654ff6Sgilles 	/* all filters are ready, actually build the filter chains */
436ec69ed85Sgilles 	iter = NULL;
437ec69ed85Sgilles 	while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) {
438ec69ed85Sgilles 		filter_chain = xcalloc(1, sizeof *filter_chain);
439ec69ed85Sgilles 		for (i = 0; i < nitems(filter_execs); i++)
440ec69ed85Sgilles 			TAILQ_INIT(&filter_chain->chain[i]);
441ec69ed85Sgilles 		dict_set(&filter_chains, filter_name, filter_chain);
442ec69ed85Sgilles 
443ec69ed85Sgilles 		if (filter->chain) {
444ec69ed85Sgilles 			for (i = 0; i < filter->chain_size; i++) {
445ec69ed85Sgilles 				subfilter = filter->chain[i];
446ec69ed85Sgilles 				for (j = 0; j < nitems(filter_execs); ++j) {
447ec69ed85Sgilles 					if (subfilter->phases & (1<<j)) {
448ec69ed85Sgilles 						filter_entry = xcalloc(1, sizeof *filter_entry);
449ec69ed85Sgilles 						filter_entry->id = generate_uid();
450ec69ed85Sgilles 						filter_entry->name = subfilter->name;
451ec69ed85Sgilles 						TAILQ_INSERT_TAIL(&filter_chain->chain[j],
452ec69ed85Sgilles 						    filter_entry, entries);
453ec69ed85Sgilles 					}
454ec69ed85Sgilles 				}
455ec69ed85Sgilles 			}
456ec69ed85Sgilles 			continue;
457ec69ed85Sgilles 		}
45800312206Sgilles 
459ec69ed85Sgilles 		for (i = 0; i < nitems(filter_execs); ++i) {
460ec69ed85Sgilles 			if (filter->phases & (1<<i)) {
461ec69ed85Sgilles 				filter_entry = xcalloc(1, sizeof *filter_entry);
462ec69ed85Sgilles 				filter_entry->id = generate_uid();
463ec69ed85Sgilles 				filter_entry->name = filter_name;
464ec69ed85Sgilles 				TAILQ_INSERT_TAIL(&filter_chain->chain[i],
465ec69ed85Sgilles 				    filter_entry, entries);
466ec69ed85Sgilles 			}
467ec69ed85Sgilles 		}
468ec69ed85Sgilles 	}
469ec69ed85Sgilles }
470ec69ed85Sgilles 
471ec69ed85Sgilles int
472ec69ed85Sgilles lka_filter_proc_in_session(uint64_t reqid, const char *proc)
473ec69ed85Sgilles {
474ec69ed85Sgilles 	struct filter_session	*fs;
475ec69ed85Sgilles 	struct filter		*filter;
476ec69ed85Sgilles 	size_t			 i;
477ec69ed85Sgilles 
47877007bffSgilles 	if ((fs = tree_get(&sessions, reqid)) == NULL)
47977007bffSgilles 		return 0;
480ec69ed85Sgilles 
48177007bffSgilles 	filter = dict_get(&filters, fs->filter_name);
4826c0f0c33Sgilles 	if (filter == NULL || (filter->proc == NULL && filter->chain == NULL))
483ec69ed85Sgilles 		return 0;
484ec69ed85Sgilles 
485ec69ed85Sgilles 	if (filter->proc)
486ec69ed85Sgilles 		return strcmp(filter->proc, proc) == 0 ? 1 : 0;
487ec69ed85Sgilles 
488ec69ed85Sgilles 	for (i = 0; i < filter->chain_size; i++)
489ec69ed85Sgilles 		if (filter->chain[i]->proc &&
490ec69ed85Sgilles 		    strcmp(filter->chain[i]->proc, proc) == 0)
491ec69ed85Sgilles 			return 1;
492ec69ed85Sgilles 
493ec69ed85Sgilles 	return 0;
494ec69ed85Sgilles }
495ec69ed85Sgilles 
496d7b0dc3bSgilles void
4976c0f0c33Sgilles lka_filter_begin(uint64_t reqid, const char *filter_name)
498590a6142Sgilles {
499590a6142Sgilles 	struct filter_session	*fs;
500590a6142Sgilles 
501a59ac10cSgilles 	if (!filters_inited) {
502590a6142Sgilles 		tree_init(&sessions);
503a59ac10cSgilles 		filters_inited = 1;
504590a6142Sgilles 	}
505590a6142Sgilles 
506590a6142Sgilles 	fs = xcalloc(1, sizeof (struct filter_session));
5074faa8b13Sgilles 	fs->id = reqid;
508ec69ed85Sgilles 	fs->filter_name = xstrdup(filter_name);
5094faa8b13Sgilles 	tree_xset(&sessions, fs->id, fs);
510417409c3Sgilles 
511417409c3Sgilles 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid);
512590a6142Sgilles }
513590a6142Sgilles 
514590a6142Sgilles void
515590a6142Sgilles lka_filter_end(uint64_t reqid)
516590a6142Sgilles {
517590a6142Sgilles 	struct filter_session	*fs;
518590a6142Sgilles 
519590a6142Sgilles 	fs = tree_xpop(&sessions, reqid);
520486f188eSgilles 	free(fs->rdns);
521aefb8623Smartijn 	free(fs->helo);
522aefb8623Smartijn 	free(fs->mail_from);
523bdf9247dSgilles 	free(fs->username);
524aefb8623Smartijn 	free(fs->lastparam);
52521522cbeSmartijn 	free(fs->filter_name);
526590a6142Sgilles 	free(fs);
527417409c3Sgilles 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-end", reqid);
528590a6142Sgilles }
529590a6142Sgilles 
530590a6142Sgilles void
531590a6142Sgilles lka_filter_data_begin(uint64_t reqid)
532590a6142Sgilles {
533590a6142Sgilles 	struct filter_session  *fs;
534590a6142Sgilles 	int	sp[2];
535590a6142Sgilles 	int	fd = -1;
536590a6142Sgilles 
537590a6142Sgilles 	fs = tree_xget(&sessions, reqid);
538590a6142Sgilles 
539590a6142Sgilles 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1)
540590a6142Sgilles 		goto end;
541ec69ed85Sgilles 	io_set_nonblocking(sp[0]);
542ec69ed85Sgilles 	io_set_nonblocking(sp[1]);
543590a6142Sgilles 	fd = sp[0];
544590a6142Sgilles 	fs->io = io_new();
545590a6142Sgilles 	io_set_fd(fs->io, sp[1]);
546590a6142Sgilles 	io_set_callback(fs->io, filter_session_io, fs);
547590a6142Sgilles 
548590a6142Sgilles end:
5491a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, fd);
5501a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
5511a5b831aSmartijn 	m_add_int(p_dispatcher, fd != -1 ? 1 : 0);
5521a5b831aSmartijn 	m_close(p_dispatcher);
553417409c3Sgilles 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-begin fd=%d", reqid, fd);
554590a6142Sgilles }
555590a6142Sgilles 
556590a6142Sgilles void
557590a6142Sgilles lka_filter_data_end(uint64_t reqid)
558590a6142Sgilles {
559590a6142Sgilles 	struct filter_session	*fs;
560590a6142Sgilles 
561590a6142Sgilles 	fs = tree_xget(&sessions, reqid);
562ec69ed85Sgilles 	if (fs->io) {
563590a6142Sgilles 		io_free(fs->io);
564590a6142Sgilles 		fs->io = NULL;
565590a6142Sgilles 	}
566417409c3Sgilles 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-end", reqid);
567ec69ed85Sgilles }
568590a6142Sgilles 
569590a6142Sgilles static void
570590a6142Sgilles filter_session_io(struct io *io, int evt, void *arg)
571590a6142Sgilles {
572590a6142Sgilles 	struct filter_session *fs = arg;
573590a6142Sgilles 	char *line = NULL;
574590a6142Sgilles 	ssize_t len;
575590a6142Sgilles 
576590a6142Sgilles 	log_trace(TRACE_IO, "filter session: %p: %s %s", fs, io_strevent(evt),
577590a6142Sgilles 	    io_strio(io));
578590a6142Sgilles 
579590a6142Sgilles 	switch (evt) {
580590a6142Sgilles 	case IO_DATAIN:
581590a6142Sgilles 	nextline:
582590a6142Sgilles 		line = io_getline(fs->io, &len);
583590a6142Sgilles 		/* No complete line received */
584590a6142Sgilles 		if (line == NULL)
585590a6142Sgilles 			return;
586590a6142Sgilles 
5874faa8b13Sgilles 		filter_data(fs->id, line);
588590a6142Sgilles 
589590a6142Sgilles 		goto nextline;
590590a6142Sgilles 	}
591590a6142Sgilles }
592590a6142Sgilles 
5931d89356fSmartijn void
5944faa8b13Sgilles lka_filter_process_response(const char *name, const char *line)
5954faa8b13Sgilles {
5964faa8b13Sgilles 	uint64_t reqid;
597ec69ed85Sgilles 	uint64_t token;
5984faa8b13Sgilles 	char *ep = NULL;
59956ac5201Sop 	const char *kind = NULL;
60056ac5201Sop 	const char *qid = NULL;
60156ac5201Sop 	const char *response = NULL;
60256ac5201Sop 	const char *parameter = NULL;
603c34ec359Smartijn 	struct filter_session *fs;
6044faa8b13Sgilles 
60556ac5201Sop 	kind = line;
60656ac5201Sop 
60756ac5201Sop 	if ((ep = strchr(kind, '|')) == NULL)
6081d89356fSmartijn 		fatalx("Missing token: %s", line);
6094faa8b13Sgilles 	qid = ep+1;
610bba303a8Sop 
611bba303a8Sop 	errno = 0;
612cde3d2dcSgilles 	reqid = strtoull(qid, &ep, 16);
61356ac5201Sop 	if (qid[0] == '\0' || *ep != '|')
614cde3d2dcSgilles 		fatalx("Invalid reqid: %s", line);
615cde3d2dcSgilles 	if (errno == ERANGE && reqid == ULLONG_MAX)
616cde3d2dcSgilles 		fatal("Invalid reqid: %s", line);
617ec69ed85Sgilles 
618ec69ed85Sgilles 	qid = ep + 1;
619cde3d2dcSgilles 	token = strtoull(qid, &ep, 16);
62056ac5201Sop 	if (qid[0] == '\0' || *ep != '|')
621cde3d2dcSgilles 		fatalx("Invalid token: %s", line);
622cde3d2dcSgilles 	if (errno == ERANGE && token == ULLONG_MAX)
623cde3d2dcSgilles 		fatal("Invalid token: %s", line);
6244faa8b13Sgilles 
6254faa8b13Sgilles 	response = ep+1;
6264faa8b13Sgilles 
627e7da3e76Sgilles 	/* session can legitimately disappear on a resume */
628d8a97f8cSgilles 	if ((fs = tree_get(&sessions, reqid)) == NULL)
629e7da3e76Sgilles 		return;
630e7da3e76Sgilles 
63156ac5201Sop 	if (strncmp(kind, "filter-dataline|", 16) == 0) {
632c34ec359Smartijn 		if (fs->phase != FILTER_DATA_LINE)
6331d89356fSmartijn 			fatalx("filter-dataline out of dataline phase");
634ec69ed85Sgilles 		filter_data_next(token, reqid, response);
6351d89356fSmartijn 		return;
6364faa8b13Sgilles 	}
637c34ec359Smartijn 	if (fs->phase == FILTER_DATA_LINE)
6381d89356fSmartijn 		fatalx("filter-result in dataline phase");
6394faa8b13Sgilles 
64056ac5201Sop 	if ((ep = strchr(response, '|')) != NULL)
64102db46c1Sgilles 		parameter = ep + 1;
64202db46c1Sgilles 
6431d89356fSmartijn 	if (strcmp(response, "proceed") == 0) {
644aefb8623Smartijn 		filter_protocol_next(token, reqid, 0);
6451d89356fSmartijn 		return;
646f6b0bf9aSgilles 	} else if (strcmp(response, "junk") == 0) {
647a0b3f7b0Sgilles 		if (fs->phase == FILTER_COMMIT)
648a0b3f7b0Sgilles 			fatalx("filter-reponse junk after DATA");
649f6b0bf9aSgilles 		filter_result_junk(reqid);
650f6b0bf9aSgilles 		return;
6511d89356fSmartijn 	} else {
6521d89356fSmartijn 		if (parameter == NULL)
6531d89356fSmartijn 			fatalx("Missing parameter: %s", line);
6541d89356fSmartijn 
65556ac5201Sop 		if (strncmp(response, "rewrite|", 8) == 0)
6561d89356fSmartijn 			filter_result_rewrite(reqid, parameter);
65756ac5201Sop 		else if (strncmp(response, "reject|", 7) == 0)
6581d89356fSmartijn 			filter_result_reject(reqid, parameter);
65956ac5201Sop 		else if (strncmp(response, "disconnect|", 11) == 0)
6601d89356fSmartijn 			filter_result_disconnect(reqid, parameter);
661*d49c07c7Sop 		else if (strncmp(response, "report|", 7) == 0)
662*d49c07c7Sop 			filter_result_report(reqid, parameter);
6631d89356fSmartijn 		else
6641d89356fSmartijn 			fatalx("Invalid directive: %s", line);
6651d89356fSmartijn 	}
6664faa8b13Sgilles }
6674faa8b13Sgilles 
668590a6142Sgilles void
66971507431Sgilles lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param)
670d7b0dc3bSgilles {
671ec69ed85Sgilles 	filter_protocol(reqid, phase, param);
672ec69ed85Sgilles }
673ec69ed85Sgilles 
6744faa8b13Sgilles static void
6756c654ff6Sgilles filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t reqid, enum filter_phase phase, const char *param)
676ec69ed85Sgilles {
677ec69ed85Sgilles 	struct filter_chain	*filter_chain;
678ec69ed85Sgilles 	struct filter_entry	*filter_entry;
679ec69ed85Sgilles 	struct filter		*filter;
68041ba6056Smartijn 	struct timeval		 tv;
681417409c3Sgilles 	const char		*phase_name = filter_execs[phase].phase_name;
682417409c3Sgilles 	int			 resume = 1;
683ec69ed85Sgilles 
684417409c3Sgilles 	if (!*token) {
685a0f66d40Sgilles 		fs->phase = phase;
686417409c3Sgilles 		resume = 0;
687417409c3Sgilles 	}
688ec69ed85Sgilles 
689a0f66d40Sgilles 	/* XXX - this sanity check requires a protocol change, stub for now */
690a0f66d40Sgilles 	phase = fs->phase;
691a0f66d40Sgilles 	if (fs->phase != phase)
692a0f66d40Sgilles 		fatalx("misbehaving filter");
693a0f66d40Sgilles 
694a0f66d40Sgilles 	/* based on token, identify the filter_entry we should apply  */
69577007bffSgilles 	filter_chain = dict_get(&filter_chains, fs->filter_name);
696a0f66d40Sgilles 	filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]);
697a0f66d40Sgilles 	if (*token) {
698ec69ed85Sgilles 		TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries)
699a0f66d40Sgilles 		    if (filter_entry->id == *token)
700ec69ed85Sgilles 			    break;
701a0f66d40Sgilles 		if (filter_entry == NULL)
702a0f66d40Sgilles 			fatalx("misbehaving filter");
703a0f66d40Sgilles 		filter_entry = TAILQ_NEXT(filter_entry, entries);
704a0f66d40Sgilles 	}
705ec69ed85Sgilles 
706a0f66d40Sgilles 	/* no filter_entry, we either had none or reached end of chain */
707a0f66d40Sgilles 	if (filter_entry == NULL) {
708417409c3Sgilles 		log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, resume=%s, "
709417409c3Sgilles 		    "action=proceed",
710417409c3Sgilles 		    fs->id, phase_name, resume ? "y" : "n");
711a0f66d40Sgilles 		filter_result_proceed(reqid);
712a0f66d40Sgilles 		return;
713a0f66d40Sgilles 	}
714a0f66d40Sgilles 
715a0f66d40Sgilles 	/* process param with current filter_entry */
716a0f66d40Sgilles 	*token = filter_entry->id;
717ec69ed85Sgilles 	filter = dict_get(&filters, filter_entry->name);
718ec69ed85Sgilles 	if (filter->proc) {
719417409c3Sgilles 		log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
720417409c3Sgilles 		    "resume=%s, action=deferred, filter=%s",
721417409c3Sgilles 		    fs->id, phase_name, resume ? "y" : "n",
722417409c3Sgilles 		    filter->name);
723ec69ed85Sgilles 		filter_protocol_query(filter, filter_entry->id, reqid,
724ec69ed85Sgilles 		    filter_execs[fs->phase].phase_name, param);
725a0f66d40Sgilles 		return;	/* deferred response */
726ec69ed85Sgilles 	}
727ec69ed85Sgilles 
728d53cf4e2Sgilles 	if (filter_execs[fs->phase].func(fs, filter, reqid, param)) {
729a0f66d40Sgilles 		if (filter->config->rewrite) {
730417409c3Sgilles 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
731417409c3Sgilles 			    "resume=%s, action=rewrite, filter=%s, query=%s, response=%s",
732417409c3Sgilles 			    fs->id, phase_name, resume ? "y" : "n",
733417409c3Sgilles 			    filter->name,
734417409c3Sgilles 			    param,
735417409c3Sgilles 			    filter->config->rewrite);
736ec69ed85Sgilles 			filter_result_rewrite(reqid, filter->config->rewrite);
737a0f66d40Sgilles 			return;
738a0f66d40Sgilles 		}
739a0f66d40Sgilles 		else if (filter->config->disconnect) {
740417409c3Sgilles 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
741417409c3Sgilles 			    "resume=%s, action=disconnect, filter=%s, query=%s, response=%s",
742417409c3Sgilles 			    fs->id, phase_name, resume ? "y" : "n",
743417409c3Sgilles 			    filter->name,
744417409c3Sgilles 			    param,
745417409c3Sgilles 			    filter->config->disconnect);
746ec69ed85Sgilles 			filter_result_disconnect(reqid, filter->config->disconnect);
747a0f66d40Sgilles 			return;
748a0f66d40Sgilles 		}
749f6b0bf9aSgilles 		else if (filter->config->junk) {
750f6b0bf9aSgilles 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
751f6b0bf9aSgilles 			    "resume=%s, action=junk, filter=%s, query=%s",
752f6b0bf9aSgilles 			    fs->id, phase_name, resume ? "y" : "n",
753f6b0bf9aSgilles 			    filter->name,
754f6b0bf9aSgilles 			    param);
755f6b0bf9aSgilles 			filter_result_junk(reqid);
756f6b0bf9aSgilles 			return;
75741ba6056Smartijn 		} else if (filter->config->report) {
75841ba6056Smartijn 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
75941ba6056Smartijn 			    "resume=%s, action=report, filter=%s, query=%s response=%s",
76041ba6056Smartijn 			    fs->id, phase_name, resume ? "y" : "n",
76141ba6056Smartijn 			    filter->name,
76241ba6056Smartijn 			    param, filter->config->report);
76341ba6056Smartijn 
76441ba6056Smartijn 			gettimeofday(&tv, NULL);
76541ba6056Smartijn 			lka_report_filter_report(fs->id, filter->name, 1,
76641ba6056Smartijn 			    "smtp-in", &tv, filter->config->report);
767b4dfc37cSgilles 		} else if (filter->config->bypass) {
768b4dfc37cSgilles 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
769b4dfc37cSgilles 			    "resume=%s, action=bypass, filter=%s, query=%s",
770b4dfc37cSgilles 			    fs->id, phase_name, resume ? "y" : "n",
771b4dfc37cSgilles 			    filter->name,
772b4dfc37cSgilles 			    param);
773b4dfc37cSgilles 			filter_result_proceed(reqid);
774b4dfc37cSgilles 			return;
77541ba6056Smartijn 		} else {
776417409c3Sgilles 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
777417409c3Sgilles 			    "resume=%s, action=reject, filter=%s, query=%s, response=%s",
778417409c3Sgilles 			    fs->id, phase_name, resume ? "y" : "n",
779417409c3Sgilles 			    filter->name,
780417409c3Sgilles 			    param,
781417409c3Sgilles 			    filter->config->reject);
782ec69ed85Sgilles 			filter_result_reject(reqid, filter->config->reject);
783ec69ed85Sgilles 			return;
784ec69ed85Sgilles 		}
785ec69ed85Sgilles 	}
786ec69ed85Sgilles 
787417409c3Sgilles 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
788417409c3Sgilles 	    "resume=%s, action=proceed, filter=%s, query=%s",
789417409c3Sgilles 	    fs->id, phase_name, resume ? "y" : "n",
790417409c3Sgilles 	    filter->name,
791417409c3Sgilles 	    param);
792417409c3Sgilles 
793a0f66d40Sgilles 	/* filter_entry resulted in proceed, try next filter */
7946c654ff6Sgilles 	filter_protocol_internal(fs, token, reqid, phase, param);
795a0f66d40Sgilles 	return;
796a0f66d40Sgilles }
797a0f66d40Sgilles 
798a0f66d40Sgilles static void
7996c654ff6Sgilles filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, const char *line)
8004faa8b13Sgilles {
801ec69ed85Sgilles 	struct filter_chain	*filter_chain;
802ec69ed85Sgilles 	struct filter_entry	*filter_entry;
803ec69ed85Sgilles 	struct filter		*filter;
8044faa8b13Sgilles 
8059235688dSgilles 	if (!token)
806ec69ed85Sgilles 		fs->phase = FILTER_DATA_LINE;
8079235688dSgilles 	if (fs->phase != FILTER_DATA_LINE)
8089235688dSgilles 		fatalx("misbehaving filter");
8099235688dSgilles 
810a0f66d40Sgilles 	/* based on token, identify the filter_entry we should apply  */
811ec69ed85Sgilles 	filter_chain = dict_get(&filter_chains, fs->filter_name);
812ec69ed85Sgilles 	filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]);
8139235688dSgilles 	if (token) {
8149235688dSgilles 		TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries)
8159235688dSgilles 		    if (filter_entry->id == token)
8169235688dSgilles 			    break;
8179235688dSgilles 		if (filter_entry == NULL)
8189235688dSgilles 			fatalx("misbehaving filter");
8199235688dSgilles 		filter_entry = TAILQ_NEXT(filter_entry, entries);
8209235688dSgilles 	}
8219235688dSgilles 
822a0f66d40Sgilles 	/* no filter_entry, we either had none or reached end of chain */
823ec69ed85Sgilles 	if (filter_entry == NULL) {
8240124f38dSeric 		io_printf(fs->io, "%s\n", line);
825ec69ed85Sgilles 		return;
826ec69ed85Sgilles 	}
827ec69ed85Sgilles 
828a0f66d40Sgilles 	/* pass data to the filter */
829ec69ed85Sgilles 	filter = dict_get(&filters, filter_entry->name);
830ec69ed85Sgilles 	filter_data_query(filter, filter_entry->id, reqid, line);
8314faa8b13Sgilles }
8324faa8b13Sgilles 
8334faa8b13Sgilles static void
83418e75a20Sgilles filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param)
83518e75a20Sgilles {
8366c654ff6Sgilles 	struct filter_session  *fs;
83718e75a20Sgilles 	uint64_t		token = 0;
838417409c3Sgilles 	char		       *nparam = NULL;
83918e75a20Sgilles 
8406c654ff6Sgilles 	fs = tree_xget(&sessions, reqid);
8416c654ff6Sgilles 
8426c654ff6Sgilles 	switch (phase) {
8436c654ff6Sgilles 	case FILTER_HELO:
8446c654ff6Sgilles 	case FILTER_EHLO:
845fc55f345Sgilles 		free(fs->helo);
846fc55f345Sgilles 		fs->helo = xstrdup(param);
847fc55f345Sgilles 		break;
848fc55f345Sgilles 	case FILTER_MAIL_FROM:
849fc55f345Sgilles 		free(fs->mail_from);
850fc55f345Sgilles 		fs->mail_from = xstrdup(param + 1);
851fc55f345Sgilles 		*strchr(fs->mail_from, '>') = '\0';
852e84423b4Sgilles 		param = fs->mail_from;
853fc55f345Sgilles 
8546c654ff6Sgilles 		break;
855417409c3Sgilles 	case FILTER_RCPT_TO:
856417409c3Sgilles 		nparam = xstrdup(param + 1);
857417409c3Sgilles 		*strchr(nparam, '>') = '\0';
858417409c3Sgilles 		param = nparam;
859417409c3Sgilles 		break;
8606c654ff6Sgilles 	case FILTER_STARTTLS:
8616c654ff6Sgilles 		/* TBD */
8626c654ff6Sgilles 		break;
8636c654ff6Sgilles 	default:
8646c654ff6Sgilles 		break;
8656c654ff6Sgilles 	}
866aefb8623Smartijn 
867aefb8623Smartijn 	free(fs->lastparam);
868aefb8623Smartijn 	fs->lastparam = xstrdup(param);
869aefb8623Smartijn 
8706c654ff6Sgilles 	filter_protocol_internal(fs, &token, reqid, phase, param);
871417409c3Sgilles 	if (nparam)
872417409c3Sgilles 		free(nparam);
87318e75a20Sgilles }
87418e75a20Sgilles 
87518e75a20Sgilles static void
876aefb8623Smartijn filter_protocol_next(uint64_t token, uint64_t reqid, enum filter_phase phase)
87718e75a20Sgilles {
8786c654ff6Sgilles 	struct filter_session  *fs;
8796c654ff6Sgilles 
8806c654ff6Sgilles 	/* session can legitimately disappear on a resume */
8816c654ff6Sgilles 	if ((fs = tree_get(&sessions, reqid)) == NULL)
8826c654ff6Sgilles 		return;
8836c654ff6Sgilles 
884aefb8623Smartijn 	filter_protocol_internal(fs, &token, reqid, phase, fs->lastparam);
88518e75a20Sgilles }
88618e75a20Sgilles 
88718e75a20Sgilles static void
8889235688dSgilles filter_data(uint64_t reqid, const char *line)
8894faa8b13Sgilles {
8906c654ff6Sgilles 	struct filter_session  *fs;
8916c654ff6Sgilles 
8926c654ff6Sgilles 	fs = tree_xget(&sessions, reqid);
8936c654ff6Sgilles 
8946c654ff6Sgilles 	filter_data_internal(fs, 0, reqid, line);
8954faa8b13Sgilles }
8964faa8b13Sgilles 
8979235688dSgilles static void
8989235688dSgilles filter_data_next(uint64_t token, uint64_t reqid, const char *line)
8999235688dSgilles {
9006c654ff6Sgilles 	struct filter_session  *fs;
9016c654ff6Sgilles 
9026c654ff6Sgilles 	/* session can legitimately disappear on a resume */
9036c654ff6Sgilles 	if ((fs = tree_get(&sessions, reqid)) == NULL)
9046c654ff6Sgilles 		return;
9056c654ff6Sgilles 
9066c654ff6Sgilles 	filter_data_internal(fs, token, reqid, line);
9074faa8b13Sgilles }
9084faa8b13Sgilles 
909d7b0dc3bSgilles static void
910ec69ed85Sgilles filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param)
911522448a1Sgilles {
912a8ae7d4bSgilles 	int	n;
91371507431Sgilles 	struct filter_session	*fs;
914ab325ad2Sgilles 	struct timeval	tv;
915ab325ad2Sgilles 
916ab325ad2Sgilles 	gettimeofday(&tv, NULL);
917a8ae7d4bSgilles 
91871507431Sgilles 	fs = tree_xget(&sessions, reqid);
919ec69ed85Sgilles 	if (strcmp(phase, "connect") == 0)
920ec69ed85Sgilles 		n = io_printf(lka_proc_get_io(filter->proc),
921cc3de1d2Sgilles 		    "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n",
9224faa8b13Sgilles 		    PROTOCOL_VERSION,
9238c71974fSop 		    (long long)tv.tv_sec, (long)tv.tv_usec,
9240aa0c976Ssunil 		    phase, reqid, token, fs->rdns, param);
925a8ae7d4bSgilles 	else
926ec69ed85Sgilles 		n = io_printf(lka_proc_get_io(filter->proc),
927cc3de1d2Sgilles 		    "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n",
9284faa8b13Sgilles 		    PROTOCOL_VERSION,
9298c71974fSop 		    (long long)tv.tv_sec, (long)tv.tv_usec,
9300aa0c976Ssunil 		    phase, reqid, token, param);
931a8ae7d4bSgilles 	if (n == -1)
932522448a1Sgilles 		fatalx("failed to write to processor");
933522448a1Sgilles }
934522448a1Sgilles 
935522448a1Sgilles static void
936ec69ed85Sgilles filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line)
9374faa8b13Sgilles {
9384faa8b13Sgilles 	int	n;
939ab325ad2Sgilles 	struct timeval	tv;
9404faa8b13Sgilles 
941ab325ad2Sgilles 	gettimeofday(&tv, NULL);
942ab325ad2Sgilles 
943ec69ed85Sgilles 	n = io_printf(lka_proc_get_io(filter->proc),
944cc3de1d2Sgilles 	    "filter|%s|%lld.%06ld|smtp-in|data-line|"
945ec69ed85Sgilles 	    "%016"PRIx64"|%016"PRIx64"|%s\n",
9464faa8b13Sgilles 	    PROTOCOL_VERSION,
9478c71974fSop 	    (long long)tv.tv_sec, (long)tv.tv_usec,
948ab325ad2Sgilles 	    reqid, token, line);
9494faa8b13Sgilles 	if (n == -1)
9504faa8b13Sgilles 		fatalx("failed to write to processor");
9514faa8b13Sgilles }
9524faa8b13Sgilles 
9534faa8b13Sgilles static void
954ec69ed85Sgilles filter_result_proceed(uint64_t reqid)
955d7b0dc3bSgilles {
9561a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
9571a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
9581a5b831aSmartijn 	m_add_int(p_dispatcher, FILTER_PROCEED);
9591a5b831aSmartijn 	m_close(p_dispatcher);
960d7b0dc3bSgilles }
961d7b0dc3bSgilles 
962d7b0dc3bSgilles static void
963*d49c07c7Sop filter_result_report(uint64_t reqid, const char *param)
964*d49c07c7Sop {
965*d49c07c7Sop 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
966*d49c07c7Sop 	m_add_id(p_dispatcher, reqid);
967*d49c07c7Sop 	m_add_int(p_dispatcher, FILTER_REPORT);
968*d49c07c7Sop 	m_add_string(p_dispatcher, param);
969*d49c07c7Sop 	m_close(p_dispatcher);
970*d49c07c7Sop }
971*d49c07c7Sop 
972*d49c07c7Sop static void
973f6b0bf9aSgilles filter_result_junk(uint64_t reqid)
974f6b0bf9aSgilles {
9751a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
9761a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
9771a5b831aSmartijn 	m_add_int(p_dispatcher, FILTER_JUNK);
9781a5b831aSmartijn 	m_close(p_dispatcher);
979f6b0bf9aSgilles }
980f6b0bf9aSgilles 
981f6b0bf9aSgilles static void
982ec69ed85Sgilles filter_result_rewrite(uint64_t reqid, const char *param)
983d7b0dc3bSgilles {
9841a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
9851a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
9861a5b831aSmartijn 	m_add_int(p_dispatcher, FILTER_REWRITE);
9871a5b831aSmartijn 	m_add_string(p_dispatcher, param);
9881a5b831aSmartijn 	m_close(p_dispatcher);
989d7b0dc3bSgilles }
990d7b0dc3bSgilles 
991d7b0dc3bSgilles static void
992ec69ed85Sgilles filter_result_reject(uint64_t reqid, const char *message)
993d7b0dc3bSgilles {
9941a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
9951a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
9961a5b831aSmartijn 	m_add_int(p_dispatcher, FILTER_REJECT);
9971a5b831aSmartijn 	m_add_string(p_dispatcher, message);
9981a5b831aSmartijn 	m_close(p_dispatcher);
999d7b0dc3bSgilles }
1000d7b0dc3bSgilles 
1001d7b0dc3bSgilles static void
1002ec69ed85Sgilles filter_result_disconnect(uint64_t reqid, const char *message)
1003d7b0dc3bSgilles {
10041a5b831aSmartijn 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
10051a5b831aSmartijn 	m_add_id(p_dispatcher, reqid);
10061a5b831aSmartijn 	m_add_int(p_dispatcher, FILTER_DISCONNECT);
10071a5b831aSmartijn 	m_add_string(p_dispatcher, message);
10081a5b831aSmartijn 	m_close(p_dispatcher);
1009d7b0dc3bSgilles }
1010d7b0dc3bSgilles 
1011d7b0dc3bSgilles 
1012d7b0dc3bSgilles /* below is code for builtin filters */
1013d7b0dc3bSgilles 
1014d7b0dc3bSgilles static int
1015d53cf4e2Sgilles filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key)
1016d7b0dc3bSgilles {
1017d7b0dc3bSgilles 	int	ret = 0;
1018d7b0dc3bSgilles 
10191b1af7a5Sgilles 	if (filter->config->rdns_table == NULL)
10201b1af7a5Sgilles 		return 0;
10211b1af7a5Sgilles 
1022ef7a27e8Seric 	if (table_match(filter->config->rdns_table, kind, key) > 0)
1023d7b0dc3bSgilles 		ret = 1;
10241b1af7a5Sgilles 
10251b1af7a5Sgilles 	return filter->config->not_rdns_table < 0 ? !ret : ret;
1026d7b0dc3bSgilles }
1027d7b0dc3bSgilles 
1028d7b0dc3bSgilles static int
1029d53cf4e2Sgilles filter_check_rdns_regex(struct filter *filter, const char *key)
1030d7b0dc3bSgilles {
1031d7b0dc3bSgilles 	int	ret = 0;
1032d7b0dc3bSgilles 
10331b1af7a5Sgilles 	if (filter->config->rdns_regex == NULL)
10341b1af7a5Sgilles 		return 0;
10351b1af7a5Sgilles 
1036ef7a27e8Seric 	if (table_match(filter->config->rdns_regex, K_REGEX, key) > 0)
1037d7b0dc3bSgilles 		ret = 1;
10381b1af7a5Sgilles 	return filter->config->not_rdns_regex < 0 ? !ret : ret;
1039d53cf4e2Sgilles }
1040d53cf4e2Sgilles 
1041d53cf4e2Sgilles static int
1042d53cf4e2Sgilles filter_check_src_table(struct filter *filter, enum table_service kind, const char *key)
1043d53cf4e2Sgilles {
1044d53cf4e2Sgilles 	int	ret = 0;
1045d53cf4e2Sgilles 
10461b1af7a5Sgilles 	if (filter->config->src_table == NULL)
10471b1af7a5Sgilles 		return 0;
10481b1af7a5Sgilles 
1049ef7a27e8Seric 	if (table_match(filter->config->src_table, kind, key) > 0)
1050d53cf4e2Sgilles 		ret = 1;
10511b1af7a5Sgilles 	return filter->config->not_src_table < 0 ? !ret : ret;
1052d53cf4e2Sgilles }
1053d53cf4e2Sgilles 
1054d53cf4e2Sgilles static int
1055d53cf4e2Sgilles filter_check_src_regex(struct filter *filter, const char *key)
1056d53cf4e2Sgilles {
1057d53cf4e2Sgilles 	int	ret = 0;
1058d53cf4e2Sgilles 
10591b1af7a5Sgilles 	if (filter->config->src_regex == NULL)
10601b1af7a5Sgilles 		return 0;
10611b1af7a5Sgilles 
1062ef7a27e8Seric 	if (table_match(filter->config->src_regex, K_REGEX, key) > 0)
1063d53cf4e2Sgilles 		ret = 1;
10641b1af7a5Sgilles 	return filter->config->not_src_regex < 0 ? !ret : ret;
1065d7b0dc3bSgilles }
1066d7b0dc3bSgilles 
1067d7b0dc3bSgilles static int
10686c654ff6Sgilles filter_check_helo_table(struct filter *filter, enum table_service kind, const char *key)
10696c654ff6Sgilles {
10706c654ff6Sgilles 	int	ret = 0;
10716c654ff6Sgilles 
10721b1af7a5Sgilles 	if (filter->config->helo_table == NULL)
10731b1af7a5Sgilles 		return 0;
10741b1af7a5Sgilles 
1075ef7a27e8Seric 	if (table_match(filter->config->helo_table, kind, key) > 0)
10766c654ff6Sgilles 		ret = 1;
10771b1af7a5Sgilles 	return filter->config->not_helo_table < 0 ? !ret : ret;
10786c654ff6Sgilles }
10796c654ff6Sgilles 
10806c654ff6Sgilles static int
10816c654ff6Sgilles filter_check_helo_regex(struct filter *filter, const char *key)
10826c654ff6Sgilles {
10836c654ff6Sgilles 	int	ret = 0;
10846c654ff6Sgilles 
10851b1af7a5Sgilles 	if (filter->config->helo_regex == NULL)
10861b1af7a5Sgilles 		return 0;
10871b1af7a5Sgilles 
1088ef7a27e8Seric 	if (table_match(filter->config->helo_regex, K_REGEX, key) > 0)
10896c654ff6Sgilles 		ret = 1;
10901b1af7a5Sgilles 	return filter->config->not_helo_regex < 0 ? !ret : ret;
10916c654ff6Sgilles }
10926c654ff6Sgilles 
10936c654ff6Sgilles static int
1094bdf9247dSgilles filter_check_auth(struct filter *filter, const char *username)
1095bdf9247dSgilles {
1096bdf9247dSgilles 	int ret = 0;
1097bdf9247dSgilles 
1098bdf9247dSgilles 	if (!filter->config->auth)
1099bdf9247dSgilles 		return 0;
1100bdf9247dSgilles 
1101bdf9247dSgilles 	ret = username ? 1 : 0;
1102bdf9247dSgilles 
1103bdf9247dSgilles 	return filter->config->not_auth < 0 ? !ret : ret;
1104bdf9247dSgilles }
1105bdf9247dSgilles 
1106bdf9247dSgilles static int
1107bdf9247dSgilles filter_check_auth_table(struct filter *filter, enum table_service kind, const char *key)
1108bdf9247dSgilles {
1109bdf9247dSgilles 	int	ret = 0;
1110bdf9247dSgilles 
1111bdf9247dSgilles 	if (filter->config->auth_table == NULL)
1112bdf9247dSgilles 		return 0;
1113bdf9247dSgilles 
1114bdf9247dSgilles 	if (key && table_match(filter->config->auth_table, kind, key) > 0)
1115bdf9247dSgilles 		ret = 1;
1116bdf9247dSgilles 
1117bdf9247dSgilles 	return filter->config->not_auth_table < 0 ? !ret : ret;
1118bdf9247dSgilles }
1119bdf9247dSgilles 
1120bdf9247dSgilles static int
1121bdf9247dSgilles filter_check_auth_regex(struct filter *filter, const char *key)
1122bdf9247dSgilles {
1123bdf9247dSgilles 	int	ret = 0;
1124bdf9247dSgilles 
1125bdf9247dSgilles 	if (filter->config->auth_regex == NULL)
1126bdf9247dSgilles 		return 0;
1127bdf9247dSgilles 
1128bdf9247dSgilles 	if (key && table_match(filter->config->auth_regex, K_REGEX, key) > 0)
1129bdf9247dSgilles 		ret = 1;
1130bdf9247dSgilles 	return filter->config->not_auth_regex < 0 ? !ret : ret;
1131bdf9247dSgilles }
1132bdf9247dSgilles 
1133bdf9247dSgilles 
1134bdf9247dSgilles static int
1135fc55f345Sgilles filter_check_mail_from_table(struct filter *filter, enum table_service kind, const char *key)
1136fc55f345Sgilles {
1137fc55f345Sgilles 	int	ret = 0;
1138fc55f345Sgilles 
11391b1af7a5Sgilles 	if (filter->config->mail_from_table == NULL)
11401b1af7a5Sgilles 		return 0;
11411b1af7a5Sgilles 
1142ef7a27e8Seric 	if (table_match(filter->config->mail_from_table, kind, key) > 0)
1143fc55f345Sgilles 		ret = 1;
11441b1af7a5Sgilles 	return filter->config->not_mail_from_table < 0 ? !ret : ret;
1145fc55f345Sgilles }
1146fc55f345Sgilles 
1147fc55f345Sgilles static int
1148fc55f345Sgilles filter_check_mail_from_regex(struct filter *filter, const char *key)
1149fc55f345Sgilles {
1150fc55f345Sgilles 	int	ret = 0;
1151fc55f345Sgilles 
11521b1af7a5Sgilles 	if (filter->config->mail_from_regex == NULL)
11531b1af7a5Sgilles 		return 0;
11541b1af7a5Sgilles 
1155ef7a27e8Seric 	if (table_match(filter->config->mail_from_regex, K_REGEX, key) > 0)
1156fc55f345Sgilles 		ret = 1;
11571b1af7a5Sgilles 	return filter->config->not_mail_from_regex < 0 ? !ret : ret;
1158fc55f345Sgilles }
1159fc55f345Sgilles 
1160fc55f345Sgilles static int
11611577e350Sgilles filter_check_rcpt_to_table(struct filter *filter, enum table_service kind, const char *key)
11621577e350Sgilles {
11631577e350Sgilles 	int	ret = 0;
11641577e350Sgilles 
11651b1af7a5Sgilles 	if (filter->config->rcpt_to_table == NULL)
11661b1af7a5Sgilles 		return 0;
11671b1af7a5Sgilles 
1168ef7a27e8Seric 	if (table_match(filter->config->rcpt_to_table, kind, key) > 0)
11691577e350Sgilles 		ret = 1;
11701b1af7a5Sgilles 	return filter->config->not_rcpt_to_table < 0 ? !ret : ret;
11711577e350Sgilles }
11721577e350Sgilles 
11731577e350Sgilles static int
11741577e350Sgilles filter_check_rcpt_to_regex(struct filter *filter, const char *key)
11751577e350Sgilles {
11761577e350Sgilles 	int	ret = 0;
11771577e350Sgilles 
11781b1af7a5Sgilles 	if (filter->config->rcpt_to_regex == NULL)
11791b1af7a5Sgilles 		return 0;
11801b1af7a5Sgilles 
1181ef7a27e8Seric 	if (table_match(filter->config->rcpt_to_regex, K_REGEX, key) > 0)
11821577e350Sgilles 		ret = 1;
11831b1af7a5Sgilles 	return filter->config->not_rcpt_to_regex < 0 ? !ret : ret;
11841577e350Sgilles }
11851577e350Sgilles 
11861577e350Sgilles static int
1187ec69ed85Sgilles filter_check_fcrdns(struct filter *filter, int fcrdns)
118815c39be5Sgilles {
118915c39be5Sgilles 	int	ret = 0;
119015c39be5Sgilles 
11911b1af7a5Sgilles 	if (!filter->config->fcrdns)
11921b1af7a5Sgilles 		return 0;
11931b1af7a5Sgilles 
11944c4f46e9Sgilles 	ret = fcrdns == 1;
11951b1af7a5Sgilles 	return filter->config->not_fcrdns < 0 ? !ret : ret;
119615c39be5Sgilles }
119715c39be5Sgilles 
119815c39be5Sgilles static int
1199ec69ed85Sgilles filter_check_rdns(struct filter *filter, const char *hostname)
1200a8ae7d4bSgilles {
1201a8ae7d4bSgilles 	int	ret = 0;
1202a8ae7d4bSgilles 	struct netaddr	netaddr;
1203a8ae7d4bSgilles 
12041b1af7a5Sgilles 	if (!filter->config->rdns)
12051b1af7a5Sgilles 		return 0;
12061b1af7a5Sgilles 
120740e5b960Sgilles 	/* this is a hack until smtp session properly deals with lack of rdns */
120840e5b960Sgilles 	ret = strcmp("<unknown>", hostname);
120940e5b960Sgilles 	if (ret == 0)
121040e5b960Sgilles 		return filter->config->not_rdns < 0 ? !ret : ret;
121140e5b960Sgilles 
1212426232efSgilles 	/* if text_to_netaddress succeeds,
1213426232efSgilles 	 * we don't have an rDNS so the filter should match
1214426232efSgilles 	 */
121540e5b960Sgilles 	ret = !text_to_netaddr(&netaddr, hostname);
12161b1af7a5Sgilles 	return filter->config->not_rdns < 0 ? !ret : ret;
1217a8ae7d4bSgilles }
1218a8ae7d4bSgilles 
1219a8ae7d4bSgilles static int
1220d53cf4e2Sgilles filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1221d7b0dc3bSgilles {
1222426232efSgilles 	return 0;
1223d7b0dc3bSgilles }
1224d7b0dc3bSgilles 
1225d7b0dc3bSgilles static int
1226417409c3Sgilles filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid)
1227d7b0dc3bSgilles {
12281b1af7a5Sgilles 	return filter_check_fcrdns(filter, fs->fcrdns) ||
1229ec69ed85Sgilles 	    filter_check_rdns(filter, fs->rdns) ||
1230d53cf4e2Sgilles 	    filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) ||
1231d53cf4e2Sgilles 	    filter_check_rdns_regex(filter, fs->rdns) ||
1232cdc7e291Sgilles 	    filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) ||
12336c654ff6Sgilles 	    filter_check_src_regex(filter, ss_to_text(&fs->ss_src)) ||
1234fc55f345Sgilles 	    filter_check_helo_table(filter, K_DOMAIN, fs->helo) ||
1235fc55f345Sgilles 	    filter_check_helo_regex(filter, fs->helo) ||
1236bdf9247dSgilles 	    filter_check_auth(filter, fs->username) ||
1237bdf9247dSgilles 	    filter_check_auth_table(filter, K_STRING, fs->username) ||
1238bdf9247dSgilles 	    filter_check_auth_table(filter, K_CREDENTIALS, fs->username) ||
1239bdf9247dSgilles 	    filter_check_auth_regex(filter, fs->username) ||
1240fc55f345Sgilles 	    filter_check_mail_from_table(filter, K_MAILADDR, fs->mail_from) ||
12411b1af7a5Sgilles 	    filter_check_mail_from_regex(filter, fs->mail_from);
1242d7b0dc3bSgilles }
1243d7b0dc3bSgilles 
1244d7b0dc3bSgilles static int
1245d53cf4e2Sgilles filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1246d7b0dc3bSgilles {
1247417409c3Sgilles 	return filter_builtins_global(fs, filter, reqid);
1248d7b0dc3bSgilles }
1249d7b0dc3bSgilles 
1250d7b0dc3bSgilles static int
1251d53cf4e2Sgilles filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1252d7b0dc3bSgilles {
1253417409c3Sgilles 	return filter_builtins_global(fs, filter, reqid);
1254d7b0dc3bSgilles }
1255d7b0dc3bSgilles 
1256d7b0dc3bSgilles static int
1257d53cf4e2Sgilles filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1258d7b0dc3bSgilles {
1259417409c3Sgilles 	return filter_builtins_global(fs, filter, reqid);
1260d53cf4e2Sgilles }
1261d7b0dc3bSgilles 
1262d53cf4e2Sgilles static int
1263d53cf4e2Sgilles filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1264d53cf4e2Sgilles {
1265417409c3Sgilles 	return filter_builtins_global(fs, filter, reqid) ||
1266417409c3Sgilles 	    filter_check_rcpt_to_table(filter, K_MAILADDR, param) ||
1267417409c3Sgilles 	    filter_check_rcpt_to_regex(filter, param);
1268d7b0dc3bSgilles }
12690c2dad68Sgilles 
1270d85cc38eSgilles static int
1271d85cc38eSgilles filter_builtins_data(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1272d85cc38eSgilles {
1273d85cc38eSgilles 	return filter_builtins_global(fs, filter, reqid);
1274d85cc38eSgilles }
1275d85cc38eSgilles 
127680acd33cSgilles static int
127780acd33cSgilles filter_builtins_commit(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
127880acd33cSgilles {
127980acd33cSgilles 	return filter_builtins_global(fs, filter, reqid);
128080acd33cSgilles }
128180acd33cSgilles 
12820c2dad68Sgilles static void
12830c2dad68Sgilles report_smtp_broadcast(uint64_t, const char *, struct timeval *, const char *,
12840c2dad68Sgilles     const char *, ...) __attribute__((__format__ (printf, 5, 6)));
12850c2dad68Sgilles 
12860c2dad68Sgilles void
12870c2dad68Sgilles lka_report_init(void)
12880c2dad68Sgilles {
12890c2dad68Sgilles 	struct reporters	*tailq;
12900c2dad68Sgilles 	size_t			 i;
12910c2dad68Sgilles 
12920c2dad68Sgilles 	dict_init(&report_smtp_in);
12930c2dad68Sgilles 	dict_init(&report_smtp_out);
12940c2dad68Sgilles 
12950c2dad68Sgilles 	for (i = 0; i < nitems(smtp_events); ++i) {
12960c2dad68Sgilles 		tailq = xcalloc(1, sizeof (struct reporters));
12970c2dad68Sgilles 		TAILQ_INIT(tailq);
12980c2dad68Sgilles 		dict_xset(&report_smtp_in, smtp_events[i].event, tailq);
12990c2dad68Sgilles 
13000c2dad68Sgilles 		tailq = xcalloc(1, sizeof (struct reporters));
13010c2dad68Sgilles 		TAILQ_INIT(tailq);
13020c2dad68Sgilles 		dict_xset(&report_smtp_out, smtp_events[i].event, tailq);
13030c2dad68Sgilles 	}
13040c2dad68Sgilles }
13050c2dad68Sgilles 
13060c2dad68Sgilles void
13070c2dad68Sgilles lka_report_register_hook(const char *name, const char *hook)
13080c2dad68Sgilles {
13090c2dad68Sgilles 	struct dict	*subsystem;
13100c2dad68Sgilles 	struct reporter_proc	*rp;
13110c2dad68Sgilles 	struct reporters	*tailq;
13120c2dad68Sgilles 	void *iter;
13130c2dad68Sgilles 	size_t	i;
13140c2dad68Sgilles 
13150c2dad68Sgilles 	if (strncmp(hook, "smtp-in|", 8) == 0) {
13160c2dad68Sgilles 		subsystem = &report_smtp_in;
13170c2dad68Sgilles 		hook += 8;
13180c2dad68Sgilles 	}
13190c2dad68Sgilles 	else if (strncmp(hook, "smtp-out|", 9) == 0) {
13200c2dad68Sgilles 		subsystem = &report_smtp_out;
13210c2dad68Sgilles 		hook += 9;
13220c2dad68Sgilles 	}
13230c2dad68Sgilles 	else
13240c2dad68Sgilles 		fatalx("Invalid message direction: %s", hook);
13250c2dad68Sgilles 
13260c2dad68Sgilles 	if (strcmp(hook, "*") == 0) {
13270c2dad68Sgilles 		iter = NULL;
13280c2dad68Sgilles 		while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) {
13290c2dad68Sgilles 			rp = xcalloc(1, sizeof *rp);
13300c2dad68Sgilles 			rp->name = xstrdup(name);
13310c2dad68Sgilles 			TAILQ_INSERT_TAIL(tailq, rp, entries);
13320c2dad68Sgilles 		}
13330c2dad68Sgilles 		return;
13340c2dad68Sgilles 	}
13350c2dad68Sgilles 
13360c2dad68Sgilles 	for (i = 0; i < nitems(smtp_events); i++)
13370c2dad68Sgilles 		if (strcmp(hook, smtp_events[i].event) == 0)
13380c2dad68Sgilles 			break;
13390c2dad68Sgilles 	if (i == nitems(smtp_events))
13400c2dad68Sgilles 		fatalx("Unrecognized report name: %s", hook);
13410c2dad68Sgilles 
13420c2dad68Sgilles 	tailq = dict_get(subsystem, hook);
13430c2dad68Sgilles 	rp = xcalloc(1, sizeof *rp);
13440c2dad68Sgilles 	rp->name = xstrdup(name);
13450c2dad68Sgilles 	TAILQ_INSERT_TAIL(tailq, rp, entries);
13460c2dad68Sgilles }
13470c2dad68Sgilles 
13480c2dad68Sgilles static void
13490c2dad68Sgilles report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event,
13500c2dad68Sgilles     const char *format, ...)
13510c2dad68Sgilles {
13520c2dad68Sgilles 	va_list		ap;
13530c2dad68Sgilles 	struct dict	*d;
13540c2dad68Sgilles 	struct reporters	*tailq;
13550c2dad68Sgilles 	struct reporter_proc	*rp;
13560c2dad68Sgilles 
13570c2dad68Sgilles 	if (strcmp("smtp-in", direction) == 0)
13580c2dad68Sgilles 		d = &report_smtp_in;
13590c2dad68Sgilles 
13600c2dad68Sgilles 	else if (strcmp("smtp-out", direction) == 0)
13610c2dad68Sgilles 		d = &report_smtp_out;
13620c2dad68Sgilles 
13630c2dad68Sgilles 	else
13640c2dad68Sgilles 		fatalx("unexpected direction: %s", direction);
13650c2dad68Sgilles 
13660c2dad68Sgilles 	tailq = dict_xget(d, event);
13670c2dad68Sgilles 	TAILQ_FOREACH(rp, tailq, entries) {
13680c2dad68Sgilles 		if (!lka_filter_proc_in_session(reqid, rp->name))
13690c2dad68Sgilles 			continue;
13700c2dad68Sgilles 
13710c2dad68Sgilles 		va_start(ap, format);
13720c2dad68Sgilles 		if (io_printf(lka_proc_get_io(rp->name),
13730c2dad68Sgilles 		    "report|%s|%lld.%06ld|%s|%s|%016"PRIx64"%s",
13748c71974fSop 		    PROTOCOL_VERSION, (long long)tv->tv_sec, (long)tv->tv_usec,
1375ff70b891Sop 		    direction, event, reqid,
1376ff70b891Sop 		    format[0] != '\n' ? "|" : "") == -1 ||
13770c2dad68Sgilles 		    io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1)
13780c2dad68Sgilles 			fatalx("failed to write to processor");
13790c2dad68Sgilles 		va_end(ap);
13800c2dad68Sgilles 	}
13810c2dad68Sgilles }
13820c2dad68Sgilles 
13830c2dad68Sgilles void
13840c2dad68Sgilles lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t reqid, const char *rdns,
13850c2dad68Sgilles     int fcrdns,
13860c2dad68Sgilles     const struct sockaddr_storage *ss_src,
13870c2dad68Sgilles     const struct sockaddr_storage *ss_dest)
13880c2dad68Sgilles {
13896c0f0c33Sgilles 	struct filter_session *fs;
13900c2dad68Sgilles 	char	src[NI_MAXHOST + 5];
13910c2dad68Sgilles 	char	dest[NI_MAXHOST + 5];
13920c2dad68Sgilles 	uint16_t	src_port = 0;
13930c2dad68Sgilles 	uint16_t	dest_port = 0;
13940c2dad68Sgilles 	const char     *fcrdns_str;
13950c2dad68Sgilles 
13960c2dad68Sgilles 	if (ss_src->ss_family == AF_INET)
13970c2dad68Sgilles 		src_port = ntohs(((const struct sockaddr_in *)ss_src)->sin_port);
13980c2dad68Sgilles 	else if (ss_src->ss_family == AF_INET6)
13990c2dad68Sgilles 		src_port = ntohs(((const struct sockaddr_in6 *)ss_src)->sin6_port);
14000c2dad68Sgilles 
14010c2dad68Sgilles 	if (ss_dest->ss_family == AF_INET)
14020c2dad68Sgilles 		dest_port = ntohs(((const struct sockaddr_in *)ss_dest)->sin_port);
14030c2dad68Sgilles 	else if (ss_dest->ss_family == AF_INET6)
14040c2dad68Sgilles 		dest_port = ntohs(((const struct sockaddr_in6 *)ss_dest)->sin6_port);
14050c2dad68Sgilles 
14060c2dad68Sgilles 	if (strcmp(ss_to_text(ss_src), "local") == 0) {
14070c2dad68Sgilles 		(void)snprintf(src, sizeof src, "unix:%s", SMTPD_SOCKET);
14080c2dad68Sgilles 		(void)snprintf(dest, sizeof dest, "unix:%s", SMTPD_SOCKET);
14090c2dad68Sgilles 	} else {
14100c2dad68Sgilles 		(void)snprintf(src, sizeof src, "%s:%d", ss_to_text(ss_src), src_port);
14110c2dad68Sgilles 		(void)snprintf(dest, sizeof dest, "%s:%d", ss_to_text(ss_dest), dest_port);
14120c2dad68Sgilles 	}
14130c2dad68Sgilles 
14140c2dad68Sgilles 	switch (fcrdns) {
14150c2dad68Sgilles 	case 1:
14160c2dad68Sgilles 		fcrdns_str = "pass";
14170c2dad68Sgilles 		break;
14180c2dad68Sgilles 	case 0:
14190c2dad68Sgilles 		fcrdns_str = "fail";
14200c2dad68Sgilles 		break;
14210c2dad68Sgilles 	default:
14220c2dad68Sgilles 		fcrdns_str = "error";
14230c2dad68Sgilles 		break;
14240c2dad68Sgilles 	}
14250c2dad68Sgilles 
14266c0f0c33Sgilles 	fs = tree_xget(&sessions, reqid);
14276c0f0c33Sgilles 	fs->rdns = xstrdup(rdns);
14286c0f0c33Sgilles 	fs->fcrdns = fcrdns;
14296c0f0c33Sgilles 	fs->ss_src = *ss_src;
14306c0f0c33Sgilles 	fs->ss_dest = *ss_dest;
1431a7061c79Sgilles 
14320c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-connect",
14330c2dad68Sgilles 	    "%s|%s|%s|%s\n", rdns, fcrdns_str, src, dest);
14340c2dad68Sgilles }
14350c2dad68Sgilles 
14360c2dad68Sgilles void
14370c2dad68Sgilles lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid)
14380c2dad68Sgilles {
14390c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-disconnect", "\n");
14400c2dad68Sgilles }
14410c2dad68Sgilles 
14420c2dad68Sgilles void
14430c2dad68Sgilles lka_report_smtp_link_greeting(const char *direction, uint64_t reqid,
14440c2dad68Sgilles     struct timeval *tv, const char *domain)
14450c2dad68Sgilles {
14460c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-greeting", "%s\n",
14470c2dad68Sgilles 	    domain);
14480c2dad68Sgilles }
14490c2dad68Sgilles 
14500c2dad68Sgilles void
14510c2dad68Sgilles lka_report_smtp_link_auth(const char *direction, struct timeval *tv, uint64_t reqid,
14520c2dad68Sgilles     const char *username, const char *result)
14530c2dad68Sgilles {
1454bdf9247dSgilles 	struct filter_session *fs;
1455bdf9247dSgilles 
1456bdf9247dSgilles 	if (strcmp(result, "pass") == 0) {
1457bdf9247dSgilles 		fs = tree_xget(&sessions, reqid);
1458bdf9247dSgilles 		fs->username = xstrdup(username);
1459bdf9247dSgilles 	}
14600c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-auth", "%s|%s\n",
146151ecec67Sop 	    result, username);
14620c2dad68Sgilles }
14630c2dad68Sgilles 
14640c2dad68Sgilles void
14650c2dad68Sgilles lka_report_smtp_link_identify(const char *direction, struct timeval *tv,
14660c2dad68Sgilles     uint64_t reqid, const char *method, const char *heloname)
14670c2dad68Sgilles {
14680c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-identify", "%s|%s\n",
14690c2dad68Sgilles 	    method, heloname);
14700c2dad68Sgilles }
14710c2dad68Sgilles 
14720c2dad68Sgilles void
14730c2dad68Sgilles lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers)
14740c2dad68Sgilles {
14750c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "link-tls", "%s\n",
14760c2dad68Sgilles 	    ciphers);
14770c2dad68Sgilles }
14780c2dad68Sgilles 
14790c2dad68Sgilles void
14800c2dad68Sgilles lka_report_smtp_tx_reset(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
14810c2dad68Sgilles {
14820c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-reset", "%08x\n",
14830c2dad68Sgilles 	    msgid);
14840c2dad68Sgilles }
14850c2dad68Sgilles 
14860c2dad68Sgilles void
14870c2dad68Sgilles lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
14880c2dad68Sgilles {
14890c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-begin", "%08x\n",
14900c2dad68Sgilles 	    msgid);
14910c2dad68Sgilles }
14920c2dad68Sgilles 
14930c2dad68Sgilles void
14940c2dad68Sgilles lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok)
14950c2dad68Sgilles {
14960c2dad68Sgilles 	const char *result;
14970c2dad68Sgilles 
14980c2dad68Sgilles 	switch (ok) {
14990c2dad68Sgilles 	case 1:
15000c2dad68Sgilles 		result = "ok";
15010c2dad68Sgilles 		break;
15020c2dad68Sgilles 	case 0:
15030c2dad68Sgilles 		result = "permfail";
15040c2dad68Sgilles 		break;
15050c2dad68Sgilles 	default:
15060c2dad68Sgilles 		result = "tempfail";
15070c2dad68Sgilles 		break;
15080c2dad68Sgilles 	}
15090c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-mail", "%08x|%s|%s\n",
1510a23c7749Seric 	    msgid, result, address);
15110c2dad68Sgilles }
15120c2dad68Sgilles 
15130c2dad68Sgilles void
15140c2dad68Sgilles lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok)
15150c2dad68Sgilles {
15160c2dad68Sgilles 	const char *result;
15170c2dad68Sgilles 
15180c2dad68Sgilles 	switch (ok) {
15190c2dad68Sgilles 	case 1:
15200c2dad68Sgilles 		result = "ok";
15210c2dad68Sgilles 		break;
15220c2dad68Sgilles 	case 0:
15230c2dad68Sgilles 		result = "permfail";
15240c2dad68Sgilles 		break;
15250c2dad68Sgilles 	default:
15260c2dad68Sgilles 		result = "tempfail";
15270c2dad68Sgilles 		break;
15280c2dad68Sgilles 	}
15290c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", "%08x|%s|%s\n",
1530a23c7749Seric 	    msgid, result, address);
15310c2dad68Sgilles }
15320c2dad68Sgilles 
15330c2dad68Sgilles void
15340c2dad68Sgilles lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid)
15350c2dad68Sgilles {
15360c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-envelope",
15370c2dad68Sgilles 	    "%08x|%016"PRIx64"\n", msgid, evpid);
15380c2dad68Sgilles }
15390c2dad68Sgilles 
15400c2dad68Sgilles void
15410c2dad68Sgilles lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, int ok)
15420c2dad68Sgilles {
15430c2dad68Sgilles 	const char *result;
15440c2dad68Sgilles 
15450c2dad68Sgilles 	switch (ok) {
15460c2dad68Sgilles 	case 1:
15470c2dad68Sgilles 		result = "ok";
15480c2dad68Sgilles 		break;
15490c2dad68Sgilles 	case 0:
15500c2dad68Sgilles 		result = "permfail";
15510c2dad68Sgilles 		break;
15520c2dad68Sgilles 	default:
15530c2dad68Sgilles 		result = "tempfail";
15540c2dad68Sgilles 		break;
15550c2dad68Sgilles 	}
15560c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-data", "%08x|%s\n",
15570c2dad68Sgilles 	    msgid, result);
15580c2dad68Sgilles }
15590c2dad68Sgilles 
15600c2dad68Sgilles void
15610c2dad68Sgilles lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz)
15620c2dad68Sgilles {
15630c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-commit", "%08x|%zd\n",
15640c2dad68Sgilles 	    msgid, msgsz);
15650c2dad68Sgilles }
15660c2dad68Sgilles 
15670c2dad68Sgilles void
15680c2dad68Sgilles lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
15690c2dad68Sgilles {
15700c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "tx-rollback", "%08x\n",
15710c2dad68Sgilles 	    msgid);
15720c2dad68Sgilles }
15730c2dad68Sgilles 
15740c2dad68Sgilles void
15750c2dad68Sgilles lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command)
15760c2dad68Sgilles {
15770c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "protocol-client", "%s\n",
15780c2dad68Sgilles 	    command);
15790c2dad68Sgilles }
15800c2dad68Sgilles 
15810c2dad68Sgilles void
15820c2dad68Sgilles lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response)
15830c2dad68Sgilles {
15840c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "protocol-server", "%s\n",
15850c2dad68Sgilles 	    response);
15860c2dad68Sgilles }
15870c2dad68Sgilles 
15880c2dad68Sgilles void
15890c2dad68Sgilles lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint64_t reqid,
15900c2dad68Sgilles     int phase, int response, const char *param)
15910c2dad68Sgilles {
15920c2dad68Sgilles 	const char *phase_name;
15930c2dad68Sgilles 	const char *response_name;
15940c2dad68Sgilles 
15950c2dad68Sgilles 	switch (phase) {
15960c2dad68Sgilles 	case FILTER_CONNECT:
15970c2dad68Sgilles 		phase_name = "connected";
15980c2dad68Sgilles 		break;
15990c2dad68Sgilles 	case FILTER_HELO:
16000c2dad68Sgilles 		phase_name = "helo";
16010c2dad68Sgilles 		break;
16020c2dad68Sgilles 	case FILTER_EHLO:
16030c2dad68Sgilles 		phase_name = "ehlo";
16040c2dad68Sgilles 		break;
16050c2dad68Sgilles 	case FILTER_STARTTLS:
16060c2dad68Sgilles 		phase_name = "tls";
16070c2dad68Sgilles 		break;
16080c2dad68Sgilles 	case FILTER_AUTH:
16090c2dad68Sgilles 		phase_name = "auth";
16100c2dad68Sgilles 		break;
16110c2dad68Sgilles 	case FILTER_MAIL_FROM:
16120c2dad68Sgilles 		phase_name = "mail-from";
16130c2dad68Sgilles 		break;
16140c2dad68Sgilles 	case FILTER_RCPT_TO:
16150c2dad68Sgilles 		phase_name = "rcpt-to";
16160c2dad68Sgilles 		break;
16170c2dad68Sgilles 	case FILTER_DATA:
16180c2dad68Sgilles 		phase_name = "data";
16190c2dad68Sgilles 		break;
16200c2dad68Sgilles 	case FILTER_DATA_LINE:
16210c2dad68Sgilles 		phase_name = "data-line";
16220c2dad68Sgilles 		break;
16230c2dad68Sgilles 	case FILTER_RSET:
16240c2dad68Sgilles 		phase_name = "rset";
16250c2dad68Sgilles 		break;
16260c2dad68Sgilles 	case FILTER_QUIT:
16270c2dad68Sgilles 		phase_name = "quit";
16280c2dad68Sgilles 		break;
16290c2dad68Sgilles 	case FILTER_NOOP:
16300c2dad68Sgilles 		phase_name = "noop";
16310c2dad68Sgilles 		break;
16320c2dad68Sgilles 	case FILTER_HELP:
16330c2dad68Sgilles 		phase_name = "help";
16340c2dad68Sgilles 		break;
16350c2dad68Sgilles 	case FILTER_WIZ:
16360c2dad68Sgilles 		phase_name = "wiz";
16370c2dad68Sgilles 		break;
16380c2dad68Sgilles 	case FILTER_COMMIT:
16390c2dad68Sgilles 		phase_name = "commit";
16400c2dad68Sgilles 		break;
16410c2dad68Sgilles 	default:
16420c2dad68Sgilles 		phase_name = "";
16430c2dad68Sgilles 	}
16440c2dad68Sgilles 
16450c2dad68Sgilles 	switch (response) {
16460c2dad68Sgilles 	case FILTER_PROCEED:
16470c2dad68Sgilles 		response_name = "proceed";
16480c2dad68Sgilles 		break;
1649*d49c07c7Sop 	case FILTER_REPORT:
1650*d49c07c7Sop 		response_name = "report";
1651*d49c07c7Sop 		break;
16520c2dad68Sgilles 	case FILTER_JUNK:
16530c2dad68Sgilles 		response_name = "junk";
16540c2dad68Sgilles 		break;
16550c2dad68Sgilles 	case FILTER_REWRITE:
16560c2dad68Sgilles 		response_name = "rewrite";
16570c2dad68Sgilles 		break;
16580c2dad68Sgilles 	case FILTER_REJECT:
16590c2dad68Sgilles 		response_name = "reject";
16600c2dad68Sgilles 		break;
16610c2dad68Sgilles 	case FILTER_DISCONNECT:
16620c2dad68Sgilles 		response_name = "disconnect";
16630c2dad68Sgilles 		break;
16640c2dad68Sgilles 	default:
16650c2dad68Sgilles 		response_name = "";
16660c2dad68Sgilles 	}
16670c2dad68Sgilles 
16680c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "filter-response",
16690c2dad68Sgilles 	    "%s|%s%s%s\n", phase_name, response_name, param ? "|" : "",
16700c2dad68Sgilles 	    param ? param : "");
16710c2dad68Sgilles }
16720c2dad68Sgilles 
16730c2dad68Sgilles void
16740c2dad68Sgilles lka_report_smtp_timeout(const char *direction, struct timeval *tv, uint64_t reqid)
16750c2dad68Sgilles {
16760c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "timeout", "\n");
16770c2dad68Sgilles }
16780c2dad68Sgilles 
16790c2dad68Sgilles void
16800c2dad68Sgilles lka_report_filter_report(uint64_t reqid, const char *name, int builtin,
16810c2dad68Sgilles     const char *direction, struct timeval *tv, const char *message)
16820c2dad68Sgilles {
16830c2dad68Sgilles 	report_smtp_broadcast(reqid, direction, tv, "filter-report",
16840c2dad68Sgilles 	    "%s|%s|%s\n", builtin ? "builtin" : "proc",
16850c2dad68Sgilles 	    name, message);
16860c2dad68Sgilles }
16870c2dad68Sgilles 
16880c2dad68Sgilles void
16890c2dad68Sgilles lka_report_proc(const char *name, const char *line)
16900c2dad68Sgilles {
16910c2dad68Sgilles 	char buffer[LINE_MAX];
16920c2dad68Sgilles 	struct timeval tv;
16930c2dad68Sgilles 	char *ep, *sp, *direction;
16940c2dad68Sgilles 	uint64_t reqid;
16950c2dad68Sgilles 
16960c2dad68Sgilles 	if (strlcpy(buffer, line + 7, sizeof(buffer)) >= sizeof(buffer))
16970c2dad68Sgilles 		fatalx("Invalid report: line too long: %s", line);
16980c2dad68Sgilles 
16990c2dad68Sgilles 	errno = 0;
17000c2dad68Sgilles 	tv.tv_sec = strtoll(buffer, &ep, 10);
17010c2dad68Sgilles 	if (ep[0] != '.' || errno != 0)
17020c2dad68Sgilles 		fatalx("Invalid report: invalid time: %s", line);
17030c2dad68Sgilles 	sp = ep + 1;
17040c2dad68Sgilles 	tv.tv_usec = strtol(sp, &ep, 10);
17050c2dad68Sgilles 	if (ep[0] != '|' || errno != 0)
17060c2dad68Sgilles 		fatalx("Invalid report: invalid time: %s", line);
17070c2dad68Sgilles 	if (ep - sp != 6)
17080c2dad68Sgilles 		fatalx("Invalid report: invalid time: %s", line);
17090c2dad68Sgilles 
17100c2dad68Sgilles 	direction = ep + 1;
17110c2dad68Sgilles 	if (strncmp(direction, "smtp-in|", 8) == 0) {
17120c2dad68Sgilles 		direction[7] = '\0';
17130c2dad68Sgilles 		direction += 7;
17140c2dad68Sgilles #if 0
17150c2dad68Sgilles 	} else if (strncmp(direction, "smtp-out|", 9) == 0) {
17160c2dad68Sgilles 		direction[8] = '\0';
17170c2dad68Sgilles 		direction += 8;
17180c2dad68Sgilles #endif
17190c2dad68Sgilles 	} else
17200c2dad68Sgilles 		fatalx("Invalid report: invalid direction: %s", line);
17210c2dad68Sgilles 
17220c2dad68Sgilles 	reqid = strtoull(sp, &ep, 16);
17230c2dad68Sgilles 	if (ep[0] != '|' || errno != 0)
17240c2dad68Sgilles 		fatalx("Invalid report: invalid reqid: %s", line);
17250c2dad68Sgilles 	sp = ep + 1;
17260c2dad68Sgilles 
17270c2dad68Sgilles 	lka_report_filter_report(reqid, name, 0, direction, &tv, sp);
17280c2dad68Sgilles }
1729