summaryrefslogtreecommitdiffstats
path: root/crypto/openssh/channels.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssh/channels.c')
-rw-r--r--crypto/openssh/channels.c316
1 files changed, 243 insertions, 73 deletions
diff --git a/crypto/openssh/channels.c b/crypto/openssh/channels.c
index 2242579..04ad255 100644
--- a/crypto/openssh/channels.c
+++ b/crypto/openssh/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.311 2011/06/22 22:08:42 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.318 2012/04/23 08:18:17 djm Exp $ */
/* $FreeBSD$ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -126,6 +126,9 @@ static int num_permitted_opens = 0;
/* Number of permitted host/port pair in the array permitted by the admin. */
static int num_adm_permitted_opens = 0;
+/* special-case port number meaning allow any port */
+#define FWD_PERMIT_ANY_PORT 0
+
/*
* If this is true, all opens are permitted. This is the case on the server
* on which we have to trust the client anyway, and the user could do
@@ -308,10 +311,13 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
buffer_init(&c->output);
buffer_init(&c->extended);
c->path = NULL;
+ c->listening_addr = NULL;
+ c->listening_port = 0;
c->ostate = CHAN_OUTPUT_OPEN;
c->istate = CHAN_INPUT_OPEN;
c->flags = 0;
channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, 0);
+ c->notbefore = 0;
c->self = found;
c->type = type;
c->ctype = ctype;
@@ -418,6 +424,10 @@ channel_free(Channel *c)
xfree(c->path);
c->path = NULL;
}
+ if (c->listening_addr) {
+ xfree(c->listening_addr);
+ c->listening_addr = NULL;
+ }
while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) {
if (cc->abandon_cb != NULL)
cc->abandon_cb(c, cc->ctx);
@@ -1372,6 +1382,8 @@ channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset)
}
if (newsock < 0) {
error("accept: %.100s", strerror(errno));
+ if (errno == EMFILE || errno == ENFILE)
+ c->notbefore = time(NULL) + 1;
return;
}
set_nodelay(newsock);
@@ -1515,6 +1527,8 @@ channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset)
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
error("accept: %.100s", strerror(errno));
+ if (errno == EMFILE || errno == ENFILE)
+ c->notbefore = time(NULL) + 1;
return;
}
set_nodelay(newsock);
@@ -1547,7 +1561,10 @@ channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset)
addrlen = sizeof(addr);
newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
- error("accept from auth socket: %.100s", strerror(errno));
+ error("accept from auth socket: %.100s",
+ strerror(errno));
+ if (errno == EMFILE || errno == ENFILE)
+ c->notbefore = time(NULL) + 1;
return;
}
nc = channel_new("accepted auth socket",
@@ -1961,6 +1978,8 @@ channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset)
if ((newsock = accept(c->sock, (struct sockaddr*)&addr,
&addrlen)) == -1) {
error("%s accept: %s", __func__, strerror(errno));
+ if (errno == EMFILE || errno == ENFILE)
+ c->notbefore = time(NULL) + 1;
return;
}
@@ -2111,16 +2130,21 @@ channel_garbage_collect(Channel *c)
}
static void
-channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset)
+channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset,
+ time_t *unpause_secs)
{
static int did_init = 0;
u_int i, oalloc;
Channel *c;
+ time_t now;
if (!did_init) {
channel_handler_init();
did_init = 1;
}
+ now = time(NULL);
+ if (unpause_secs != NULL)
+ *unpause_secs = 0;
for (i = 0, oalloc = channels_alloc; i < oalloc; i++) {
c = channels[i];
if (c == NULL)
@@ -2131,10 +2155,30 @@ channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset)
else
continue;
}
- if (ftab[c->type] != NULL)
- (*ftab[c->type])(c, readset, writeset);
+ if (ftab[c->type] != NULL) {
+ /*
+ * Run handlers that are not paused.
+ */
+ if (c->notbefore <= now)
+ (*ftab[c->type])(c, readset, writeset);
+ else if (unpause_secs != NULL) {
+ /*
+ * Collect the time that the earliest
+ * channel comes off pause.
+ */
+ debug3("%s: chan %d: skip for %d more seconds",
+ __func__, c->self,
+ (int)(c->notbefore - now));
+ if (*unpause_secs == 0 ||
+ (c->notbefore - now) < *unpause_secs)
+ *unpause_secs = c->notbefore - now;
+ }
+ }
channel_garbage_collect(c);
}
+ if (unpause_secs != NULL && *unpause_secs != 0)
+ debug3("%s: first channel unpauses in %d seconds",
+ __func__, (int)*unpause_secs);
}
/*
@@ -2143,7 +2187,7 @@ channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset)
*/
void
channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
- u_int *nallocp, int rekeying)
+ u_int *nallocp, time_t *minwait_secs, int rekeying)
{
u_int n, sz, nfdset;
@@ -2166,7 +2210,8 @@ channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
memset(*writesetp, 0, sz);
if (!rekeying)
- channel_handler(channel_pre, *readsetp, *writesetp);
+ channel_handler(channel_pre, *readsetp, *writesetp,
+ minwait_secs);
}
/*
@@ -2176,7 +2221,7 @@ channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
void
channel_after_select(fd_set *readset, fd_set *writeset)
{
- channel_handler(channel_post, readset, writeset);
+ channel_handler(channel_post, readset, writeset, NULL);
}
@@ -2696,6 +2741,45 @@ channel_set_hpn(int disabled, u_int buf_size)
hpn_disabled, buffer_size);
}
+/*
+ * Determine whether or not a port forward listens to loopback, the
+ * specified address or wildcard. On the client, a specified bind
+ * address will always override gateway_ports. On the server, a
+ * gateway_ports of 1 (``yes'') will override the client's specification
+ * and force a wildcard bind, whereas a value of 2 (``clientspecified'')
+ * will bind to whatever address the client asked for.
+ *
+ * Special-case listen_addrs are:
+ *
+ * "0.0.0.0" -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR
+ * "" (empty string), "*" -> wildcard v4/v6
+ * "localhost" -> loopback v4/v6
+ */
+static const char *
+channel_fwd_bind_addr(const char *listen_addr, int *wildcardp,
+ int is_client, int gateway_ports)
+{
+ const char *addr = NULL;
+ int wildcard = 0;
+
+ if (listen_addr == NULL) {
+ /* No address specified: default to gateway_ports setting */
+ if (gateway_ports)
+ wildcard = 1;
+ } else if (gateway_ports || is_client) {
+ if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
+ strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) ||
+ *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 ||
+ (!is_client && gateway_ports == 1))
+ wildcard = 1;
+ else if (strcmp(listen_addr, "localhost") != 0)
+ addr = listen_addr;
+ }
+ if (wildcardp != NULL)
+ *wildcardp = wildcard;
+ return addr;
+}
+
static int
channel_setup_fwd_listener(int type, const char *listen_addr,
u_short listen_port, int *allocated_listen_port,
@@ -2721,36 +2805,9 @@ channel_setup_fwd_listener(int type, const char *listen_addr,
return 0;
}
- /*
- * Determine whether or not a port forward listens to loopback,
- * specified address or wildcard. On the client, a specified bind
- * address will always override gateway_ports. On the server, a
- * gateway_ports of 1 (``yes'') will override the client's
- * specification and force a wildcard bind, whereas a value of 2
- * (``clientspecified'') will bind to whatever address the client
- * asked for.
- *
- * Special-case listen_addrs are:
- *
- * "0.0.0.0" -> wildcard v4/v6 if SSH_OLD_FORWARD_ADDR
- * "" (empty string), "*" -> wildcard v4/v6
- * "localhost" -> loopback v4/v6
- */
- addr = NULL;
- if (listen_addr == NULL) {
- /* No address specified: default to gateway_ports setting */
- if (gateway_ports)
- wildcard = 1;
- } else if (gateway_ports || is_client) {
- if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
- strcmp(listen_addr, "0.0.0.0") == 0 && is_client == 0) ||
- *listen_addr == '\0' || strcmp(listen_addr, "*") == 0 ||
- (!is_client && gateway_ports == 1))
- wildcard = 1;
- else if (strcmp(listen_addr, "localhost") != 0)
- addr = listen_addr;
- }
-
+ /* Determine the bind address, cf. channel_fwd_bind_addr() comment */
+ addr = channel_fwd_bind_addr(listen_addr, &wildcard,
+ is_client, gateway_ports);
debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s",
type, wildcard, (addr == NULL) ? "NULL" : addr);
@@ -2862,7 +2919,12 @@ channel_setup_fwd_listener(int type, const char *listen_addr,
0, "port listener", 1);
c->path = xstrdup(host);
c->host_port = port_to_connect;
- c->listening_port = listen_port;
+ c->listening_addr = addr == NULL ? NULL : xstrdup(addr);
+ if (listen_port == 0 && allocated_listen_port != NULL &&
+ !(datafellows & SSH_BUG_DYNAMIC_RPORT))
+ c->listening_port = *allocated_listen_port;
+ else
+ c->listening_port = listen_port;
success = 1;
}
if (success == 0)
@@ -2880,9 +2942,44 @@ channel_cancel_rport_listener(const char *host, u_short port)
for (i = 0; i < channels_alloc; i++) {
Channel *c = channels[i];
+ if (c == NULL || c->type != SSH_CHANNEL_RPORT_LISTENER)
+ continue;
+ if (strcmp(c->path, host) == 0 && c->listening_port == port) {
+ debug2("%s: close channel %d", __func__, i);
+ channel_free(c);
+ found = 1;
+ }
+ }
+
+ return (found);
+}
+
+int
+channel_cancel_lport_listener(const char *lhost, u_short lport,
+ int cport, int gateway_ports)
+{
+ u_int i;
+ int found = 0;
+ const char *addr = channel_fwd_bind_addr(lhost, NULL, 1, gateway_ports);
- if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER &&
- strcmp(c->path, host) == 0 && c->listening_port == port) {
+ for (i = 0; i < channels_alloc; i++) {
+ Channel *c = channels[i];
+ if (c == NULL || c->type != SSH_CHANNEL_PORT_LISTENER)
+ continue;
+ if (c->listening_port != lport)
+ continue;
+ if (cport == CHANNEL_CANCEL_PORT_STATIC) {
+ /* skip dynamic forwardings */
+ if (c->host_port == 0)
+ continue;
+ } else {
+ if (c->host_port != cport)
+ continue;
+ }
+ if ((c->listening_addr == NULL && addr != NULL) ||
+ (c->listening_addr != NULL && addr == NULL))
+ continue;
+ if (addr == NULL || strcmp(c->listening_addr, addr) == 0) {
debug2("%s: close channel %d", __func__, i);
channel_free(c);
found = 1;
@@ -2913,37 +3010,44 @@ channel_setup_remote_fwd_listener(const char *listen_address,
}
/*
+ * Translate the requested rfwd listen host to something usable for
+ * this server.
+ */
+static const char *
+channel_rfwd_bind_host(const char *listen_host)
+{
+ if (listen_host == NULL) {
+ if (datafellows & SSH_BUG_RFWD_ADDR)
+ return "127.0.0.1";
+ else
+ return "localhost";
+ } else if (*listen_host == '\0' || strcmp(listen_host, "*") == 0) {
+ if (datafellows & SSH_BUG_RFWD_ADDR)
+ return "0.0.0.0";
+ else
+ return "";
+ } else
+ return listen_host;
+}
+
+/*
* Initiate forwarding of connections to port "port" on remote host through
* the secure channel to host:port from local side.
+ * Returns handle (index) for updating the dynamic listen port with
+ * channel_update_permitted_opens().
*/
-
int
channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
const char *host_to_connect, u_short port_to_connect)
{
- int type, success = 0;
+ int type, success = 0, idx = -1;
/* Send the forward request to the remote side. */
if (compat20) {
- const char *address_to_bind;
- if (listen_host == NULL) {
- if (datafellows & SSH_BUG_RFWD_ADDR)
- address_to_bind = "127.0.0.1";
- else
- address_to_bind = "localhost";
- } else if (*listen_host == '\0' ||
- strcmp(listen_host, "*") == 0) {
- if (datafellows & SSH_BUG_RFWD_ADDR)
- address_to_bind = "0.0.0.0";
- else
- address_to_bind = "";
- } else
- address_to_bind = listen_host;
-
packet_start(SSH2_MSG_GLOBAL_REQUEST);
packet_put_cstring("tcpip-forward");
- packet_put_char(1); /* boolean: want reply */
- packet_put_cstring(address_to_bind);
+ packet_put_char(1); /* boolean: want reply */
+ packet_put_cstring(channel_rfwd_bind_host(listen_host));
packet_put_int(listen_port);
packet_send();
packet_write_wait();
@@ -2975,25 +3079,25 @@ channel_request_remote_forwarding(const char *listen_host, u_short listen_port,
/* Record that connection to this host/port is permitted. */
permitted_opens = xrealloc(permitted_opens,
num_permitted_opens + 1, sizeof(*permitted_opens));
- permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host_to_connect);
- permitted_opens[num_permitted_opens].port_to_connect = port_to_connect;
- permitted_opens[num_permitted_opens].listen_port = listen_port;
- num_permitted_opens++;
+ idx = num_permitted_opens++;
+ permitted_opens[idx].host_to_connect = xstrdup(host_to_connect);
+ permitted_opens[idx].port_to_connect = port_to_connect;
+ permitted_opens[idx].listen_port = listen_port;
}
- return (success ? 0 : -1);
+ return (idx);
}
/*
* Request cancellation of remote forwarding of connection host:port from
* local side.
*/
-void
+int
channel_request_rforward_cancel(const char *host, u_short port)
{
int i;
if (!compat20)
- return;
+ return -1;
for (i = 0; i < num_permitted_opens; i++) {
if (permitted_opens[i].host_to_connect != NULL &&
@@ -3002,12 +3106,12 @@ channel_request_rforward_cancel(const char *host, u_short port)
}
if (i >= num_permitted_opens) {
debug("%s: requested forward not found", __func__);
- return;
+ return -1;
}
packet_start(SSH2_MSG_GLOBAL_REQUEST);
packet_put_cstring("cancel-tcpip-forward");
packet_put_char(0);
- packet_put_cstring(host == NULL ? "" : host);
+ packet_put_cstring(channel_rfwd_bind_host(host));
packet_put_int(port);
packet_send();
@@ -3015,6 +3119,8 @@ channel_request_rforward_cancel(const char *host, u_short port)
permitted_opens[i].port_to_connect = 0;
xfree(permitted_opens[i].host_to_connect);
permitted_opens[i].host_to_connect = NULL;
+
+ return 0;
}
/*
@@ -3083,6 +3189,35 @@ channel_add_permitted_opens(char *host, int port)
all_opens_permitted = 0;
}
+/*
+ * Update the listen port for a dynamic remote forward, after
+ * the actual 'newport' has been allocated. If 'newport' < 0 is
+ * passed then they entry will be invalidated.
+ */
+void
+channel_update_permitted_opens(int idx, int newport)
+{
+ if (idx < 0 || idx >= num_permitted_opens) {
+ debug("channel_update_permitted_opens: index out of range:"
+ " %d num_permitted_opens %d", idx, num_permitted_opens);
+ return;
+ }
+ debug("%s allowed port %d for forwarding to host %s port %d",
+ newport > 0 ? "Updating" : "Removing",
+ newport,
+ permitted_opens[idx].host_to_connect,
+ permitted_opens[idx].port_to_connect);
+ if (newport >= 0) {
+ permitted_opens[idx].listen_port =
+ (datafellows & SSH_BUG_DYNAMIC_RPORT) ? 0 : newport;
+ } else {
+ permitted_opens[idx].listen_port = 0;
+ permitted_opens[idx].port_to_connect = 0;
+ xfree(permitted_opens[idx].host_to_connect);
+ permitted_opens[idx].host_to_connect = NULL;
+ }
+}
+
int
channel_add_adm_permitted_opens(char *host, int port)
{
@@ -3097,6 +3232,17 @@ channel_add_adm_permitted_opens(char *host, int port)
}
void
+channel_disable_adm_local_opens(void)
+{
+ if (num_adm_permitted_opens == 0) {
+ permitted_adm_opens = xmalloc(sizeof(*permitted_adm_opens));
+ permitted_adm_opens[num_adm_permitted_opens].host_to_connect
+ = NULL;
+ num_adm_permitted_opens = 1;
+ }
+}
+
+void
channel_clear_permitted_opens(void)
{
int i;
@@ -3137,12 +3283,36 @@ channel_print_adm_permitted_opens(void)
return;
}
for (i = 0; i < num_adm_permitted_opens; i++)
- if (permitted_adm_opens[i].host_to_connect != NULL)
+ if (permitted_adm_opens[i].host_to_connect == NULL)
+ printf(" none");
+ else
printf(" %s:%d", permitted_adm_opens[i].host_to_connect,
permitted_adm_opens[i].port_to_connect);
printf("\n");
}
+/* returns port number, FWD_PERMIT_ANY_PORT or -1 on error */
+int
+permitopen_port(const char *p)
+{
+ int port;
+
+ if (strcmp(p, "*") == 0)
+ return FWD_PERMIT_ANY_PORT;
+ if ((port = a2port(p)) > 0)
+ return port;
+ return -1;
+}
+
+static int
+port_match(u_short allowedport, u_short requestedport)
+{
+ if (allowedport == FWD_PERMIT_ANY_PORT ||
+ allowedport == requestedport)
+ return 1;
+ return 0;
+}
+
/* Try to start non-blocking connect to next host in cctx list */
static int
connect_next(struct channel_connect *cctx)
@@ -3245,7 +3415,7 @@ channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname)
for (i = 0; i < num_permitted_opens; i++) {
if (permitted_opens[i].host_to_connect != NULL &&
- permitted_opens[i].listen_port == listen_port) {
+ port_match(permitted_opens[i].listen_port, listen_port)) {
return connect_to(
permitted_opens[i].host_to_connect,
permitted_opens[i].port_to_connect, ctype, rname);
@@ -3266,7 +3436,7 @@ channel_connect_to(const char *host, u_short port, char *ctype, char *rname)
if (!permit) {
for (i = 0; i < num_permitted_opens; i++)
if (permitted_opens[i].host_to_connect != NULL &&
- permitted_opens[i].port_to_connect == port &&
+ port_match(permitted_opens[i].port_to_connect, port) &&
strcmp(permitted_opens[i].host_to_connect, host) == 0)
permit = 1;
}
@@ -3275,7 +3445,7 @@ channel_connect_to(const char *host, u_short port, char *ctype, char *rname)
permit_adm = 0;
for (i = 0; i < num_adm_permitted_opens; i++)
if (permitted_adm_opens[i].host_to_connect != NULL &&
- permitted_adm_opens[i].port_to_connect == port &&
+ port_match(permitted_adm_opens[i].port_to_connect, port) &&
strcmp(permitted_adm_opens[i].host_to_connect, host)
== 0)
permit_adm = 1;
OpenPOWER on IntegriCloud