diff --git a/finit.c b/finit.c index 1a13a3cf..ef05865c 100644 --- a/finit.c +++ b/finit.c @@ -50,6 +50,8 @@ char *hostname = NULL; char *rcsd = NULL; char *console = NULL; +uev_ctx_t *ctx = NULL; /* Main loop context */ + static void parse_kernel_cmdline(void) { FILE *fp; @@ -140,7 +142,7 @@ static void banner(void) int main(int argc, char* argv[]) { - uev_ctx_t ctx; + uev_ctx_t loop; if (getpid() != 1) return client(argc, argv); @@ -158,7 +160,8 @@ int main(int argc, char* argv[]) /* * Initalize event context. */ - uev_init(&ctx); + uev_init(&loop); + ctx = &loop; /* * Mount base file system, kernel is assumed to run devtmpfs for /dev @@ -188,7 +191,7 @@ int main(int argc, char* argv[]) * Load plugins first, finit.conf may contain references to * features implemented by plugins. */ - print_result(plugin_load_all(&ctx, PLUGIN_PATH)); + print_result(plugin_load_all(&loop, PLUGIN_PATH)); /* * Parse configuration file @@ -220,7 +223,7 @@ int main(int argc, char* argv[]) run_interactive("rm -rf /tmp/* /var/run/* /var/lock/*", "Cleanup temporary directories"); /* Base FS up, enable standard SysV init signals */ - sig_setup(&ctx); + sig_setup(&loop); _d("Base FS up, calling hooks ..."); plugin_run_hooks(HOOK_BASEFS_UP); @@ -250,9 +253,6 @@ 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); @@ -273,7 +273,7 @@ int main(int argc, char* argv[]) /* * Enter main loop to monior /dev/initctl and services */ - return uev_run(&ctx, 0); + return uev_run(&loop, 0); } /** diff --git a/inetd.c b/inetd.c index 49ef6818..8fbe35c3 100644 --- a/inetd.c +++ b/inetd.c @@ -21,6 +21,8 @@ * THE SOFTWARE. */ +#include "inetd.h" /* XXX: Move back down after upgrading to libuEv >1.0.4 */ + #include #include #include @@ -29,10 +31,10 @@ #include "libuev/uev.h" #include "libite/lite.h" -#include "inetd.h" #include "finit.h" -#include "svc.h" #include "helpers.h" +#include "private.h" +#include "svc.h" /* Socket callback, looks up correct svc and starts it as an inetd service */ @@ -52,38 +54,38 @@ static void socket_cb(uev_ctx_t *UNUSED(ctx), uev_t *w, void *arg, int UNUSED(ev /* Launch Inet socket for service. * TODO: Add filtering ALLOW/DENY per interface. */ -static void spawn_socket(uev_ctx_t *ctx, svc_t *svc) +static void spawn_socket(inetd_t *inetd) { int sd; socklen_t len = sizeof(struct sockaddr); struct sockaddr_in s; - if (!svc->inetd.type) { - FLOG_ERROR("Skipping invalid inetd service %s", svc->cmd); + if (!inetd->type) { + FLOG_ERROR("Skipping invalid inetd service %s", inetd->name); return; } - sd = socket(AF_INET, svc->inetd.type | SOCK_NONBLOCK, svc->inetd.proto); + sd = socket(AF_INET, inetd->type | SOCK_NONBLOCK, inetd->proto); if (-1 == sd) { - FLOG_PERROR("Failed opening inetd socket type %d proto %d", svc->inetd.type, svc->inetd.proto); + FLOG_PERROR("Failed opening inetd socket type %d proto %d", inetd->type, inetd->proto); return; } memset(&s, 0, sizeof(s)); s.sin_family = AF_INET; s.sin_addr.s_addr = INADDR_ANY; - s.sin_port = htons(svc->inetd.port); + 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", - svc->inetd.port, svc->inetd.name); + inetd->port, inetd->name); close(sd); return; } - if (svc->inetd.port) { - if (svc->inetd.type == SOCK_STREAM) { + if (inetd->port) { + if (inetd->type == SOCK_STREAM) { if (-1 == listen(sd, 20)) { - FLOG_PERROR("Failed listening to inetd service %s", svc->inetd.name); + FLOG_PERROR("Failed listening to inetd service %s", inetd->name); close(sd); return; } @@ -96,9 +98,7 @@ static void spawn_socket(uev_ctx_t *ctx, svc_t *svc) } } - _d("Initializing inetd %s service %s type %d proto %d on socket %d ...", - svc->inetd.name, basename(svc->cmd), svc->inetd.type, svc->inetd.proto, sd); - uev_io_init(ctx, &svc->inetd.watcher, socket_cb, svc, sd, UEV_READ); + 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. */ @@ -163,41 +163,41 @@ int inetd_stream_peek(int sd, char *ifname) /* Inetd monitor, called by svc_monitor() */ int inetd_respawn(pid_t pid) { - svc_t *svc; + svc_t *svc = svc_find_by_pid(pid); - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc && SVC_CMD_INETD == svc->type) { inetd_t *inetd = &svc->inetd; - if (SVC_CMD_INETD != svc->type) - continue; + svc->pid = 0; + if (!inetd->forking) + uev_io_set(&inetd->watcher, inetd->watcher.fd, UEV_READ); - if (svc->pid == pid) { - svc->pid = 0; - if (!svc->inetd.forking) - uev_io_set(&inetd->watcher, inetd->watcher.fd, UEV_READ); - - return 1; /* It was us! */ - } + return 1; /* It was us! */ } - return 0; /* Not an inetd service */ + return 0; /* Not an inetd service */ } -/* Called when changing runlevel to start Inet socket handlers */ -void inetd_runlevel(uev_ctx_t *ctx, int runlevel) + +void inetd_start(inetd_t *inetd) { - svc_t *svc; + if (inetd->watcher.fd == -1) + spawn_socket(inetd); +} - for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { - if (SVC_CMD_INETD != svc->type) - continue; +void inetd_stop(inetd_t *inetd) +{ + svc_t *svc = (svc_t *)inetd->arg; - if (!ISSET(svc->runlevels, runlevel)) - continue; - - - spawn_socket(ctx, svc); + if (inetd->watcher.fd != -1) { + uev_io_stop(&inetd->watcher); + close(inetd->watcher.fd); + inetd->watcher.fd = -1; } + + /* Stop any running service, not allowed anymore. */ + if (svc->pid) + svc_stop(svc); } static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv) @@ -222,7 +222,7 @@ static int getent(char *service, char *proto, struct servent **sv, struct protoe /* * Find exact match. */ -inetd_filter_t *inetd_filter_find(inetd_t *inetd, char *ifname) +static inetd_filter_t *find_filter(inetd_t *inetd, char *ifname) { inetd_filter_t *filter; @@ -245,7 +245,7 @@ inetd_filter_t *inetd_filter_find(inetd_t *inetd, char *ifname) */ inetd_filter_t *inetd_filter_match(inetd_t *inetd, char *ifname) { - inetd_filter_t *filter = inetd_filter_find(inetd, ifname); + inetd_filter_t *filter = find_filter(inetd, ifname); if (filter) return filter; @@ -264,7 +264,7 @@ inetd_filter_t *inetd_filter_match(inetd_t *inetd, char *ifname) return NULL; } -int inetd_allow_iface(inetd_t *inetd, char *ifname) +int inetd_allow(inetd_t *inetd, char *ifname) { inetd_filter_t *filter; @@ -274,7 +274,7 @@ int inetd_allow_iface(inetd_t *inetd, char *ifname) filter = inetd_filter_match(inetd, ifname); if (filter) { _d("Filter %s for inetd %s already exists, skipping.", ifname ?: "*", inetd->name); - return 1; + return 0; } _d("Allow iface %s for service %s (port %d)", ifname ?: "*", inetd->name, inetd->port); @@ -293,7 +293,7 @@ int inetd_allow_iface(inetd_t *inetd, char *ifname) return 0; } -int inetd_deny_iface(inetd_t *inetd, char *ifname) +int inetd_deny(inetd_t *inetd, char *ifname) { inetd_filter_t *filter; @@ -304,7 +304,7 @@ int inetd_deny_iface(inetd_t *inetd, char *ifname) if (!ifname[0]) ifname = NULL; - filter = inetd_filter_find(inetd, ifname); + filter = find_filter(inetd, ifname); if (filter) { _d("%s filter %s for inetd %s already exists, cannot set deny filter for same, skipping.", filter->deny ? "Deny" : "Allow", ifname ?: "*", inetd->name); @@ -327,7 +327,7 @@ int inetd_deny_iface(inetd_t *inetd, char *ifname) return 0; } -int inetd_is_iface_allowed(inetd_t *inetd, char *ifname) +int inetd_is_allowed(inetd_t *inetd, char *ifname) { inetd_filter_t *filter; @@ -373,47 +373,6 @@ int inetd_match(inetd_t *inetd, char *service, char *proto, char *port) return 0; } -static void deny_others(inetd_t *inetd) -{ - int first = 1; - svc_t *svc; - - while ((svc = svc_iterator(first))) { - inetd_filter_t *filter; - - first = 0; - - /* Skip non-inetd services */ - if (svc->type != SVC_CMD_INETD) - continue; - - /* Skip ourselves */ - if (&svc->inetd == inetd) - continue; - - /* Skip different service types (telnet != ssh) */ - if (strcmp(svc->inetd.name, inetd->name)) - continue; - - /* Deny all their interfaces from using my service */ - LIST_FOREACH(filter, &svc->inetd.filters, link) { - if (filter->deny) - continue; - - inetd_deny_iface(inetd, filter->ifname); - } - - /* Deny my interfaces from using their service ... */ - LIST_FOREACH(filter, &inetd->filters, link) { - if (filter->deny) - continue; - - inetd_deny_iface(&svc->inetd, filter->ifname); - } - } -} - - /* * This function is called to add a new, unique, inetd service. When an * ifname is given as argument this means *only* run service on this @@ -433,11 +392,11 @@ static void deny_others(inetd_t *inetd) * (any!) previous rule and add its ifname to their deny list. * * If equivalent service exists already svc_register() will instead call - * inetd_allow_iface(). + * inetd_allow(). */ -int inetd_add(inetd_t *inetd, char *service, char *proto, char *ifname, char *port, int forking) +int inetd_new(inetd_t *inetd, char *service, char *proto, int forking) { - int dport, cport = -1, result; + int result; struct servent *sv = NULL; struct protoent *pv = NULL; @@ -448,11 +407,11 @@ int inetd_add(inetd_t *inetd, char *service, char *proto, char *ifname, char *po if (result) return result; - dport = ntohs(sv->s_port); - _d("Adding service %s (default port %d proto %s:%d custom port %s)", - service, dport, sv->s_proto, pv->p_proto, port ?: "N/A"); - - /* Save inetd service name */ + /* Setup defaults */ + inetd->std = 1; + inetd->port = ntohs(sv->s_port); + inetd->proto = pv->p_proto; + inetd->forking = !!forking; strlcpy(inetd->name, service, sizeof(inetd->name)); /* Naïve mapping tcp->stream, udp->dgram, other->dgram */ @@ -461,27 +420,78 @@ int inetd_add(inetd_t *inetd, char *service, char *proto, char *ifname, char *po else inetd->type = SOCK_DGRAM; - /* Check custom port */ + /* Reset descriptor, used internally */ + inetd->watcher.fd = -1; + + _d("New service %s (default port %d proto %s:%d)", service, inetd->port, sv->s_proto, pv->p_proto); + + return 0; +} + +int inetd_del(inetd_t *inetd) +{ + inetd_filter_t *filter, *tmp; + + LIST_FOREACH_SAFE(filter, &inetd->filters, link, tmp) + free(filter); + + return 0; +} + +int inetd_init(inetd_t *inetd, void *arg, char *ifname, char *port) +{ + int result, cport = -1; + svc_t *svc; + + if (!inetd || !arg) + return errno = EINVAL; + if (port) cport = atonum(port); - /* Compare with default/standard port */ - if (cport == -1 || cport == dport) { - inetd->std = 1; - inetd->port = dport; - } else { + if (cport != -1 && cport != inetd->port) { inetd->std = 0; inetd->port = cport; } - inetd->forking = !!forking; - inetd->proto = pv->p_proto; + /* Setup socket callback argument */ + inetd->arg = arg; /* Poor man's tcpwrappers filtering */ - result = inetd_allow_iface(inetd, ifname); + result = inetd_allow(inetd, ifname); /* For each similar service, on other port, add their ifnames as deny to ours. */ - deny_others(inetd); + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + inetd_filter_t *filter; + + /* Skip non-inetd services */ + if (svc->type != SVC_CMD_INETD) + continue; + + /* Skip ourselves */ + if (&svc->inetd == inetd) + continue; + + /* Skip different service types (telnet != ssh) */ + if (strcmp(svc->inetd.name, inetd->name)) + continue; + + /* Deny all their interfaces from using my service */ + LIST_FOREACH(filter, &svc->inetd.filters, link) { + if (filter->deny) + continue; + + inetd_deny(inetd, filter->ifname); + } + + /* Deny my interfaces from using their service ... */ + LIST_FOREACH(filter, &inetd->filters, link) { + if (filter->deny) + continue; + + inetd_deny(&svc->inetd, filter->ifname); + } + } return result; } diff --git a/inetd.h b/inetd.h index 48c11b56..0431411e 100644 --- a/inetd.h +++ b/inetd.h @@ -39,6 +39,7 @@ typedef struct inetd_filter { typedef struct { uev_t watcher; + void *arg; /* svc_t pointer for the socket callback */ int type; /* Socket type: SOCK_STREAM/SOCK_DGRAM */ int std; /* Standard proto/port from /etc/services */ @@ -54,15 +55,20 @@ typedef struct { int inetd_dgram_peek (int sd, char *ifname); int inetd_stream_peek (int sd, char *ifname); -int inetd_respawn (pid_t pid); -void inetd_runlevel(uev_ctx_t *ctx, int runlevel); +void inetd_start (inetd_t *inetd); +void inetd_stop (inetd_t *inetd); -int inetd_match (inetd_t *inetd, char *service, char *proto, char *port); -int inetd_add (inetd_t *inetd, char *service, char *proto, char *ifname, char *port, int forking); +int inetd_respawn (pid_t pid); -inetd_filter_t *inetd_filter_find (inetd_t *inetd, char *ifname); -int inetd_allow_iface (inetd_t *inetd, char *ifname); -int inetd_is_iface_allowed (inetd_t *inetd, char *ifname); +int inetd_new (inetd_t *inetd, char *service, char *proto, int forking); +int inetd_del (inetd_t *inetd); + +int inetd_init (inetd_t *inetd, void *arg, char *ifname, char *port); +int inetd_match (inetd_t *inetd, char *service, char *proto, char *port); + +int inetd_allow (inetd_t *inetd, char *ifname); +int inetd_deny (inetd_t *inetd, char *ifname); +int inetd_is_allowed (inetd_t *inetd, char *ifname); #endif /* FINIT_INETD_H_ */ diff --git a/private.h b/private.h index f3150012..57e8b5bc 100644 --- a/private.h +++ b/private.h @@ -28,6 +28,8 @@ #include "svc.h" #include "plugin.h" +uev_ctx_t *ctx; /* Main loop context */ + void svc_bootstrap (void); void svc_monitor (pid_t lost); diff --git a/svc.c b/svc.c index 0873ef09..8a9e7a4e 100644 --- a/svc.c +++ b/svc.c @@ -69,7 +69,7 @@ svc_t *svc_new(void) svc_t *svc = NULL; __connect_shm(); - if (svc_counter < MAX_NUM_SVC) { + if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { svc = &services[svc_counter++]; memset(svc, 0, sizeof(*svc)); } else { @@ -81,19 +81,39 @@ svc_t *svc_new(void) } /** - * svc_iterator - Iterates over all registered services. - * @restart: Get first &svc_t object by setting @restart + * svc_del - Delete a service object + * @svc: Pointer to an &svc_t object + * + * Note, this function is not safe for public use. Should only be used + * by this module until the storage of svc_t's has been refactored! * * Returns: - * The first &svc_t when @restart is set, otherwise the next &svc_t until + * Always succeeds, returning POSIX OK(0). + */ +static int svc_del(svc_t *svc) +{ + if (svc_counter >= 0 && svc_counter < MAX_NUM_SVC) { + svc = &services[--svc_counter]; + memset(svc, 0, sizeof(*svc)); + } + + return 0; +} + +/** + * svc_iterator - Iterates over all registered services. + * @first: Get first &svc_t object, or next until end. + * + * Returns: + * The first &svc_t when @first is set, otherwise the next &svc_t until * the end when %NULL is returned. */ -svc_t *svc_iterator(int restart) +svc_t *svc_iterator(int first) { static int i = 0; __connect_shm(); - if (restart) + if (first) i = 0; if (i >= svc_counter) @@ -111,12 +131,9 @@ svc_t *svc_iterator(int restart) */ svc_t *svc_find(char *path) { - int first = 1; svc_t *svc; - while ((svc = svc_iterator(first))) { - first = 0; - + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { if (!strncmp(path, svc->cmd, strlen(svc->cmd))) { _d("Found a matching svc for %s", path); return svc; @@ -126,14 +143,30 @@ svc_t *svc_find(char *path) return NULL; } -svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) +/** + * svc_find_by_pid - Find an service object by its PID + * @pid: Process ID to match + * + * Returns: + * A pointer to an &svc_t object, or %NULL if not found. + */ +svc_t *svc_find_by_pid(pid_t pid) { - int first = 1; svc_t *svc; - while ((svc = svc_iterator(first))) { - first = 0; + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + if (svc->pid == pid) + return svc; + } + return NULL; +} + +svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) +{ + svc_t *svc; + + for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { if (svc->type != SVC_CMD_INETD) continue; @@ -163,6 +196,7 @@ void svc_bootstrap(void) for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { svc_cmd_t cmd; + /* Inetd services cannot be part of bootstrap currently. */ if (svc->type == SVC_CMD_INETD) continue; @@ -217,9 +251,17 @@ void svc_runlevel(int newlevel) for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { svc_cmd_t cmd; - if (svc->type == SVC_CMD_INETD) - continue; + /* Inetd services have slightly different semantics */ + if (svc->type == SVC_CMD_INETD) { + if (!ISSET(svc->runlevels, runlevel)) + inetd_stop(&svc->inetd); + else + inetd_start(&svc->inetd); + continue; + } + + /* All other services consult their callback here */ cmd = svc_enabled(svc, 0, NULL); if (svc->pid) { if (cmd == SVC_STOP) @@ -352,8 +394,8 @@ int svc_register(int type, char *line, char *username) /* Check if known inetd, then add ifname for filtering only. */ svc = svc_find_inetd(cmd, service, proto, port); - if (svc && type == SVC_CMD_INETD) - return inetd_allow_iface(&svc->inetd, iface); + if (svc) + return inetd_allow(&svc->inetd, iface); svc = svc_new(); if (!svc) { @@ -376,8 +418,18 @@ int svc_register(int type, char *line, char *username) strlcpy(svc->username, username, sizeof(svc->username)); } - if (svc->type == SVC_CMD_INETD) - inetd_add(&svc->inetd, service, proto, iface, port, forking); + if (svc->type == SVC_CMD_INETD) { + int result; + + result = inetd_new(&svc->inetd, service, proto, forking); + result += inetd_init(&svc->inetd, svc, iface, port); + + if (result) { + _e("Failed registering new inetd service %s.", service); + inetd_del(&svc->inetd); + return svc_del(svc); + } + } if (plugin) { /* Internal plugin provides this service */ @@ -599,7 +651,7 @@ int svc_start(svc_t *svc) inetd_dgram_peek(sd, ifname); } - if (!inetd_is_iface_allowed(&svc->inetd, 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) @@ -767,7 +819,6 @@ int svc_reload(svc_t *svc) /** * Local Variables: - * compile-command: "gcc -g -DUNITTEST -o unittest svc.c" * version-control: t * indent-tabs-mode: t * c-file-style: "linux" diff --git a/svc.h b/svc.h index 17ea5d8c..c909fe86 100644 --- a/svc.h +++ b/svc.h @@ -110,8 +110,9 @@ static inline svc_t *finit_svc_connect(void) svc_t *svc_new (void); svc_t *svc_find (char *path); +svc_t *svc_find_by_pid (pid_t pid); svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port); -svc_t *svc_iterator (int restart); +svc_t *svc_iterator (int first); void svc_runlevel (int newlevel); int svc_register (int type, char *line, char *username);