-/* $OpenBSD: channels.c,v 1.279 2008/06/12 03:40:52 djm Exp $ */
+/* $OpenBSD: channels.c,v 1.302 2010/01/26 01:28:35 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
#include <arpa/inet.h>
#include <errno.h>
+#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
*/
static void
channel_register_fds(Channel *c, int rfd, int wfd, int efd,
- int extusage, int nonblock)
+ int extusage, int nonblock, int is_tty)
{
/* Update the maximum file descriptor value. */
channel_max_fd = MAX(channel_max_fd, rfd);
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;
- /* XXX ugly hack: nonblock is only set by the server */
- if (nonblock && isatty(c->rfd)) {
+ if ((c->isatty = is_tty) != 0)
debug2("channel %d: rfd %d isatty", c->self, c->rfd);
- c->isatty = 1;
- if (!isatty(c->wfd)) {
- error("channel %d: wfd %d is not a tty?",
- c->self, c->wfd);
- }
- } else {
- c->isatty = 0;
- }
- c->wfd_isatty = isatty(c->wfd);
+ c->wfd_isatty = is_tty || isatty(c->wfd);
/* enable nonblocking mode */
if (nonblock) {
buffer_init(&c->input);
buffer_init(&c->output);
buffer_init(&c->extended);
+ c->path = NULL;
c->ostate = CHAN_OUTPUT_OPEN;
c->istate = CHAN_INPUT_OPEN;
c->flags = 0;
- channel_register_fds(c, rfd, wfd, efd, extusage, nonblock);
+ channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, 0);
c->self = found;
c->type = type;
c->ctype = ctype;
c->open_confirm_ctx = NULL;
c->input_filter = NULL;
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;
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);
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);
xfree(c->remote_name);
c->remote_name = NULL;
}
+ if (c->path) {
+ xfree(c->path);
+ c->path = NULL;
+ }
while ((cc = TAILQ_FIRST(&c->status_confirms)) != NULL) {
if (cc->abandon_cb != NULL)
cc->abandon_cb(c, cc->ctx);
bzero(cc, sizeof(*cc));
xfree(cc);
}
+ if (c->filter_cleanup != NULL && c->filter_ctx != NULL)
+ c->filter_cleanup(c->self, c->filter_ctx);
channels[c->self] = NULL;
xfree(c);
}
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:
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:
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:
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:
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:
Channel *c = channel_lookup(id);
if (c == NULL) {
- logit("channel_register_open_comfirm: %d: bad id", id);
+ logit("channel_register_open_confirm: %d: bad id", id);
return;
}
c->open_confirm = fn;
void
channel_register_filter(int id, channel_infilter_fn *ifn,
- channel_outfilter_fn *ofn, void *ctx)
+ channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx)
{
Channel *c = channel_lookup(id);
c->input_filter = ifn;
c->output_filter = ofn;
c->filter_ctx = ctx;
+ c->filter_cleanup = cfn;
}
void
channel_set_fds(int id, int rfd, int wfd, int efd,
- int extusage, int nonblock, u_int window_max)
+ int extusage, int nonblock, int is_tty, u_int window_max)
{
Channel *c = channel_lookup(id);
if (c == NULL || c->type != SSH_CHANNEL_LARVAL)
fatal("channel_activate for non-larval channel %d.", id);
- channel_register_fds(c, rfd, wfd, efd, extusage, nonblock);
+ channel_register_fds(c, rfd, wfd, efd, extusage, nonblock, is_tty);
c->type = SSH_CHANNEL_OPEN;
c->local_window = c->local_window_max = window_max;
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
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 */
}
}
+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
channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
{
char *p, *host;
- u_int len, have, i, found;
+ u_int len, have, i, found, need;
char username[256];
struct {
u_int8_t version;
if (have < len)
return 0;
p = buffer_ptr(&c->input);
+
+ need = 1;
+ /* SOCKS4A uses an invalid IP address 0.0.0.x */
+ if (p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] != 0) {
+ debug2("channel %d: socks4a request", c->self);
+ /* ... and needs an extra string (the hostname) */
+ need = 2;
+ }
+ /* Check for terminating NUL on the string(s) */
for (found = 0, i = len; i < have; i++) {
if (p[i] == '\0') {
- found = 1;
- break;
+ found++;
+ if (found == need)
+ break;
}
if (i > 1024) {
/* the peer is probably sending garbage */
return -1;
}
}
- if (!found)
+ if (found < need)
return 0;
buffer_get(&c->input, (char *)&s4_req.version, 1);
buffer_get(&c->input, (char *)&s4_req.command, 1);
p = buffer_ptr(&c->input);
len = strlen(p);
debug2("channel %d: decode socks4: user %s/%d", c->self, p, len);
+ len++; /* trailing '\0' */
if (len > have)
fatal("channel %d: decode socks4: len %d > have %d",
c->self, len, have);
strlcpy(username, p, sizeof(username));
buffer_consume(&c->input, len);
- buffer_consume(&c->input, 1); /* trailing '\0' */
- host = inet_ntoa(s4_req.dest_addr);
- strlcpy(c->path, host, sizeof(c->path));
+ if (c->path != NULL) {
+ xfree(c->path);
+ c->path = NULL;
+ }
+ if (need == 1) { /* SOCKS4: one string */
+ host = inet_ntoa(s4_req.dest_addr);
+ c->path = xstrdup(host);
+ } else { /* SOCKS4A: two strings */
+ have = buffer_len(&c->input);
+ p = buffer_ptr(&c->input);
+ len = strlen(p);
+ debug2("channel %d: decode socks4a: host %s/%d",
+ c->self, p, len);
+ len++; /* trailing '\0' */
+ if (len > have)
+ fatal("channel %d: decode socks4a: len %d > have %d",
+ c->self, len, have);
+ if (len > NI_MAXHOST) {
+ error("channel %d: hostname \"%.100s\" too long",
+ c->self, p);
+ return -1;
+ }
+ c->path = xstrdup(p);
+ buffer_consume(&c->input, len);
+ }
c->host_port = ntohs(s4_req.dest_port);
debug2("channel %d: dynamic request: socks4 host %s port %u command %u",
- c->self, host, c->host_port, s4_req.command);
+ c->self, c->path, c->host_port, s4_req.command);
if (s4_req.command != 1) {
- debug("channel %d: cannot handle: socks4 cn %d",
- c->self, s4_req.command);
+ debug("channel %d: cannot handle: %s cn %d",
+ c->self, need == 1 ? "SOCKS4" : "SOCKS4A", s4_req.command);
return -1;
}
s4_rsp.version = 0; /* vn: 0 for reply */
u_int8_t atyp;
} s5_req, s5_rsp;
u_int16_t dest_port;
- u_char *p, dest_addr[255+1];
+ u_char *p, dest_addr[255+1], ntop[INET6_ADDRSTRLEN];
u_int have, need, i, found, nmethods, addrlen, af;
debug2("channel %d: decode socks5", c->self);
buffer_get(&c->input, (char *)&dest_addr, addrlen);
buffer_get(&c->input, (char *)&dest_port, 2);
dest_addr[addrlen] = '\0';
- if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
- strlcpy(c->path, (char *)dest_addr, sizeof(c->path));
- else if (inet_ntop(af, dest_addr, c->path, sizeof(c->path)) == NULL)
- return -1;
+ if (c->path != NULL) {
+ xfree(c->path);
+ c->path = NULL;
+ }
+ if (s5_req.atyp == SSH_SOCKS5_DOMAIN) {
+ if (addrlen >= NI_MAXHOST) {
+ error("channel %d: dynamic request: socks5 hostname "
+ "\"%.100s\" too long", c->self, dest_addr);
+ return -1;
+ }
+ c->path = xstrdup(dest_addr);
+ } else {
+ if (inet_ntop(af, dest_addr, ntop, sizeof(ntop)) == NULL)
+ return -1;
+ c->path = xstrdup(ntop);
+ }
c->host_port = ntohs(dest_port);
debug2("channel %d: dynamic request: socks5 host %s port %u command %u",
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)
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. */
channel_post_x11_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
Channel *nc;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
int newsock;
socklen_t addrlen;
char buf[16384], *remote_ipaddr;
if (FD_ISSET(c->sock, readset)) {
debug("X11 connection requested.");
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (c->single_connection) {
debug2("single_connection: closing X11 listener.");
channel_close_fd(&c->sock);
channel_post_port_listener(Channel *c, fd_set *readset, fd_set *writeset)
{
Channel *nc;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
int newsock, nextstate;
socklen_t addrlen;
char *rtype;
}
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
error("accept: %.100s", strerror(errno));
return;
c->local_window_max, c->local_maxpacket, 0, rtype, 1);
nc->listening_port = c->listening_port;
nc->host_port = c->host_port;
- strlcpy(nc->path, c->path, sizeof(nc->path));
+ 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);
- }
}
}
{
Channel *nc;
int newsock;
- struct sockaddr addr;
+ struct sockaddr_storage addr;
socklen_t addrlen;
if (FD_ISSET(c->sock, readset)) {
addrlen = sizeof(addr);
- newsock = accept(c->sock, &addr, &addrlen);
+ newsock = accept(c->sock, (struct sockaddr *)&addr, &addrlen);
if (newsock < 0) {
error("accept from auth socket: %.100s", strerror(errno));
return;
if (c->rfd != -1 && (force || FD_ISSET(c->rfd, readset))) {
errno = 0;
len = read(c->rfd, buf, sizeof(buf));
- if (len < 0 && (errno == EINTR || (errno == EAGAIN && !force)))
+ if (len < 0 && (errno == EINTR ||
+ ((errno == EAGAIN || errno == EWOULDBLOCK) && !force)))
return 1;
#ifndef PTY_ZEROREAD
if (len <= 0) {
c->local_consumed += dlen + 4;
len = write(c->wfd, buf, dlen);
xfree(data);
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 && (errno == EINTR || errno == EAGAIN ||
+ errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
if (c->type != SSH_CHANNEL_OPEN)
#endif
len = write(c->wfd, buf, dlen);
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 &&
+ (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
if (c->type != SSH_CHANNEL_OPEN) {
}
return -1;
}
+#ifndef BROKEN_TCGETATTR_ICANON
if (compat20 && c->isatty && dlen >= 1 && buf[0] != '\r') {
if (tcgetattr(c->wfd, &tio) == 0 &&
!(tio.c_lflag & ECHO) && (tio.c_lflag & ICANON)) {
packet_send();
}
}
+#endif
buffer_consume(&c->output, len);
if (compat20 && len > 0) {
c->local_consumed += len;
buffer_len(&c->extended));
debug2("channel %d: written %d to efd %d",
c->self, len, c->efd);
- if (len < 0 && (errno == EINTR || errno == EAGAIN))
+ if (len < 0 && (errno == EINTR || errno == EAGAIN ||
+ errno == EWOULDBLOCK))
return 1;
if (len <= 0) {
debug2("channel %d: closing write-efd %d",
len = read(c->efd, buf, sizeof(buf));
debug2("channel %d: read %d from efd %d",
c->self, len, c->efd);
- if (len < 0 && (errno == EINTR ||
- (errno == EAGAIN && !c->detach_close)))
+ if (len < 0 && (errno == EINTR || ((errno == EAGAIN ||
+ errno == EWOULDBLOCK) && !c->detach_close)))
return 1;
if (len <= 0) {
debug2("channel %d: closing read-efd %d",
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))
- 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)
{
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)
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;
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
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);
xfree(lang);
}
packet_check_eom();
- /* Free the channel. This will also close the socket. */
- channel_free(c);
+ /* Schedule the channel for cleanup/deletion. */
+ chan_mark_dead(c);
}
/* ARGSUSED */
{
Channel *c;
struct channel_confirm *cc;
- int remote_id;
+ int id;
/* Reset keepalive timeout */
- keep_alive_timeouts = 0;
+ packet_set_alive_timeouts(0);
- remote_id = packet_get_int();
+ id = packet_get_int();
packet_check_eom();
- debug2("channel_input_confirm: type %d id %d", type, remote_id);
+ debug2("channel_input_status_confirm: type %d id %d", type, id);
- if ((c = channel_lookup(remote_id)) == NULL) {
- logit("channel_input_success_failure: %d: unknown", remote_id);
+ if ((c = channel_lookup(id)) == NULL) {
+ logit("channel_input_status_confirm: %d: unknown", id);
return;
}
;
}
static int
-channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_port,
+channel_setup_fwd_listener(int type, const char *listen_addr,
+ u_short listen_port, int *allocated_listen_port,
const char *host_to_connect, u_short port_to_connect, int gateway_ports)
{
Channel *c;
struct addrinfo hints, *ai, *aitop;
const char *host, *addr;
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
+ in_port_t *lport_p;
host = (type == SSH_CHANNEL_RPORT_LISTENER) ?
listen_addr : host_to_connect;
error("No forward host name.");
return 0;
}
- if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) {
+ if (strlen(host) >= NI_MAXHOST) {
error("Forward host name too long.");
return 0;
}
}
return 0;
}
-
+ if (allocated_listen_port != NULL)
+ *allocated_listen_port = 0;
for (ai = aitop; ai; ai = ai->ai_next) {
- if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ switch (ai->ai_family) {
+ case AF_INET:
+ lport_p = &((struct sockaddr_in *)ai->ai_addr)->
+ sin_port;
+ break;
+ case AF_INET6:
+ lport_p = &((struct sockaddr_in6 *)ai->ai_addr)->
+ sin6_port;
+ break;
+ default:
continue;
+ }
+ /*
+ * If allocating a port for -R forwards, then use the
+ * same port for all address families.
+ */
+ if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 &&
+ allocated_listen_port != NULL && *allocated_listen_port > 0)
+ *lport_p = htons(*allocated_listen_port);
+
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop),
strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
error("channel_setup_fwd_listener: getnameinfo failed");
}
channel_set_reuseaddr(sock);
+ if (ai->ai_family == AF_INET6)
+ sock_set_v6only(sock);
- debug("Local forwarding listening on %s port %s.", ntop, strport);
+ debug("Local forwarding listening on %s port %s.",
+ ntop, strport);
/* Bind the socket to the address. */
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
close(sock);
continue;
}
+
+ /*
+ * listen_port == 0 requests a dynamically allocated port -
+ * record what we got.
+ */
+ if (type == SSH_CHANNEL_RPORT_LISTENER && listen_port == 0 &&
+ allocated_listen_port != NULL &&
+ *allocated_listen_port == 0) {
+ *allocated_listen_port = get_sock_port(sock, 1);
+ debug("Allocated listen port %d",
+ *allocated_listen_port);
+ }
+
/* Allocate a channel number for the socket. */
c = channel_new("port listener", type, sock, sock, -1,
CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT,
0, "port listener", 1);
- strlcpy(c->path, host, sizeof(c->path));
+ c->path = xstrdup(host);
c->host_port = port_to_connect;
c->listening_port = listen_port;
success = 1;
Channel *c = channels[i];
if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER &&
- strncmp(c->path, host, sizeof(c->path)) == 0 &&
- c->listening_port == port) {
+ strcmp(c->path, host) == 0 && c->listening_port == port) {
debug2("%s: close channel %d", __func__, i);
channel_free(c);
found = 1;
const char *host_to_connect, u_short port_to_connect, int gateway_ports)
{
return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER,
- listen_host, listen_port, host_to_connect, port_to_connect,
+ listen_host, listen_port, NULL, host_to_connect, port_to_connect,
gateway_ports);
}
/* protocol v2 remote port fwd, used by sshd */
int
channel_setup_remote_fwd_listener(const char *listen_address,
- u_short listen_port, int gateway_ports)
+ u_short listen_port, int *allocated_listen_port, int gateway_ports)
{
return channel_setup_fwd_listener(SSH_CHANNEL_RPORT_LISTENER,
- listen_address, listen_port, NULL, 0, gateway_ports);
+ listen_address, listen_port, allocated_listen_port,
+ NULL, 0, gateway_ports);
}
/*
void
channel_print_adm_permitted_opens(void)
{
- static int i;
+ int i;
+ printf("permitopen");
+ if (num_adm_permitted_opens == 0) {
+ printf(" any\n");
+ return;
+ }
for (i = 0; i < num_adm_permitted_opens; i++)
if (permitted_adm_opens[i].host_to_connect != NULL)
printf(" %s:%d", permitted_adm_opens[i].host_to_connect,
permitted_adm_opens[i].port_to_connect);
+ printf("\n");
}
/* Try to start non-blocking connect to next host in cctx list */
struct channel_connect cctx;
Channel *c;
+ memset(&cctx, 0, sizeof(cctx));
memset(&hints, 0, sizeof(hints));
hints.ai_family = IPv4or6;
hints.ai_socktype = SOCK_STREAM;
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) {
}
static int
-connect_local_xsocket(u_int dnr)
+connect_local_xsocket_path(const char *pathname)
{
int sock;
struct sockaddr_un addr;
error("socket: %.100s", strerror(errno));
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
- snprintf(addr.sun_path, sizeof addr.sun_path, _PATH_UNIX_X, dnr);
+ strlcpy(addr.sun_path, pathname, sizeof addr.sun_path);
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
return sock;
close(sock);
return -1;
}
+static int
+connect_local_xsocket(u_int dnr)
+{
+ char buf[1024];
+ snprintf(buf, sizeof buf, _PATH_UNIX_X, dnr);
+ return connect_local_xsocket_path(buf);
+}
+
int
x11_connect_display(void)
{
* connection to the real X server.
*/
+ /* Check if the display is from launchd. */
+#ifdef __APPLE__
+ if (strncmp(display, "/tmp/launch", 11) == 0) {
+ sock = connect_local_xsocket_path(display);
+ if (sock < 0)
+ return -1;
+
+ /* OK, we now have a connection to the display. */
+ return sock;
+ }
+#endif
/*
* Check if it is a unix domain socket. Unix domain displays are in
* one of the following formats: unix:d[.s], :d[.s], ::d[.s]