aboutsummaryrefslogtreecommitdiff
path: root/crypto/openssh/channels.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/openssh/channels.c')
-rw-r--r--crypto/openssh/channels.c279
1 files changed, 214 insertions, 65 deletions
diff --git a/crypto/openssh/channels.c b/crypto/openssh/channels.c
index e8b8aa07e16e..d8c53a4a8f2b 100644
--- a/crypto/openssh/channels.c
+++ b/crypto/openssh/channels.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: channels.c,v 1.296 2009/05/25 06:48:00 andreas Exp $ */
+/* $OpenBSD: channels.c,v 1.303 2010/01/30 21:12:08 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -53,6 +53,7 @@
#include <arpa/inet.h>
#include <errno.h>
+#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
@@ -228,12 +229,16 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
channel_max_fd = MAX(channel_max_fd, wfd);
channel_max_fd = MAX(channel_max_fd, efd);
- /* XXX set close-on-exec -markus */
+ if (rfd != -1)
+ fcntl(rfd, F_SETFD, FD_CLOEXEC);
+ if (wfd != -1 && wfd != rfd)
+ fcntl(wfd, F_SETFD, FD_CLOEXEC);
+ if (efd != -1 && efd != rfd && efd != wfd)
+ fcntl(efd, F_SETFD, FD_CLOEXEC);
c->rfd = rfd;
c->wfd = wfd;
c->sock = (rfd == wfd) ? rfd : -1;
- c->ctl_fd = -1; /* XXX: set elsewhere */
c->efd = efd;
c->extended_usage = extusage;
@@ -322,6 +327,10 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
c->output_filter = NULL;
c->filter_ctx = NULL;
c->filter_cleanup = NULL;
+ c->ctl_chan = -1;
+ c->mux_rcb = NULL;
+ c->mux_ctx = NULL;
+ c->delayed = 1; /* prevent call to channel_post handler */
TAILQ_INIT(&c->status_confirms);
debug("channel %d: new [%s]", found, remote_name);
return c;
@@ -363,11 +372,10 @@ channel_close_fd(int *fdp)
static void
channel_close_fds(Channel *c)
{
- debug3("channel %d: close_fds r %d w %d e %d c %d",
- c->self, c->rfd, c->wfd, c->efd, c->ctl_fd);
+ debug3("channel %d: close_fds r %d w %d e %d",
+ c->self, c->rfd, c->wfd, c->efd);
channel_close_fd(&c->sock);
- channel_close_fd(&c->ctl_fd);
channel_close_fd(&c->rfd);
channel_close_fd(&c->wfd);
channel_close_fd(&c->efd);
@@ -393,8 +401,6 @@ channel_free(Channel *c)
if (c->sock != -1)
shutdown(c->sock, SHUT_RDWR);
- if (c->ctl_fd != -1)
- shutdown(c->ctl_fd, SHUT_RDWR);
channel_close_fds(c);
buffer_free(&c->input);
buffer_free(&c->output);
@@ -516,6 +522,7 @@ channel_still_open(void)
case SSH_CHANNEL_X11_LISTENER:
case SSH_CHANNEL_PORT_LISTENER:
case SSH_CHANNEL_RPORT_LISTENER:
+ case SSH_CHANNEL_MUX_LISTENER:
case SSH_CHANNEL_CLOSED:
case SSH_CHANNEL_AUTH_SOCKET:
case SSH_CHANNEL_DYNAMIC:
@@ -529,6 +536,7 @@ channel_still_open(void)
case SSH_CHANNEL_OPENING:
case SSH_CHANNEL_OPEN:
case SSH_CHANNEL_X11_OPEN:
+ case SSH_CHANNEL_MUX_CLIENT:
return 1;
case SSH_CHANNEL_INPUT_DRAINING:
case SSH_CHANNEL_OUTPUT_DRAINING:
@@ -560,6 +568,8 @@ channel_find_open(void)
case SSH_CHANNEL_X11_LISTENER:
case SSH_CHANNEL_PORT_LISTENER:
case SSH_CHANNEL_RPORT_LISTENER:
+ case SSH_CHANNEL_MUX_LISTENER:
+ case SSH_CHANNEL_MUX_CLIENT:
case SSH_CHANNEL_OPENING:
case SSH_CHANNEL_CONNECTING:
case SSH_CHANNEL_ZOMBIE:
@@ -610,6 +620,8 @@ channel_open_message(void)
case SSH_CHANNEL_CLOSED:
case SSH_CHANNEL_AUTH_SOCKET:
case SSH_CHANNEL_ZOMBIE:
+ case SSH_CHANNEL_MUX_CLIENT:
+ case SSH_CHANNEL_MUX_LISTENER:
continue;
case SSH_CHANNEL_LARVAL:
case SSH_CHANNEL_OPENING:
@@ -620,12 +632,12 @@ channel_open_message(void)
case SSH_CHANNEL_INPUT_DRAINING:
case SSH_CHANNEL_OUTPUT_DRAINING:
snprintf(buf, sizeof buf,
- " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %d)\r\n",
+ " #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cc %d)\r\n",
c->self, c->remote_name,
c->type, c->remote_id,
c->istate, buffer_len(&c->input),
c->ostate, buffer_len(&c->output),
- c->rfd, c->wfd, c->ctl_fd);
+ c->rfd, c->wfd, c->ctl_chan);
buffer_append(&buffer, buf, strlen(buf));
continue;
default:
@@ -832,9 +844,6 @@ channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
FD_SET(c->efd, readset);
}
/* XXX: What about efd? races? */
- if (compat20 && c->ctl_fd != -1 &&
- c->istate == CHAN_INPUT_OPEN && c->ostate == CHAN_OUTPUT_OPEN)
- FD_SET(c->ctl_fd, readset);
}
/* ARGSUSED */
@@ -979,6 +988,28 @@ channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset)
}
}
+static void
+channel_pre_mux_client(Channel *c, fd_set *readset, fd_set *writeset)
+{
+ if (c->istate == CHAN_INPUT_OPEN &&
+ buffer_check_alloc(&c->input, CHAN_RBUF))
+ FD_SET(c->rfd, readset);
+ if (c->istate == CHAN_INPUT_WAIT_DRAIN) {
+ /* clear buffer immediately (discard any partial packet) */
+ buffer_clear(&c->input);
+ chan_ibuf_empty(c);
+ /* Start output drain. XXX just kill chan? */
+ chan_rcvd_oclose(c);
+ }
+ if (c->ostate == CHAN_OUTPUT_OPEN ||
+ c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
+ if (buffer_len(&c->output) > 0)
+ FD_SET(c->wfd, writeset);
+ else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN)
+ chan_obuf_empty(c);
+ }
+}
+
/* try to decode a socks4 header */
/* ARGSUSED */
static int
@@ -1210,6 +1241,30 @@ channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
return 1;
}
+Channel *
+channel_connect_stdio_fwd(const char *host_to_connect, u_short port_to_connect,
+ int in, int out)
+{
+ Channel *c;
+
+ debug("channel_connect_stdio_fwd %s:%d", host_to_connect,
+ port_to_connect);
+
+ c = channel_new("stdio-forward", SSH_CHANNEL_OPENING, in, out,
+ -1, CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
+ 0, "stdio-forward", /*nonblock*/0);
+
+ c->path = xstrdup(host_to_connect);
+ c->host_port = port_to_connect;
+ c->listening_port = 0;
+ c->force_drain = 1;
+
+ channel_register_fds(c, in, out, -1, 0, 1, 0);
+ port_open_helper(c, "direct-tcpip");
+
+ return c;
+}
+
/* dynamic port forwarding */
static void
channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
@@ -1219,7 +1274,6 @@ channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
int ret;
have = buffer_len(&c->input);
- c->delayed = 0;
debug2("channel %d: pre_dynamic: have %d", c->self, have);
/* buffer_dump(&c->input); */
/* check if the fixed size part of the packet is in buffer. */
@@ -1322,6 +1376,13 @@ port_open_helper(Channel *c, char *rtype)
char *remote_ipaddr = get_peer_ipaddr(c->sock);
int remote_port = get_peer_port(c->sock);
+ if (remote_port == -1) {
+ /* Fake addr/port to appease peers that validate it (Tectia) */
+ xfree(remote_ipaddr);
+ remote_ipaddr = xstrdup("127.0.0.1");
+ remote_port = 65535;
+ }
+
direct = (strcmp(rtype, "direct-tcpip") == 0);
snprintf(buf, sizeof buf,
@@ -1423,16 +1484,8 @@ channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset)
if (c->path != NULL)
nc->path = xstrdup(c->path);
- if (nextstate == SSH_CHANNEL_DYNAMIC) {
- /*
- * do not call the channel_post handler until
- * this flag has been reset by a pre-handler.
- * otherwise the FD_ISSET calls might overflow
- */
- nc->delayed = 1;
- } else {
+ if (nextstate != SSH_CHANNEL_DYNAMIC)
port_open_helper(nc, rtype);
- }
}
}
@@ -1722,36 +1775,6 @@ channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
return 1;
}
-/* ARGSUSED */
-static int
-channel_handle_ctl(Channel *c, fd_set *readset, fd_set *writeset)
-{
- char buf[16];
- int len;
-
- /* Monitor control fd to detect if the slave client exits */
- if (c->ctl_fd != -1 && FD_ISSET(c->ctl_fd, readset)) {
- len = read(c->ctl_fd, buf, sizeof(buf));
- if (len < 0 &&
- (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
- return 1;
- if (len <= 0) {
- debug2("channel %d: ctl read<=0", c->self);
- if (c->type != SSH_CHANNEL_OPEN) {
- debug2("channel %d: not open", c->self);
- chan_mark_dead(c);
- return -1;
- } else {
- chan_read_failed(c);
- chan_write_failed(c);
- }
- return -1;
- } else
- fatal("%s: unexpected data on ctl fd", __func__);
- }
- return 1;
-}
-
static int
channel_check_window(Channel *c)
{
@@ -1777,17 +1800,136 @@ channel_check_window(Channel *c)
static void
channel_post_open(Channel *c, fd_set *readset, fd_set *writeset)
{
- if (c->delayed)
- return;
channel_handle_rfd(c, readset, writeset);
channel_handle_wfd(c, readset, writeset);
if (!compat20)
return;
channel_handle_efd(c, readset, writeset);
- channel_handle_ctl(c, readset, writeset);
channel_check_window(c);
}
+static u_int
+read_mux(Channel *c, u_int need)
+{
+ char buf[CHAN_RBUF];
+ int len;
+ u_int rlen;
+
+ if (buffer_len(&c->input) < need) {
+ rlen = need - buffer_len(&c->input);
+ len = read(c->rfd, buf, MIN(rlen, CHAN_RBUF));
+ if (len <= 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ debug2("channel %d: ctl read<=0 rfd %d len %d",
+ c->self, c->rfd, len);
+ chan_read_failed(c);
+ return 0;
+ }
+ } else
+ buffer_append(&c->input, buf, len);
+ }
+ return buffer_len(&c->input);
+}
+
+static void
+channel_post_mux_client(Channel *c, fd_set *readset, fd_set *writeset)
+{
+ u_int need;
+ ssize_t len;
+
+ if (!compat20)
+ fatal("%s: entered with !compat20", __func__);
+
+ if (c->rfd != -1 && FD_ISSET(c->rfd, readset) &&
+ (c->istate == CHAN_INPUT_OPEN ||
+ c->istate == CHAN_INPUT_WAIT_DRAIN)) {
+ /*
+ * Don't not read past the precise end of packets to
+ * avoid disrupting fd passing.
+ */
+ if (read_mux(c, 4) < 4) /* read header */
+ return;
+ need = get_u32(buffer_ptr(&c->input));
+#define CHANNEL_MUX_MAX_PACKET (256 * 1024)
+ if (need > CHANNEL_MUX_MAX_PACKET) {
+ debug2("channel %d: packet too big %u > %u",
+ c->self, CHANNEL_MUX_MAX_PACKET, need);
+ chan_rcvd_oclose(c);
+ return;
+ }
+ if (read_mux(c, need + 4) < need + 4) /* read body */
+ return;
+ if (c->mux_rcb(c) != 0) {
+ debug("channel %d: mux_rcb failed", c->self);
+ chan_mark_dead(c);
+ return;
+ }
+ }
+
+ if (c->wfd != -1 && FD_ISSET(c->wfd, writeset) &&
+ buffer_len(&c->output) > 0) {
+ len = write(c->wfd, buffer_ptr(&c->output),
+ buffer_len(&c->output));
+ if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ return;
+ if (len <= 0) {
+ chan_mark_dead(c);
+ return;
+ }
+ buffer_consume(&c->output, len);
+ }
+}
+
+static void
+channel_post_mux_listener(Channel *c, fd_set *readset, fd_set *writeset)
+{
+ Channel *nc;
+ struct sockaddr_storage addr;
+ socklen_t addrlen;
+ int newsock;
+ uid_t euid;
+ gid_t egid;
+
+ if (!FD_ISSET(c->sock, readset))
+ return;
+
+ debug("multiplexing control connection");
+
+ /*
+ * Accept connection on control socket
+ */
+ memset(&addr, 0, sizeof(addr));
+ addrlen = sizeof(addr);
+ if ((newsock = accept(c->sock, (struct sockaddr*)&addr,
+ &addrlen)) == -1) {
+ error("%s accept: %s", __func__, strerror(errno));
+ return;
+ }
+
+ if (getpeereid(newsock, &euid, &egid) < 0) {
+ error("%s getpeereid failed: %s", __func__,
+ strerror(errno));
+ close(newsock);
+ return;
+ }
+ if ((euid != 0) && (getuid() != euid)) {
+ error("multiplex uid mismatch: peer euid %u != uid %u",
+ (u_int)euid, (u_int)getuid());
+ close(newsock);
+ return;
+ }
+ nc = channel_new("multiplex client", SSH_CHANNEL_MUX_CLIENT,
+ newsock, newsock, -1, c->local_window_max,
+ c->local_maxpacket, 0, "mux-control", 1);
+ nc->mux_rcb = c->mux_rcb;
+ debug3("%s: new mux channel %d fd %d", __func__,
+ nc->self, nc->sock);
+ /* establish state */
+ nc->mux_rcb(nc);
+ /* mux state transitions must not elicit protocol messages */
+ nc->flags |= CHAN_LOCAL;
+}
+
/* ARGSUSED */
static void
channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset)
@@ -1816,6 +1958,8 @@ channel_handler_init_20(void)
channel_pre[SSH_CHANNEL_AUTH_SOCKET] = &channel_pre_listener;
channel_pre[SSH_CHANNEL_CONNECTING] = &channel_pre_connecting;
channel_pre[SSH_CHANNEL_DYNAMIC] = &channel_pre_dynamic;
+ channel_pre[SSH_CHANNEL_MUX_LISTENER] = &channel_pre_listener;
+ channel_pre[SSH_CHANNEL_MUX_CLIENT] = &channel_pre_mux_client;
channel_post[SSH_CHANNEL_OPEN] = &channel_post_open;
channel_post[SSH_CHANNEL_PORT_LISTENER] = &channel_post_port_listener;
@@ -1824,6 +1968,8 @@ channel_handler_init_20(void)
channel_post[SSH_CHANNEL_AUTH_SOCKET] = &channel_post_auth_listener;
channel_post[SSH_CHANNEL_CONNECTING] = &channel_post_connecting;
channel_post[SSH_CHANNEL_DYNAMIC] = &channel_post_open;
+ channel_post[SSH_CHANNEL_MUX_LISTENER] = &channel_post_mux_listener;
+ channel_post[SSH_CHANNEL_MUX_CLIENT] = &channel_post_mux_client;
}
static void
@@ -1910,17 +2056,23 @@ static void
channel_handler(chan_fn *ftab[], fd_set *readset, fd_set *writeset)
{
static int did_init = 0;
- u_int i;
+ u_int i, oalloc;
Channel *c;
if (!did_init) {
channel_handler_init();
did_init = 1;
}
- for (i = 0; i < channels_alloc; i++) {
+ for (i = 0, oalloc = channels_alloc; i < oalloc; i++) {
c = channels[i];
if (c == NULL)
continue;
+ if (c->delayed) {
+ if (ftab == channel_pre)
+ c->delayed = 0;
+ else
+ continue;
+ }
if (ftab[c->type] != NULL)
(*ftab[c->type])(c, readset, writeset);
channel_garbage_collect(c);
@@ -2577,6 +2729,8 @@ channel_setup_fwd_listener(int type, const char *listen_addr,
}
channel_set_reuseaddr(sock);
+ if (ai->ai_family == AF_INET6)
+ sock_set_v6only(sock);
debug("Local forwarding listening on %s port %s.",
ntop, strport);
@@ -3108,13 +3262,8 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
continue;
}
}
-#ifdef IPV6_V6ONLY
- if (ai->ai_family == AF_INET6) {
- int on = 1;
- if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
- error("setsockopt IPV6_V6ONLY: %.100s", strerror(errno));
- }
-#endif
+ if (ai->ai_family == AF_INET6)
+ sock_set_v6only(sock);
if (x11_use_localhost)
channel_set_reuseaddr(sock);
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {