xref: /openbsd/usr.sbin/smtpd/lka_filter.c (revision 097a140d)
1 /*	$OpenBSD: lka_filter.c,v 1.67 2021/01/23 16:11:11 rob Exp $	*/
2 
3 /*
4  * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/tree.h>
22 #include <sys/socket.h>
23 
24 #include <netinet/in.h>
25 
26 #include <errno.h>
27 #include <event.h>
28 #include <imsg.h>
29 #include <inttypes.h>
30 #include <limits.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include "smtpd.h"
36 #include "log.h"
37 
38 #define	PROTOCOL_VERSION	"0.6"
39 
40 struct filter;
41 struct filter_session;
42 static void	filter_protocol_internal(struct filter_session *, uint64_t *, uint64_t, enum filter_phase, const char *);
43 static void	filter_protocol(uint64_t, enum filter_phase, const char *);
44 static void	filter_protocol_next(uint64_t, uint64_t, enum filter_phase);
45 static void	filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *);
46 
47 static void	filter_data_internal(struct filter_session *, uint64_t, uint64_t, const char *);
48 static void	filter_data(uint64_t, const char *);
49 static void	filter_data_next(uint64_t, uint64_t, const char *);
50 static void	filter_data_query(struct filter *, uint64_t, uint64_t, const char *);
51 
52 static int	filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *);
53 static int	filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *);
54 static int	filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *);
55 static int	filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *);
56 static int	filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *);
57 static int	filter_builtins_data(struct filter_session *, struct filter *, uint64_t, const char *);
58 static int	filter_builtins_commit(struct filter_session *, struct filter *, uint64_t, const char *);
59 
60 static void	filter_result_proceed(uint64_t);
61 static void	filter_result_junk(uint64_t);
62 static void	filter_result_rewrite(uint64_t, const char *);
63 static void	filter_result_reject(uint64_t, const char *);
64 static void	filter_result_disconnect(uint64_t, const char *);
65 
66 static void	filter_session_io(struct io *, int, void *);
67 void		lka_filter_process_response(const char *, const char *);
68 
69 
70 struct filter_session {
71 	uint64_t	id;
72 	struct io	*io;
73 
74 	char *lastparam;
75 
76 	char *filter_name;
77 	struct sockaddr_storage ss_src;
78 	struct sockaddr_storage ss_dest;
79 	char *rdns;
80 	int fcrdns;
81 
82 	char *helo;
83 	char *username;
84 	char *mail_from;
85 
86 	enum filter_phase	phase;
87 };
88 
89 static struct filter_exec {
90 	enum filter_phase	phase;
91 	const char	       *phase_name;
92 	int		       (*func)(struct filter_session *, struct filter *, uint64_t, const char *);
93 } filter_execs[FILTER_PHASES_COUNT] = {
94 	{ FILTER_CONNECT,	"connect",	filter_builtins_connect },
95 	{ FILTER_HELO,		"helo",		filter_builtins_helo },
96 	{ FILTER_EHLO,		"ehlo",		filter_builtins_helo },
97 	{ FILTER_STARTTLS,     	"starttls",	filter_builtins_notimpl },
98 	{ FILTER_AUTH,     	"auth",		filter_builtins_notimpl },
99 	{ FILTER_MAIL_FROM,    	"mail-from",	filter_builtins_mail_from },
100 	{ FILTER_RCPT_TO,    	"rcpt-to",	filter_builtins_rcpt_to },
101 	{ FILTER_DATA,    	"data",		filter_builtins_data },
102 	{ FILTER_DATA_LINE,    	"data-line",   	filter_builtins_notimpl },
103 	{ FILTER_RSET,    	"rset",		filter_builtins_notimpl },
104 	{ FILTER_QUIT,    	"quit",		filter_builtins_notimpl },
105 	{ FILTER_NOOP,    	"noop",		filter_builtins_notimpl },
106 	{ FILTER_HELP,    	"help",		filter_builtins_notimpl },
107 	{ FILTER_WIZ,    	"wiz",		filter_builtins_notimpl },
108 	{ FILTER_COMMIT,    	"commit",      	filter_builtins_commit },
109 };
110 
111 struct filter {
112 	uint64_t		id;
113 	uint32_t		phases;
114 	const char	       *name;
115 	const char	       *proc;
116 	struct filter  	      **chain;
117 	size_t 			chain_size;
118 	struct filter_config   *config;
119 };
120 static struct dict filters;
121 
122 struct filter_entry {
123 	TAILQ_ENTRY(filter_entry)	entries;
124 	uint64_t			id;
125 	const char		       *name;
126 };
127 
128 struct filter_chain {
129 	TAILQ_HEAD(, filter_entry)		chain[nitems(filter_execs)];
130 };
131 
132 static struct tree	sessions;
133 static int		filters_inited;
134 
135 static struct dict	filter_chains;
136 
137 struct reporter_proc {
138 	TAILQ_ENTRY(reporter_proc)	entries;
139 	const char		       *name;
140 };
141 TAILQ_HEAD(reporters, reporter_proc);
142 
143 static struct dict	report_smtp_in;
144 static struct dict	report_smtp_out;
145 
146 static struct smtp_events {
147 	const char     *event;
148 } smtp_events[] = {
149 	{ "link-connect" },
150 	{ "link-disconnect" },
151 	{ "link-greeting" },
152 	{ "link-identify" },
153 	{ "link-tls" },
154 	{ "link-auth" },
155 
156 	{ "tx-reset" },
157 	{ "tx-begin" },
158 	{ "tx-mail" },
159 	{ "tx-rcpt" },
160 	{ "tx-envelope" },
161 	{ "tx-data" },
162 	{ "tx-commit" },
163 	{ "tx-rollback" },
164 
165 	{ "protocol-client" },
166 	{ "protocol-server" },
167 
168 	{ "filter-report" },
169 	{ "filter-response" },
170 
171 	{ "timeout" },
172 };
173 
174 static int			processors_inited = 0;
175 static struct dict		processors;
176 
177 struct processor_instance {
178 	char			*name;
179 	struct io		*io;
180 	struct io		*errfd;
181 	int			 ready;
182 	uint32_t		 subsystems;
183 };
184 
185 static void	processor_io(struct io *, int, void *);
186 static void	processor_errfd(struct io *, int, void *);
187 void		lka_filter_process_response(const char *, const char *);
188 
189 int
190 lka_proc_ready(void)
191 {
192 	void	*iter;
193 	struct processor_instance	*pi;
194 
195 	iter = NULL;
196 	while (dict_iter(&processors, &iter, NULL, (void **)&pi))
197 		if (!pi->ready)
198 			return 0;
199 	return 1;
200 }
201 
202 static void
203 lka_proc_config(struct processor_instance *pi)
204 {
205 	io_printf(pi->io, "config|smtpd-version|%s\n", SMTPD_VERSION);
206 	io_printf(pi->io, "config|smtp-session-timeout|%d\n", SMTPD_SESSION_TIMEOUT);
207 	if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_IN)
208 		io_printf(pi->io, "config|subsystem|smtp-in\n");
209 	if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_OUT)
210 		io_printf(pi->io, "config|subsystem|smtp-out\n");
211 	io_printf(pi->io, "config|admd|%s\n",
212 	    env->sc_admd != NULL ? env->sc_admd : env->sc_hostname);
213 	io_printf(pi->io, "config|ready\n");
214 }
215 
216 void
217 lka_proc_forked(const char *name, uint32_t subsystems, int fd)
218 {
219 	struct processor_instance	*processor;
220 
221 	if (!processors_inited) {
222 		dict_init(&processors);
223 		processors_inited = 1;
224 	}
225 
226 	processor = xcalloc(1, sizeof *processor);
227 	processor->name = xstrdup(name);
228 	processor->io = io_new();
229 	processor->subsystems = subsystems;
230 
231 	io_set_nonblocking(fd);
232 
233 	io_set_fd(processor->io, fd);
234 	io_set_callback(processor->io, processor_io, processor->name);
235 	dict_xset(&processors, name, processor);
236 }
237 
238 void
239 lka_proc_errfd(const char *name, int fd)
240 {
241 	struct processor_instance	*processor;
242 
243 	processor = dict_xget(&processors, name);
244 
245 	io_set_nonblocking(fd);
246 
247 	processor->errfd = io_new();
248 	io_set_fd(processor->errfd, fd);
249 	io_set_callback(processor->errfd, processor_errfd, processor->name);
250 
251 	lka_proc_config(processor);
252 }
253 
254 struct io *
255 lka_proc_get_io(const char *name)
256 {
257 	struct processor_instance *processor;
258 
259 	processor = dict_xget(&processors, name);
260 
261 	return processor->io;
262 }
263 
264 static void
265 processor_register(const char *name, const char *line)
266 {
267 	struct processor_instance *processor;
268 
269 	processor = dict_xget(&processors, name);
270 
271 	if (strcmp(line, "register|ready") == 0) {
272 		processor->ready = 1;
273 		return;
274 	}
275 
276 	if (strncmp(line, "register|report|", 16) == 0) {
277 		lka_report_register_hook(name, line+16);
278 		return;
279 	}
280 
281 	if (strncmp(line, "register|filter|", 16) == 0) {
282 		lka_filter_register_hook(name, line+16);
283 		return;
284 	}
285 
286 	fatalx("Invalid register line received: %s", line);
287 }
288 
289 static void
290 processor_io(struct io *io, int evt, void *arg)
291 {
292 	struct processor_instance *processor;
293 	const char		*name = arg;
294 	char			*line = NULL;
295 	ssize_t			 len;
296 
297 	switch (evt) {
298 	case IO_DATAIN:
299 		while ((line = io_getline(io, &len)) != NULL) {
300 			if (strncmp("register|", line, 9) == 0) {
301 				processor_register(name, line);
302 				continue;
303 			}
304 
305 			processor = dict_xget(&processors, name);
306 			if (!processor->ready)
307 				fatalx("Non-register message before register|"
308 				    "ready: %s", line);
309 			else if (strncmp(line, "filter-result|", 14) == 0 ||
310 			    strncmp(line, "filter-dataline|", 16) == 0)
311 				lka_filter_process_response(name, line);
312 			else if (strncmp(line, "report|", 7) == 0)
313 				lka_report_proc(name, line);
314 			else
315 				fatalx("Invalid filter message type: %s", line);
316 		}
317 	}
318 }
319 
320 static void
321 processor_errfd(struct io *io, int evt, void *arg)
322 {
323 	const char	*name = arg;
324 	char		*line = NULL;
325 	ssize_t		 len;
326 
327 	switch (evt) {
328 	case IO_DATAIN:
329 		while ((line = io_getline(io, &len)) != NULL)
330 			log_warnx("%s: %s", name, line);
331 	}
332 }
333 
334 void
335 lka_filter_init(void)
336 {
337 	void		*iter;
338 	const char	*name;
339 	struct filter  	*filter;
340 	struct filter_config	*filter_config;
341 	size_t		i;
342 	char		 buffer[LINE_MAX];	/* for traces */
343 
344 	dict_init(&filters);
345 	dict_init(&filter_chains);
346 
347 	/* first pass, allocate and init individual filters */
348 	iter = NULL;
349 	while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) {
350 		switch (filter_config->filter_type) {
351 		case FILTER_TYPE_BUILTIN:
352 			filter = xcalloc(1, sizeof(*filter));
353 			filter->name = name;
354 			filter->phases |= (1<<filter_config->phase);
355 			filter->config = filter_config;
356 			dict_set(&filters, name, filter);
357 			log_trace(TRACE_FILTERS, "filters init type=builtin, name=%s, hooks=%08x",
358 			    name, filter->phases);
359 			break;
360 
361 		case FILTER_TYPE_PROC:
362 			filter = xcalloc(1, sizeof(*filter));
363 			filter->name = name;
364 			filter->proc = filter_config->proc;
365 			filter->config = filter_config;
366 			dict_set(&filters, name, filter);
367 			log_trace(TRACE_FILTERS, "filters init type=proc, name=%s, proc=%s",
368 			    name, filter_config->proc);
369 			break;
370 
371 		case FILTER_TYPE_CHAIN:
372 			break;
373 		}
374 	}
375 
376 	/* second pass, allocate and init filter chains but don't build yet */
377 	iter = NULL;
378 	while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) {
379 		switch (filter_config->filter_type) {
380 		case FILTER_TYPE_CHAIN:
381 			filter = xcalloc(1, sizeof(*filter));
382 			filter->name = name;
383 			filter->chain = xcalloc(filter_config->chain_size, sizeof(void **));
384 			filter->chain_size = filter_config->chain_size;
385 			filter->config = filter_config;
386 
387 			buffer[0] = '\0';
388 			for (i = 0; i < filter->chain_size; ++i) {
389 				filter->chain[i] = dict_xget(&filters, filter_config->chain[i]);
390 				if (i)
391 					(void)strlcat(buffer, ", ", sizeof buffer);
392 				(void)strlcat(buffer, filter->chain[i]->name, sizeof buffer);
393 			}
394 			log_trace(TRACE_FILTERS, "filters init type=chain, name=%s { %s }", name, buffer);
395 
396 			dict_set(&filters, name, filter);
397 			break;
398 
399 		case FILTER_TYPE_BUILTIN:
400 		case FILTER_TYPE_PROC:
401 			break;
402 		}
403 	}
404 }
405 
406 void
407 lka_filter_register_hook(const char *name, const char *hook)
408 {
409 	struct filter		*filter;
410 	const char	*filter_name;
411 	void		*iter;
412 	size_t	i;
413 
414 	if (strncasecmp(hook, "smtp-in|", 8) == 0) {
415 		hook += 8;
416 	}
417 	else
418 		fatalx("Invalid message direction: %s", hook);
419 
420 	for (i = 0; i < nitems(filter_execs); i++)
421 		if (strcmp(hook, filter_execs[i].phase_name) == 0)
422 			break;
423 	if (i == nitems(filter_execs))
424 		fatalx("Unrecognized report name: %s", hook);
425 
426 	iter = NULL;
427 	while (dict_iter(&filters, &iter, &filter_name, (void **)&filter))
428 		if (filter->proc && strcmp(name, filter->proc) == 0)
429 			filter->phases |= (1<<filter_execs[i].phase);
430 }
431 
432 void
433 lka_filter_ready(void)
434 {
435 	struct filter  	*filter;
436 	struct filter  	*subfilter;
437 	const char	*filter_name;
438 	struct filter_entry	*filter_entry;
439 	struct filter_chain	*filter_chain;
440 	void		*iter;
441 	size_t		i;
442 	size_t		j;
443 
444 	/* all filters are ready, actually build the filter chains */
445 	iter = NULL;
446 	while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) {
447 		filter_chain = xcalloc(1, sizeof *filter_chain);
448 		for (i = 0; i < nitems(filter_execs); i++)
449 			TAILQ_INIT(&filter_chain->chain[i]);
450 		dict_set(&filter_chains, filter_name, filter_chain);
451 
452 		if (filter->chain) {
453 			for (i = 0; i < filter->chain_size; i++) {
454 				subfilter = filter->chain[i];
455 				for (j = 0; j < nitems(filter_execs); ++j) {
456 					if (subfilter->phases & (1<<j)) {
457 						filter_entry = xcalloc(1, sizeof *filter_entry);
458 						filter_entry->id = generate_uid();
459 						filter_entry->name = subfilter->name;
460 						TAILQ_INSERT_TAIL(&filter_chain->chain[j],
461 						    filter_entry, entries);
462 					}
463 				}
464 			}
465 			continue;
466 		}
467 
468 		for (i = 0; i < nitems(filter_execs); ++i) {
469 			if (filter->phases & (1<<i)) {
470 				filter_entry = xcalloc(1, sizeof *filter_entry);
471 				filter_entry->id = generate_uid();
472 				filter_entry->name = filter_name;
473 				TAILQ_INSERT_TAIL(&filter_chain->chain[i],
474 				    filter_entry, entries);
475 			}
476 		}
477 	}
478 }
479 
480 int
481 lka_filter_proc_in_session(uint64_t reqid, const char *proc)
482 {
483 	struct filter_session	*fs;
484 	struct filter		*filter;
485 	size_t			 i;
486 
487 	if ((fs = tree_get(&sessions, reqid)) == NULL)
488 		return 0;
489 
490 	filter = dict_get(&filters, fs->filter_name);
491 	if (filter == NULL || (filter->proc == NULL && filter->chain == NULL))
492 		return 0;
493 
494 	if (filter->proc)
495 		return strcmp(filter->proc, proc) == 0 ? 1 : 0;
496 
497 	for (i = 0; i < filter->chain_size; i++)
498 		if (filter->chain[i]->proc &&
499 		    strcmp(filter->chain[i]->proc, proc) == 0)
500 			return 1;
501 
502 	return 0;
503 }
504 
505 void
506 lka_filter_begin(uint64_t reqid, const char *filter_name)
507 {
508 	struct filter_session	*fs;
509 
510 	if (!filters_inited) {
511 		tree_init(&sessions);
512 		filters_inited = 1;
513 	}
514 
515 	fs = xcalloc(1, sizeof (struct filter_session));
516 	fs->id = reqid;
517 	fs->filter_name = xstrdup(filter_name);
518 	tree_xset(&sessions, fs->id, fs);
519 
520 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid);
521 }
522 
523 void
524 lka_filter_end(uint64_t reqid)
525 {
526 	struct filter_session	*fs;
527 
528 	fs = tree_xpop(&sessions, reqid);
529 	free(fs->rdns);
530 	free(fs->helo);
531 	free(fs->mail_from);
532 	free(fs->username);
533 	free(fs->lastparam);
534 	free(fs->filter_name);
535 	free(fs);
536 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-end", reqid);
537 }
538 
539 void
540 lka_filter_data_begin(uint64_t reqid)
541 {
542 	struct filter_session  *fs;
543 	int	sp[2];
544 	int	fd = -1;
545 
546 	fs = tree_xget(&sessions, reqid);
547 
548 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1)
549 		goto end;
550 	io_set_nonblocking(sp[0]);
551 	io_set_nonblocking(sp[1]);
552 	fd = sp[0];
553 	fs->io = io_new();
554 	io_set_fd(fs->io, sp[1]);
555 	io_set_callback(fs->io, filter_session_io, fs);
556 
557 end:
558 	m_create(p_dispatcher, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, fd);
559 	m_add_id(p_dispatcher, reqid);
560 	m_add_int(p_dispatcher, fd != -1 ? 1 : 0);
561 	m_close(p_dispatcher);
562 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-begin fd=%d", reqid, fd);
563 }
564 
565 void
566 lka_filter_data_end(uint64_t reqid)
567 {
568 	struct filter_session	*fs;
569 
570 	fs = tree_xget(&sessions, reqid);
571 	if (fs->io) {
572 		io_free(fs->io);
573 		fs->io = NULL;
574 	}
575 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-end", reqid);
576 }
577 
578 static void
579 filter_session_io(struct io *io, int evt, void *arg)
580 {
581 	struct filter_session *fs = arg;
582 	char *line = NULL;
583 	ssize_t len;
584 
585 	log_trace(TRACE_IO, "filter session: %p: %s %s", fs, io_strevent(evt),
586 	    io_strio(io));
587 
588 	switch (evt) {
589 	case IO_DATAIN:
590 	nextline:
591 		line = io_getline(fs->io, &len);
592 		/* No complete line received */
593 		if (line == NULL)
594 			return;
595 
596 		filter_data(fs->id, line);
597 
598 		goto nextline;
599 	}
600 }
601 
602 void
603 lka_filter_process_response(const char *name, const char *line)
604 {
605 	uint64_t reqid;
606 	uint64_t token;
607 	char buffer[LINE_MAX];
608 	char *ep = NULL;
609 	char *kind = NULL;
610 	char *qid = NULL;
611 	/*char *phase = NULL;*/
612 	char *response = NULL;
613 	char *parameter = NULL;
614 	struct filter_session *fs;
615 
616 	(void)strlcpy(buffer, line, sizeof buffer);
617 	if ((ep = strchr(buffer, '|')) == NULL)
618 		fatalx("Missing token: %s", line);
619 	ep[0] = '\0';
620 
621 	kind = buffer;
622 
623 	qid = ep+1;
624 	if ((ep = strchr(qid, '|')) == NULL)
625 		fatalx("Missing reqid: %s", line);
626 	ep[0] = '\0';
627 
628 	reqid = strtoull(qid, &ep, 16);
629 	if (qid[0] == '\0' || *ep != '\0')
630 		fatalx("Invalid reqid: %s", line);
631 	if (errno == ERANGE && reqid == ULLONG_MAX)
632 		fatal("Invalid reqid: %s", line);
633 
634 	qid = ep+1;
635 	if ((ep = strchr(qid, '|')) == NULL)
636 		fatal("Missing directive: %s", line);
637 	ep[0] = '\0';
638 
639 	token = strtoull(qid, &ep, 16);
640 	if (qid[0] == '\0' || *ep != '\0')
641 		fatalx("Invalid token: %s", line);
642 	if (errno == ERANGE && token == ULLONG_MAX)
643 		fatal("Invalid token: %s", line);
644 
645 	response = ep+1;
646 
647 	/* session can legitimately disappear on a resume */
648 	if ((fs = tree_get(&sessions, reqid)) == NULL)
649 		return;
650 
651 	if (strcmp(kind, "filter-dataline") == 0) {
652 		if (fs->phase != FILTER_DATA_LINE)
653 			fatalx("filter-dataline out of dataline phase");
654 		filter_data_next(token, reqid, response);
655 		return;
656 	}
657 	if (fs->phase == FILTER_DATA_LINE)
658 		fatalx("filter-result in dataline phase");
659 
660 	if ((ep = strchr(response, '|'))) {
661 		parameter = ep + 1;
662 		ep[0] = '\0';
663 	}
664 
665 	if (strcmp(response, "proceed") == 0) {
666 		if (parameter != NULL)
667 			fatalx("Unexpected parameter after proceed: %s", line);
668 		filter_protocol_next(token, reqid, 0);
669 		return;
670 	} else if (strcmp(response, "junk") == 0) {
671 		if (parameter != NULL)
672 			fatalx("Unexpected parameter after junk: %s", line);
673 		if (fs->phase == FILTER_COMMIT)
674 			fatalx("filter-reponse junk after DATA");
675 		filter_result_junk(reqid);
676 		return;
677 	} else {
678 		if (parameter == NULL)
679 			fatalx("Missing parameter: %s", line);
680 
681 		if (strcmp(response, "rewrite") == 0)
682 			filter_result_rewrite(reqid, parameter);
683 		else if (strcmp(response, "reject") == 0)
684 			filter_result_reject(reqid, parameter);
685 		else if (strcmp(response, "disconnect") == 0)
686 			filter_result_disconnect(reqid, parameter);
687 		else
688 			fatalx("Invalid directive: %s", line);
689 	}
690 }
691 
692 void
693 lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param)
694 {
695 	filter_protocol(reqid, phase, param);
696 }
697 
698 static void
699 filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t reqid, enum filter_phase phase, const char *param)
700 {
701 	struct filter_chain	*filter_chain;
702 	struct filter_entry	*filter_entry;
703 	struct filter		*filter;
704 	struct timeval		 tv;
705 	const char		*phase_name = filter_execs[phase].phase_name;
706 	int			 resume = 1;
707 
708 	if (!*token) {
709 		fs->phase = phase;
710 		resume = 0;
711 	}
712 
713 	/* XXX - this sanity check requires a protocol change, stub for now */
714 	phase = fs->phase;
715 	if (fs->phase != phase)
716 		fatalx("misbehaving filter");
717 
718 	/* based on token, identify the filter_entry we should apply  */
719 	filter_chain = dict_get(&filter_chains, fs->filter_name);
720 	filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]);
721 	if (*token) {
722 		TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries)
723 		    if (filter_entry->id == *token)
724 			    break;
725 		if (filter_entry == NULL)
726 			fatalx("misbehaving filter");
727 		filter_entry = TAILQ_NEXT(filter_entry, entries);
728 	}
729 
730 	/* no filter_entry, we either had none or reached end of chain */
731 	if (filter_entry == NULL) {
732 		log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, resume=%s, "
733 		    "action=proceed",
734 		    fs->id, phase_name, resume ? "y" : "n");
735 		filter_result_proceed(reqid);
736 		return;
737 	}
738 
739 	/* process param with current filter_entry */
740 	*token = filter_entry->id;
741 	filter = dict_get(&filters, filter_entry->name);
742 	if (filter->proc) {
743 		log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
744 		    "resume=%s, action=deferred, filter=%s",
745 		    fs->id, phase_name, resume ? "y" : "n",
746 		    filter->name);
747 		filter_protocol_query(filter, filter_entry->id, reqid,
748 		    filter_execs[fs->phase].phase_name, param);
749 		return;	/* deferred response */
750 	}
751 
752 	if (filter_execs[fs->phase].func(fs, filter, reqid, param)) {
753 		if (filter->config->rewrite) {
754 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
755 			    "resume=%s, action=rewrite, filter=%s, query=%s, response=%s",
756 			    fs->id, phase_name, resume ? "y" : "n",
757 			    filter->name,
758 			    param,
759 			    filter->config->rewrite);
760 			    filter_result_rewrite(reqid, filter->config->rewrite);
761 			return;
762 		}
763 		else if (filter->config->disconnect) {
764 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
765 			    "resume=%s, action=disconnect, filter=%s, query=%s, response=%s",
766 			    fs->id, phase_name, resume ? "y" : "n",
767 			    filter->name,
768 			    param,
769 			    filter->config->disconnect);
770 			filter_result_disconnect(reqid, filter->config->disconnect);
771 			return;
772 		}
773 		else if (filter->config->junk) {
774 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
775 			    "resume=%s, action=junk, filter=%s, query=%s",
776 			    fs->id, phase_name, resume ? "y" : "n",
777 			    filter->name,
778 			    param);
779 			filter_result_junk(reqid);
780 			return;
781 		} else if (filter->config->report) {
782 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
783 			    "resume=%s, action=report, filter=%s, query=%s response=%s",
784 			    fs->id, phase_name, resume ? "y" : "n",
785 			    filter->name,
786 			    param, filter->config->report);
787 
788 			gettimeofday(&tv, NULL);
789 			lka_report_filter_report(fs->id, filter->name, 1,
790 			    "smtp-in", &tv, filter->config->report);
791 		} else if (filter->config->bypass) {
792 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
793 			    "resume=%s, action=bypass, filter=%s, query=%s",
794 			    fs->id, phase_name, resume ? "y" : "n",
795 			    filter->name,
796 			    param);
797 			filter_result_proceed(reqid);
798 			return;
799 		} else {
800 			log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
801 			    "resume=%s, action=reject, filter=%s, query=%s, response=%s",
802 			    fs->id, phase_name, resume ? "y" : "n",
803 			    filter->name,
804 			    param,
805 			    filter->config->reject);
806 			filter_result_reject(reqid, filter->config->reject);
807 			return;
808 		}
809 	}
810 
811 	log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, "
812 	    "resume=%s, action=proceed, filter=%s, query=%s",
813 	    fs->id, phase_name, resume ? "y" : "n",
814 	    filter->name,
815 	    param);
816 
817 	/* filter_entry resulted in proceed, try next filter */
818 	filter_protocol_internal(fs, token, reqid, phase, param);
819 	return;
820 }
821 
822 static void
823 filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, const char *line)
824 {
825 	struct filter_chain	*filter_chain;
826 	struct filter_entry	*filter_entry;
827 	struct filter		*filter;
828 
829 	if (!token)
830 		fs->phase = FILTER_DATA_LINE;
831 	if (fs->phase != FILTER_DATA_LINE)
832 		fatalx("misbehaving filter");
833 
834 	/* based on token, identify the filter_entry we should apply  */
835 	filter_chain = dict_get(&filter_chains, fs->filter_name);
836 	filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]);
837 	if (token) {
838 		TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries)
839 		    if (filter_entry->id == token)
840 			    break;
841 		if (filter_entry == NULL)
842 			fatalx("misbehaving filter");
843 		filter_entry = TAILQ_NEXT(filter_entry, entries);
844 	}
845 
846 	/* no filter_entry, we either had none or reached end of chain */
847 	if (filter_entry == NULL) {
848 		io_printf(fs->io, "%s\n", line);
849 		return;
850 	}
851 
852 	/* pass data to the filter */
853 	filter = dict_get(&filters, filter_entry->name);
854 	filter_data_query(filter, filter_entry->id, reqid, line);
855 }
856 
857 static void
858 filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param)
859 {
860 	struct filter_session  *fs;
861 	uint64_t		token = 0;
862 	char		       *nparam = NULL;
863 
864 	fs = tree_xget(&sessions, reqid);
865 
866 	switch (phase) {
867 	case FILTER_HELO:
868 	case FILTER_EHLO:
869 		free(fs->helo);
870 		fs->helo = xstrdup(param);
871 		break;
872 	case FILTER_MAIL_FROM:
873 		free(fs->mail_from);
874 		fs->mail_from = xstrdup(param + 1);
875 		*strchr(fs->mail_from, '>') = '\0';
876 		param = fs->mail_from;
877 
878 		break;
879 	case FILTER_RCPT_TO:
880 		nparam = xstrdup(param + 1);
881 		*strchr(nparam, '>') = '\0';
882 		param = nparam;
883 		break;
884 	case FILTER_STARTTLS:
885 		/* TBD */
886 		break;
887 	default:
888 		break;
889 	}
890 
891 	free(fs->lastparam);
892 	fs->lastparam = xstrdup(param);
893 
894 	filter_protocol_internal(fs, &token, reqid, phase, param);
895 	if (nparam)
896 		free(nparam);
897 }
898 
899 static void
900 filter_protocol_next(uint64_t token, uint64_t reqid, enum filter_phase phase)
901 {
902 	struct filter_session  *fs;
903 
904 	/* session can legitimately disappear on a resume */
905 	if ((fs = tree_get(&sessions, reqid)) == NULL)
906 		return;
907 
908 	filter_protocol_internal(fs, &token, reqid, phase, fs->lastparam);
909 }
910 
911 static void
912 filter_data(uint64_t reqid, const char *line)
913 {
914 	struct filter_session  *fs;
915 
916 	fs = tree_xget(&sessions, reqid);
917 
918 	filter_data_internal(fs, 0, reqid, line);
919 }
920 
921 static void
922 filter_data_next(uint64_t token, uint64_t reqid, const char *line)
923 {
924 	struct filter_session  *fs;
925 
926 	/* session can legitimately disappear on a resume */
927 	if ((fs = tree_get(&sessions, reqid)) == NULL)
928 		return;
929 
930 	filter_data_internal(fs, token, reqid, line);
931 }
932 
933 static void
934 filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param)
935 {
936 	int	n;
937 	struct filter_session	*fs;
938 	struct timeval	tv;
939 
940 	gettimeofday(&tv, NULL);
941 
942 	fs = tree_xget(&sessions, reqid);
943 	if (strcmp(phase, "connect") == 0)
944 		n = io_printf(lka_proc_get_io(filter->proc),
945 		    "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n",
946 		    PROTOCOL_VERSION,
947 		    tv.tv_sec, tv.tv_usec,
948 		    phase, reqid, token, fs->rdns, param);
949 	else
950 		n = io_printf(lka_proc_get_io(filter->proc),
951 		    "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n",
952 		    PROTOCOL_VERSION,
953 		    tv.tv_sec, tv.tv_usec,
954 		    phase, reqid, token, param);
955 	if (n == -1)
956 		fatalx("failed to write to processor");
957 }
958 
959 static void
960 filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line)
961 {
962 	int	n;
963 	struct timeval	tv;
964 
965 	gettimeofday(&tv, NULL);
966 
967 	n = io_printf(lka_proc_get_io(filter->proc),
968 	    "filter|%s|%lld.%06ld|smtp-in|data-line|"
969 	    "%016"PRIx64"|%016"PRIx64"|%s\n",
970 	    PROTOCOL_VERSION,
971 	    tv.tv_sec, tv.tv_usec,
972 	    reqid, token, line);
973 	if (n == -1)
974 		fatalx("failed to write to processor");
975 }
976 
977 static void
978 filter_result_proceed(uint64_t reqid)
979 {
980 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
981 	m_add_id(p_dispatcher, reqid);
982 	m_add_int(p_dispatcher, FILTER_PROCEED);
983 	m_close(p_dispatcher);
984 }
985 
986 static void
987 filter_result_junk(uint64_t reqid)
988 {
989 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
990 	m_add_id(p_dispatcher, reqid);
991 	m_add_int(p_dispatcher, FILTER_JUNK);
992 	m_close(p_dispatcher);
993 }
994 
995 static void
996 filter_result_rewrite(uint64_t reqid, const char *param)
997 {
998 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
999 	m_add_id(p_dispatcher, reqid);
1000 	m_add_int(p_dispatcher, FILTER_REWRITE);
1001 	m_add_string(p_dispatcher, param);
1002 	m_close(p_dispatcher);
1003 }
1004 
1005 static void
1006 filter_result_reject(uint64_t reqid, const char *message)
1007 {
1008 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
1009 	m_add_id(p_dispatcher, reqid);
1010 	m_add_int(p_dispatcher, FILTER_REJECT);
1011 	m_add_string(p_dispatcher, message);
1012 	m_close(p_dispatcher);
1013 }
1014 
1015 static void
1016 filter_result_disconnect(uint64_t reqid, const char *message)
1017 {
1018 	m_create(p_dispatcher, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1);
1019 	m_add_id(p_dispatcher, reqid);
1020 	m_add_int(p_dispatcher, FILTER_DISCONNECT);
1021 	m_add_string(p_dispatcher, message);
1022 	m_close(p_dispatcher);
1023 }
1024 
1025 
1026 /* below is code for builtin filters */
1027 
1028 static int
1029 filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key)
1030 {
1031 	int	ret = 0;
1032 
1033 	if (filter->config->rdns_table == NULL)
1034 		return 0;
1035 
1036 	if (table_match(filter->config->rdns_table, kind, key) > 0)
1037 		ret = 1;
1038 
1039 	return filter->config->not_rdns_table < 0 ? !ret : ret;
1040 }
1041 
1042 static int
1043 filter_check_rdns_regex(struct filter *filter, const char *key)
1044 {
1045 	int	ret = 0;
1046 
1047 	if (filter->config->rdns_regex == NULL)
1048 		return 0;
1049 
1050 	if (table_match(filter->config->rdns_regex, K_REGEX, key) > 0)
1051 		ret = 1;
1052 	return filter->config->not_rdns_regex < 0 ? !ret : ret;
1053 }
1054 
1055 static int
1056 filter_check_src_table(struct filter *filter, enum table_service kind, const char *key)
1057 {
1058 	int	ret = 0;
1059 
1060 	if (filter->config->src_table == NULL)
1061 		return 0;
1062 
1063 	if (table_match(filter->config->src_table, kind, key) > 0)
1064 		ret = 1;
1065 	return filter->config->not_src_table < 0 ? !ret : ret;
1066 }
1067 
1068 static int
1069 filter_check_src_regex(struct filter *filter, const char *key)
1070 {
1071 	int	ret = 0;
1072 
1073 	if (filter->config->src_regex == NULL)
1074 		return 0;
1075 
1076 	if (table_match(filter->config->src_regex, K_REGEX, key) > 0)
1077 		ret = 1;
1078 	return filter->config->not_src_regex < 0 ? !ret : ret;
1079 }
1080 
1081 static int
1082 filter_check_helo_table(struct filter *filter, enum table_service kind, const char *key)
1083 {
1084 	int	ret = 0;
1085 
1086 	if (filter->config->helo_table == NULL)
1087 		return 0;
1088 
1089 	if (table_match(filter->config->helo_table, kind, key) > 0)
1090 		ret = 1;
1091 	return filter->config->not_helo_table < 0 ? !ret : ret;
1092 }
1093 
1094 static int
1095 filter_check_helo_regex(struct filter *filter, const char *key)
1096 {
1097 	int	ret = 0;
1098 
1099 	if (filter->config->helo_regex == NULL)
1100 		return 0;
1101 
1102 	if (table_match(filter->config->helo_regex, K_REGEX, key) > 0)
1103 		ret = 1;
1104 	return filter->config->not_helo_regex < 0 ? !ret : ret;
1105 }
1106 
1107 static int
1108 filter_check_auth(struct filter *filter, const char *username)
1109 {
1110 	int ret = 0;
1111 
1112 	if (!filter->config->auth)
1113 		return 0;
1114 
1115 	ret = username ? 1 : 0;
1116 
1117 	return filter->config->not_auth < 0 ? !ret : ret;
1118 }
1119 
1120 static int
1121 filter_check_auth_table(struct filter *filter, enum table_service kind, const char *key)
1122 {
1123 	int	ret = 0;
1124 
1125 	if (filter->config->auth_table == NULL)
1126 		return 0;
1127 
1128 	if (key && table_match(filter->config->auth_table, kind, key) > 0)
1129 		ret = 1;
1130 
1131 	return filter->config->not_auth_table < 0 ? !ret : ret;
1132 }
1133 
1134 static int
1135 filter_check_auth_regex(struct filter *filter, const char *key)
1136 {
1137 	int	ret = 0;
1138 
1139 	if (filter->config->auth_regex == NULL)
1140 		return 0;
1141 
1142 	if (key && table_match(filter->config->auth_regex, K_REGEX, key) > 0)
1143 		ret = 1;
1144 	return filter->config->not_auth_regex < 0 ? !ret : ret;
1145 }
1146 
1147 
1148 static int
1149 filter_check_mail_from_table(struct filter *filter, enum table_service kind, const char *key)
1150 {
1151 	int	ret = 0;
1152 
1153 	if (filter->config->mail_from_table == NULL)
1154 		return 0;
1155 
1156 	if (table_match(filter->config->mail_from_table, kind, key) > 0)
1157 		ret = 1;
1158 	return filter->config->not_mail_from_table < 0 ? !ret : ret;
1159 }
1160 
1161 static int
1162 filter_check_mail_from_regex(struct filter *filter, const char *key)
1163 {
1164 	int	ret = 0;
1165 
1166 	if (filter->config->mail_from_regex == NULL)
1167 		return 0;
1168 
1169 	if (table_match(filter->config->mail_from_regex, K_REGEX, key) > 0)
1170 		ret = 1;
1171 	return filter->config->not_mail_from_regex < 0 ? !ret : ret;
1172 }
1173 
1174 static int
1175 filter_check_rcpt_to_table(struct filter *filter, enum table_service kind, const char *key)
1176 {
1177 	int	ret = 0;
1178 
1179 	if (filter->config->rcpt_to_table == NULL)
1180 		return 0;
1181 
1182 	if (table_match(filter->config->rcpt_to_table, kind, key) > 0)
1183 		ret = 1;
1184 	return filter->config->not_rcpt_to_table < 0 ? !ret : ret;
1185 }
1186 
1187 static int
1188 filter_check_rcpt_to_regex(struct filter *filter, const char *key)
1189 {
1190 	int	ret = 0;
1191 
1192 	if (filter->config->rcpt_to_regex == NULL)
1193 		return 0;
1194 
1195 	if (table_match(filter->config->rcpt_to_regex, K_REGEX, key) > 0)
1196 		ret = 1;
1197 	return filter->config->not_rcpt_to_regex < 0 ? !ret : ret;
1198 }
1199 
1200 static int
1201 filter_check_fcrdns(struct filter *filter, int fcrdns)
1202 {
1203 	int	ret = 0;
1204 
1205 	if (!filter->config->fcrdns)
1206 		return 0;
1207 
1208 	ret = fcrdns == 1;
1209 	return filter->config->not_fcrdns < 0 ? !ret : ret;
1210 }
1211 
1212 static int
1213 filter_check_rdns(struct filter *filter, const char *hostname)
1214 {
1215 	int	ret = 0;
1216 	struct netaddr	netaddr;
1217 
1218 	if (!filter->config->rdns)
1219 		return 0;
1220 
1221 	/* this is a hack until smtp session properly deals with lack of rdns */
1222 	ret = strcmp("<unknown>", hostname);
1223 	if (ret == 0)
1224 		return filter->config->not_rdns < 0 ? !ret : ret;
1225 
1226 	/* if text_to_netaddress succeeds,
1227 	 * we don't have an rDNS so the filter should match
1228 	 */
1229 	ret = !text_to_netaddr(&netaddr, hostname);
1230 	return filter->config->not_rdns < 0 ? !ret : ret;
1231 }
1232 
1233 static int
1234 filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1235 {
1236 	return 0;
1237 }
1238 
1239 static int
1240 filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid)
1241 {
1242 	return filter_check_fcrdns(filter, fs->fcrdns) ||
1243 	    filter_check_rdns(filter, fs->rdns) ||
1244 	    filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) ||
1245 	    filter_check_rdns_regex(filter, fs->rdns) ||
1246 	    filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) ||
1247 	    filter_check_src_regex(filter, ss_to_text(&fs->ss_src)) ||
1248 	    filter_check_helo_table(filter, K_DOMAIN, fs->helo) ||
1249 	    filter_check_helo_regex(filter, fs->helo) ||
1250 	    filter_check_auth(filter, fs->username) ||
1251 	    filter_check_auth_table(filter, K_STRING, fs->username) ||
1252 	    filter_check_auth_table(filter, K_CREDENTIALS, fs->username) ||
1253 	    filter_check_auth_regex(filter, fs->username) ||
1254 	    filter_check_mail_from_table(filter, K_MAILADDR, fs->mail_from) ||
1255 	    filter_check_mail_from_regex(filter, fs->mail_from);
1256 }
1257 
1258 static int
1259 filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1260 {
1261 	return filter_builtins_global(fs, filter, reqid);
1262 }
1263 
1264 static int
1265 filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1266 {
1267 	return filter_builtins_global(fs, filter, reqid);
1268 }
1269 
1270 static int
1271 filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1272 {
1273 	return filter_builtins_global(fs, filter, reqid);
1274 }
1275 
1276 static int
1277 filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1278 {
1279 	return filter_builtins_global(fs, filter, reqid) ||
1280 	    filter_check_rcpt_to_table(filter, K_MAILADDR, param) ||
1281 	    filter_check_rcpt_to_regex(filter, param);
1282 }
1283 
1284 static int
1285 filter_builtins_data(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1286 {
1287 	return filter_builtins_global(fs, filter, reqid);
1288 }
1289 
1290 static int
1291 filter_builtins_commit(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param)
1292 {
1293 	return filter_builtins_global(fs, filter, reqid);
1294 }
1295 
1296 static void
1297 report_smtp_broadcast(uint64_t, const char *, struct timeval *, const char *,
1298     const char *, ...) __attribute__((__format__ (printf, 5, 6)));
1299 
1300 void
1301 lka_report_init(void)
1302 {
1303 	struct reporters	*tailq;
1304 	size_t			 i;
1305 
1306 	dict_init(&report_smtp_in);
1307 	dict_init(&report_smtp_out);
1308 
1309 	for (i = 0; i < nitems(smtp_events); ++i) {
1310 		tailq = xcalloc(1, sizeof (struct reporters));
1311 		TAILQ_INIT(tailq);
1312 		dict_xset(&report_smtp_in, smtp_events[i].event, tailq);
1313 
1314 		tailq = xcalloc(1, sizeof (struct reporters));
1315 		TAILQ_INIT(tailq);
1316 		dict_xset(&report_smtp_out, smtp_events[i].event, tailq);
1317 	}
1318 }
1319 
1320 void
1321 lka_report_register_hook(const char *name, const char *hook)
1322 {
1323 	struct dict	*subsystem;
1324 	struct reporter_proc	*rp;
1325 	struct reporters	*tailq;
1326 	void *iter;
1327 	size_t	i;
1328 
1329 	if (strncmp(hook, "smtp-in|", 8) == 0) {
1330 		subsystem = &report_smtp_in;
1331 		hook += 8;
1332 	}
1333 	else if (strncmp(hook, "smtp-out|", 9) == 0) {
1334 		subsystem = &report_smtp_out;
1335 		hook += 9;
1336 	}
1337 	else
1338 		fatalx("Invalid message direction: %s", hook);
1339 
1340 	if (strcmp(hook, "*") == 0) {
1341 		iter = NULL;
1342 		while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) {
1343 			rp = xcalloc(1, sizeof *rp);
1344 			rp->name = xstrdup(name);
1345 			TAILQ_INSERT_TAIL(tailq, rp, entries);
1346 		}
1347 		return;
1348 	}
1349 
1350 	for (i = 0; i < nitems(smtp_events); i++)
1351 		if (strcmp(hook, smtp_events[i].event) == 0)
1352 			break;
1353 	if (i == nitems(smtp_events))
1354 		fatalx("Unrecognized report name: %s", hook);
1355 
1356 	tailq = dict_get(subsystem, hook);
1357 	rp = xcalloc(1, sizeof *rp);
1358 	rp->name = xstrdup(name);
1359 	TAILQ_INSERT_TAIL(tailq, rp, entries);
1360 }
1361 
1362 static void
1363 report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event,
1364     const char *format, ...)
1365 {
1366 	va_list		ap;
1367 	struct dict	*d;
1368 	struct reporters	*tailq;
1369 	struct reporter_proc	*rp;
1370 
1371 	if (strcmp("smtp-in", direction) == 0)
1372 		d = &report_smtp_in;
1373 
1374 	else if (strcmp("smtp-out", direction) == 0)
1375 		d = &report_smtp_out;
1376 
1377 	else
1378 		fatalx("unexpected direction: %s", direction);
1379 
1380 	tailq = dict_xget(d, event);
1381 	TAILQ_FOREACH(rp, tailq, entries) {
1382 		if (!lka_filter_proc_in_session(reqid, rp->name))
1383 			continue;
1384 
1385 		va_start(ap, format);
1386 		if (io_printf(lka_proc_get_io(rp->name),
1387 		    "report|%s|%lld.%06ld|%s|%s|%016"PRIx64"%s",
1388 		    PROTOCOL_VERSION, tv->tv_sec, tv->tv_usec, direction,
1389 		    event, reqid, format[0] != '\n' ? "|" : "") == -1 ||
1390 		    io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1)
1391 			fatalx("failed to write to processor");
1392 		va_end(ap);
1393 	}
1394 }
1395 
1396 void
1397 lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t reqid, const char *rdns,
1398     int fcrdns,
1399     const struct sockaddr_storage *ss_src,
1400     const struct sockaddr_storage *ss_dest)
1401 {
1402 	struct filter_session *fs;
1403 	char	src[NI_MAXHOST + 5];
1404 	char	dest[NI_MAXHOST + 5];
1405 	uint16_t	src_port = 0;
1406 	uint16_t	dest_port = 0;
1407 	const char     *fcrdns_str;
1408 
1409 	if (ss_src->ss_family == AF_INET)
1410 		src_port = ntohs(((const struct sockaddr_in *)ss_src)->sin_port);
1411 	else if (ss_src->ss_family == AF_INET6)
1412 		src_port = ntohs(((const struct sockaddr_in6 *)ss_src)->sin6_port);
1413 
1414 	if (ss_dest->ss_family == AF_INET)
1415 		dest_port = ntohs(((const struct sockaddr_in *)ss_dest)->sin_port);
1416 	else if (ss_dest->ss_family == AF_INET6)
1417 		dest_port = ntohs(((const struct sockaddr_in6 *)ss_dest)->sin6_port);
1418 
1419 	if (strcmp(ss_to_text(ss_src), "local") == 0) {
1420 		(void)snprintf(src, sizeof src, "unix:%s", SMTPD_SOCKET);
1421 		(void)snprintf(dest, sizeof dest, "unix:%s", SMTPD_SOCKET);
1422 	} else {
1423 		(void)snprintf(src, sizeof src, "%s:%d", ss_to_text(ss_src), src_port);
1424 		(void)snprintf(dest, sizeof dest, "%s:%d", ss_to_text(ss_dest), dest_port);
1425 	}
1426 
1427 	switch (fcrdns) {
1428 	case 1:
1429 		fcrdns_str = "pass";
1430 		break;
1431 	case 0:
1432 		fcrdns_str = "fail";
1433 		break;
1434 	default:
1435 		fcrdns_str = "error";
1436 		break;
1437 	}
1438 
1439 	fs = tree_xget(&sessions, reqid);
1440 	fs->rdns = xstrdup(rdns);
1441 	fs->fcrdns = fcrdns;
1442 	fs->ss_src = *ss_src;
1443 	fs->ss_dest = *ss_dest;
1444 
1445 	report_smtp_broadcast(reqid, direction, tv, "link-connect",
1446 	    "%s|%s|%s|%s\n", rdns, fcrdns_str, src, dest);
1447 }
1448 
1449 void
1450 lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid)
1451 {
1452 	report_smtp_broadcast(reqid, direction, tv, "link-disconnect", "\n");
1453 }
1454 
1455 void
1456 lka_report_smtp_link_greeting(const char *direction, uint64_t reqid,
1457     struct timeval *tv, const char *domain)
1458 {
1459 	report_smtp_broadcast(reqid, direction, tv, "link-greeting", "%s\n",
1460 	    domain);
1461 }
1462 
1463 void
1464 lka_report_smtp_link_auth(const char *direction, struct timeval *tv, uint64_t reqid,
1465     const char *username, const char *result)
1466 {
1467 	struct filter_session *fs;
1468 
1469 	if (strcmp(result, "pass") == 0) {
1470 		fs = tree_xget(&sessions, reqid);
1471 		fs->username = xstrdup(username);
1472 	}
1473 	report_smtp_broadcast(reqid, direction, tv, "link-auth", "%s|%s\n",
1474 	    username, result);
1475 }
1476 
1477 void
1478 lka_report_smtp_link_identify(const char *direction, struct timeval *tv,
1479     uint64_t reqid, const char *method, const char *heloname)
1480 {
1481 	report_smtp_broadcast(reqid, direction, tv, "link-identify", "%s|%s\n",
1482 	    method, heloname);
1483 }
1484 
1485 void
1486 lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers)
1487 {
1488 	report_smtp_broadcast(reqid, direction, tv, "link-tls", "%s\n",
1489 	    ciphers);
1490 }
1491 
1492 void
1493 lka_report_smtp_tx_reset(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
1494 {
1495 	report_smtp_broadcast(reqid, direction, tv, "tx-reset", "%08x\n",
1496 	    msgid);
1497 }
1498 
1499 void
1500 lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
1501 {
1502 	report_smtp_broadcast(reqid, direction, tv, "tx-begin", "%08x\n",
1503 	    msgid);
1504 }
1505 
1506 void
1507 lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok)
1508 {
1509 	const char *result;
1510 
1511 	switch (ok) {
1512 	case 1:
1513 		result = "ok";
1514 		break;
1515 	case 0:
1516 		result = "permfail";
1517 		break;
1518 	default:
1519 		result = "tempfail";
1520 		break;
1521 	}
1522 	report_smtp_broadcast(reqid, direction, tv, "tx-mail", "%08x|%s|%s\n",
1523 	    msgid, result, address);
1524 }
1525 
1526 void
1527 lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok)
1528 {
1529 	const char *result;
1530 
1531 	switch (ok) {
1532 	case 1:
1533 		result = "ok";
1534 		break;
1535 	case 0:
1536 		result = "permfail";
1537 		break;
1538 	default:
1539 		result = "tempfail";
1540 		break;
1541 	}
1542 	report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", "%08x|%s|%s\n",
1543 	    msgid, result, address);
1544 }
1545 
1546 void
1547 lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid)
1548 {
1549 	report_smtp_broadcast(reqid, direction, tv, "tx-envelope",
1550 	    "%08x|%016"PRIx64"\n", msgid, evpid);
1551 }
1552 
1553 void
1554 lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, int ok)
1555 {
1556 	const char *result;
1557 
1558 	switch (ok) {
1559 	case 1:
1560 		result = "ok";
1561 		break;
1562 	case 0:
1563 		result = "permfail";
1564 		break;
1565 	default:
1566 		result = "tempfail";
1567 		break;
1568 	}
1569 	report_smtp_broadcast(reqid, direction, tv, "tx-data", "%08x|%s\n",
1570 	    msgid, result);
1571 }
1572 
1573 void
1574 lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz)
1575 {
1576 	report_smtp_broadcast(reqid, direction, tv, "tx-commit", "%08x|%zd\n",
1577 	    msgid, msgsz);
1578 }
1579 
1580 void
1581 lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid)
1582 {
1583 	report_smtp_broadcast(reqid, direction, tv, "tx-rollback", "%08x\n",
1584 	    msgid);
1585 }
1586 
1587 void
1588 lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command)
1589 {
1590 	report_smtp_broadcast(reqid, direction, tv, "protocol-client", "%s\n",
1591 	    command);
1592 }
1593 
1594 void
1595 lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response)
1596 {
1597 	report_smtp_broadcast(reqid, direction, tv, "protocol-server", "%s\n",
1598 	    response);
1599 }
1600 
1601 void
1602 lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint64_t reqid,
1603     int phase, int response, const char *param)
1604 {
1605 	const char *phase_name;
1606 	const char *response_name;
1607 
1608 	switch (phase) {
1609 	case FILTER_CONNECT:
1610 		phase_name = "connected";
1611 		break;
1612 	case FILTER_HELO:
1613 		phase_name = "helo";
1614 		break;
1615 	case FILTER_EHLO:
1616 		phase_name = "ehlo";
1617 		break;
1618 	case FILTER_STARTTLS:
1619 		phase_name = "tls";
1620 		break;
1621 	case FILTER_AUTH:
1622 		phase_name = "auth";
1623 		break;
1624 	case FILTER_MAIL_FROM:
1625 		phase_name = "mail-from";
1626 		break;
1627 	case FILTER_RCPT_TO:
1628 		phase_name = "rcpt-to";
1629 		break;
1630 	case FILTER_DATA:
1631 		phase_name = "data";
1632 		break;
1633 	case FILTER_DATA_LINE:
1634 		phase_name = "data-line";
1635 		break;
1636 	case FILTER_RSET:
1637 		phase_name = "rset";
1638 		break;
1639 	case FILTER_QUIT:
1640 		phase_name = "quit";
1641 		break;
1642 	case FILTER_NOOP:
1643 		phase_name = "noop";
1644 		break;
1645 	case FILTER_HELP:
1646 		phase_name = "help";
1647 		break;
1648 	case FILTER_WIZ:
1649 		phase_name = "wiz";
1650 		break;
1651 	case FILTER_COMMIT:
1652 		phase_name = "commit";
1653 		break;
1654 	default:
1655 		phase_name = "";
1656 	}
1657 
1658 	switch (response) {
1659 	case FILTER_PROCEED:
1660 		response_name = "proceed";
1661 		break;
1662 	case FILTER_JUNK:
1663 		response_name = "junk";
1664 		break;
1665 	case FILTER_REWRITE:
1666 		response_name = "rewrite";
1667 		break;
1668 	case FILTER_REJECT:
1669 		response_name = "reject";
1670 		break;
1671 	case FILTER_DISCONNECT:
1672 		response_name = "disconnect";
1673 		break;
1674 	default:
1675 		response_name = "";
1676 	}
1677 
1678 	report_smtp_broadcast(reqid, direction, tv, "filter-response",
1679 	    "%s|%s%s%s\n", phase_name, response_name, param ? "|" : "",
1680 	    param ? param : "");
1681 }
1682 
1683 void
1684 lka_report_smtp_timeout(const char *direction, struct timeval *tv, uint64_t reqid)
1685 {
1686 	report_smtp_broadcast(reqid, direction, tv, "timeout", "\n");
1687 }
1688 
1689 void
1690 lka_report_filter_report(uint64_t reqid, const char *name, int builtin,
1691     const char *direction, struct timeval *tv, const char *message)
1692 {
1693 	report_smtp_broadcast(reqid, direction, tv, "filter-report",
1694 	    "%s|%s|%s\n", builtin ? "builtin" : "proc",
1695 	    name, message);
1696 }
1697 
1698 void
1699 lka_report_proc(const char *name, const char *line)
1700 {
1701 	char buffer[LINE_MAX];
1702 	struct timeval tv;
1703 	char *ep, *sp, *direction;
1704 	uint64_t reqid;
1705 
1706 	if (strlcpy(buffer, line + 7, sizeof(buffer)) >= sizeof(buffer))
1707 		fatalx("Invalid report: line too long: %s", line);
1708 
1709 	errno = 0;
1710 	tv.tv_sec = strtoll(buffer, &ep, 10);
1711 	if (ep[0] != '.' || errno != 0)
1712 		fatalx("Invalid report: invalid time: %s", line);
1713 	sp = ep + 1;
1714 	tv.tv_usec = strtol(sp, &ep, 10);
1715 	if (ep[0] != '|' || errno != 0)
1716 		fatalx("Invalid report: invalid time: %s", line);
1717 	if (ep - sp != 6)
1718 		fatalx("Invalid report: invalid time: %s", line);
1719 
1720 	direction = ep + 1;
1721 	if (strncmp(direction, "smtp-in|", 8) == 0) {
1722 		direction[7] = '\0';
1723 		direction += 7;
1724 #if 0
1725 	} else if (strncmp(direction, "smtp-out|", 9) == 0) {
1726 		direction[8] = '\0';
1727 		direction += 8;
1728 #endif
1729 	} else
1730 		fatalx("Invalid report: invalid direction: %s", line);
1731 
1732 	reqid = strtoull(sp, &ep, 16);
1733 	if (ep[0] != '|' || errno != 0)
1734 		fatalx("Invalid report: invalid reqid: %s", line);
1735 	sp = ep + 1;
1736 
1737 	lka_report_filter_report(reqid, name, 0, direction, &tv, sp);
1738 }
1739