Hi, This is another try to add table-procexec to smtpd. This allows for table backends to communicate with smtpd with a very simple line protocol, similar to filter proc-exec.
The code is simple enough and after a bit of time can be used as a replace for table-proc (which uses imsg). Currently it is not replacing anything and is just available as an extra. I have a WIP perl-ldap table which can talk this line protocol and its on github right now (quite old) - https://github.com/bsd-ac/table-ldap_perl OK to import? Cheers, Aisha diff --git a/usr.sbin/smtpd/smtpctl/Makefile b/usr.sbin/smtpd/smtpctl/Makefile index ef8148be8c9..2e8beff1ad1 100644 --- a/usr.sbin/smtpd/smtpctl/Makefile +++ b/usr.sbin/smtpd/smtpctl/Makefile @@ -48,6 +48,7 @@ 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 125a6a5dfbe..ca54d54ea66 100644 --- a/usr.sbin/smtpd/smtpd.h +++ b/usr.sbin/smtpd/smtpd.h @@ -1662,6 +1662,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 d914b43f705..3fcfcd1c19d 100644 --- a/usr.sbin/smtpd/smtpd/Makefile +++ b/usr.sbin/smtpd/smtpd/Makefile @@ -63,6 +63,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..4f9adfe4c57 100644 --- a/usr.sbin/smtpd/table.c +++ b/usr.sbin/smtpd/table.c @@ -36,8 +36,8 @@ 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 *); @@ -49,6 +49,7 @@ static struct table_backend *backends[] = { &table_backend_db, &table_backend_getpwnam, &table_backend_proc, + &table_backend_procexec, NULL }; @@ -67,7 +68,7 @@ table_backend_lookup(const char *backend) return NULL; } -static const char * +const char * table_service_name(enum table_service s) { switch (s) { diff --git a/usr.sbin/smtpd/table_procexec.c b/usr.sbin/smtpd/table_procexec.c new file mode 100644 index 00000000000..9375da5c0ad --- /dev/null +++ b/usr.sbin/smtpd/table_procexec.c @@ -0,0 +1,326 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2023 Aisha Tammy <ai...@bsd.ac> + * Copyright (c) 2020 Gilles Chehade <gil...@poolp.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 <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <paths.h> +#include <poll.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "log.h" +#include "smtpd.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; + + struct procexec_handle *pe_handle = (struct procexec_handle *)arg; + + line = NULL; + len = 0; + 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; + + len = strlen(oline); + line[len - 1] = '\0'; + + 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; + int retval; + + 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"); + goto tempfail; + } + 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); + retval = procexec_parse(pe_handle, reqid, query, dst); + goto retval; + 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); + retval = procexec_parse(pe_handle, reqid, query, dst); + goto retval; + 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); + retval = procexec_parse(pe_handle, reqid, query, dst); + goto retval; + } + +tempfail: + return -1; + +retval: + return retval; +}