/* This file is part of Mailfromd. Copyright (C) 2005-2020 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <syslog.h> #include <signal.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <mailutils/mailutils.h> #include <mailutils/alloc.h> #include "libmf.h" #include "srvman.h" struct mfd_server { struct mfd_server *prev; /* Link to the previous server */ struct mfd_server *next; /* Link to the next server */ char *id; /* Server ID */ struct sockaddr *sa; /* Socket address */ socklen_t salen; /* Length of the sa */ int backlog; /* Backlog value for listen(2) */ int fd; /* Socket descriptor */ int flags; /* SRV_* flags */ mfd_server_prefork_hook_t prefork_hook; /* Pre-fork function */ mfd_server_func_t conn; /* Connection handler */ mfd_srvman_hook_t free_hook; void *data; /* Server-specific data */ mu_acl_t acl; /* Access Control List */ size_t max_children; /* Maximum number of sub-processes to run. */ size_t num_children; /* Current number of running sub-processes. */ pid_t *pidtab; /* Array of child PIDs */ size_t pidtab_size; /* Number of elements in pidtab */ }; struct srvman_param srvman_param; typedef RETSIGTYPE (*sig_handler_t) (int); union srvman_sockaddr { struct sockaddr sa; struct sockaddr_in s_in; struct sockaddr_un s_un; }; struct srvman { struct mfd_server *head, *tail; /* List of servers */ size_t num_children; /* Current number of running sub-processes. */ sigset_t sigmask; /* A set of signals to handle by the manager. */ sig_handler_t sigtab[NSIG]; /* Keeps old signal handlers. */ }; static struct srvman srvman; static mu_debug_handle_t debug_handle; static sig_handler_t set_signal(int sig, sig_handler_t handler) { #ifdef HAVE_SIGACTION struct sigaction act, oldact; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(sig, &act, &oldact); return oldact.sa_handler; #else return signal(sig, handler); #endif } static int volatile need_cleanup; static int volatile stop; void mfd_srvman_stop() { stop = 1; } static RETSIGTYPE srvman_signal (int signo) { switch (signo) { case SIGCHLD: need_cleanup = 1; break; default: /* FIXME: */ stop = 1; break; } #ifndef HAVE_SIGACTION signal(signo, srvman_signal); #endif } static void set_signal_handlers() { int i; for (i = 0; i < NSIG; i++) if (sigismember (&srvman.sigmask, i)) srvman.sigtab[i] = set_signal(i, srvman_signal); } static void restore_signal_handlers() { int i; for (i = 0; i < NSIG; i++) if (sigismember (&srvman.sigmask, i)) set_signal(i, srvman.sigtab[i]); } struct sockaddr * srvman_url_to_sockaddr(mu_url_t url, socklen_t *psalen) { int rc; const char *sval; struct sockaddr *sa; socklen_t socklen; if (rc = mu_url_sget_scheme(url, &sval)) { mu_error(_("cannot get URL scheme: %s"), mu_strerror(rc)); return NULL; } if (strcmp(sval, "unix") == 0 || strcmp(sval, "local") == 0) { struct sockaddr_un *s_un; size_t slen; rc = mu_url_sget_path(url, &sval); if (rc) { if (rc == MU_ERR_NOENT) rc = mu_url_sget_host(url, &sval); if (rc) { mu_error(_("cannot get URL path: %s"), mu_strerror(rc)); return NULL; } } slen = strlen(sval); if (slen >= sizeof s_un->sun_path) { mu_error(_("socket path name too long: %s"), sval); return NULL; } socklen = sizeof (s_un[0]); sa = mu_alloc (socklen); s_un = (struct sockaddr_un *)sa; s_un->sun_family = AF_UNIX; strcpy(s_un->sun_path, sval); } else if (strcmp(sval, "inet") == 0) { struct sockaddr_in *s_in; unsigned n; short port; struct hostent *hp; if (rc = mu_url_get_port(url, &n)) { mu_error(_("cannot get URL port: %s"), mu_strerror(rc)); return NULL; } if (n == 0 || (port = n) != n) { mu_error(_("port out of range: %u"), n); return NULL; } if (rc = mu_url_sget_host(url, &sval)) { mu_error(_("cannot get URL host: %s"), mu_strerror(rc)); return NULL; } hp = gethostbyname(sval); if (!hp) { mu_error(_("unknown host name: %s"), sval); return NULL; } if (hp->h_addrtype != AF_INET || hp->h_length != 4) { mu_error(_("unsupported address family")); return NULL; } socklen = sizeof s_in[0]; sa = mu_alloc(socklen); s_in = (struct sockaddr_in *)sa; s_in->sin_family = AF_INET; memcpy(&s_in->sin_addr, hp->h_addr, 4); s_in->sin_port = htons(port); #ifdef GACOPYZ_IPV6 } else if (strcmp(sval, "inet6") == 0) { struct addrinfo hints; struct addrinfo *res; const char *host = NULL; const char *port; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; rc = mu_url_sget_host(url, &host); if (rc == MU_ERR_NOENT) hints.ai_flags |= AI_PASSIVE; else if (rc) { mu_error(_("cannot get URL host: %s"), mu_strerror(rc)); return NULL; } if (mu_url_sget_portstr(url, &port)) { mu_error(_("cannot get URL port: %s"), mu_strerror(rc)); return NULL; } rc = getaddrinfo(host, port, &hints, &res); switch (rc) { case 0: break; case EAI_SYSTEM: mu_error(_("%s:%s: cannot parse address: %s"), host ? host : "NULL", port, strerror(errno)); return NULL; case EAI_BADFLAGS: case EAI_SOCKTYPE: mu_error(_("%s:%d: internal error converting %s:%s"), __FILE__, __LINE__, host ? host : "NULL", port); return NULL; case EAI_MEMORY: mu_alloc_die(); default: mu_error("%s:%s: %s", host ? host : "NULL", port, gai_strerror(rc)); return NULL; } if (res->ai_next) { char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; rc = getnameinfo(res->ai_addr, res->ai_addrlen, hostbuf, sizeof hostbuf, servbuf, sizeof servbuf, NI_NUMERICHOST|NI_NUMERICSERV); mu_diag_output(MU_DIAG_WARNING, _("%s:%s resolves to several addresses; " "using %s:%s"), host, port, hostbuf, servbuf); } socklen = res->ai_addrlen; sa = mu_alloc(socklen); memcpy(sa, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); #endif } else { mu_error(_("unsupported protocol: %s"), sval); return NULL; } if (psalen) *psalen = socklen; return sa; } /* Yield 1 if SRV has run out of the children limit */ #define SERVER_BUSY(srv) \ ((srv)->max_children && (srv)->num_children >= (srv)->max_children) static void register_child(struct mfd_server *srv, pid_t pid) { size_t i; mu_debug(debug_handle, MU_DEBUG_TRACE5, ("registering child %lu", (unsigned long)pid)); for (i = 0; i < srv->pidtab_size; i++) if (srv->pidtab[i] == 0) break; if (i == srv->pidtab_size) { int j; if (srv->pidtab_size == 0) srv->pidtab_size = DEFAULT_PIDTAB_SIZE; srv->pidtab = mu_2nrealloc(srv->pidtab, &srv->pidtab_size, sizeof(srv->pidtab[0])); for (j = i; j < srv->pidtab_size; j++) srv->pidtab[j] = 0; } srv->pidtab[i] = pid; srv->num_children++; srvman.num_children++; } static void report_exit_status(const char *tag, pid_t pid, int status, int expect_term) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) mu_debug(debug_handle, MU_DEBUG_TRACE0, ("%s [%lu] exited successfully", tag, (unsigned long) pid)); else mu_error(_("%s [%lu] failed with status %d"), tag, (unsigned long) pid, WEXITSTATUS(status)); } else if (WIFSIGNALED (status)) { if (expect_term && WTERMSIG(status) == SIGTERM) mu_debug(debug_handle, MU_DEBUG_TRACE0, ("%s [%lu] terminated on signal %d", tag, (unsigned long) pid, WTERMSIG(status))); else mu_error(_("%s [%lu] terminated on signal %d"), tag, (unsigned long) pid, WTERMSIG(status)); } else if (WIFSTOPPED(status)) mu_error(_("%s [%lu] stopped on signal %d"), tag, (unsigned long) pid, WSTOPSIG(status)); else mu_error(_("%s [%lu] terminated with unrecognized status"), tag, (unsigned long) pid); } /* Remove (unregister) PID from the list of running instances and log its exit STATUS. Return 1 to command main loop to recompute the set of active descriptors. */ static int unregister_child(pid_t pid, int status) { struct mfd_server *srv; mu_debug(debug_handle, MU_DEBUG_TRACE5, ("unregistering child %lu (status %x)", (unsigned long)pid, status)); for (srv = srvman.head; srv; srv = srv->next) { size_t i; for (i = 0; i < srv->pidtab_size; i++) if (srv->pidtab[i] == pid) { int rc = SERVER_BUSY(srv); srv->pidtab[i] = 0; srv->num_children--; srvman.num_children--; /* FIXME: expect_term? */ report_exit_status(srv->id, pid, status, 0); return rc; } } /* FIXME */ return 0; } static int children_cleanup() { int rc = 0; pid_t pid; int status; mu_debug(debug_handle, MU_DEBUG_TRACE1, ("cleaning up subprocesses")); while ((pid = waitpid(-1, &status, WNOHANG)) > 0) rc |= unregister_child(pid, status); return rc; } static void server_remove(struct mfd_server *srv) { struct mfd_server *p; mu_debug(debug_handle, MU_DEBUG_TRACE4, ("removing server %s", srv->id)); if ((p = srv->prev) != NULL) p->next = srv->next; else srvman.head = srv->next; if ((p = srv->next) != NULL) p->prev = srv->prev; else srvman.tail = srv->prev; } static void server_signal_children(struct mfd_server *srv, int sig) { int i; mu_debug(debug_handle, MU_DEBUG_TRACE4, ("server %s: sending children signal %d", srv->id, sig)); for (i = 0; i < srv->pidtab_size; i++) if (srv->pidtab[i]) kill(srv->pidtab[i], sig); } void mfd_server_shutdown(struct mfd_server *srv) { if (srv->fd == -1) return; mu_debug(debug_handle, MU_DEBUG_TRACE1, ("shutting down %s", srv->id)); close(srv->fd); srv->fd = -1; } struct mfd_server * mfd_server_new(const char *id, mu_url_t url, mfd_server_func_t conn, int flags) { struct mfd_server *srv; struct sockaddr *sa; socklen_t salen; sa = srvman_url_to_sockaddr(url, &salen); if (!sa) return NULL; srv = mu_zalloc(sizeof(*srv)); srv->id = mu_strdup(id); srv->sa = sa; srv->salen = salen; srv->backlog = 8; srv->conn = conn; srv->flags = flags; return srv; } void mfd_server_free(struct mfd_server *srv) { server_remove(srv); if (srv->free_hook) srv->free_hook(srv->data); free(srv->id); free(srv->sa); mu_acl_destroy(&srv->acl); free(srv->pidtab); free(srv); } void mfd_server_set_prefork_hook(struct mfd_server *srv, mfd_server_prefork_hook_t hook) { srv->prefork_hook = hook; } void mfd_server_set_data(struct mfd_server *srv, void *data, mfd_srvman_hook_t free_hook) { srv->data = data; srv->free_hook = free_hook; } void mfd_server_set_max_children(struct mfd_server *srv, size_t n) { srv->max_children = n; } void mfd_server_set_acl(struct mfd_server *srv, mu_acl_t acl) { srv->acl = acl; } void mfd_server_set_backlog(struct mfd_server *srv, int value) { srv->backlog = value; } void mfd_srvman_attach_server(struct mfd_server *srv) { srv->next = NULL; srv->prev = srvman.tail; if (srvman.tail) srvman.tail->next = srv; else srvman.head = srv; srvman.tail = srv; } static int check_acl(const char *id, mu_acl_t acl, struct sockaddr *sa, socklen_t salen) { mu_acl_result_t res; int rc = mu_acl_check_sockaddr(acl, sa, salen, &res); char *p = mu_sys_sockaddr_to_astr(sa, salen); if (rc) { mu_error(_("server %s: access from %s blocked: " "cannot check ACLs: %s"), id, p, mu_strerror(rc)); } else { switch (res) { case mu_acl_result_undefined: mu_diag_output(MU_DIAG_INFO, _("server %s: undefined ACL result; " "access from %s allowed"), id, p); break; case mu_acl_result_accept: mu_debug(debug_handle, MU_DEBUG_TRACE0, ("server %s: allowed access from %s", id, p)); break; case mu_acl_result_deny: mu_error(_("server %s: access from %s blocked"), id, p); rc = 1; } } free(p); return rc; } static void server_run(int connfd, struct mfd_server *srv, struct sockaddr *sa, socklen_t salen) { if (srvman_param.acl && check_acl(srv->id, srvman_param.acl, sa, salen)) return; if (srv->acl && check_acl(srv->id, srv->acl, sa, salen)) return; if (((srvman_param.flags | srv->flags) & SRV_SINGLE_PROCESS)) { if ((!srvman_param.prefork_hook || srvman_param.prefork_hook(sa, salen, srvman_param.data) == 0) && (!srv->prefork_hook || srv->prefork_hook(srv->id, sa, salen, srv->data, srvman_param.data) == 0)) srv->conn(srv->id, connfd, sa, salen, srv->data, srvman_param.data); } else { pid_t pid; if (srv->prefork_hook && srv->prefork_hook(srv->id, sa, salen, srv->data, srvman_param.data)) return; pid = fork(); if (pid == -1) mu_error("fork: %s", strerror(errno)); else if (pid == 0) { /* Child. */ FD_SET(connfd, &srvman_param.keepfds); close_fds_except(&srvman_param.keepfds); restore_signal_handlers(); exit(srv->conn(srv->id, connfd, sa, salen, srv->data, srvman_param.data)); } else register_child(srv, pid); } } /* Accept incoming connection for server SRV. Return 1 to command main loop to recompute the set of active descriptors. */ static int server_accept(struct mfd_server *srv) { int connfd; union srvman_sockaddr client; socklen_t size = sizeof(client); if (srv->fd == -1) { mu_error(_("removing shut down server %s"), srv->id); mfd_server_free(srv); return 1; } if (SERVER_BUSY(srv)) { mu_error(_("server %s: too many children (%lu)"), srv->id, (unsigned long) srv->num_children); return 1; } connfd = accept(srv->fd, &client.sa, &size); if (connfd == -1) { if (errno != EINTR) { mu_error(_("server %s: accept failed: %s"), srv->id, mu_strerror(errno)); mfd_server_shutdown(srv); mfd_server_free(srv); return 1; } /* FIXME: Call srv->intr otherwise? */ } else { server_run(connfd, srv, &client.sa, size); close(connfd); } return 0; } static int connection_loop(fd_set *fdset) { struct mfd_server *srv; int rc = 0; for (srv = srvman.head; srv; ) { struct mfd_server *next = srv->next; if (FD_ISSET(srv->fd, fdset)) rc |= server_accept(srv); srv = next; } return rc; } int compute_fdset(fd_set *fdset) { struct mfd_server *p; int maxfd = 0; FD_ZERO(fdset); for (p = srvman.head; p; p = p->next) { if (SERVER_BUSY(p)) continue; FD_SET(p->fd, fdset); if (p->fd > maxfd) maxfd = p->fd; } mu_debug(debug_handle, MU_DEBUG_TRACE3, ("recomputed fdset: %d fds", maxfd)); return maxfd; } void mfd_srvman_run(sigset_t *set) { int recompute_fd = 1; int maxfd; fd_set fdset; if (!srvman.head) return; mu_debug(debug_handle, MU_DEBUG_TRACE1, ("server manager starting")); if (set) srvman.sigmask = *set; else sigemptyset(&srvman.sigmask); sigaddset(&srvman.sigmask, SIGCHLD); set_signal_handlers(); if (srvman_param.shutdown_timeout == 0) srvman_param.shutdown_timeout = DEFAULT_SHUTDOWN_TIMEOUT; for (stop = 0; srvman.head && !stop;) { int rc; struct timeval *to; fd_set rdset; if (need_cleanup) { need_cleanup = 0; recompute_fd = children_cleanup(); } if (recompute_fd) { maxfd = compute_fdset(&fdset); recompute_fd = 0; } if (!maxfd) { mu_debug(debug_handle, MU_DEBUG_TRACE1, ("no active fds, pausing")); pause(); recompute_fd = 1; continue; } if (srvman_param.max_children && srvman.num_children >= srvman_param.max_children) { mu_error(_("too many children (%lu)"), (unsigned long) srvman.num_children); pause(); continue; } if (srvman_param.idle_hook && srvman_param.idle_hook(srvman_param.data)) { mu_debug(debug_handle, MU_DEBUG_TRACE1, ("break requested by idle hook")); break; } if (stop) break; rdset = fdset; to = NULL; /* FIXME */ rc = select(maxfd + 1, &rdset, NULL, NULL, to); if (rc == -1 && errno == EINTR) continue; if (rc < 0) { mu_error(_("select failed: %s"), mu_strerror(errno)); break; } recompute_fd = connection_loop(&rdset); } restore_signal_handlers(); mu_debug(debug_handle, MU_DEBUG_TRACE1, ("server manager finishing")); } static int server_prep(struct mfd_server *srv, int fd) { struct stat st; struct sockaddr_un *s_un; int t; switch (srv->sa->sa_family) { case AF_UNIX: s_un = (struct sockaddr_un *) srv->sa; if (stat(s_un->sun_path, &st)) { if (errno != ENOENT) { mu_error(_("%s: file %s exists but cannot be stat'd: %s"), srv->id, s_un->sun_path, mu_strerror(errno)); return 1; } } else if (!S_ISSOCK(st.st_mode)) { mu_error(_("%s: file %s is not a socket"), srv->id, s_un->sun_path); return 1; } else if (!(srvman_param.flags & SRV_KEEP_EXISTING) && !(srv->flags & SRV_KEEP_EXISTING)) { if (unlink(s_un->sun_path)) { mu_error(_("%s: cannot unlink file %s: %s"), srv->id, s_un->sun_path, mu_strerror(errno)); return 1; } } else { mu_error(_("socket `%s' already exists"), s_un->sun_path); return 1; } break; case AF_INET: if (!(srvman_param.flags & SRV_KEEP_EXISTING) && !(srv->flags & SRV_KEEP_EXISTING)) { t = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t)); } } if (bind(fd, srv->sa, srv->salen) == -1) { char *p = mu_sys_sockaddr_to_astr(srv->sa, srv->salen); mu_error(_("%s: cannot bind to %s: %s"), srv->id, p, mu_strerror(errno)); free(p); return 1; } if (listen(fd, srv->backlog) == -1) { char *p = mu_sys_sockaddr_to_astr(srv->sa, srv->salen); mu_error(_("%s: listen on %s failed: %s"), srv->id, p, mu_strerror(errno)); free(p); return 1; } return 0; } static int server_open(struct mfd_server *srv) { int fd = socket(srv->sa->sa_family, SOCK_STREAM, 0); if (fd == -1) { mu_error("%s: socket: %s", srv->id, mu_strerror(errno)); return 1; } if (server_prep(srv, fd)) { close(fd); return 1; } srv->fd = fd; return 0; } int mfd_srvman_open() { struct mfd_server *p; mu_debug(debug_handle, MU_DEBUG_TRACE1, ("opening servers")); if (!srvman.head) mu_error(_("no servers configured")); for (p = srvman.head; p; ) { struct mfd_server *next = p->next; if (server_open(p)) server_remove(p); p = next; } return srvman.head == NULL; } void mfd_srvman_shutdown() { struct mfd_server *p; time_t start = time(NULL); for (p = srvman.head; p; p = p->next) server_signal_children(p, SIGTERM); do { children_cleanup(); if (srvman.num_children == 0) break; sleep(1); } while (time(NULL) - start < srvman_param.shutdown_timeout); mu_debug(debug_handle, MU_DEBUG_TRACE1, ("shutting down servers")); for (p = srvman.head; p; p = p->next) { server_signal_children(p, SIGKILL); mfd_server_shutdown(p); } } void mfd_srvman_free() { struct mfd_server *p; for (p = srvman.head; p; ) { struct mfd_server *next = p->next; mfd_server_free(p); p = next; } if (srvman_param.free_hook) srvman_param.free_hook(srvman_param.data); mu_acl_destroy(&srvman_param.acl); } size_t mfd_srvman_count_servers() { size_t count = 0; struct mfd_server *p; for (p = srvman.head; p; p = p->next) count++; return count; } void srvman_init() { debug_handle = mu_debug_register_category("srvman"); }