upstream commit
Add optional rdomain qualifier to sshd_config's
ListenAddress option to allow listening on a different rdomain(4), e.g.
ListenAddress 0.0.0.0 rdomain 4
Upstream-ID: 24b6622c376feeed9e9be8b9605e593695ac9091
diff --git a/channels.c b/channels.c
index 83442be..07dc957 100644
--- a/channels.c
+++ b/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.375 2017/09/24 13:45:34 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.376 2017/10/25 00:15:35 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1668,19 +1668,6 @@
free(local_ipaddr);
}
-static void
-channel_set_reuseaddr(int fd)
-{
- int on = 1;
-
- /*
- * Set socket options.
- * Allow local port reuse in TIME_WAIT.
- */
- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
- error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno));
-}
-
void
channel_set_x11_refuse_time(struct ssh *ssh, u_int refuse_time)
{
@@ -3368,7 +3355,7 @@
continue;
}
- channel_set_reuseaddr(sock);
+ set_reuseaddr(sock);
if (ai->ai_family == AF_INET6)
sock_set_v6only(sock);
@@ -4439,7 +4426,7 @@
if (ai->ai_family == AF_INET6)
sock_set_v6only(sock);
if (x11_use_localhost)
- channel_set_reuseaddr(sock);
+ set_reuseaddr(sock);
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
debug2("%s: bind port %d: %.100s", __func__,
port, strerror(errno));
diff --git a/misc.c b/misc.c
index cb8bf5c..cc22fbe 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.116 2017/10/24 19:41:45 millert Exp $ */
+/* $OpenBSD: misc.c,v 1.117 2017/10/25 00:15:35 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005,2006 Damien Miller. All rights reserved.
@@ -167,6 +167,44 @@
error("setsockopt TCP_NODELAY: %.100s", strerror(errno));
}
+/* Allow local port reuse in TIME_WAIT */
+int
+set_reuseaddr(int fd)
+{
+ int on = 1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+ error("setsockopt SO_REUSEADDR fd %d: %s", fd, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/* Set routing table */
+int
+set_rdomain(int fd, const char *name)
+{
+ int rtable;
+ const char *errstr;
+
+ if (name == NULL)
+ return 0; /* default table */
+
+ rtable = (int)strtonum(name, 0, 255, &errstr);
+ if (errstr != NULL) {
+ /* Shouldn't happen */
+ error("Invalid routing domain \"%s\": %s", name, errstr);
+ return -1;
+ }
+ if (setsockopt(fd, SOL_SOCKET, SO_RTABLE,
+ &rtable, sizeof(rtable)) == -1) {
+ error("Failed to set routing domain %d on fd %d: %s",
+ rtable, fd, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
/* Characters considered whitespace in strsep calls. */
#define WHITESPACE " \t\r\n"
#define QUOTE "\""
diff --git a/misc.h b/misc.h
index 19fdf5c..f36081f 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.65 2017/10/23 05:08:00 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.66 2017/10/25 00:15:35 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -48,6 +48,8 @@
int set_nonblock(int);
int unset_nonblock(int);
void set_nodelay(int);
+int set_reuseaddr(int);
+int set_rdomain(int, const char *);
int a2port(const char *);
int a2tun(const char *, int *);
char *put_host_port(const char *, u_short);
diff --git a/servconf.c b/servconf.c
index a96df4f..68db047 100644
--- a/servconf.c
+++ b/servconf.c
@@ -1,5 +1,5 @@
-/* $OpenBSD: servconf.c,v 1.314 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: servconf.c,v 1.315 2017/10/25 00:15:35 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@@ -15,10 +15,16 @@
#include <sys/types.h>
#include <sys/socket.h>
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
+#ifdef HAVE_NET_ROUTE_H
+#include <net/route.h>
+#endif
#include <ctype.h>
#include <netdb.h>
@@ -58,8 +64,10 @@
#include "myproposal.h"
#include "digest.h"
-static void add_listen_addr(ServerOptions *, char *, int);
-static void add_one_listen_addr(ServerOptions *, char *, int);
+static void add_listen_addr(ServerOptions *, const char *,
+ const char *, int);
+static void add_one_listen_addr(ServerOptions *, const char *,
+ const char *, int);
/* Use of privilege separation or not */
extern int use_privsep;
@@ -81,6 +89,7 @@
options->queued_listen_addrs = NULL;
options->num_queued_listens = 0;
options->listen_addrs = NULL;
+ options->num_listen_addrs = 0;
options->address_family = -1;
options->num_host_key_files = 0;
options->num_host_cert_files = 0;
@@ -252,7 +261,7 @@
if (options->address_family == -1)
options->address_family = AF_UNSPEC;
if (options->listen_addrs == NULL)
- add_listen_addr(options, NULL, 0);
+ add_listen_addr(options, NULL, NULL, 0);
if (options->pid_file == NULL)
options->pid_file = xstrdup(_PATH_SSH_DAEMON_PID_FILE);
if (options->login_grace_time == -1)
@@ -658,23 +667,51 @@
}
static void
-add_listen_addr(ServerOptions *options, char *addr, int port)
+add_listen_addr(ServerOptions *options, const char *addr,
+ const char *rdomain, int port)
{
u_int i;
- if (port == 0)
- for (i = 0; i < options->num_ports; i++)
- add_one_listen_addr(options, addr, options->ports[i]);
- else
- add_one_listen_addr(options, addr, port);
+ if (port > 0)
+ add_one_listen_addr(options, addr, rdomain, port);
+ else {
+ for (i = 0; i < options->num_ports; i++) {
+ add_one_listen_addr(options, addr, rdomain,
+ options->ports[i]);
+ }
+ }
}
static void
-add_one_listen_addr(ServerOptions *options, char *addr, int port)
+add_one_listen_addr(ServerOptions *options, const char *addr,
+ const char *rdomain, int port)
{
struct addrinfo hints, *ai, *aitop;
char strport[NI_MAXSERV];
int gaierr;
+ u_int i;
+
+ /* Find listen_addrs entry for this rdomain */
+ for (i = 0; i < options->num_listen_addrs; i++) {
+ if (rdomain == NULL && options->listen_addrs[i].rdomain == NULL)
+ break;
+ if (rdomain == NULL || options->listen_addrs[i].rdomain == NULL)
+ continue;
+ if (strcmp(rdomain, options->listen_addrs[i].rdomain) == 0)
+ break;
+ }
+ if (i >= options->num_listen_addrs) {
+ /* No entry for this rdomain; allocate one */
+ if (i >= INT_MAX)
+ fatal("%s: too many listen addresses", __func__);
+ options->listen_addrs = xrecallocarray(options->listen_addrs,
+ options->num_listen_addrs, options->num_listen_addrs + 1,
+ sizeof(*options->listen_addrs));
+ i = options->num_listen_addrs++;
+ if (rdomain != NULL)
+ options->listen_addrs[i].rdomain = xstrdup(rdomain);
+ }
+ /* options->listen_addrs[i] points to the addresses for this rdomain */
memset(&hints, 0, sizeof(hints));
hints.ai_family = options->address_family;
@@ -687,8 +724,37 @@
ssh_gai_strerror(gaierr));
for (ai = aitop; ai->ai_next; ai = ai->ai_next)
;
- ai->ai_next = options->listen_addrs;
- options->listen_addrs = aitop;
+ ai->ai_next = options->listen_addrs[i].addrs;
+ options->listen_addrs[i].addrs = aitop;
+}
+
+/* Returns nonzero if the routing domain name is valid */
+static int
+valid_rdomain(const char *name)
+{
+ const char *errstr;
+ long long num;
+ struct rt_tableinfo info;
+ int mib[6];
+ size_t miblen = sizeof(mib);
+
+ if (name == NULL)
+ return 1;
+
+ num = strtonum(name, 0, 255, &errstr);
+ if (errstr != NULL)
+ return 0;
+
+ /* Check whether the table actually exists */
+ memset(mib, 0, sizeof(mib));
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[4] = NET_RT_TABLE;
+ mib[5] = (int)num;
+ if (sysctl(mib, 6, &info, &miblen, NULL, 0) == -1)
+ return 0;
+
+ return 1;
}
/*
@@ -696,18 +762,19 @@
* and AddressFamily options.
*/
static void
-queue_listen_addr(ServerOptions *options, char *addr, int port)
+queue_listen_addr(ServerOptions *options, const char *addr,
+ const char *rdomain, int port)
{
- options->queued_listen_addrs = xreallocarray(
- options->queued_listen_addrs, options->num_queued_listens + 1,
- sizeof(addr));
- options->queued_listen_ports = xreallocarray(
- options->queued_listen_ports, options->num_queued_listens + 1,
- sizeof(port));
- options->queued_listen_addrs[options->num_queued_listens] =
- xstrdup(addr);
- options->queued_listen_ports[options->num_queued_listens] = port;
- options->num_queued_listens++;
+ struct queued_listenaddr *qla;
+
+ options->queued_listen_addrs = xrecallocarray(
+ options->queued_listen_addrs,
+ options->num_queued_listens, options->num_queued_listens + 1,
+ sizeof(*options->queued_listen_addrs));
+ qla = &options->queued_listen_addrs[options->num_queued_listens++];
+ qla->addr = xstrdup(addr);
+ qla->port = port;
+ qla->rdomain = rdomain == NULL ? NULL : xstrdup(rdomain);
}
/*
@@ -717,6 +784,7 @@
process_queued_listen_addrs(ServerOptions *options)
{
u_int i;
+ struct queued_listenaddr *qla;
if (options->num_ports == 0)
options->ports[options->num_ports++] = SSH_DEFAULT_PORT;
@@ -724,15 +792,13 @@
options->address_family = AF_UNSPEC;
for (i = 0; i < options->num_queued_listens; i++) {
- add_listen_addr(options, options->queued_listen_addrs[i],
- options->queued_listen_ports[i]);
- free(options->queued_listen_addrs[i]);
- options->queued_listen_addrs[i] = NULL;
+ qla = &options->queued_listen_addrs[i];
+ add_listen_addr(options, qla->addr, qla->rdomain, qla->port);
+ free(qla->addr);
+ free(qla->rdomain);
}
free(options->queued_listen_addrs);
options->queued_listen_addrs = NULL;
- free(options->queued_listen_ports);
- options->queued_listen_ports = NULL;
options->num_queued_listens = 0;
}
@@ -1127,20 +1193,33 @@
/* check for bare IPv6 address: no "[]" and 2 or more ":" */
if (strchr(arg, '[') == NULL && (p = strchr(arg, ':')) != NULL
&& strchr(p+1, ':') != NULL) {
- queue_listen_addr(options, arg, 0);
- break;
- }
- p = hpdelim(&arg);
- if (p == NULL)
- fatal("%s line %d: bad address:port usage",
- filename, linenum);
- p = cleanhostname(p);
- if (arg == NULL)
port = 0;
- else if ((port = a2port(arg)) <= 0)
- fatal("%s line %d: bad port number", filename, linenum);
+ p = arg;
+ } else {
+ p = hpdelim(&arg);
+ if (p == NULL)
+ fatal("%s line %d: bad address:port usage",
+ filename, linenum);
+ p = cleanhostname(p);
+ if (arg == NULL)
+ port = 0;
+ else if ((port = a2port(arg)) <= 0)
+ fatal("%s line %d: bad port number",
+ filename, linenum);
+ }
+ /* Optional routing table */
+ arg2 = NULL;
+ if ((arg = strdelim(&cp)) != NULL) {
+ if (strcmp(arg, "rdomain") != 0 ||
+ (arg2 = strdelim(&cp)) == NULL)
+ fatal("%s line %d: bad ListenAddress syntax",
+ filename, linenum);
+ if (!valid_rdomain(arg2))
+ fatal("%s line %d: bad routing domain",
+ filename, linenum);
+ }
- queue_listen_addr(options, p, port);
+ queue_listen_addr(options, p, arg2, port);
break;
@@ -2251,45 +2330,61 @@
printf("\n");
}
-void
-dump_config(ServerOptions *o)
+static char *
+format_listen_addrs(struct listenaddr *la)
{
- u_int i;
- int ret;
+ int r;
struct addrinfo *ai;
- char addr[NI_MAXHOST], port[NI_MAXSERV], *s = NULL;
+ char addr[NI_MAXHOST], port[NI_MAXSERV];
char *laddr1 = xstrdup(""), *laddr2 = NULL;
- /* these are usually at the top of the config */
- for (i = 0; i < o->num_ports; i++)
- printf("port %d\n", o->ports[i]);
- dump_cfg_fmtint(sAddressFamily, o->address_family);
-
/*
* ListenAddress must be after Port. add_one_listen_addr pushes
* addresses onto a stack, so to maintain ordering we need to
* print these in reverse order.
*/
- for (ai = o->listen_addrs; ai; ai = ai->ai_next) {
- if ((ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
+ for (ai = la->addrs; ai; ai = ai->ai_next) {
+ if ((r = getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
sizeof(addr), port, sizeof(port),
NI_NUMERICHOST|NI_NUMERICSERV)) != 0) {
- error("getnameinfo failed: %.100s",
- (ret != EAI_SYSTEM) ? gai_strerror(ret) :
- strerror(errno));
- } else {
- laddr2 = laddr1;
- if (ai->ai_family == AF_INET6)
- xasprintf(&laddr1, "listenaddress [%s]:%s\n%s",
- addr, port, laddr2);
- else
- xasprintf(&laddr1, "listenaddress %s:%s\n%s",
- addr, port, laddr2);
- free(laddr2);
+ error("getnameinfo: %.100s", ssh_gai_strerror(r));
+ continue;
}
+ laddr2 = laddr1;
+ if (ai->ai_family == AF_INET6) {
+ xasprintf(&laddr1, "listenaddress [%s]:%s%s%s\n%s",
+ addr, port,
+ la->rdomain == NULL ? "" : " rdomain ",
+ la->rdomain == NULL ? "" : la->rdomain,
+ laddr2);
+ } else {
+ xasprintf(&laddr1, "listenaddress %s:%s%s%s\n%s",
+ addr, port,
+ la->rdomain == NULL ? "" : " rdomain ",
+ la->rdomain == NULL ? "" : la->rdomain,
+ laddr2);
+ }
+ free(laddr2);
}
- printf("%s", laddr1);
- free(laddr1);
+ return laddr1;
+}
+
+void
+dump_config(ServerOptions *o)
+{
+ char *s;
+ u_int i;
+
+ /* these are usually at the top of the config */
+ for (i = 0; i < o->num_ports; i++)
+ printf("port %d\n", o->ports[i]);
+ dump_cfg_fmtint(sAddressFamily, o->address_family);
+
+ for (i = 0; i < o->num_listen_addrs; i++) {
+ s = format_listen_addrs(&o->listen_addrs[i]);
+ printf("%s", s);
+ free(s);
+ }
/* integer arguments */
#ifdef USE_PAM
diff --git a/servconf.h b/servconf.h
index 1ff3bc5..3d0a065 100644
--- a/servconf.h
+++ b/servconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.127 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: servconf.h,v 1.128 2017/10/25 00:15:35 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -51,14 +51,31 @@
struct ssh;
struct fwd_perm_list;
+/*
+ * Used to store addresses from ListenAddr directives. These may be
+ * incomplete, as they may specify addresses that need to be merged
+ * with any ports requested by ListenPort.
+ */
+struct queued_listenaddr {
+ char *addr;
+ int port; /* <=0 if unspecified */
+ char *rdomain;
+};
+
+/* Resolved listen addresses, grouped by optional routing domain */
+struct listenaddr {
+ char *rdomain;
+ struct addrinfo *addrs;
+};
+
typedef struct {
u_int num_ports;
u_int ports_from_cmdline;
int ports[MAX_PORTS]; /* Port number to listen on. */
+ struct queued_listenaddr *queued_listen_addrs;
u_int num_queued_listens;
- char **queued_listen_addrs;
- int *queued_listen_ports;
- struct addrinfo *listen_addrs; /* Addresses for server to listen. */
+ struct listenaddr *listen_addrs;
+ u_int num_listen_addrs;
int address_family; /* Address family used by the server. */
char **host_key_files; /* Files containing host keys. */
diff --git a/sshd.c b/sshd.c
index 0e3ac26..93b02b6 100644
--- a/sshd.c
+++ b/sshd.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: sshd.c,v 1.493 2017/10/05 15:52:03 djm Exp $ */
+/* $OpenBSD: sshd.c,v 1.494 2017/10/25 00:15:35 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -1015,13 +1015,13 @@
* Listen for TCP connections
*/
static void
-server_listen(void)
+listen_on_addrs(struct listenaddr *la)
{
- int ret, listen_sock, on = 1;
+ int ret, listen_sock;
struct addrinfo *ai;
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
- for (ai = options.listen_addrs; ai; ai = ai->ai_next) {
+ for (ai = la->addrs; ai; ai = ai->ai_next) {
if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
continue;
if (num_listen_socks >= MAX_LISTEN_SOCKS)
@@ -1051,13 +1051,13 @@
close(listen_sock);
continue;
}
- /*
- * Set socket options.
- * Allow local port reuse in TIME_WAIT.
- */
- if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR,
- &on, sizeof(on)) == -1)
- error("setsockopt SO_REUSEADDR: %s", strerror(errno));
+ /* Socket options */
+ set_reuseaddr(listen_sock);
+ if (la->rdomain != NULL &&
+ set_rdomain(listen_sock, la->rdomain) == -1) {
+ close(listen_sock);
+ continue;
+ }
/* Only communicate in IPv6 over AF_INET6 sockets. */
if (ai->ai_family == AF_INET6)
@@ -1079,9 +1079,28 @@
if (listen(listen_sock, SSH_LISTEN_BACKLOG) < 0)
fatal("listen on [%s]:%s: %.100s",
ntop, strport, strerror(errno));
- logit("Server listening on %s port %s.", ntop, strport);
+ logit("Server listening on %s port %s%s%s.",
+ ntop, strport,
+ la->rdomain == NULL ? "" : " rdomain ",
+ la->rdomain == NULL ? "" : la->rdomain);
}
- freeaddrinfo(options.listen_addrs);
+}
+
+static void
+server_listen(void)
+{
+ u_int i;
+
+ for (i = 0; i < options.num_listen_addrs; i++) {
+ listen_on_addrs(&options.listen_addrs[i]);
+ freeaddrinfo(options.listen_addrs[i].addrs);
+ free(options.listen_addrs[i].rdomain);
+ memset(&options.listen_addrs[i], 0,
+ sizeof(options.listen_addrs[i]));
+ }
+ free(options.listen_addrs);
+ options.listen_addrs = NULL;
+ options.num_listen_addrs = 0;
if (!num_listen_socks)
fatal("Cannot bind any address.");
diff --git a/sshd_config.5 b/sshd_config.5
index 77a8354..b63a022 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -33,8 +33,8 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
-.\" $OpenBSD: sshd_config.5,v 1.255 2017/10/13 16:50:45 jmc Exp $
-.Dd $Mdocdate: October 13 2017 $
+.\" $OpenBSD: sshd_config.5,v 1.256 2017/10/25 00:15:35 djm Exp $
+.Dd $Mdocdate: October 25 2017 $
.Dt SSHD_CONFIG 5
.Os
.Sh NAME
@@ -909,31 +909,48 @@
.It
.Cm ListenAddress
.Sm off
-.Ar host | Ar IPv4_addr | Ar IPv6_addr
+.Ar hostname | Ar address
.Sm on
+.Oo rdomain Ar domain Oc
.It
.Cm ListenAddress
.Sm off
-.Ar host | Ar IPv4_addr : Ar port
+.Ar hostname : Ar port
.Sm on
+.Oo rdomain Ar domain Oc
+.It
+.Cm ListenAddress
+.Sm off
+.Ar IPv4_address : Ar port
+.Sm on
+.Oo rdomain Ar domain Oc
.It
.Cm ListenAddress
.Sm off
.Oo
-.Ar host | Ar IPv6_addr Oc : Ar port
+.Ar hostname | address Oc : Ar port
.Sm on
+.Oo rdomain Ar domain Oc
.El
.Pp
+The optional
+.Cm rdomain
+qualifier requests
+.Xr sshd 8
+listen in an explicit routing domain.
If
.Ar port
is not specified,
sshd will listen on the address and all
.Cm Port
options specified.
-The default is to listen on all local addresses.
+The default is to listen on all local addresses on the current default
+routing domain.
Multiple
.Cm ListenAddress
options are permitted.
+For more information on routing domains, see
+.Xr rdomain 4.
.It Cm LoginGraceTime
The server disconnects after this time if the user has not
successfully logged in.