ListenAdress Exclusion

Damien Miller djm at mindrot.org
Tue Jun 24 16:28:16 EST 2014


On Mon, 23 Jun 2014, Larry Becke wrote:

> I was wondering what everyone's thoughts were on a simpler way to exclude
> addresses from having listeners on them.
> 
> I know a lot of people have multiple subnets, especially larger
> corporations.
> 
> Some networks are non-route-able, and therefor unsuitable for use with SSH,
> aside from communication between other servers on the same subnet.

I made this patch earlier this year. It adds a ListenFilter sshd_config
option to select addresses to listen on by network/mask.

It required getifaddrs(), which is available on BSDish/Linux systems but
probably not legacy Unix so porting it might be problematic.

It also won't cope well with machines with dynamic interfaces: the
addresses to listen on will be selected when sshd starts only.

Usage:

sshd_config =>

ListenAddress *
ListenFilter 127.0.0.0/8 10.0.0.0/8

Will listen only on addresses matching 127.* and 10.*

If there is enough interest then it might be worth polishing up and
committing.

-d

Index: servconf.c
===================================================================
RCS file: /var/cvs/openssh/servconf.c,v
retrieving revision 1.247
diff -u -p -r1.247 servconf.c
--- servconf.c	4 Feb 2014 00:12:57 -0000	1.247
+++ servconf.c	24 Mar 2014 07:29:56 -0000
@@ -153,6 +153,7 @@ initialize_server_options(ServerOptions 
 	options->ip_qos_interactive = -1;
 	options->ip_qos_bulk = -1;
 	options->version_addendum = NULL;
+	options->num_listen_filters = 0;
 }
 
 void
