From 6ece4b8fa391b40ee63e2a3d4f70526c60ef3c37 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 15 Jan 2016 10:07:15 +0100 Subject: [PATCH] inetd: Re-integrate the inetd subsystem into the new service model This patch introduces a new service type called SVC_TYPE_INETD_CONN, which represents a running inetd connection. These are handled in much the same way as tasks. I.e. they are considered one-time jobs, with the difference that they are removed upon completion. --- inetd.c | 221 ++++++++++++++++++++++++++++++++---------------------- inetd.h | 14 ++-- initctl.c | 7 +- service.c | 124 ++++++++++++++---------------- svc.c | 11 ++- svc.h | 18 +++-- 6 files changed, 219 insertions(+), 176 deletions(-) diff --git a/inetd.c b/inetd.c index a191710c..a48272da 100644 --- a/inetd.c +++ b/inetd.c @@ -43,75 +43,8 @@ #opt, inetd->name); \ } while (0); -/* Socket callback, looks up correct svc and starts it as an inetd service */ -static void socket_cb(uev_t *w, void *arg, int UNUSED(events)) -{ - svc_t *svc = (svc_t *)arg; - - if (SVC_START != service_enabled(svc, -1, NULL)) - return; - - if (!svc->inetd.forking) - uev_io_stop(w); - - service_start(svc); -} - -/* Launch Inet socket for service. - * TODO: Add filtering ALLOW/DENY per interface. - */ -static void spawn_socket(inetd_t *inetd) -{ - int sd; - socklen_t len = sizeof(struct sockaddr); - struct sockaddr_in s; - - if (!inetd->type) { - FLOG_ERROR("Skipping invalid inetd service %s", inetd->name); - return; - } - - _d("Spawning server socket for inetd %s ...", inetd->name); - sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK | SOCK_CLOEXEC, inetd->proto); - if (-1 == sd) { - FLOG_PERROR("Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto); - return; - } - - ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEADDR); -#ifdef SO_REUSEPORT - ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEPORT); -#endif - - memset(&s, 0, sizeof(s)); - s.sin_family = AF_INET; - s.sin_addr.s_addr = INADDR_ANY; - s.sin_port = htons(inetd->port); - if (bind(sd, (struct sockaddr *)&s, len) < 0) { - FLOG_PERROR("Failed binding to port %d, maybe another %s server is already running", - inetd->port, inetd->name); - close(sd); - return; - } - - if (inetd->port) { - if (inetd->type == SOCK_STREAM) { - if (-1 == listen(sd, 20)) { - FLOG_PERROR("Failed listening to inetd service %s", inetd->name); - close(sd); - return; - } - } else { /* SOCK_DGRAM */ - /* Set extra sockopt to get ifindex from inbound packets */ - ENABLE_SOCKOPT(sd, SOL_IP, IP_PKTINFO); - } - } - - uev_io_init(ctx, &inetd->watcher, socket_cb, inetd->arg, sd, UEV_READ); -} - /* Peek into SOCK_DGRAM socket to figure out where an inbound packet comes from. */ -int inetd_dgram_peek(int sd, char *ifname) +static int inetd_dgram_peek(int sd, char *ifname) { char cmbuf[0x100]; struct msghdr msgh; @@ -139,7 +72,7 @@ int inetd_dgram_peek(int sd, char *ifname) } /* Peek into SOCK_STREAM on accepted client socket to figure out inbound interface */ -int inetd_stream_peek(int sd, char *ifname) +static int inetd_stream_peek(int sd, char *ifname) { struct ifaddrs *ifaddr, *ifa; struct sockaddr_in sin; @@ -173,46 +106,146 @@ int inetd_stream_peek(int sd, char *ifname) return 0; } -/* Inetd monitor, called by service_monitor() */ -int inetd_respawn(pid_t pid) +static int get_stdin(svc_t *svc) { - svc_t *svc = svc_find_by_pid(pid); + char ifname[IF_NAMESIZE] = "UNKNOWN"; + int stdin = svc->inetd.watcher.fd; - if (svc_is_inetd(svc)) { - inetd_t *inetd = &svc->inetd; + if (svc->inetd.type == SOCK_STREAM) { + /* Open new client socket from server socket */ + stdin = accept(stdin, NULL, NULL); + if (stdin < 0) { + FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); + return -1; + } - svc->pid = 0; + _d("New client socket %d accepted for inetd service %d/tcp", stdin, svc->inetd.port); - if (svc_in_runlevel(svc, runlevel) && !inetd->forking) - uev_io_set(&inetd->watcher, inetd->watcher.fd, UEV_READ); - - return 1; /* It was us! */ + inetd_stream_peek(stdin, ifname); + } else { /* SOCK_DGRAM */ + inetd_dgram_peek(stdin, ifname); } - return 0; /* Not an inetd service */ + if (!inetd_is_allowed(&svc->inetd, ifname)) { + FLOG_INFO("Service %s on port %d not allowed from interface %s.", + svc->inetd.name, svc->inetd.port, ifname); + if (svc->inetd.type == SOCK_STREAM) + close(stdin); + + return -1; + } + + return stdin; } +/* Socket callback, looks up correct svc and starts it as an inetd service */ +static void socket_cb(uev_t *w, void *arg, int UNUSED(events)) +{ + svc_t *svc = (svc_t *)arg, *task; + int stdin; -void inetd_start(inetd_t *inetd) + stdin = get_stdin(svc); + if (stdin < 0) { + FLOG_ERROR("%s: Unable to accept incoming connection", + svc->cmd); + return; + } + + task = svc_new(svc->cmd, svc->inetd.next_id++, SVC_TYPE_INETD_CONN); + if (!task) { + FLOG_ERROR("%s: Unable to allocate service for inetd client", + svc->cmd); + return; + } + + /* Copy inherited attributes from inetd */ + task->runlevels = svc->runlevels; + task->inetd = svc->inetd; + memcpy(task->cond, svc->cond, sizeof(task->cond)); + memcpy(task->username, svc->username, sizeof(task->username)); + memcpy(task->group, svc->group, sizeof(task->group)); + memcpy(task->args, svc->args, sizeof(task->args)); + snprintf(task->desc, sizeof(task->desc), "%s Connection", svc->desc); + + task->stdin = stdin; + service_step(task); + + if (!svc->inetd.forking) { + svc->block = SVC_BLOCK_INETD_BUSY; + service_step(svc); + } +} + +/* Launch Inet socket for service. + * TODO: Add filtering ALLOW/DENY per interface. + */ +static int spawn_socket(inetd_t *inetd) +{ + int sd; + socklen_t len = sizeof(struct sockaddr); + struct sockaddr_in s; + + if (!inetd->type) { + FLOG_ERROR("Skipping invalid inetd service %s", inetd->name); + return -EINVAL; + } + + _d("Spawning server socket for inetd %s ...", inetd->name); + sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK | SOCK_CLOEXEC, inetd->proto); + if (-1 == sd) { + FLOG_PERROR("Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto); + return -errno; + } + + ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEADDR); +#ifdef SO_REUSEPORT + ENABLE_SOCKOPT(sd, SOL_SOCKET, SO_REUSEPORT); +#endif + + memset(&s, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = INADDR_ANY; + s.sin_port = htons(inetd->port); + if (bind(sd, (struct sockaddr *)&s, len) < 0) { + FLOG_PERROR("Failed binding to port %d, maybe another %s server is already running", + inetd->port, inetd->name); + close(sd); + return -errno; + } + + if (inetd->port) { + if (inetd->type == SOCK_STREAM) { + if (-1 == listen(sd, 20)) { + FLOG_PERROR("Failed listening to inetd service %s", inetd->name); + close(sd); + return -errno; + } + } else { /* SOCK_DGRAM */ + /* Set extra sockopt to get ifindex from inbound packets */ + ENABLE_SOCKOPT(sd, SOL_IP, IP_PKTINFO); + } + } + + uev_io_init(ctx, &inetd->watcher, socket_cb, inetd->svc, sd, UEV_READ); + return 0; +} + +int inetd_start(inetd_t *inetd) { if (inetd->watcher.fd == -1) - spawn_socket(inetd); + return spawn_socket(inetd); + + return -EEXIST; } void inetd_stop(inetd_t *inetd) { - svc_t *svc = (svc_t *)inetd->arg; - if (inetd->watcher.fd != -1) { uev_io_stop(&inetd->watcher); shutdown(inetd->watcher.fd, SHUT_RDWR); close(inetd->watcher.fd); inetd->watcher.fd = -1; } - - /* Stop any running service, not allowed anymore. */ - if (svc->pid) - service_stop(svc, SVC_WAITING_STATE); } static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv) @@ -497,7 +530,7 @@ int inetd_filter_str(inetd_t *inetd, char *str, size_t len) * If equivalent service exists already service_register() will instead call * inetd_allow(). */ -int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forking, void *arg) +int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forking, svc_t *svc) { int result; struct servent *sv = NULL; @@ -515,6 +548,7 @@ int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forkin inetd->port = ntohs(sv->s_port); inetd->proto = pv->p_proto; inetd->forking = !!forking; + inetd->next_id = 2; if (!name) name = service; strlcpy(inetd->name, name, sizeof(inetd->name)); @@ -526,11 +560,18 @@ int inetd_new(inetd_t *inetd, char *name, char *service, char *proto, int forkin else inetd->type = SOCK_DGRAM; + if (inetd->type == SOCK_DGRAM && inetd->forking) { + FLOG_WARN("%s: 'nowait' is not applicable on UDP services, ignoring", + svc->cmd); + + inetd->forking = 0; + } + /* Reset descriptor, used internally */ inetd->watcher.fd = -1; /* Setup socket callback argument */ - inetd->arg = arg; + inetd->svc = svc; _d("New service %s (default port %d proto %s:%d)", name, inetd->port, sv->s_proto, pv->p_proto); diff --git a/inetd.h b/inetd.h index 9cddd523..83073cfd 100644 --- a/inetd.h +++ b/inetd.h @@ -30,6 +30,7 @@ #include "queue.h" #include "libuev/uev.h" +typedef struct svc svc_t; typedef struct inetd_filter { TAILQ_ENTRY(inetd_filter) link; @@ -39,28 +40,25 @@ typedef struct inetd_filter { typedef struct { uev_t watcher; - void *arg; /* svc_t pointer for the socket callback */ + svc_t *svc; /* svc_t pointer for the socket callback */ int type; /* Socket type: SOCK_STREAM/SOCK_DGRAM */ int std; /* Standard proto/port from /etc/services */ int proto; int port; int forking; + int next_id; /* Next child job's id */ char name[10]; int (*cmd)(int type); /* internal inetd service, like 'time' */ TAILQ_HEAD(, inetd_filter) filters; } inetd_t; -int inetd_dgram_peek (int sd, char *ifname); -int inetd_stream_peek (int sd, char *ifname); - -void inetd_start (inetd_t *inetd); +int inetd_start (inetd_t *inetd); void inetd_stop (inetd_t *inetd); -int inetd_respawn (pid_t pid); - -int inetd_new (inetd_t *inetd, char *name, char *service, char *proto, int forking, void *arg); +int inetd_new (inetd_t *inetd, char *name, char *service, char *proto, + int forking, svc_t *svc); int inetd_del (inetd_t *inetd); int inetd_match (inetd_t *inetd, char *service, char *proto); diff --git a/initctl.c b/initctl.c index f03e020d..4e34734c 100644 --- a/initctl.c +++ b/initctl.c @@ -234,7 +234,12 @@ static int show_status(char *arg) else snprintf(jobid, sizeof(jobid), "%d:%d", svc->job, svc->id); - printf("%-5s %7s %-6d ", jobid, svc_status(svc), svc->pid); + printf("%-5s %7s ", jobid, svc_status(svc)); + if (svc_is_inetd(svc)) + printf("inetd "); + else + printf("%-6d ", svc->pid); + lvls = runlevel_string(svc->runlevels); if (strchr(lvls, '\e')) printf("%-18.18s ", lvls); diff --git a/service.c b/service.c index ebac78a9..bd4d808d 100644 --- a/service.c +++ b/service.c @@ -112,7 +112,7 @@ static int is_norespawn(void) */ static int service_start(svc_t *svc) { - int sd = 0; + int result = 0; pid_t pid; sigset_t nmask, omask; @@ -137,48 +137,20 @@ static int service_start(svc_t *svc) if (is_norespawn()) return 1; -#ifndef INETD_DISABLED - if (svc_is_inetd(svc)) { - char ifname[IF_NAMESIZE] = "UNKNOWN"; - - sd = svc->inetd.watcher.fd; - - if (svc->inetd.type == SOCK_STREAM) { - /* Open new client socket from server socket */ - sd = accept(sd, NULL, NULL); - if (sd < 0) { - FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); - return 1; - } - - _d("New client socket %d accepted for inetd service %d/tcp", sd, svc->inetd.port); - - /* Find ifname by means of getsockname() and getifaddrs() */ - inetd_stream_peek(sd, ifname); - } else { /* SOCK_DGRAM */ - /* Find ifname by means of IP_PKTINFO sockopt --> ifindex + if_indextoname() */ - inetd_dgram_peek(sd, ifname); - } - - if (!inetd_is_allowed(&svc->inetd, ifname)) { - FLOG_INFO("Service %s on port %d not allowed from interface %s.", - svc->inetd.name, svc->inetd.port, ifname); - if (svc->inetd.type == SOCK_STREAM) - close(sd); - - return 1; - } - - FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); - } else -#endif if (verbose) { - if (svc_is_daemon(svc)) + if (svc_is_daemon(svc) || svc_is_inetd(svc)) print_desc("Starting ", svc->desc); else print_desc("", svc->desc); } + if (svc_is_inetd(svc)) { + result = inetd_start(&svc->inetd); + if (verbose) + print_result(result); + return result; + } + /* Block sigchild while forking. */ sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); @@ -219,11 +191,10 @@ static int service_start(svc_t *svc) args[i] = svc->args[i]; args[i] = NULL; - /* Redirect inetd socket to stdin for service */ - if (svc_is_inetd(svc)) { - /* sd set previously */ - dup2(sd, STDIN_FILENO); - close(sd); + /* Redirect inetd socket to stdin for connection */ + if (svc_is_inetd_conn(svc)) { + dup2(svc->stdin, STDIN_FILENO); + close(svc->stdin); dup2(STDIN_FILENO, STDOUT_FILENO); dup2(STDIN_FILENO, STDERR_FILENO); } else if (debug) { @@ -254,7 +225,7 @@ static int service_start(svc_t *svc) else status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ - if (svc_is_inetd(svc)) { + if (svc_is_inetd_conn(svc)) { if (svc->inetd.type == SOCK_STREAM) { close(STDIN_FILENO); close(STDOUT_FILENO); @@ -266,21 +237,17 @@ static int service_start(svc_t *svc) } svc->pid = pid; - if (svc_is_inetd(svc)) { - if (svc->inetd.type == SOCK_STREAM) - close(sd); - } else { - int result = 0; + if (svc_is_inetd_conn(svc) && svc->inetd.type == SOCK_STREAM) + close(svc->stdin); - if (SVC_TYPE_RUN == svc->type) { - result = WEXITSTATUS(complete(svc->cmd, pid)); - svc->pid = 0; - } - - if (verbose) - print_result(result); + if (SVC_TYPE_RUN == svc->type) { + result = WEXITSTATUS(complete(svc->cmd, pid)); + svc->pid = 0; } + if (verbose) + print_result(result); + return 0; } @@ -298,6 +265,20 @@ static int service_stop(svc_t *svc) if (!svc) return 1; + if (svc_is_inetd(svc)) { + int do_print = runlevel != 1 && verbose && + svc->block != SVC_BLOCK_INETD_BUSY; + + if (do_print) + print_desc("Stopping ", svc->desc); + + inetd_stop(&svc->inetd); + + if (do_print) + print_result(0); + return 0; + } + if (svc->pid <= 1) { _d("Bad PID %d for %s, SIGTERM", svc->pid, svc->desc); return 1; @@ -371,12 +352,12 @@ static void service_reload_dynamic_finish(void) svc_clean_dynamic(service_unregister); _d("Starting services after reconf ..."); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); _d("Calling reconf hooks ..."); plugin_run_hooks(HOOK_SVC_RECONF); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); _d("Reconfiguration done"); } @@ -397,7 +378,7 @@ void service_reload_dynamic(void) _d("Stopping services services not allowed after reconf ..."); in_dyn_teardown = 1; cond_reload(); - service_step_all(SVC_TYPE_SERVICE); + service_step_all(SVC_TYPE_SERVICE | SVC_TYPE_INETD); /* Need to wait for any services to stop? If so, exit early * and perform second stage from service_monitor later. */ @@ -684,7 +665,6 @@ int service_register(int type, char *line, time_t mtime, char *username) if (svc_is_inetd(svc)) { char *iface, *name = service; - svc->state = SVC_WAITING_STATE; if (svc->inetd.cmd && plugin) name = plugin->name; @@ -714,8 +694,6 @@ int service_register(int type, char *line, time_t mtime, char *username) void service_unregister(svc_t *svc) { - if (svc->state != SVC_HALTED_STATE) - _e("Failed stopping %s, removing anyway from list of monitored services.", svc->cmd); svc_del(svc); } @@ -752,11 +730,6 @@ void service_monitor(pid_t lost) if (tty_respawn(lost)) return; -#ifndef INETD_DISABLED - if (inetd_respawn(lost)) - return; -#endif - svc = svc_find_by_pid(lost); if (!svc) { _d("collected unknown PID %d", lost); @@ -805,6 +778,15 @@ restart: break; case SVC_DONE_STATE: + if (svc_is_inetd_conn(svc)) { + if (svc->inetd.svc->block == SVC_BLOCK_INETD_BUSY) { + svc->inetd.svc->block = 0; + service_step(svc->inetd.svc); + } + service_unregister(svc); + return; + } + if (svc_is_changed(svc)) *state = SVC_HALTED_STATE; break; @@ -816,6 +798,7 @@ restart: case SVC_TYPE_INETD: *state = SVC_HALTED_STATE; break; + case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: *state = SVC_DONE_STATE; @@ -841,16 +824,19 @@ restart: err = service_start(svc); if (err) { (*restart_counter)++; - break; + + if (!svc_is_inetd_conn(svc)) + break; } svc_mark_clean(svc); switch (svc->type) { + case SVC_TYPE_INETD: case SVC_TYPE_SERVICE: *state = SVC_RUNNING_STATE; break; - case SVC_TYPE_INETD: + case SVC_TYPE_INETD_CONN: case SVC_TYPE_TASK: case SVC_TYPE_RUN: *state = SVC_STOPPING_STATE; @@ -868,7 +854,7 @@ restart: break; } - if (!svc->pid) { + if (!svc->pid && !svc_is_inetd(svc)) { (*restart_counter)++; /* TODO: There should be an async wait here * before moving back to READY */ diff --git a/svc.c b/svc.c index 42589985..3b7ed7d7 100644 --- a/svc.c +++ b/svc.c @@ -426,11 +426,20 @@ char *svc_status(svc_t *svc) return "crashing"; case SVC_BLOCK_USER: return "blocked"; + case SVC_BLOCK_INETD_BUSY: + return "busy"; } case SVC_DONE_STATE: return "done"; case SVC_STOPPING_STATE: - return "stopping"; + switch (svc->type) { + case SVC_TYPE_INETD_CONN: + case SVC_TYPE_RUN: + case SVC_TYPE_TASK: + return "active"; + default: + return "stopping"; + } case SVC_WAITING_STATE: return "waiting"; case SVC_READY_STATE: diff --git a/svc.h b/svc.h index b951abc2..678f60fd 100644 --- a/svc.h +++ b/svc.h @@ -39,11 +39,12 @@ typedef enum { } svc_cmd_t; typedef enum { - SVC_TYPE_FREE = 0, /* Free to allocate */ - SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */ - SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */ - SVC_TYPE_RUN = 4, /* Like task, but wait for completion */ - SVC_TYPE_INETD = 8 /* Classic inetd service */ + SVC_TYPE_FREE = 0, /* Free to allocate */ + SVC_TYPE_SERVICE = 1, /* Monitored, will be respawned */ + SVC_TYPE_TASK = 2, /* One-shot, runs in parallell */ + SVC_TYPE_RUN = 4, /* Like task, but wait for completion */ + SVC_TYPE_INETD = 8, /* Classic inetd service */ + SVC_TYPE_INETD_CONN = 16, /* Single inetd connection */ } svc_type_t; #define SVC_TYPE_ANY (-1) @@ -62,6 +63,7 @@ typedef enum { SVC_BLOCK_MISSING, SVC_BLOCK_CRASHING, SVC_BLOCK_USER, + SVC_BLOCK_INETD_BUSY, } svc_block_t; #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ @@ -96,6 +98,7 @@ typedef struct svc { /* For inetd services */ inetd_t inetd; + int stdin; /* Identity */ char username[MAX_USER_LEN]; @@ -171,8 +174,9 @@ static inline int svc_is_changed(svc_t *svc) { return svc && 0 != svc->dirty; } static inline int svc_is_updated(svc_t *svc) { return svc && 1 == svc->dirty; } const char *svc_dirtystr (svc_t *svc); -static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; } -static inline int svc_is_daemon (svc_t *svc) { return svc && SVC_TYPE_SERVICE == svc->type; } +static inline int svc_is_inetd (svc_t *svc) { return svc && SVC_TYPE_INETD == svc->type; } +static inline int svc_is_inetd_conn(svc_t *svc) { return svc && SVC_TYPE_INETD_CONN == svc->type; } +static inline int svc_is_daemon (svc_t *svc) { return svc && SVC_TYPE_SERVICE == svc->type; } #endif /* FINIT_SVC_H_ */