diff --git a/inetd.c b/inetd.c index e3fb8211..49ef6818 100644 --- a/inetd.c +++ b/inetd.c @@ -26,20 +26,24 @@ #include #include +#include "libuev/uev.h" +#include "libite/lite.h" + +#include "inetd.h" #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)) + if (SVC_START != svc_enabled(svc, -1, NULL)) return; - if (!svc->forking) + if (!svc->inetd.forking) uev_io_stop(w); svc_start(svc); @@ -54,32 +58,32 @@ static void spawn_socket(uev_ctx_t *ctx, svc_t *svc) socklen_t len = sizeof(struct sockaddr); struct sockaddr_in s; - if (!svc->sock_type) { + if (!svc->inetd.type) { FLOG_ERROR("Skipping invalid inetd service %s", svc->cmd); return; } - sd = socket(AF_INET, svc->sock_type | SOCK_NONBLOCK, svc->proto); + sd = socket(AF_INET, svc->inetd.type | SOCK_NONBLOCK, svc->inetd.proto); if (-1 == sd) { - FLOG_PERROR("Failed opening inetd socket type %d proto %d", svc->sock_type, svc->proto); + FLOG_PERROR("Failed opening inetd socket type %d proto %d", svc->inetd.type, svc->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->port); + s.sin_port = htons(svc->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->port, svc->service); + svc->inetd.port, svc->inetd.name); close(sd); return; } - if (svc->port) { - if (svc->sock_type == SOCK_STREAM) { + if (svc->inetd.port) { + if (svc->inetd.type == SOCK_STREAM) { if (-1 == listen(sd, 20)) { - FLOG_PERROR("Failed listening to inetd service %s", svc->service); + FLOG_PERROR("Failed listening to inetd service %s", svc->inetd.name); close(sd); return; } @@ -93,8 +97,8 @@ 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->service, basename(svc->cmd), svc->sock_type, svc->proto, sd); - uev_io_init(ctx, &svc->watcher, socket_cb, svc, sd, UEV_READ); + 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); } /* Peek into SOCK_DGRAM socket to figure out where an inbound packet comes from. */ @@ -162,13 +166,15 @@ int inetd_respawn(pid_t pid) svc_t *svc; for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) { + inetd_t *inetd = &svc->inetd; + 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); + if (!svc->inetd.forking) + uev_io_set(&inetd->watcher, inetd->watcher.fd, UEV_READ); return 1; /* It was us! */ } @@ -194,6 +200,291 @@ void inetd_runlevel(uev_ctx_t *ctx, int runlevel) } } +static int getent(char *service, char *proto, struct servent **sv, struct protoent **pv) +{ + *sv = getservbyname(service, proto); + if (!*sv) { + _pe("Invalid inetd %s/%s (service/proto), skipping", service, proto); + return errno = EINVAL; + } + + if (pv) { + *pv = getprotobyname((*sv)->s_proto); + if (!*pv) { + _pe("Cannot find proto %s, skipping.", (*sv)->s_proto); + return errno = EINVAL; + } + } + + return 0; +} + +/* + * Find exact match. + */ +inetd_filter_t *inetd_filter_find(inetd_t *inetd, char *ifname) +{ + inetd_filter_t *filter; + + if (!ifname) + ifname = ""; + + LIST_FOREACH(filter, &inetd->filters, link) { + _d("Checking filters for %s: '%s' vs '%s' (exact match) ...", + inetd->name, filter->ifname, ifname); + + if (!strcmp(filter->ifname, ifname)) + return filter; + } + + return NULL; +} + +/* + * First try exact match, then fall back to any match. + */ +inetd_filter_t *inetd_filter_match(inetd_t *inetd, char *ifname) +{ + inetd_filter_t *filter = inetd_filter_find(inetd, ifname); + + if (filter) + return filter; + + if (!ifname) + ifname = ""; + + LIST_FOREACH(filter, &inetd->filters, link) { + _d("Checking filters for %s: '%s' vs '%s' (any match) ...", + inetd->name, filter->ifname, ifname); + + if (!strlen(filter->ifname)) + return filter; + } + + return NULL; +} + +int inetd_allow_iface(inetd_t *inetd, char *ifname) +{ + inetd_filter_t *filter; + + if (!inetd) + return errno = EINVAL; + + filter = inetd_filter_match(inetd, ifname); + if (filter) { + _d("Filter %s for inetd %s already exists, skipping.", ifname ?: "*", inetd->name); + return 1; + } + + _d("Allow iface %s for service %s (port %d)", ifname ?: "*", inetd->name, inetd->port); + filter = calloc(1, sizeof(*filter)); + if (!filter) { + _e("Out of memory, cannot add filter to service %s", inetd->name); + return errno = ENOMEM; + } + + if (!ifname) + ifname = ""; + filter->deny = 0; + strlcpy(filter->ifname, ifname, sizeof(filter->ifname)); + LIST_INSERT_HEAD(&inetd->filters, filter, link); + + return 0; +} + +int inetd_deny_iface(inetd_t *inetd, char *ifname) +{ + inetd_filter_t *filter; + + if (!inetd) + return errno = EINVAL; + + /* Reset to NULL for debug output below */ + if (!ifname[0]) + ifname = NULL; + + filter = inetd_filter_find(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); + return 1; + } + + _d("Deny iface %s for service %s (port %d)", ifname ?: "*", inetd->name, inetd->port); + filter = calloc(1, sizeof(*filter)); + if (!filter) { + _e("Out of memory, cannot add filter to service %s", inetd->name); + return errno = ENOMEM; + } + + if (!ifname) + ifname = ""; + filter->deny = 1; + strlcpy(filter->ifname, ifname, sizeof(filter->ifname)); + LIST_INSERT_HEAD(&inetd->filters, filter, link); + + return 0; +} + +int inetd_is_iface_allowed(inetd_t *inetd, char *ifname) +{ + inetd_filter_t *filter; + + if (!inetd) { + errno = EINVAL; + return 0; + } + + filter = inetd_filter_match(inetd, ifname); + if (filter) { + _d("Found matching filter for %s, deny: %d ... ", inetd->name, filter->deny); + return !filter->deny; + } + + _d("No matching filter for %s ... ", inetd->name); + + return 0; +} + +int inetd_match(inetd_t *inetd, char *service, char *proto, char *port) +{ + int cport = port ? atonum(port) : -1; + + if (!inetd || !service || !proto) + return errno = EINVAL; + + if (strncmp(inetd->name, service, sizeof(inetd->name))) + return 0; + + if (cport != -1) { + if (inetd->port == cport) + return 1; + } else { + struct servent *sv = NULL; + + if (getent(service, proto, &sv, NULL)) + return 0; + + if (inetd->port == ntohs(sv->s_port)) + return 1; + } + + 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 + * interface. If a similar service runs on another port, this function + * must add this ifname as "deny" to that other service. + * + * Example: + * inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i + * inetd ssh/tcp nowait [2345] /usr/sbin/sshd -i + * + * In this example eth0:222 is very specific, so when ssh/tcp (default) + * is added we must find the previous 'ssh' service and add its eth0 as + * deny, so we don't accept port 22 session on eth0. + * + * In the reverse case, where the default (ssh/tcp) entry is listed + * before the specific (eth0:222), the new inetd service must find the + * (any!) previous rule and add its ifname to their deny list. + * + * If equivalent service exists already svc_register() will instead call + * inetd_allow_iface(). + */ +int inetd_add(inetd_t *inetd, char *service, char *proto, char *ifname, char *port, int forking) +{ + int dport, cport = -1, result; + struct servent *sv = NULL; + struct protoent *pv = NULL; + + if (!inetd || !service || !proto) + return errno = EINVAL; + + result = getent(service, proto, &sv, &pv); + 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 */ + strlcpy(inetd->name, service, sizeof(inetd->name)); + + /* Naïve mapping tcp->stream, udp->dgram, other->dgram */ + if (!strcasecmp(sv->s_proto, "tcp")) + inetd->type = SOCK_STREAM; + else + inetd->type = SOCK_DGRAM; + + /* Check custom port */ + if (port) + cport = atonum(port); + + /* Compare with default/standard port */ + if (cport == -1 || cport == dport) { + inetd->std = 1; + inetd->port = dport; + } else { + inetd->std = 0; + inetd->port = cport; + } + + inetd->forking = !!forking; + inetd->proto = pv->p_proto; + + /* Poor man's tcpwrappers filtering */ + result = inetd_allow_iface(inetd, ifname); + + /* For each similar service, on other port, add their ifnames as deny to ours. */ + deny_others(inetd); + + return result; +} /** * Local Variables: diff --git a/inetd.h b/inetd.h index 8559b1dd..48c11b56 100644 --- a/inetd.h +++ b/inetd.h @@ -24,14 +24,46 @@ #ifndef FINIT_INETD_H_ #define FINIT_INETD_H_ +#include +#include + +#include "queue.h" #include "libuev/uev.h" + +typedef struct inetd_filter { + LIST_ENTRY(inetd_filter) link; + int deny; /* 0:allow, 1:deny */ + char ifname[IFNAMSIZ]; /* E.g., eth0 */ +} inetd_filter_t; + +typedef struct { + uev_t watcher; + + int type; /* Socket type: SOCK_STREAM/SOCK_DGRAM */ + int std; /* Standard proto/port from /etc/services */ + int proto; + int port; + int forking; + char name[10]; + int (*cmd)(int type); /* internal inetd service, like 'time' */ + + LIST_HEAD(, inetd_filter) filters; +} inetd_t; + 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); +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); + +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); + #endif /* FINIT_INETD_H_ */ /** diff --git a/svc.c b/svc.c index 69e97979..ad274eea 100644 --- a/svc.c +++ b/svc.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include "finit.h" @@ -81,30 +80,6 @@ svc_t *svc_new(void) return svc; } -/** - * svc_find - Find a service object by its full path name - * @name: Full path name, e.g., /sbin/syslogd - * - * Returns: - * A pointer to an &svc_t object, or %NULL if not found. - */ -svc_t *svc_find(char *name) -{ - int i; - - __connect_shm(); - for (i = 0; i < svc_counter; i++) { - svc_t *svc = &services[i]; - - if (!strncmp(name, svc->cmd, strlen(svc->cmd))) { - _d("Found a matching svc for %s", name); - return svc; - } - } - - return NULL; -} - /** * svc_iterator - Iterates over all registered services. * @restart: Get first &svc_t object by setting @restart @@ -127,6 +102,53 @@ svc_t *svc_iterator(int restart) return &services[i++]; } +/** + * svc_find - Find a service object by its full path name + * @path: Full path name, e.g., /sbin/syslogd + * + * Returns: + * A pointer to an &svc_t object, or %NULL if not found. + */ +svc_t *svc_find(char *path) +{ + int first = 1; + svc_t *svc; + + while ((svc = svc_iterator(first))) { + first = 0; + + if (!strncmp(path, svc->cmd, strlen(svc->cmd))) { + _d("Found a matching svc for %s", path); + return svc; + } + } + + return NULL; +} + +svc_t *svc_find_inetd(char *path, char *service, char *proto, char *port) +{ + int first = 1; + svc_t *svc; + + while ((svc = svc_iterator(first))) { + first = 0; + + if (svc->type != SVC_CMD_INETD) + continue; + + if (strncmp(path, svc->cmd, strlen(svc->cmd))) + continue; + + if (inetd_match(&svc->inetd, service, proto, port)) { + _d("Found a matching svc for %s", path); + return svc; + } + } + + return NULL; +} + /** * svc_bootstrap - Start bootstrap services and tasks * @@ -262,12 +284,10 @@ void svc_runlevel(int newlevel) int svc_register(int type, char *line, char *username) { int i = 0, forking = 0; - char *service = NULL, *proto = NULL; + char *service = NULL, *proto = NULL, *iface = NULL, *port = NULL; char *cmd, *desc, *runlevels = NULL; svc_t *svc; plugin_t *plugin = NULL; - struct servent *sv = NULL; - struct protoent *pv = NULL; if (!line) { _e("Invalid input argument."); @@ -287,7 +307,7 @@ int svc_register(int type, char *line, char *username) while (cmd) { if (cmd[0] != '/' && strchr(cmd, '/')) - service = cmd; /* inetd port/proto */ + service = cmd; /* inetd service/proto */ else if (!strncasecmp(cmd, "nowait", 6)) forking = 1; else if (!strncasecmp(cmd, "wait", 4)) @@ -305,22 +325,18 @@ int svc_register(int type, char *line, char *username) goto incomplete; } + /* Example: inetd ssh@eth0:222/tcp */ 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; - } + port = strchr(service, ':'); + if (port) + *port++ = 0; - _d("Got service %s:%d proto %s:%d", service, ntohs(sv->s_port), sv->s_proto, pv->p_proto); + iface = strchr(service, '@'); + if (iface) + *iface++ = 0; } /* Find plugin that provides a callback for this inetd service */ @@ -332,6 +348,11 @@ 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); + svc = svc_new(); if (!svc) { _e("Out of memory, cannot register service %s", cmd); @@ -353,23 +374,12 @@ int svc_register(int type, char *line, char *username) 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; - } + if (svc->type == SVC_CMD_INETD) + inetd_add(&svc->inetd, service, proto, iface, port, forking); if (plugin) { /* Internal plugin provides this service */ - svc->internal_cmd = plugin->inetd.cmd; + svc->inetd.cmd = plugin->inetd.cmd; } else { /* External program to call */ strlcpy(svc->cmd, cmd, sizeof(svc->cmd)); @@ -550,7 +560,7 @@ int svc_start(svc_t *svc) respawn = svc->pid != 0; /* Don't try and start service if it doesn't exist. */ - if (!fexist(svc->cmd) && !svc->internal_cmd) { + if (!fexist(svc->cmd) && !svc->inetd.cmd) { char msg[80]; snprintf(msg, sizeof(msg), "Service %s does not exist!", svc->cmd); @@ -565,17 +575,17 @@ int svc_start(svc_t *svc) return 0; if (SVC_CMD_INETD == svc->type) { - sd = svc->watcher.fd; + sd = svc->inetd.watcher.fd; - if (svc->sock_type == SOCK_STREAM) { + 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->port); + FLOG_PERROR("Failed accepting inetd service %d/tcp", svc->inetd.port); return 0; } - _d("New client socket %d accepted for inetd service %d/tcp", sd, svc->port); + _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); @@ -584,9 +594,16 @@ int svc_start(svc_t *svc) inetd_dgram_peek(sd, ifname); } - /* XXX: Add poor man's tcpwrappers here, we now know inbound ifname ... */ + if (!inetd_is_iface_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); - FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->service, ifname); + return 0; + } + + FLOG_INFO("Starting inetd service %s for requst from iface %s ...", svc->inetd.name, ifname); } else if (SVC_CMD_SERVICE != svc->type) print_desc("", svc->desc); @@ -652,17 +669,22 @@ int svc_start(svc_t *svc) _e("%starting %s: %s", respawn ? "Res" : "S", svc->cmd, buf); } - if (svc->internal_cmd) - status = svc->internal_cmd(svc->sock_type); + if (svc->inetd.cmd) + status = svc->inetd.cmd(svc->inetd.type); else status = execv(svc->cmd, args); /* XXX: Maybe use execve() to be able to launch scripts? */ + if (SVC_CMD_INETD == svc->type) { + if (svc->inetd.type == SOCK_STREAM) + close(sd); + } + exit(status); } svc->pid = pid; if (SVC_CMD_INETD == svc->type) { - if (svc->sock_type == SOCK_STREAM) + if (svc->inetd.type == SOCK_STREAM) close(sd); } else if (SVC_CMD_RUN == svc->type) diff --git a/svc.h b/svc.h index 83afceb8..17ea5d8c 100644 --- a/svc.h +++ b/svc.h @@ -31,6 +31,7 @@ #include /* shmat() */ #include /* pid_t */ +#include "inetd.h" #include "queue.h" /* BSD sys/queue.h API */ #include "libuev/uev.h" @@ -64,20 +65,12 @@ typedef struct svc { int reload; int runlevels; - /* 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]; - int (*internal_cmd)(int sock_type); - /* Incremented for each restart by service monitor. */ unsigned int restart_counter; + /* For inetd services */ + inetd_t inetd; + /* Identity */ char username[MAX_USER_LEN]; char group[MAX_USER_LEN]; @@ -116,7 +109,8 @@ static inline svc_t *finit_svc_connect(void) } svc_t *svc_new (void); -svc_t *svc_find (char *name); +svc_t *svc_find (char *path); +svc_t *svc_find_inetd (char *path, char *service, char *proto, char *port); svc_t *svc_iterator (int restart); void svc_runlevel (int newlevel);