Refactor inetd support, now with support for custom ports and iface filtering

This patch adds support for custom port and interface filtering to inetd
services, in a very condensed syntax:

    inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i
    inetd ssh@eth1:22/tcp  nowait [2345] /usr/sbin/sshd -i

In this example eth0 and eth1 are allowed inbound interfaces for SSH
connections, on port 222 and 22, respectively.  Attempting to connect
from any other interface is denied.  Also, if the system is not in
runlevel 2, 3, 4, or 5, ports 222 and 22 will not even be opened.

If eth0 is your upstream interface you may want to avoid using the
default port.  To run ssh on port 222, and all others on port 22:

    inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i
    inetd ssh/tcp          nowait [2345] /usr/sbin/sshd -i

This actually adds a deny rule for eth0 on ssh/tcp, implicitly.  You can
even list the services in the reverse order with the same result:

    inetd ssh/tcp          nowait [2345] /usr/sbin/sshd -i
    inetd ssh@eth0:222/tcp nowait [2345] /usr/sbin/sshd -i

There is no specific deny syntax available yet, see the TODO file for
more details on how this can be implemented.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson committed 2015-02-26 01:37:11 +01:00
1 parent 365932510f
commit ded4b3926e
4 files changed
+430 -91

No files matched your search

+306 -15
View File
@@ -26,20 +26,24 @@
#include <netinet/in.h>
#include <sys/socket.h>
#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:
+32
View File
@@ -24,14 +24,46 @@
#ifndef FINIT_INETD_H_
#define FINIT_INETD_H_
#include <netdb.h>
#include <net/if.h>
#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_ */
/**
+86 -64
View File
@@ -25,7 +25,6 @@
#include <string.h>
#include <sys/wait.h>
#include <utmp.h>
#include <netdb.h>
#include <net/if.h>
#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)
+6 -12
View File
@@ -31,6 +31,7 @@
#include <sys/shm.h> /* shmat() */
#include <sys/types.h> /* 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);