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
This commit is contained in:
djm@openbsd.org 2017-10-25 00:15:35 +00:00 committed by Damien Miller
parent b9903ee8ee
commit acf559e1cf
7 changed files with 284 additions and 109 deletions

View File

@ -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 @@ port_open_helper(struct ssh *ssh, Channel *c, char *rtype)
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 @@ channel_setup_fwd_listener_tcpip(struct ssh *ssh, int type,
continue;
}
channel_set_reuseaddr(sock);
set_reuseaddr(sock);
if (ai->ai_family == AF_INET6)
sock_set_v6only(sock);
@ -4439,7 +4426,7 @@ x11_create_display_inet(struct ssh *ssh, int x11_display_offset,
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));

40
misc.c
View File

@ -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 @@ set_nodelay(int fd)
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 "\""

4
misc.h
View File

@ -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 @@ char *strdelim(char **);
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);

View File

@ -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 @@ initialize_server_options(ServerOptions *options)
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 @@ fill_default_server_options(ServerOptions *options)
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 @@ derelativise_path(const char *path)
}
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 @@ add_one_listen_addr(ServerOptions *options, char *addr, int port)
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 @@ add_one_listen_addr(ServerOptions *options, char *addr, int port)
* 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 @@ static void
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 @@ process_queued_listen_addrs(ServerOptions *options)
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 @@ process_server_config_line(ServerOptions *options, char *line,
/* 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 @@ dump_cfg_strarray_oneline(ServerOpCodes code, u_int count, char **vals)
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);
}
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);
}
printf("%s", laddr1);
free(laddr1);
/* integer arguments */
#ifdef USE_PAM

View File

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

45
sshd.c
View File

@ -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 @@ server_accept_inetd(int *sock_in, int *sock_out)
* 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 @@ server_listen(void)
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 @@ server_listen(void)
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.");

View File

@ -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 @@ The following forms may be used:
.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.