-/* $OpenBSD: channels.c,v 1.268 2007/01/03 03:01:40 stevesk Exp $ */
+/* $OpenBSD: channels.c,v 1.296 2009/05/25 06:48:00 andreas Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
#include <unistd.h>
#include <stdarg.h>
+#include "openbsd-compat/sys-queue.h"
#include "xmalloc.h"
#include "ssh.h"
#include "ssh1.h"
/* helper */
static void port_open_helper(Channel *c, char *rtype);
+/* non-blocking connect helpers */
+static int connect_next(struct channel_connect *);
+static void channel_connect_ctx_free(struct channel_connect *);
+
+
+static int hpn_disabled = 0;
+static int hpn_buffer_size = 2 * 1024 * 1024;
+
/* -- channel core */
+
+
Channel *
channel_by_id(int id)
{
*/
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);
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->local_window_max = window;
c->local_consumed = 0;
c->local_maxpacket = maxpack;
+ c->dynamic_window = 0;
c->remote_id = -1;
c->remote_name = xstrdup(remote_name);
c->remote_window = 0;
c->single_connection = 0;
c->detach_user = NULL;
c->detach_close = 0;
- c->confirm = NULL;
- c->confirm_ctx = NULL;
+ c->open_confirm = NULL;
+ c->open_confirm_ctx = NULL;
c->input_filter = NULL;
c->output_filter = NULL;
+ c->filter_ctx = NULL;
+ c->filter_cleanup = NULL;
+ TAILQ_INIT(&c->status_confirms);
debug("channel %d: new [%s]", found, remote_name);
return c;
}
{
char *s;
u_int i, n;
+ struct channel_confirm *cc;
for (n = 0, i = 0; i < channels_alloc; i++)
if (channels[i])
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);
+ TAILQ_REMOVE(&c->status_confirms, cc, entry);
+ 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);
}
}
void
-channel_register_confirm(int id, channel_callback_fn *fn, void *ctx)
+channel_register_status_confirm(int id, channel_confirm_cb *cb,
+ channel_confirm_abandon_cb *abandon_cb, void *ctx)
+{
+ struct channel_confirm *cc;
+ Channel *c;
+
+ if ((c = channel_lookup(id)) == NULL)
+ fatal("channel_register_expect: %d: bad id", id);
+
+ cc = xmalloc(sizeof(*cc));
+ cc->cb = cb;
+ cc->abandon_cb = abandon_cb;
+ cc->ctx = ctx;
+ TAILQ_INSERT_TAIL(&c->status_confirms, cc, entry);
+}
+
+void
+channel_register_open_confirm(int id, channel_callback_fn *fn, void *ctx)
{
Channel *c = channel_lookup(id);
if (c == NULL) {
- logit("channel_register_comfirm: %d: bad id", id);
+ logit("channel_register_open_confirm: %d: bad id", id);
return;
}
- c->confirm = fn;
- c->confirm_ctx = ctx;
+ c->open_confirm = fn;
+ c->open_confirm_ctx = ctx;
}
void
void
channel_register_filter(int id, channel_infilter_fn *ifn,
- channel_outfilter_fn *ofn)
+ 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->sock, writeset);
}
+int channel_tcpwinsz () {
+ u_int32_t tcpwinsz = 0;
+ socklen_t optsz = sizeof(tcpwinsz);
+ int ret = -1;
+
+ /* if we aren't on a socket return 128KB*/
+ if(!packet_connection_is_on_socket())
+ return(128*1024);
+ ret = getsockopt(packet_get_connection_in(),
+ SOL_SOCKET, SO_RCVBUF, &tcpwinsz, &optsz);
+ /* return no more than 64MB */
+ if ((ret == 0) && tcpwinsz > BUFFER_MAX_LEN_HPN)
+ tcpwinsz = BUFFER_MAX_LEN_HPN;
+ return(tcpwinsz);
+}
+
static void
channel_pre_open(Channel *c, fd_set *readset, fd_set *writeset)
{
u_int limit = compat20 ? c->remote_window : packet_get_maxsize();
+ /* check buffer limits */
+ if ((!c->tcpwinsz) || (c->dynamic_window > 0))
+ c->tcpwinsz = channel_tcpwinsz();
+
+ limit = MIN(limit, 2 * c->tcpwinsz);
+
if (c->istate == CHAN_INPUT_OPEN &&
limit > 0 &&
buffer_len(&c->input) < limit &&
}
}
/** XXX check close conditions, too */
- if (compat20 && c->efd != -1) {
+ if (compat20 && c->efd != -1 &&
+ !(c->istate == CHAN_INPUT_CLOSED && c->ostate == CHAN_OUTPUT_CLOSED)) {
if (c->extended_usage == CHAN_EXTENDED_WRITE &&
buffer_len(&c->extended) > 0)
FD_SET(c->efd, writeset);
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",
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) {
/*
{
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;
static void
channel_post_connecting(Channel *c, fd_set *readset, fd_set *writeset)
{
- int err = 0;
+ int err = 0, sock;
socklen_t sz = sizeof(err);
if (FD_ISSET(c->sock, writeset)) {
error("getsockopt SO_ERROR failed");
}
if (err == 0) {
- debug("channel %d: connected", c->self);
+ debug("channel %d: connected to %s port %d",
+ c->self, c->connect_ctx.host, c->connect_ctx.port);
+ channel_connect_ctx_free(&c->connect_ctx);
c->type = SSH_CHANNEL_OPEN;
if (compat20) {
packet_start(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
packet_put_int(c->self);
}
} else {
- debug("channel %d: not connected: %s",
+ debug("channel %d: connection failed: %s",
c->self, strerror(err));
+ /* Try next address, if any */
+ if ((sock = connect_next(&c->connect_ctx)) > 0) {
+ close(c->sock);
+ c->sock = c->rfd = c->wfd = sock;
+ channel_max_fd = channel_find_maxfd();
+ return;
+ }
+ /* Exhausted all addresses */
+ error("connect_to %.100s port %d: failed.",
+ c->connect_ctx.host, c->connect_ctx.port);
+ channel_connect_ctx_free(&c->connect_ctx);
if (compat20) {
packet_start(SSH2_MSG_CHANNEL_OPEN_FAILURE);
packet_put_int(c->remote_id);
channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset)
{
char buf[CHAN_RBUF];
- int len;
+ int len, force;
- if (c->rfd != -1 &&
- (c->detach_close || FD_ISSET(c->rfd, readset))) {
+ force = c->isatty && c->detach_close && c->istate != CHAN_INPUT_CLOSED;
+ 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 && !(c->isatty && c->detach_close))))
+ ((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",
/* 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))
+ 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 &&
!(c->flags & (CHAN_CLOSE_SENT|CHAN_CLOSE_RCVD)) &&
- c->local_window < c->local_window_max/2 &&
+ ((c->local_window_max - c->local_window >
+ c->local_maxpacket*3) ||
+ c->local_window < c->local_window_max/2) &&
c->local_consumed > 0) {
+ u_int addition = 0;
+ /* adjust max window size if we are in a dynamic environment */
+ if (c->dynamic_window && (c->tcpwinsz > c->local_window_max)) {
+ /* grow the window somewhat aggressively to maintain pressure */
+ addition = 1.5*(c->tcpwinsz - c->local_window_max);
+ c->local_window_max += addition;
+ }
packet_start(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
packet_put_int(c->remote_id);
- packet_put_int(c->local_consumed);
+ packet_put_int(c->local_consumed + addition);
packet_send();
debug2("channel %d: window %d sent adjust %d",
c->self, c->local_window,
c->local_consumed);
- c->local_window += c->local_consumed;
+ c->local_window += c->local_consumed + addition;
c->local_consumed = 0;
}
return 1;
/* If there is data to send to the connection, enqueue some of it now. */
-void
+int
channel_output_poll(void)
{
Channel *c;
u_int i, len;
+ int packet_length = 0;
for (i = 0; i < channels_alloc; i++) {
c = channels[i];
packet_start(SSH2_MSG_CHANNEL_DATA);
packet_put_int(c->remote_id);
packet_put_string(data, dlen);
- packet_send();
+ packet_length = packet_send();
c->remote_window -= dlen + 4;
xfree(data);
}
SSH2_MSG_CHANNEL_DATA : SSH_MSG_CHANNEL_DATA);
packet_put_int(c->remote_id);
packet_put_string(buffer_ptr(&c->input), len);
- packet_send();
+ packet_length = packet_send();
buffer_consume(&c->input, len);
c->remote_window -= len;
}
packet_put_int(c->remote_id);
packet_put_int(SSH2_EXTENDED_DATA_STDERR);
packet_put_string(buffer_ptr(&c->extended), len);
- packet_send();
+ packet_length = packet_send();
buffer_consume(&c->extended, len);
c->remote_window -= len;
debug2("channel %d: sent ext data %d", c->self, len);
}
}
+ return (packet_length);
}
return;
/* Get the data. */
- data = packet_get_string(&data_len);
+ data = packet_get_string_ptr(&data_len);
/*
* Ignore data for protocol > 1.3 if output end is no longer open.
c->local_window -= data_len;
c->local_consumed += data_len;
}
- xfree(data);
return;
}
if (data_len > c->local_window) {
logit("channel %d: rcvd too much data %d, win %d",
c->self, data_len, c->local_window);
- xfree(data);
return;
}
c->local_window -= data_len;
}
- packet_check_eom();
if (c->datagram)
buffer_put_string(&c->output, data, data_len);
else
buffer_append(&c->output, data, data_len);
- xfree(data);
+ packet_check_eom();
}
/* ARGSUSED */
if (compat20) {
c->remote_window = packet_get_int();
c->remote_maxpacket = packet_get_int();
- if (c->confirm) {
+ if (c->open_confirm) {
debug2("callback start");
- c->confirm(c->self, c->confirm_ctx);
+ c->open_confirm(c->self, c->open_confirm_ctx);
debug2("callback done");
}
debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
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 = NULL;
u_short host_port;
char *host, *originator_string;
- int remote_id, sock = -1;
+ int remote_id;
remote_id = packet_get_int();
host = packet_get_string(NULL);
originator_string = xstrdup("unknown (remote did not supply name)");
}
packet_check_eom();
- sock = channel_connect_to(host, host_port);
- if (sock != -1) {
- c = channel_new("connected socket",
- SSH_CHANNEL_CONNECTING, sock, sock, -1, 0, 0, 0,
- originator_string, 1);
- c->remote_id = remote_id;
- }
+ c = channel_connect_to(host, host_port,
+ "connected socket", originator_string);
xfree(originator_string);
+ xfree(host);
if (c == NULL) {
packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE);
packet_put_int(remote_id);
packet_send();
- }
- xfree(host);
+ } else
+ c->remote_id = remote_id;
}
+/* ARGSUSED */
+void
+channel_input_status_confirm(int type, u_int32_t seq, void *ctxt)
+{
+ Channel *c;
+ struct channel_confirm *cc;
+ int id;
+
+ /* Reset keepalive timeout */
+ packet_set_alive_timeouts(0);
+
+ id = packet_get_int();
+ packet_check_eom();
+
+ debug2("channel_input_status_confirm: type %d id %d", type, id);
+
+ if ((c = channel_lookup(id)) == NULL) {
+ logit("channel_input_status_confirm: %d: unknown", id);
+ return;
+ }
+ ;
+ if ((cc = TAILQ_FIRST(&c->status_confirms)) == NULL)
+ return;
+ cc->cb(type, c, cc->ctx);
+ TAILQ_REMOVE(&c->status_confirms, cc, entry);
+ bzero(cc, sizeof(*cc));
+ xfree(cc);
+}
/* -- tcp forwarding */
IPv4or6 = af;
}
+
+void
+channel_set_hpn(int external_hpn_disabled, int external_hpn_buffer_size)
+{
+ hpn_disabled = external_hpn_disabled;
+ hpn_buffer_size = external_hpn_buffer_size;
+ debug("HPN Disabled: %d, HPN Buffer Size: %d", hpn_disabled, hpn_buffer_size);
+}
+
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;
}
wildcard = 1;
} else if (gateway_ports || is_client) {
if (((datafellows & SSH_OLD_FORWARD_ADDR) &&
- strcmp(listen_addr, "0.0.0.0") == 0) ||
+ 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;
if (addr == NULL) {
/* This really shouldn't happen */
packet_disconnect("getaddrinfo: fatal error: %s",
- gai_strerror(r));
+ ssh_gai_strerror(r));
} else {
error("channel_setup_fwd_listener: "
- "getaddrinfo(%.64s): %s", addr, gai_strerror(r));
+ "getaddrinfo(%.64s): %s", addr,
+ ssh_gai_strerror(r));
}
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);
- 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. */
+ /* explicitly test for hpn disabled option. if true use smaller window size */
+ if (hpn_disabled)
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));
+ else
+ c = channel_new("port listener", type, sock, sock, -1,
+ hpn_buffer_size, CHAN_TCP_PACKET_DEFAULT,
+ 0, "port listener", 1);
+ 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);
}
/*
num_adm_permitted_opens = 0;
}
-/* return socket to remote host, port */
+void
+channel_print_adm_permitted_opens(void)
+{
+ 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 */
static int
-connect_to(const char *host, u_short port)
+connect_next(struct channel_connect *cctx)
{
- struct addrinfo hints, *ai, *aitop;
+ int sock, saved_errno;
char ntop[NI_MAXHOST], strport[NI_MAXSERV];
- int gaierr;
- int sock = -1;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = IPv4or6;
- hints.ai_socktype = SOCK_STREAM;
- snprintf(strport, sizeof strport, "%d", port);
- if ((gaierr = getaddrinfo(host, strport, &hints, &aitop)) != 0) {
- error("connect_to %.100s: unknown host (%s)", host,
- gai_strerror(gaierr));
- return -1;
- }
- for (ai = aitop; ai; ai = ai->ai_next) {
- if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ for (; cctx->ai; cctx->ai = cctx->ai->ai_next) {
+ if (cctx->ai->ai_family != AF_INET &&
+ cctx->ai->ai_family != AF_INET6)
continue;
- if (getnameinfo(ai->ai_addr, ai->ai_addrlen, ntop, sizeof(ntop),
- strport, sizeof(strport), NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
- error("connect_to: getnameinfo failed");
+ if (getnameinfo(cctx->ai->ai_addr, cctx->ai->ai_addrlen,
+ ntop, sizeof(ntop), strport, sizeof(strport),
+ NI_NUMERICHOST|NI_NUMERICSERV) != 0) {
+ error("connect_next: getnameinfo failed");
continue;
}
- sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
- if (sock < 0) {
- if (ai->ai_next == NULL)
+ if ((sock = socket(cctx->ai->ai_family, cctx->ai->ai_socktype,
+ cctx->ai->ai_protocol)) == -1) {
+ if (cctx->ai->ai_next == NULL)
error("socket: %.100s", strerror(errno));
else
verbose("socket: %.100s", strerror(errno));
}
if (set_nonblock(sock) == -1)
fatal("%s: set_nonblock(%d)", __func__, sock);
- if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
- errno != EINPROGRESS) {
- error("connect_to %.100s port %s: %.100s", ntop, strport,
+ if (connect(sock, cctx->ai->ai_addr,
+ cctx->ai->ai_addrlen) == -1 && errno != EINPROGRESS) {
+ debug("connect_next: host %.100s ([%.100s]:%s): "
+ "%.100s", cctx->host, ntop, strport,
strerror(errno));
+ saved_errno = errno;
close(sock);
+ errno = saved_errno;
continue; /* fail -- try next */
}
- break; /* success */
+ debug("connect_next: host %.100s ([%.100s]:%s) "
+ "in progress, fd=%d", cctx->host, ntop, strport, sock);
+ cctx->ai = cctx->ai->ai_next;
+ set_nodelay(sock);
+ return sock;
+ }
+ return -1;
+}
+
+static void
+channel_connect_ctx_free(struct channel_connect *cctx)
+{
+ xfree(cctx->host);
+ if (cctx->aitop)
+ freeaddrinfo(cctx->aitop);
+ bzero(cctx, sizeof(*cctx));
+ cctx->host = NULL;
+ cctx->ai = cctx->aitop = NULL;
+}
+/* Return CONNECTING channel to remote host, port */
+static Channel *
+connect_to(const char *host, u_short port, char *ctype, char *rname)
+{
+ struct addrinfo hints;
+ int gaierr;
+ int sock = -1;
+ char strport[NI_MAXSERV];
+ 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;
+ snprintf(strport, sizeof strport, "%d", port);
+ if ((gaierr = getaddrinfo(host, strport, &hints, &cctx.aitop)) != 0) {
+ error("connect_to %.100s: unknown host (%s)", host,
+ ssh_gai_strerror(gaierr));
+ return NULL;
}
- freeaddrinfo(aitop);
- if (!ai) {
- error("connect_to %.100s port %d: failed.", host, port);
- return -1;
+
+ cctx.host = xstrdup(host);
+ cctx.port = port;
+ cctx.ai = cctx.aitop;
+
+ if ((sock = connect_next(&cctx)) == -1) {
+ error("connect to %.100s port %d failed: %s",
+ host, port, strerror(errno));
+ channel_connect_ctx_free(&cctx);
+ return NULL;
}
- /* success */
- set_nodelay(sock);
- return sock;
+ c = channel_new(ctype, SSH_CHANNEL_CONNECTING, sock, sock, -1,
+ CHAN_TCP_WINDOW_DEFAULT, CHAN_TCP_PACKET_DEFAULT, 0, rname, 1);
+ c->connect_ctx = cctx;
+ return c;
}
-int
-channel_connect_by_listen_address(u_short listen_port)
+Channel *
+channel_connect_by_listen_address(u_short listen_port, char *ctype, char *rname)
{
int i;
- for (i = 0; i < num_permitted_opens; i++)
+ for (i = 0; i < num_permitted_opens; i++) {
if (permitted_opens[i].host_to_connect != NULL &&
- permitted_opens[i].listen_port == listen_port)
+ permitted_opens[i].listen_port == listen_port) {
return connect_to(
permitted_opens[i].host_to_connect,
- permitted_opens[i].port_to_connect);
+ permitted_opens[i].port_to_connect, ctype, rname);
+ }
+ }
error("WARNING: Server requests forwarding for unknown listen_port %d",
listen_port);
- return -1;
+ return NULL;
}
/* Check if connecting to that port is permitted and connect. */
-int
-channel_connect_to(const char *host, u_short port)
+Channel *
+channel_connect_to(const char *host, u_short port, char *ctype, char *rname)
{
int i, permit, permit_adm = 1;
if (!permit || !permit_adm) {
logit("Received request to connect to host %.100s port %d, "
"but the request was denied.", host, port);
- return -1;
+ return NULL;
}
- return connect_to(host, port);
+ return connect_to(host, port, ctype, rname);
}
void
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof strport, "%d", port);
if ((gaierr = getaddrinfo(NULL, strport, &hints, &aitop)) != 0) {
- error("getaddrinfo: %.100s", gai_strerror(gaierr));
+ error("getaddrinfo: %.100s", ssh_gai_strerror(gaierr));
return -1;
}
for (ai = aitop; ai; ai = ai->ai_next) {
error("setsockopt IPV6_V6ONLY: %.100s", strerror(errno));
}
#endif
- channel_set_reuseaddr(sock);
+ if (x11_use_localhost)
+ channel_set_reuseaddr(sock);
if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
debug2("bind port %d: %.100s", port, strerror(errno));
close(sock);
- if (ai->ai_next)
- continue;
-
for (n = 0; n < num_socks; n++) {
close(socks[n]);
}
break;
}
socks[num_socks++] = sock;
-#ifndef DONT_TRY_OTHER_AF
if (num_socks == NUM_SOCKS)
break;
-#else
- if (x11_use_localhost) {
- if (num_socks == NUM_SOCKS)
- break;
- } else {
- break;
- }
-#endif
}
freeaddrinfo(aitop);
if (num_socks > 0)
*chanids = xcalloc(num_socks + 1, sizeof(**chanids));
for (n = 0; n < num_socks; n++) {
sock = socks[n];
+ /* Is this really necassary? */
+ if (hpn_disabled)
nc = channel_new("x11 listener",
SSH_CHANNEL_X11_LISTENER, sock, sock, -1,
CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT,
0, "X11 inet listener", 1);
+ else
+ nc = channel_new("x11 listener",
+ SSH_CHANNEL_X11_LISTENER, sock, sock, -1,
+ hpn_buffer_size, CHAN_X11_PACKET_DEFAULT,
+ 0, "X11 inet listener", 1);
nc->single_connection = single_connection;
(*chanids)[n] = nc->self;
}
}
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]
hints.ai_socktype = SOCK_STREAM;
snprintf(strport, sizeof strport, "%u", 6000 + display_number);
if ((gaierr = getaddrinfo(buf, strport, &hints, &aitop)) != 0) {
- error("%.100s: unknown host. (%s)", buf, gai_strerror(gaierr));
+ error("%.100s: unknown host. (%s)", buf,
+ ssh_gai_strerror(gaierr));
return -1;
}
for (ai = aitop; ai; ai = ai->ai_next) {