diff --git a/Makefile b/Makefile index 952569c8..e489a49e 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ ARCHIVEZ = ../$(ARCHIVE).xz EXEC = finit reboot HEADERS = plugin.h svc.h helpers.h queue.h DISTFILES = LICENSE README CHANGELOG finit.conf services -OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o tty.o +OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o tty.o inetd.o SRCS = $(OBJS:.o=.c) DEPS = $(addprefix .,$(SRCS:.c=.d)) diff --git a/conf.c b/conf.c index 4e674800..393e1b3f 100644 --- a/conf.c +++ b/conf.c @@ -244,6 +244,12 @@ static int parse_conf(char *file) continue; } + /* Classic inetd service */ + if (MATCH_CMD(line, "inetd ", x)) { + svc_register(SVC_CMD_INETD, x, NULL); + continue; + } + if (MATCH_CMD(line, "console ", x)) { if (console) free(console); console = strdup(strip_line(x)); diff --git a/finit.c b/finit.c index 4c880d24..f6af17de 100644 --- a/finit.c +++ b/finit.c @@ -36,6 +36,7 @@ #include "sig.h" #include "tty.h" #include "lite.h" +#include "inetd.h" int debug = 0; int verbose = 1; @@ -251,6 +252,9 @@ int main(int argc, char* argv[]) */ svc_runlevel(cfglevel); + /* Start inetd services */ + inetd_runlevel(&ctx, runlevel); + _d("Running svc up hooks ..."); plugin_run_hooks(HOOK_SVC_UP); diff --git a/inetd.c b/inetd.c new file mode 100644 index 00000000..170a3bc7 --- /dev/null +++ b/inetd.c @@ -0,0 +1,137 @@ +/* Classic inetd services launcher for Finit + * + * Copyright (c) 2015 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +//#include +//#include +#include +#include + +#include "finit.h" +#include "svc.h" +#include "helpers.h" +#include "libuev/uev.h" + +/* Socket callback, looks up correct svc and starts it as an inetd service */ +static void socket_cb(uev_ctx_t *UNUSED(ctx), uev_t *w, void *arg, int UNUSED(events)) +{ + svc_t *svc = (svc_t *)arg; + + if (SVC_START != svc_enabled(svc, 1, NULL)) + return; + + if (!svc->forking) + uev_io_stop(w); + + svc_start(svc); +} + +/* Launch Inet socket for service. + * TODO: Add filtering ALLOW/DENY per interface. + */ +static void spawn_socket(uev_ctx_t *ctx, svc_t *svc) +{ + int sd; + socklen_t len = sizeof(struct sockaddr); + struct sockaddr_in s; + + if (!svc->sock_type) { + FLOG_ERROR("Skipping invalid inetd service %s", svc->cmd); + return; + } + + sd = socket(AF_INET, svc->sock_type | SOCK_NONBLOCK, svc->proto); + if (-1 == sd) { + FLOG_PERROR("Failed opening inetd socket type %d proto %d", svc->sock_type, svc->proto); + return; + } + + memset(&s, 0, sizeof(s)); + s.sin_family = AF_INET; + s.sin_addr.s_addr = INADDR_ANY; + s.sin_port = htons(svc->port); + if (bind(sd, (struct sockaddr *)&s, len) < 0) { + FLOG_PERROR("Failed binding to port %d, maybe another %s server is already running", + svc->port, svc->service); + close(sd); + return; + } + + if (svc->port && svc->sock_type != SOCK_DGRAM) { + if (-1 == listen(sd, 20)) { + FLOG_PERROR("Failed listening to inetd service %s", svc->service); + close(sd); + return; + } + } + + _d("Initializing inetd %s service %s type %d proto %d on socket %d ...", + svc->service, basename(svc->cmd), svc->sock_type, svc->proto, sd); + uev_io_init(ctx, &svc->watcher, socket_cb, svc, sd, UEV_READ); +} + +/* Inetd monitor, called by svc_monitor() */ +int inetd_respawn(pid_t pid) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (SVC_CMD_INETD != svc->type) + continue; + + if (svc->pid == pid) { + svc->pid = 0; + if (!svc->forking) + uev_io_set(&svc->watcher, svc->watcher.fd, UEV_READ); + + return 1; /* It was us! */ + } + } + + return 0; /* Not an inetd service */ +} + +/* Called when changing runlevel to start Inet socket handlers */ +void inetd_runlevel(uev_ctx_t *ctx, int runlevel) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (SVC_CMD_INETD != svc->type) + continue; + + if (!ISSET(svc->runlevels, runlevel)) + continue; + + + spawn_socket(ctx, svc); + } +} + + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/inetd.h b/inetd.h new file mode 100644 index 00000000..9670f07d --- /dev/null +++ b/inetd.h @@ -0,0 +1,40 @@ +/* Classic inetd services launcher for Finit + * + * Copyright (c) 2015 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_INETD_H_ +#define FINIT_INETD_H_ + +#include "libuev/uev.h" + +int inetd_respawn (pid_t pid); +void inetd_runlevel(uev_ctx_t *ctx, int runlevel); + +#endif /* FINIT_INETD_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/svc.c b/svc.c index f4c62cdf..63da2c30 100644 --- a/svc.c +++ b/svc.c @@ -1,7 +1,7 @@ /* Finit service monitor, task starter and generic API for managing svc_t * * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2013 Joachim Nilsson + * Copyright (c) 2008-2015 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,7 @@ #include #include #include +#include #include "finit.h" #include "helpers.h" @@ -33,6 +34,7 @@ #include "tty.h" #include "lite.h" #include "svc.h" +#include "inetd.h" /* The registered services are kept in shared memory for easy read-only access * by 3rd party APIs. Mostly because of the ability to, from the outside, query @@ -136,8 +138,12 @@ void svc_bootstrap(void) _d("Bootstrapping all services in runlevel S from %s", FINIT_CONF); for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - svc_cmd_t cmd = svc_enabled(svc, 0, NULL); + svc_cmd_t cmd; + if (svc->type == SVC_CMD_INETD) + continue; + + cmd = svc_enabled(svc, 0, NULL); if (SVC_START == cmd || (SVC_RELOAD == cmd)) svc_start(svc); } @@ -186,15 +192,19 @@ void svc_runlevel(int newlevel) utmp_save(prevlevel, newlevel); for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - int run = svc_enabled(svc, 0, NULL); + svc_cmd_t cmd; + if (svc->type == SVC_CMD_INETD) + continue; + + cmd = svc_enabled(svc, 0, NULL); if (svc->pid) { - if (run == SVC_STOP) + if (cmd == SVC_STOP) svc_stop(svc); - else if (run == SVC_RELOAD) + else if (cmd == SVC_RELOAD) svc_reload(svc); } else { - if (run == SVC_START) + if (cmd == SVC_START) svc_start(svc); } } @@ -235,9 +245,10 @@ void svc_runlevel(int newlevel) * "service @username [!0-6,S] /path/to/daemon arg -- Description text" * "task @username [!0-6,S] /path/to/task arg -- Description text" * "run @username [!0-6,S] /path/to/cmd arg -- Description text" + * "inetd tcp/ssh nowait [2345] @root:root /usr/sbin/sshd -i -- Description" * * If the username is left out the command is started as root. - + * Inetd services, launched on demand (SVC_CMD_INETD) * The [] brackets are there to denote the allowed runlevels. Allowed * runlevels mimic that of SysV init with the addition of the 'S' * runlevel, which is only run once at startup. It can be seen as the @@ -249,9 +260,12 @@ void svc_runlevel(int newlevel) */ int svc_register(int type, char *line, char *username) { - int i = 0; + int i = 0, forking = 0; + char *service = NULL, *proto = NULL; char *cmd, *desc, *runlevels = NULL; svc_t *svc; + struct servent *sv = NULL; + struct protoent *pv = NULL; if (!line) { _e("Invalid input argument."); @@ -270,7 +284,13 @@ int svc_register(int type, char *line, char *username) } while (cmd) { - if (cmd[0] == '@') /* @username */ + if (cmd[0] != '/' && strchr(cmd, '/')) + service = cmd; /* inetd port/proto */ + else if (!strncasecmp(cmd, "nowait", 6)) + forking = 1; + else if (!strncasecmp(cmd, "wait", 4)) + forking = 0; + else if (cmd[0] == '@') /* @username[:group] */ username = &cmd[1]; else if (cmd[0] == '[') /* [runlevels] */ runlevels = &cmd[0]; @@ -283,6 +303,24 @@ int svc_register(int type, char *line, char *username) goto incomplete; } + if (service) { + proto = strchr(service, '/'); + *proto++ = 0; + sv = getservbyname(service, proto); + if (!sv) { + _pe("Invalid inetd %s/%s (service/proto), skipping", service, proto); + return errno = EINVAL; + } + + pv = getprotobyname(sv->s_proto); + if (!pv) { + _pe("Cannot find proto %s, skipping.", sv->s_proto); + return errno = EINVAL; + } + + _d("Got service %s:%d proto %s:%d", service, ntohs(sv->s_port), sv->s_proto, pv->p_proto); + } + svc = svc_new(); if (!svc) { _e("Out of memory, cannot register service %s", cmd); @@ -294,8 +332,29 @@ int svc_register(int type, char *line, char *username) if (desc) strlcpy(svc->desc, desc + 3, sizeof(svc->desc)); - if (username) + if (username) { + char *ptr = strchr(username, ':'); + + if (ptr) { + *ptr++ = 0; + strlcpy(svc->group, ptr, sizeof(svc->group)); + } strlcpy(svc->username, username, sizeof(svc->username)); + } + + if (sv) { + strlcpy(svc->service, service, sizeof(svc->service)); + svc->port = ntohs(sv->s_port); + svc->proto = pv->p_proto; + + /* Naïve mapping tcp->stream, udp->dgram, other->dgram */ + if (!strcasecmp(sv->s_proto, "tcp")) + svc->sock_type = SOCK_STREAM; + else + svc->sock_type = SOCK_DGRAM; + + svc->forking = forking; + } strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); @@ -355,6 +414,9 @@ static void restart_any_lost_procs(void) svc_t *svc; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->type == SVC_CMD_INETD) + continue; + if (svc->pid > 0 && pid_alive(svc->pid)) continue; @@ -384,6 +446,9 @@ void svc_monitor(pid_t lost) if (tty_respawn(lost)) return; + if (inetd_respawn(lost)) + return; + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { if (lost != svc->pid) continue; @@ -420,7 +485,7 @@ void svc_monitor(pid_t lost) /* Remember: svc_enabled() must be called before calling svc_start() */ int svc_start(svc_t *svc) { - int respawn; + int respawn, sd = 0; pid_t pid; sigset_t nmask, omask; @@ -443,6 +508,21 @@ int svc_start(svc_t *svc) if (is_norespawn()) return 0; + if (SVC_CMD_INETD == svc->type) { + sd = svc->watcher.fd; + + if (svc->sock_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->port); + return 0; + } + + _d("New client socket %d accepted for inetd service %d/tcp", sd, svc->port); + } + } + if (SVC_CMD_SERVICE != svc->type) print_desc("", svc->desc); else if (!respawn) @@ -478,7 +558,14 @@ int svc_start(svc_t *svc) args[i] = svc->args[i]; args[i] = NULL; - if (debug) { + /* Redirect inetd socket to stdin for service */ + if (SVC_CMD_INETD == svc->type) { + /* sd set previously */ + dup2(sd, STDIN_FILENO); + close(sd); + dup2(STDIN_FILENO, STDOUT_FILENO); + dup2(STDIN_FILENO, STDERR_FILENO); + } else if (debug) { int fd; char buf[CMD_SIZE] = ""; @@ -507,6 +594,9 @@ int svc_start(svc_t *svc) } svc->pid = pid; + if (SVC_CMD_INETD == svc->type && svc->sock_type == SOCK_STREAM) + close(sd); + if (SVC_CMD_RUN == svc->type) print_result(WEXITSTATUS(complete(svc->cmd, pid))); else if (!respawn) diff --git a/svc.h b/svc.h index c48e12e6..58f6b1f6 100644 --- a/svc.h +++ b/svc.h @@ -1,7 +1,7 @@ /* Finit service monitor and generic API for managing svc_t structures * * Copyright (c) 2008-2010 Claudio Matsuoka - * Copyright (c) 2008-2014 Joachim Nilsson + * Copyright (c) 2008-2015 Joachim Nilsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,6 +31,9 @@ #include /* shmat() */ #include /* pid_t */ +#include "queue.h" /* BSD sys/queue.h API */ +#include "libuev/uev.h" + typedef enum { SVC_STOP = 0, /* Disabled */ SVC_START, /* Enabled */ @@ -40,7 +43,8 @@ typedef enum { typedef enum { SVC_CMD_SERVICE = 0, /* Monitored, will be respawned */ SVC_CMD_TASK, /* One-shot, runs in parallell */ - SVC_CMD_RUN /* Like task, but wait for completion */ + SVC_CMD_RUN, /* Like task, but wait for completion */ + SVC_CMD_INETD /* Classic inetd service */ } svc_type_t; #define FINIT_SHM_ID 0x494E4954 /* "INIT", see ascii(7) */ @@ -59,11 +63,28 @@ typedef struct svc { svc_type_t type; int reload; int runlevels; - unsigned int restart_counter; /* Incremented for each restart by service monitor. */ + + /* Event loop handler, used for inetd services */ + uev_t watcher; + + /* For inetd services */ + int sock_type; + int port; + int proto; + int forking; + char service[10]; + + /* Incremented for each restart by service monitor. */ + unsigned int restart_counter; + + /* Identity */ + char username[MAX_USER_LEN]; + char group[MAX_USER_LEN]; + + /* Command, arguments and service description */ char cmd[MAX_ARG_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_ARG_LEN]; char desc[MAX_STR_LEN]; - char username[MAX_USER_LEN]; /* For external plugins. If @cb is set a plugin is loaded. * @dynamic: Set by plugins that want dynamic events.