Hi all, I've made a refactored version of table-procexec, hopefully with a lot less redundancy in code.
This patch adds the table-procexec backend which is configured with a timeout of 500 milliseconds. Currently this is hardcoded, but that is easy enough to change and shouldnt be the holdback. In case a table times out and the response has not reached smtpd, this sets the table status to indicate that and also starts an event to discard the next line coming on the socket. After which we are "clear" for communication. Comments would be very welcome and testing even more so. I am not the most proficient C coder... Cheers, Aisha diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y index 832f4f2aec9..ff7b9a9a340 100644 --- a/usr.sbin/smtpd/parse.y +++ b/usr.sbin/smtpd/parse.y @@ -2543,13 +2543,6 @@ table : TABLE STRING STRING { config = p+1; } } - if (config != NULL && *config != '/') { - yyerror("invalid backend parameter for table: %s", - $2); - free($2); - free($3); - YYERROR; - } table = table_create(conf, backend, $2, config); if (!table_config(table)) { yyerror("invalid configuration file %s for table %s", diff --git a/usr.sbin/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile index ef8148be8c9..46831d647dc 100644 --- a/usr.sbin/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -47,7 +47,7 @@ SRCS+= table.c SRCS+= table_static.c SRCS+= table_db.c SRCS+= table_getpwnam.c -SRCS+= table_proc.c +SRCS+= table_procexec.c SRCS+= unpack_dns.c SRCS+= spfwalk.c diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h index e6fc114d0a6..8ef80add4e7 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1663,6 +1663,7 @@ int table_regex_match(const char *, const char *); void table_open_all(struct smtpd *); void table_dump_all(struct smtpd *); void table_close_all(struct smtpd *); +const char *table_service_name(enum table_service ); /* to.c */ diff --git a/usr.sbin/smtpd/smtpd/Makefile b/usr.sbin/smtpd/smtpd/Makefile index b31d4e42224..64e73c3bb70 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -62,7 +62,7 @@ SRCS+= compress_gzip.c SRCS+= table_db.c SRCS+= table_getpwnam.c -SRCS+= table_proc.c +SRCS+= table_procexec.c SRCS+= table_static.c SRCS+= queue_fs.c diff --git a/usr.sbin/smtpd/table.c b/usr.sbin/smtpd/table.c index 7328cf5df6e..81102ef90e1 100644 --- a/usr.sbin/smtpd/table.c +++ b/usr.sbin/smtpd/table.c @@ -35,9 +35,8 @@ struct table_backend *table_backend_lookup(const char *); extern struct table_backend table_backend_static; extern struct table_backend table_backend_db; extern struct table_backend table_backend_getpwnam; -extern struct table_backend table_backend_proc; +extern struct table_backend table_backend_procexec; -static const char * table_service_name(enum table_service); static int table_parse_lookup(enum table_service, const char *, const char *, union lookup *); static int parse_sockaddr(struct sockaddr *, int, const char *); @@ -48,7 +47,7 @@ static struct table_backend *backends[] = { &table_backend_static, &table_backend_db, &table_backend_getpwnam, - &table_backend_proc, + &table_backend_procexec, NULL }; @@ -67,7 +66,7 @@ table_backend_lookup(const char *backend) return NULL; } -static const char * +const char * table_service_name(enum table_service s) { switch (s) { @@ -198,10 +197,9 @@ table_create(struct smtpd *conf, const char *backend, const char *name, PATH_LIBEXEC"/table-%s\"", backend); } if (stat(path, &sb) == 0) { - tb = table_backend_lookup("proc"); - (void)strlcpy(path, backend, sizeof(path)); + tb = table_backend_lookup("proc-exec"); if (config) { - (void)strlcat(path, ":", sizeof(path)); + (void)strlcat(path, " ", sizeof(path)); if (strlcat(path, config, sizeof(path)) >= sizeof(path)) fatalx("table_create: config file path too long"); diff --git a/usr.sbin/smtpd/table_proc.c b/usr.sbin/smtpd/table_proc.c deleted file mode 100644 index 56893a0fb61..00000000000 --- a/usr.sbin/smtpd/table_proc.c +++ /dev/null @@ -1,265 +0,0 @@ -/* $OpenBSD: table_proc.c,v 1.17 2021/06/14 17:58:16 eric Exp $ */ - -/* - * Copyright (c) 2013 Eric Faurot <e...@openbsd.org> - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <errno.h> -#include <string.h> - -#include "smtpd.h" -#include "log.h" - -struct table_proc_priv { - pid_t pid; - struct imsgbuf ibuf; -}; - -static struct imsg imsg; -static size_t rlen; -static char *rdata; - -extern char **environ; - -static void -table_proc_call(struct table_proc_priv *p) -{ - ssize_t n; - - if (imsg_flush(&p->ibuf) == -1) { - log_warn("warn: table-proc: imsg_flush"); - fatalx("table-proc: exiting"); - } - - while (1) { - if ((n = imsg_get(&p->ibuf, &imsg)) == -1) { - log_warn("warn: table-proc: imsg_get"); - break; - } - if (n) { - rlen = imsg.hdr.len - IMSG_HEADER_SIZE; - rdata = imsg.data; - - if (imsg.hdr.type != PROC_TABLE_OK) { - log_warnx("warn: table-proc: bad response"); - break; - } - return; - } - - if ((n = imsg_read(&p->ibuf)) == -1 && errno != EAGAIN) { - log_warn("warn: table-proc: imsg_read"); - break; - } - - if (n == 0) { - log_warnx("warn: table-proc: pipe closed"); - break; - } - } - - fatalx("table-proc: exiting"); -} - -static void -table_proc_read(void *dst, size_t len) -{ - if (len > rlen) { - log_warnx("warn: table-proc: bad msg len"); - fatalx("table-proc: exiting"); - } - - if (dst) - memmove(dst, rdata, len); - - rlen -= len; - rdata += len; -} - -static void -table_proc_end(void) -{ - if (rlen) { - log_warnx("warn: table-proc: bogus data"); - fatalx("table-proc: exiting"); - } - imsg_free(&imsg); -} - -/* - * API - */ - -static int -table_proc_open(struct table *table) -{ - struct table_proc_priv *priv; - struct table_open_params op; - int fd; - - priv = xcalloc(1, sizeof(*priv)); - - fd = fork_proc_backend("table", table->t_config, table->t_name); - if (fd == -1) - fatalx("table-proc: exiting"); - - imsg_init(&priv->ibuf, fd); - - memset(&op, 0, sizeof op); - op.version = PROC_TABLE_API_VERSION; - (void)strlcpy(op.name, table->t_name, sizeof op.name); - imsg_compose(&priv->ibuf, PROC_TABLE_OPEN, 0, 0, -1, &op, sizeof op); - - table_proc_call(priv); - table_proc_end(); - - table->t_handle = priv; - - return (1); -} - -static int -table_proc_update(struct table *table) -{ - struct table_proc_priv *priv = table->t_handle; - int r; - - imsg_compose(&priv->ibuf, PROC_TABLE_UPDATE, 0, 0, -1, NULL, 0); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - table_proc_end(); - - return (r); -} - -static void -table_proc_close(struct table *table) -{ - struct table_proc_priv *priv = table->t_handle; - - imsg_compose(&priv->ibuf, PROC_TABLE_CLOSE, 0, 0, -1, NULL, 0); - if (imsg_flush(&priv->ibuf) == -1) - fatal("imsg_flush"); - - table->t_handle = NULL; -} - -static int -imsg_add_params(struct ibuf *buf) -{ - size_t count = 0; - - if (imsg_add(buf, &count, sizeof(count)) == -1) - return (-1); - - return (0); -} - -static int -table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst) -{ - struct table_proc_priv *priv = table->t_handle; - struct ibuf *buf; - int r; - - buf = imsg_create(&priv->ibuf, - dst ? PROC_TABLE_LOOKUP : PROC_TABLE_CHECK, 0, 0, - sizeof(s) + strlen(k) + 1); - - if (buf == NULL) - return (-1); - if (imsg_add(buf, &s, sizeof(s)) == -1) - return (-1); - if (imsg_add_params(buf) == -1) - return (-1); - if (imsg_add(buf, k, strlen(k) + 1) == -1) - return (-1); - imsg_close(&priv->ibuf, buf); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - - if (r == 1 && dst) { - if (rlen == 0) { - log_warnx("warn: table-proc: empty response"); - fatalx("table-proc: exiting"); - } - if (rdata[rlen - 1] != '\0') { - log_warnx("warn: table-proc: not NUL-terminated"); - fatalx("table-proc: exiting"); - } - *dst = strdup(rdata); - if (*dst == NULL) - r = -1; - table_proc_read(NULL, rlen); - } - - table_proc_end(); - - return (r); -} - -static int -table_proc_fetch(struct table *table, enum table_service s, char **dst) -{ - struct table_proc_priv *priv = table->t_handle; - struct ibuf *buf; - int r; - - buf = imsg_create(&priv->ibuf, PROC_TABLE_FETCH, 0, 0, sizeof(s)); - if (buf == NULL) - return (-1); - if (imsg_add(buf, &s, sizeof(s)) == -1) - return (-1); - if (imsg_add_params(buf) == -1) - return (-1); - imsg_close(&priv->ibuf, buf); - - table_proc_call(priv); - table_proc_read(&r, sizeof(r)); - - if (r == 1) { - if (rlen == 0) { - log_warnx("warn: table-proc: empty response"); - fatalx("table-proc: exiting"); - } - if (rdata[rlen - 1] != '\0') { - log_warnx("warn: table-proc: not NUL-terminated"); - fatalx("table-proc: exiting"); - } - *dst = strdup(rdata); - if (*dst == NULL) - r = -1; - table_proc_read(NULL, rlen); - } - - table_proc_end(); - - return (r); -} - -struct table_backend table_backend_proc = { - "proc", - K_ANY, - NULL, - NULL, - NULL, - table_proc_open, - table_proc_update, - table_proc_close, - table_proc_lookup, - table_proc_fetch, -}; diff --git a/usr.sbin/smtpd/table_procexec.c b/usr.sbin/smtpd/table_procexec.c new file mode 100644 index 00000000000..c991ed79192 --- /dev/null +++ b/usr.sbin/smtpd/table_procexec.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2013 Eric Faurot <e...@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <paths.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> + +#include <err.h> +#include <inttypes.h> + +#include "smtpd.h" +#include "log.h" + +#define PROCEXEC_VERSION "1" +#define PROCEXEC_TIMEOUT 500 + +static int table_procexec_open(struct table *); +static int table_procexec_update(struct table *); +static void table_procexec_close(struct table *); +static int table_procexec_lookup(struct table *, enum table_service, const char *, char **); +static int table_procexec_fetch(struct table *, enum table_service, char **); + +enum procexec_query; +static int +table_procexec_helper(struct table *, enum procexec_query, enum table_service, const char *, char **); + +struct table_backend table_backend_procexec = { + "proc-exec", + K_ANY, + NULL, + NULL, + NULL, + table_procexec_open, + table_procexec_update, + table_procexec_close, + table_procexec_lookup, + table_procexec_fetch, +}; + +struct procexec_handle { + FILE *backend_w; + FILE *backend_r; + int read_fd; + pid_t pid; + int status; + useconds_t msec; +}; + +enum procexec_query { + T_UPDATE, + T_LOOKUP, + T_FETCH +}; + +static int +table_procexec_update(struct table *t) { + return table_procexec_helper(t, T_UPDATE, (0), NULL, NULL); +} + +static int +table_procexec_lookup(struct table *t, enum table_service service, const char *key, char **dst) { + return table_procexec_helper(t, T_LOOKUP, service, key, dst); +} + +static int +table_procexec_fetch(struct table *t, enum table_service service, char **dst){ + return table_procexec_helper(t, T_FETCH, service, NULL, dst); +} + +static void +table_set_ready(int fd, short evt, void *arg){ + char *line = NULL; + size_t len = 0; + ssize_t nchar; + + struct procexec_handle *pe_handle = (struct procexec_handle *)arg; + + nchar = getline(&line, &len, pe_handle->backend_r); + + if(nchar <= 15 || strncmp(line, "TABLE-INIT|READY", 16) != 0){ + pe_handle->status = -1; + } + else{ + pe_handle->status = 1; + } + free(line); +} + +static int +table_procexec_open(struct table *t) { + struct procexec_handle *pe_handle; + pid_t pid; + int sp[2]; + + pe_handle = xcalloc(1, sizeof(*pe_handle)); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1){ + fatalx("procexec - socket pair: %s", t->t_name); + } + + if ((pid = fork()) == -1) { + fatalx("procexec - fork: %s", t->t_name); + } + + if (pid > 0) { + close(sp[0]); + FILE *backend_w, *backend_r; + if ((backend_w = fdopen(sp[1], "w")) == NULL) + fatalx("procexec - backend_w: %s", t->t_name); + + if ((backend_r = fdopen(sp[1], "r")) == NULL) + fatalx("procexec - backend_r: %s", t->t_name); + pe_handle->read_fd = sp[1]; + + pe_handle->pid = pid; + pe_handle->backend_w = backend_w; + pe_handle->backend_r = backend_r; + pe_handle->status = 0; + pe_handle->msec = PROCEXEC_TIMEOUT; + t->t_handle = pe_handle; + + fprintf(backend_w, "TABLE-INIT|%s|%d\n", PROCEXEC_VERSION, PROCEXEC_TIMEOUT); + fflush(backend_w); + + struct event *ev = (struct event *)xcalloc(1, sizeof(struct event)); + event_set(ev, sp[1], EV_READ, table_set_ready, (void *)pe_handle); + event_add(ev, NULL); + + return 1; + } + else { + close(sp[1]); + dup2(sp[0], STDIN_FILENO); + dup2(sp[0], STDOUT_FILENO); + + int execr; + char exec[_POSIX_ARG_MAX]; + execr = snprintf(exec, sizeof(exec), "exec %s" , t->t_config); + if (execr < 0 || execr >= (int) sizeof(exec)) + fatalx("procexec - execr: %s", t->t_name); + execl("/bin/sh", "/bin/sh", "-c", exec, (char *)NULL); + } + return 1; +} + +static void +table_procexec_close(struct table *t) { + struct procexec_handle *pe_handle = (struct procexec_handle *)t->t_handle; + fclose(pe_handle->backend_r); + fclose(pe_handle->backend_w); + close(pe_handle->read_fd); + waitpid(pe_handle->pid, 0, 0); + free(pe_handle); + pe_handle = NULL; +} + +static void +procexec_clear_io(int fd, short evt, void *arg){ + char *line; + size_t len; + ssize_t nchar; + + struct procexec_handle *pe_handle = (struct procexec_handle *)arg; + + line = NULL; + len = 0; + nchar = getline(&line, &len, pe_handle->backend_r); + free(line); + pe_handle->status = 1; +} + +static int +procexec_parse(struct procexec_handle *pe_handle, uint64_t reqid, enum procexec_query query, char **dst) { + char *oline, *line, *tline; + size_t len; + ssize_t nchar; + uint64_t resid; + + oline = NULL; + len = 0; + struct pollfd pfd = { pe_handle->read_fd, POLLIN, 0 }; + int ret = 0; + ret = poll(&pfd, 1, PROCEXEC_TIMEOUT); + if(ret == 1){ + oline = NULL; + len = 0; + nchar = getline(&oline, &len, pe_handle->backend_r); + } + else { + log_trace(TRACE_IO, "io: table backend io has timed out"); + pe_handle->status = -1; + + struct event *ev = (struct event *)xcalloc(1, sizeof(struct event)); + event_set(ev, pe_handle->read_fd, EV_READ, procexec_clear_io, (void *)pe_handle); + event_add(ev, NULL); + + goto tempfail; + } + if (len == 0 || nchar < 0) + goto tempfail; + line = oline; + + if(len < 13 || strncmp(line, "TABLE-RESULT|", 13) != 0) + goto tempfail; + line += 13; + len -= 13; + + resid = strtoull(line, &tline, 16); + if (len < 17 || *tline != '|') + goto tempfail; + if (errno == ERANGE && resid == ULLONG_MAX) + goto tempfail; + if (reqid != resid) + goto tempfail; + line += 17; + len -= 17; + + if(strncmp(line, "NOT-FOUND", 9) == 0) + goto failure; + if(len < 7 || strncmp(line, "FAILURE", 7) == 0 || strncmp(line, "SUCCESS", 7) != 0) + goto failure; + line += 8; + len -= 8; + switch(query){ + case T_UPDATE: + goto success; + break; + case T_LOOKUP: + case T_FETCH: + *dst = xcalloc(len, sizeof(*dst)); + if (strlcpy(*dst, line, len) >= len){ + free(*dst); + goto tempfail; + } + goto success; + break; + } + + failure: + free(oline); + return 0; + + tempfail: + free(oline); + return -1; + + success: + free(oline); + return 1; +} + +static int +table_procexec_helper(struct table *t, enum procexec_query query, enum table_service service, const char *key, char **dst) { + struct procexec_handle *pe_handle; + FILE* backend_w; + struct timeval tv; + uint64_t reqid; + + pe_handle = (struct procexec_handle *)t->t_handle; + if(pe_handle == NULL || pe_handle->status != 1){ + log_trace(TRACE_IO, "io: table backend io is %s", (pe_handle->status == -1)?"broken":"not ready"); + return -1; + } + backend_w = pe_handle->backend_w; + + reqid = generate_uid(); + gettimeofday(&tv, NULL); + + switch(query){ + case T_UPDATE: + fprintf(backend_w, "TABLE|%s|%lld.%06ld|UPDATE|%016" PRIx64 "\n", + PROCEXEC_VERSION, tv.tv_sec, tv.tv_usec, reqid); + fflush(backend_w); + return procexec_parse(pe_handle, reqid, query, dst); + break; + case T_LOOKUP: + fprintf(backend_w, "TABLE|%s|%lld.%06ld|LOOKUP|%016" PRIx64 "|%s|%s\n", + PROCEXEC_VERSION, tv.tv_sec, tv.tv_usec, reqid, table_service_name(service), key); + fflush(backend_w); + return procexec_parse(pe_handle, reqid, query, dst); + break; + case T_FETCH: + fprintf(backend_w, "TABLE|%s|%lld.%06ld|FETCH|%016" PRIx64 "|%s\n", + PROCEXEC_VERSION, tv.tv_sec, tv.tv_usec, reqid, table_service_name(service)); + fflush(backend_w); + return procexec_parse(pe_handle, reqid, query, dst); + break; + } + return -1; +}