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