@@ -348,6 +349,7 @@ typedef enum {
 	sKexAlgorithms, sIPQoS, sVersionAddendum,
 	sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
 	sAuthenticationMethods, sHostKeyAgent,
+	sListenFilter,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
 
@@ -474,6 +476,7 @@ static struct {
 	{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
 	{ "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
 	{ "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
+	{ "listenfilter", sListenFilter, SSHCFG_GLOBAL },
 	{ NULL, sBadOption, 0 }
 };
 
@@ -1620,6 +1623,25 @@ process_server_config_line(ServerOptions
 		}
 		return 0;
 
+	case sListenFilter:
+		if (*activep && options->num_listen_filters == 0) {
+			while ((arg = strdelim(&cp)) && *arg != '\0') {
+				if (options->num_listen_filters >=
+				    MAX_LISTEN_FILTERS)
+					fatal("%s line %d: "
+					    "too many listen address filters.",
+					    filename, linenum);
+				if (addr_match_list(NULL, arg) != 0)
+					fatal("%s line %d: "
+					    "invalid ListenFilter address %s",
+					    filename, linenum, arg);
+				options->listen_filter[
+				    options->num_listen_filters++] =
+				    xstrdup(arg);
+			}
+		}
+		return 0;
+
 	case sDeprecated:
 		logit("%s line %d: Deprecated option %s",
 		    filename, linenum, arg);
@@ -2056,6 +2078,8 @@ dump_config(ServerOptions *o)
 	dump_cfg_strarray(sAcceptEnv, o->num_accept_env, o->accept_env);
 	dump_cfg_strarray_oneline(sAuthenticationMethods,
 	    o->num_auth_methods, o->auth_methods);
+	dump_cfg_strarray_oneline(sListenFilter, o->num_listen_filters,
+	    o->listen_filter);
 
 	/* other arguments */
 	for (i = 0; i < o->num_subsystems; i++)
Index: servconf.h
===================================================================
RCS file: /var/cvs/openssh/servconf.h,v
retrieving revision 1.104
diff -u -p -r1.104 servconf.h
--- servconf.h	4 Feb 2014 00:12:57 -0000	1.104
+++ servconf.h	24 Mar 2014 07:29:56 -0000
@@ -29,6 +29,7 @@
 #define MAX_MATCH_GROUPS	256	/* Max # of groups for Match. */
 #define MAX_AUTHKEYS_FILES	256	/* Max # of authorized_keys files. */
 #define MAX_AUTH_METHODS	256	/* Max # of AuthenticationMethods. */
+#define MAX_LISTEN_FILTERS	4	/* Max # of ListenFilters. */
 
 /* permit_root_login */
 #define	PERMIT_NOT_SET		-1
@@ -183,6 +184,9 @@ typedef struct {
 
 	u_int	num_auth_methods;
 	char   *auth_methods[MAX_AUTH_METHODS];
+
+	u_int	num_listen_filters;
+	char   *listen_filter[MAX_LISTEN_FILTERS];
 }       ServerOptions;
 
 /* Information about the incoming connection as used by Match */
Index: sshd.c
===================================================================
RCS file: /var/cvs/openssh/sshd.c,v
retrieving revision 1.448
diff -u -p -r1.448 sshd.c
--- sshd.c	26 Feb 2014 23:20:08 -0000	1.448
+++ sshd.c	24 Mar 2014 07:29:56 -0000
@@ -71,6 +71,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <ifaddrs.h>
 
 #include <openssl/dh.h>
 #include <openssl/bn.h>
@@ -121,6 +122,7 @@
 #include "roaming.h"
 #include "ssh-sandbox.h"
 #include "version.h"
+#include "match.h"
 
 #ifdef LIBWRAP
 #include <tcpd.h>
@@ -1073,6 +1075,114 @@ server_accept_inetd(int *sock_in, int *s
 	debug("inetd sockets after dupping: %d, %d", *sock_in, *sock_out);
 }
 
+static const char *
+render_addr(struct sockaddr *addr)
+{
+	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
+	static char ret[NI_MAXHOST+NI_MAXSERV+2+1+1];
+	int r;
+
+	if (addr->sa_family != AF_INET && addr->sa_family != AF_INET6) {
+		snprintf(ret, sizeof(ret), "<UNSUPPORTED FAMILY %d>",
+		    addr->sa_family);
+		return ret;
+	}
+
+	if ((r = getnameinfo(addr,
+	    addr->sa_family == AF_INET ?
+	    sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6),
+	    ntop, sizeof(ntop), strport, sizeof(strport),
+	    NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
+		snprintf(ret, sizeof(ret), "<GETNAMEINFO ERROR %.40s>",
+		    ssh_gai_strerror(r));
+		return ret;
+	}
+	snprintf(ret, sizeof(ret), "[%s]:%s", ntop, strport);
+	return ret;
+}
+
+#define E_S4(a) ((struct sockaddr_in *)((a)->ai_addr))
+#define E_S6(a) ((struct sockaddr_in6 *)((a)->ai_addr))
+
+/* Expand wildcard addresses in addrlist to actual interface addresses */
+static struct addrinfo *
+expand_local_addrs(struct addrinfo *addrlist)
+{
+	struct ifaddrs *ifaddrs, *ifa;
+	struct addrinfo *ai, *ret = NULL, **rl = &ret;
+	int i, v6, have_wild4 = 0, have_wild6 = 0;
+
+	if (addrlist == NULL)
+		return NULL;
+
+	if (getifaddrs(&ifaddrs) != 0)
+		fatal("%s: getifaddrs: %s", __func__, strerror(errno));
+
+	for (i = 0, ai = addrlist; ai; ai = ai->ai_next, i++) {
+		if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
+			debug3("%s: addr %d AF %d", __func__, i, ai->ai_family);
+			continue;
+		}
+		v6 = ai->ai_family == AF_INET6;
+
+		debug3("%s: addr %d %s: %s", __func__,
+		    i, v6 ? "IPv6" : "IPv4", render_addr(ai->ai_addr));
+
+		/* If the address is not all-zero then copy as-is */
+		if ((!v6 && E_S4(ai)->sin_addr.s_addr != 0) ||
+		    (v6 && IN6_IS_ADDR_UNSPECIFIED(E_S6(ai)))) {
+			*rl = xcalloc(1, sizeof(*ai));
+			**rl = *ai;
+			(*rl)->ai_addr = xcalloc(1, ai->ai_addrlen);
+			memcpy((*rl)->ai_addr, ai->ai_addr, ai->ai_addrlen);
+			(*rl)->ai_canonname = NULL;
+			(*rl)->ai_next = NULL;
+			rl = &(*rl)->ai_next;
+			continue;
+		}
+		/* If we've already seen a wildcard of this family then skip */
+		if (v6) {
+			if (have_wild6)
+				continue;
+			have_wild6 = 1;
+		} else {
+			if (have_wild4)
+				continue;
+			have_wild4 = 1;
+		}
+		debug3("%s: expanding address", __func__);
+		/* Append all interface addresses with matching family here */
+		for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
+			if (ifa->ifa_addr == NULL ||
+			    ifa->ifa_addr->sa_family != ai->ai_family)
+				continue;
+			/* Append */
+			*rl = xcalloc(1, sizeof(*ai));
+			**rl = *ai;
+			(*rl)->ai_addr = xcalloc(1, ai->ai_addrlen);
+			memcpy((*rl)->ai_addr, ifa->ifa_addr,
+			    (*rl)->ai_addrlen);
+			if (v6)
+				E_S6(*rl)->sin6_port = E_S6(ai)->sin6_port;
+			else
+				E_S4(*rl)->sin_port = E_S4(ai)->sin_port;
+			(*rl)->ai_canonname = NULL;
+			(*rl)->ai_next = NULL;
+			debug3("%s: copied address %s from interface %s",
+			    __func__, render_addr((*rl)->ai_addr),
+			    ifa->ifa_name);
+			rl = &(*rl)->ai_next;
+		}
+	}
+	debug3("%s: result:", __func__);
+	for (i = 0, ai = ret; ai; ai = ai->ai_next, i++) {
+		debug3("%s: addr %d: %s",
+		    __func__, i, render_addr(ai->ai_addr));
+	}
+	freeaddrinfo(addrlist);
+	return ret;
+}
+
 /*
  * Listen for TCP connections
  */
@@ -1082,6 +1192,10 @@ server_listen(void)
 	int ret, listen_sock, on = 1;
 	struct addrinfo *ai;
 	char ntop[NI_MAXHOST], strport[NI_MAXSERV];
+	u_int matched, i;
+
+	if (options.num_listen_filters > 0)
+		options.listen_addrs = expand_local_addrs(options.listen_addrs);
 
 	for (ai = options.listen_addrs; ai; ai = ai->ai_next) {
 		if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
@@ -1094,6 +1208,18 @@ server_listen(void)
 		    NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
 			error("getnameinfo failed: %.100s",
 			    ssh_gai_strerror(ret));
+			continue;
+		}
+		for (i = matched = 0; i < options.num_listen_filters; i++) {
+			if (addr_match_cidr_list(ntop,
+			    options.listen_filter[i]) == 1) {
+				matched = 1;
+				break;
+			}
+		}
+		if (options.num_listen_filters > 0 && !matched) {
+			debug("%s: listen address [%s]:%s did not match filter",
+			    __func__, ntop, strport);
 			continue;
 		}
 		/* Create socket for listening. */


More information about the openssh-unix-dev mailing list