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. */