]> andersk Git - gssapi-openssh.git/blobdiff - openssh/channels.c
merged OpenSSH 5.3p1 to trunk
[gssapi-openssh.git] / openssh / channels.c
index 520bff82292345903f6619c6b4d42b9287a8dc53..1fbbfffae58e0f19b314bf9b8646255a9beb6db6 100644 (file)
@@ -1,3 +1,4 @@
+/* $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 "includes.h"
-RCSID("$OpenBSD: channels.c,v 1.173 2002/04/22 21:04:52 markus Exp $");
 
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "openbsd-compat/sys-queue.h"
+#include "xmalloc.h"
 #include "ssh.h"
 #include "ssh1.h"
 #include "ssh2.h"
 #include "packet.h"
-#include "xmalloc.h"
-#include "uidswap.h"
 #include "log.h"
 #include "misc.h"
+#include "buffer.h"
 #include "channels.h"
 #include "compat.h"
 #include "canohost.h"
@@ -56,7 +77,6 @@ RCSID("$OpenBSD: channels.c,v 1.173 2002/04/22 21:04:52 markus Exp $");
 #include "authfd.h"
 #include "pathnames.h"
 
-
 /* -- channel core */
 
 /*
@@ -69,7 +89,7 @@ static Channel **channels = NULL;
  * Size of the channel array.  All slots of the array must always be
  * initialized (at least the type field); unused slots set to NULL
  */
-static int channels_alloc = 0;
+static u_int channels_alloc = 0;
 
 /*
  * Maximum file descriptor value used in any of the channels.  This is
@@ -92,11 +112,18 @@ typedef struct {
        u_short listen_port;            /* Remote side should listen port number. */
 } ForwardPermission;
 
-/* List of all permitted host/port pairs to connect. */
+/* List of all permitted host/port pairs to connect by the user. */
 static ForwardPermission permitted_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
 
-/* Number of permitted host/port pairs in the array. */
+/* List of all permitted host/port pairs to connect by the admin. */
+static ForwardPermission permitted_adm_opens[SSH_MAX_FORWARDS_PER_DIRECTION];
+
+/* Number of permitted host/port pairs in the array permitted by the user. */
 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;
+
 /*
  * 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
@@ -110,6 +137,9 @@ static int all_opens_permitted = 0;
 /* Maximum number of fake X11 displays to try. */
 #define MAX_DISPLAYS  1000
 
+/* Saved X11 local (client) display. */
+static char *x11_saved_display = NULL;
+
 /* Saved X11 authentication protocol name. */
 static char *x11_saved_proto = NULL;
 
@@ -121,7 +151,7 @@ static u_int x11_saved_data_len = 0;
  * Fake X11 authentication data.  This is what the server will be sending us;
  * we should replace any occurrences of this by the real data.
  */
-static char *x11_fake_data = NULL;
+static u_char *x11_fake_data = NULL;
 static u_int x11_fake_data_len;
 
 
@@ -129,43 +159,75 @@ static u_int x11_fake_data_len;
 
 #define        NUM_SOCKS       10
 
-/* Name and directory of socket for authentication agent forwarding. */
-static char *auth_sock_name = NULL;
-static char *auth_sock_dir = NULL;
-
 /* AF_UNSPEC or AF_INET or AF_INET6 */
 static int IPv4or6 = AF_UNSPEC;
 
 /* 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_lookup(int id)
+channel_by_id(int id)
 {
        Channel *c;
 
-       if (id < 0 || id >= channels_alloc) {
-               log("channel_lookup: %d: bad id", id);
+       if (id < 0 || (u_int)id >= channels_alloc) {
+               logit("channel_by_id: %d: bad id", id);
                return NULL;
        }
        c = channels[id];
        if (c == NULL) {
-               log("channel_lookup: %d: bad id: channel free", id);
+               logit("channel_by_id: %d: bad id: channel free", id);
                return NULL;
        }
        return c;
 }
 
+/*
+ * Returns the channel if it is allowed to receive protocol messages.
+ * Private channels, like listening sockets, may not receive messages.
+ */
+Channel *
+channel_lookup(int id)
+{
+       Channel *c;
+
+       if ((c = channel_by_id(id)) == NULL)
+               return (NULL);
+
+       switch (c->type) {
+       case SSH_CHANNEL_X11_OPEN:
+       case SSH_CHANNEL_LARVAL:
+       case SSH_CHANNEL_CONNECTING:
+       case SSH_CHANNEL_DYNAMIC:
+       case SSH_CHANNEL_OPENING:
+       case SSH_CHANNEL_OPEN:
+       case SSH_CHANNEL_INPUT_DRAINING:
+       case SSH_CHANNEL_OUTPUT_DRAINING:
+               return (c);
+       }
+       logit("Non-public channel %d, type %d.", id, c->type);
+       return (NULL);
+}
+
 /*
  * Register filedescriptors for a channel, used when allocating a channel or
  * when the channel consumer/producer is ready, e.g. shell exec'd
  */
-
 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);
@@ -177,20 +239,13 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
        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)) {
-               debug("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;
-       }
+       if ((c->isatty = is_tty) != 0)
+               debug2("channel %d: rfd %d isatty", c->self, c->rfd);
+       c->wfd_isatty = is_tty || isatty(c->wfd);
 
        /* enable nonblocking mode */
        if (nonblock) {
@@ -207,48 +262,51 @@ channel_register_fds(Channel *c, int rfd, int wfd, int efd,
  * Allocate a new channel object and set its type and socket. This will cause
  * remote_name to be freed.
  */
-
 Channel *
 channel_new(char *ctype, int type, int rfd, int wfd, int efd,
-    int window, int maxpack, int extusage, char *remote_name, int nonblock)
+    u_int window, u_int maxpack, int extusage, char *remote_name, int nonblock)
 {
-       int i, found;
+       int found;
+       u_int i;
        Channel *c;
 
        /* Do initial allocation if this is the first call. */
        if (channels_alloc == 0) {
                channels_alloc = 10;
-               channels = xmalloc(channels_alloc * sizeof(Channel *));
+               channels = xcalloc(channels_alloc, sizeof(Channel *));
                for (i = 0; i < channels_alloc; i++)
                        channels[i] = NULL;
-               fatal_add_cleanup((void (*) (void *)) channel_free_all, NULL);
        }
        /* Try to find a free slot where to put the new channel. */
        for (found = -1, i = 0; i < channels_alloc; i++)
                if (channels[i] == NULL) {
                        /* Found a free slot. */
-                       found = i;
+                       found = (int)i;
                        break;
                }
-       if (found == -1) {
+       if (found < 0) {
                /* There are no free slots.  Take last+1 slot and expand the array.  */
                found = channels_alloc;
+               if (channels_alloc > 10000)
+                       fatal("channel_new: internal error: channels_alloc %d "
+                           "too big.", channels_alloc);
+               channels = xrealloc(channels, channels_alloc + 10,
+                   sizeof(Channel *));
                channels_alloc += 10;
                debug2("channel: expanding %d", channels_alloc);
-               channels = xrealloc(channels, channels_alloc * sizeof(Channel *));
                for (i = found; i < channels_alloc; i++)
                        channels[i] = NULL;
        }
        /* Initialize and return new channel. */
-       c = channels[found] = xmalloc(sizeof(Channel));
-       memset(c, 0, sizeof(Channel));
+       c = channels[found] = xcalloc(1, sizeof(Channel));
        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;
@@ -256,15 +314,22 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
        c->local_window_max = window;
        c->local_consumed = 0;
        c->local_maxpacket = maxpack;
+       c->dynamic_window = 0;
        c->remote_id = -1;
-       c->remote_name = remote_name;
+       c->remote_name = xstrdup(remote_name);
        c->remote_window = 0;
        c->remote_maxpacket = 0;
        c->force_drain = 0;
        c->single_connection = 0;
        c->detach_user = NULL;
-       c->confirm = NULL;
+       c->detach_close = 0;
+       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;
 }
@@ -272,7 +337,8 @@ channel_new(char *ctype, int type, int rfd, int wfd, int efd,
 static int
 channel_find_maxfd(void)
 {
-       int i, max = 0;
+       u_int i;
+       int max = 0;
        Channel *c;
 
        for (i = 0; i < channels_alloc; i++) {
@@ -301,39 +367,41 @@ channel_close_fd(int *fdp)
 }
 
 /* Close all channel fd/socket. */
-
 static void
 channel_close_fds(Channel *c)
 {
-       debug3("channel_close_fds: channel %d: r %d w %d e %d",
-           c->self, c->rfd, c->wfd, c->efd);
+       debug3("channel %d: close_fds r %d w %d e %d c %d",
+           c->self, c->rfd, c->wfd, c->efd, c->ctl_fd);
 
        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);
 }
 
 /* Free the channel and close its fd/socket. */
-
 void
 channel_free(Channel *c)
 {
        char *s;
-       int i, n;
+       u_int i, n;
+       struct channel_confirm *cc;
 
        for (n = 0, i = 0; i < channels_alloc; i++)
                if (channels[i])
                        n++;
-       debug("channel_free: channel %d: %s, nchannels %d", c->self,
+       debug("channel %d: free: %s, nchannels %u", c->self,
            c->remote_name ? c->remote_name : "???", n);
 
        s = channel_open_message();
-       debug3("channel_free: status: %s", s);
+       debug3("channel %d: status: %s", c->self, s);
        xfree(s);
 
        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);
@@ -342,6 +410,19 @@ channel_free(Channel *c)
                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);
 }
@@ -349,7 +430,7 @@ channel_free(Channel *c)
 void
 channel_free_all(void)
 {
-       int i;
+       u_int i;
 
        for (i = 0; i < channels_alloc; i++)
                if (channels[i] != NULL)
@@ -360,11 +441,10 @@ channel_free_all(void)
  * Closes the sockets/fds of all channels.  This is used to close extra file
  * descriptors after a fork.
  */
-
 void
 channel_close_all(void)
 {
-       int i;
+       u_int i;
 
        for (i = 0; i < channels_alloc; i++)
                if (channels[i] != NULL)
@@ -374,11 +454,10 @@ channel_close_all(void)
 /*
  * Stop listening to channels.
  */
-
 void
 channel_stop_listening(void)
 {
-       int i;
+       u_int i;
        Channel *c;
 
        for (i = 0; i < channels_alloc; i++) {
@@ -401,7 +480,6 @@ channel_stop_listening(void)
  * Returns true if no channel has too much buffered data, and false if one or
  * more channel is overfull.
  */
-
 int
 channel_not_very_much_buffered_data(void)
 {
@@ -414,13 +492,13 @@ channel_not_very_much_buffered_data(void)
 #if 0
                        if (!compat20 &&
                            buffer_len(&c->input) > packet_get_maxsize()) {
-                               debug("channel %d: big input buffer %d",
+                               debug2("channel %d: big input buffer %d",
                                    c->self, buffer_len(&c->input));
                                return 0;
                        }
 #endif
                        if (buffer_len(&c->output) > packet_get_maxsize()) {
-                               debug("channel %d: big output buffer %d > %d",
+                               debug2("channel %d: big output buffer %u > %u",
                                    c->self, buffer_len(&c->output),
                                    packet_get_maxsize());
                                return 0;
@@ -431,11 +509,10 @@ channel_not_very_much_buffered_data(void)
 }
 
 /* Returns true if any channel is still open. */
-
 int
 channel_still_open(void)
 {
-       int i;
+       u_int i;
        Channel *c;
 
        for (i = 0; i < channels_alloc; i++) {
@@ -474,16 +551,15 @@ channel_still_open(void)
 }
 
 /* Returns the id of an open channel suitable for keepaliving */
-
 int
 channel_find_open(void)
 {
-       int i;
+       u_int i;
        Channel *c;
 
        for (i = 0; i < channels_alloc; i++) {
                c = channels[i];
-               if (c == NULL)
+               if (c == NULL || c->remote_id < 0)
                        continue;
                switch (c->type) {
                case SSH_CHANNEL_CLOSED:
@@ -519,14 +595,13 @@ channel_find_open(void)
  * suitable for sending to the client.  The message contains crlf pairs for
  * newlines.
  */
-
 char *
 channel_open_message(void)
 {
        Buffer buffer;
        Channel *c;
        char buf[1024], *cp;
-       int i;
+       u_int i;
 
        buffer_init(&buffer);
        snprintf(buf, sizeof buf, "The following connections are open:\r\n");
@@ -551,12 +626,13 @@ channel_open_message(void)
                case SSH_CHANNEL_X11_OPEN:
                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)\r\n",
+                       snprintf(buf, sizeof buf,
+                           "  #%d %.300s (t%d r%d i%d/%d o%d/%d fd %d/%d cfd %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->rfd, c->wfd, c->ctl_fd);
                        buffer_append(&buffer, buf, strlen(buf));
                        continue;
                default:
@@ -574,11 +650,12 @@ void
 channel_send_open(int id)
 {
        Channel *c = channel_lookup(id);
+
        if (c == NULL) {
-               log("channel_send_open: %d: bad id", id);
+               logit("channel_send_open: %d: bad id", id);
                return;
        }
-       debug("send channel open %d", id);
+       debug2("channel %d: send open", id);
        packet_start(SSH2_MSG_CHANNEL_OPEN);
        packet_put_cstring(c->ctype);
        packet_put_int(c->self);
@@ -588,68 +665,102 @@ channel_send_open(int id)
 }
 
 void
-channel_request_start(int local_id, char *service, int wantconfirm)
+channel_request_start(int id, char *service, int wantconfirm)
 {
-       Channel *c = channel_lookup(local_id);
+       Channel *c = channel_lookup(id);
+
        if (c == NULL) {
-               log("channel_request_start: %d: unknown channel id", local_id);
+               logit("channel_request_start: %d: unknown channel id", id);
                return;
        }
-       debug("channel request %d: %s", local_id, service) ;
+       debug2("channel %d: request %s confirm %d", id, service, wantconfirm);
        packet_start(SSH2_MSG_CHANNEL_REQUEST);
        packet_put_int(c->remote_id);
        packet_put_cstring(service);
        packet_put_char(wantconfirm);
 }
+
+void
+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_confirm(int id, channel_callback_fn *fn)
+channel_register_open_confirm(int id, channel_callback_fn *fn, void *ctx)
 {
        Channel *c = channel_lookup(id);
+
        if (c == NULL) {
-               log("channel_register_comfirm: %d: bad id", id);
+               logit("channel_register_open_confirm: %d: bad id", id);
                return;
        }
-       c->confirm = fn;
+       c->open_confirm = fn;
+       c->open_confirm_ctx = ctx;
 }
+
 void
-channel_register_cleanup(int id, channel_callback_fn *fn)
+channel_register_cleanup(int id, channel_callback_fn *fn, int do_close)
 {
-       Channel *c = channel_lookup(id);
+       Channel *c = channel_by_id(id);
+
        if (c == NULL) {
-               log("channel_register_cleanup: %d: bad id", id);
+               logit("channel_register_cleanup: %d: bad id", id);
                return;
        }
        c->detach_user = fn;
+       c->detach_close = do_close;
 }
+
 void
 channel_cancel_cleanup(int id)
 {
-       Channel *c = channel_lookup(id);
+       Channel *c = channel_by_id(id);
+
        if (c == NULL) {
-               log("channel_cancel_cleanup: %d: bad id", id);
+               logit("channel_cancel_cleanup: %d: bad id", id);
                return;
        }
        c->detach_user = NULL;
+       c->detach_close = 0;
 }
+
 void
-channel_register_filter(int id, channel_filter_fn *fn)
+channel_register_filter(int id, channel_infilter_fn *ifn,
+    channel_outfilter_fn *ofn, channel_filter_cleanup_fn *cfn, void *ctx)
 {
        Channel *c = channel_lookup(id);
+
        if (c == NULL) {
-               log("channel_register_filter: %d: bad id", id);
+               logit("channel_register_filter: %d: bad id", id);
                return;
        }
-       c->input_filter = fn;
+       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);
@@ -666,25 +777,27 @@ channel_set_fds(int id, int rfd, int wfd, int efd,
  * 'channel_post*': perform any appropriate operations for channels which
  * have events pending.
  */
-typedef void chan_fn(Channel *c, fd_set * readset, fd_set * writeset);
+typedef void chan_fn(Channel *c, fd_set *readset, fd_set *writeset);
 chan_fn *channel_pre[SSH_CHANNEL_MAX_TYPE];
 chan_fn *channel_post[SSH_CHANNEL_MAX_TYPE];
 
+/* ARGSUSED */
 static void
-channel_pre_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_listener(Channel *c, fd_set *readset, fd_set *writeset)
 {
        FD_SET(c->sock, readset);
 }
 
+/* ARGSUSED */
 static void
-channel_pre_connecting(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_connecting(Channel *c, fd_set *readset, fd_set *writeset)
 {
        debug3("channel %d: waiting for connection", c->self);
        FD_SET(c->sock, writeset);
 }
 
 static void
-channel_pre_open_13(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_open_13(Channel *c, fd_set *readset, fd_set *writeset)
 {
        if (buffer_len(&c->input) < packet_get_maxsize())
                FD_SET(c->sock, readset);
@@ -692,14 +805,37 @@ channel_pre_open_13(Channel *c, fd_set * readset, fd_set * writeset)
                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)
+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)
+           buffer_len(&c->input) < limit &&
+           buffer_check_alloc(&c->input, CHAN_RBUF))
                FD_SET(c->rfd, readset);
        if (c->ostate == CHAN_OUTPUT_OPEN ||
            c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
@@ -707,14 +843,15 @@ channel_pre_open(Channel *c, fd_set * readset, fd_set * writeset)
                        FD_SET(c->wfd, writeset);
                } else if (c->ostate == CHAN_OUTPUT_WAIT_DRAIN) {
                        if (CHANNEL_EFD_OUTPUT_ACTIVE(c))
-                               debug2("channel %d: obuf_empty delayed efd %d/(%d)",
-                                   c->self, c->efd, buffer_len(&c->extended));
+                               debug2("channel %d: obuf_empty delayed efd %d/(%d)",
+                                   c->self, c->efd, buffer_len(&c->extended));
                        else
                                chan_obuf_empty(c);
                }
        }
        /** 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);
@@ -723,22 +860,28 @@ channel_pre_open(Channel *c, fd_set * readset, fd_set * writeset)
                    buffer_len(&c->extended) < c->remote_window)
                        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_input_draining(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_input_draining(Channel *c, fd_set *readset, fd_set *writeset)
 {
        if (buffer_len(&c->input) == 0) {
                packet_start(SSH_MSG_CHANNEL_CLOSE);
                packet_put_int(c->remote_id);
                packet_send();
                c->type = SSH_CHANNEL_CLOSED;
-               debug("channel %d: closing after input drain.", c->self);
+               debug2("channel %d: closing after input drain.", c->self);
        }
 }
 
+/* ARGSUSED */
 static void
-channel_pre_output_draining(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_output_draining(Channel *c, fd_set *readset, fd_set *writeset)
 {
        if (buffer_len(&c->output) == 0)
                chan_mark_dead(c);
@@ -774,7 +917,7 @@ x11_open_helper(Buffer *b)
                proto_len = ucp[6] + 256 * ucp[7];
                data_len = ucp[8] + 256 * ucp[9];
        } else {
-               debug("Initial X11 packet contains bad byte order byte: 0x%x",
+               debug2("Initial X11 packet contains bad byte order byte: 0x%x",
                    ucp[0]);
                return -1;
        }
@@ -787,14 +930,14 @@ x11_open_helper(Buffer *b)
        /* Check if authentication protocol matches. */
        if (proto_len != strlen(x11_saved_proto) ||
            memcmp(ucp + 12, x11_saved_proto, proto_len) != 0) {
-               debug("X11 connection uses different authentication protocol.");
+               debug2("X11 connection uses different authentication protocol.");
                return -1;
        }
        /* Check if authentication data matches our fake data. */
        if (data_len != x11_fake_data_len ||
            memcmp(ucp + 12 + ((proto_len + 3) & ~3),
                x11_fake_data, x11_fake_data_len) != 0) {
-               debug("X11 auth data does not match fake data.");
+               debug2("X11 auth data does not match fake data.");
                return -1;
        }
        /* Check fake data length */
@@ -814,9 +957,10 @@ x11_open_helper(Buffer *b)
 }
 
 static void
-channel_pre_x11_open_13(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_x11_open_13(Channel *c, fd_set *readset, fd_set *writeset)
 {
        int ret = x11_open_helper(&c->output);
+
        if (ret == 1) {
                /* Start normal processing for the channel. */
                c->type = SSH_CHANNEL_OPEN;
@@ -826,7 +970,7 @@ channel_pre_x11_open_13(Channel *c, fd_set * readset, fd_set * writeset)
                 * We have received an X11 connection that has bad
                 * authentication information.
                 */
-               log("X11 connection rejected because of wrong authentication.");
+               logit("X11 connection rejected because of wrong authentication.");
                buffer_clear(&c->input);
                buffer_clear(&c->output);
                channel_close_fd(&c->sock);
@@ -839,7 +983,7 @@ channel_pre_x11_open_13(Channel *c, fd_set * readset, fd_set * writeset)
 }
 
 static void
-channel_pre_x11_open(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_x11_open(Channel *c, fd_set *readset, fd_set *writeset)
 {
        int ret = x11_open_helper(&c->output);
 
@@ -849,8 +993,8 @@ channel_pre_x11_open(Channel *c, fd_set * readset, fd_set * writeset)
                c->type = SSH_CHANNEL_OPEN;
                channel_pre_open(c, readset, writeset);
        } else if (ret == -1) {
-               log("X11 connection rejected because of wrong authentication.");
-               debug("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate);
+               logit("X11 connection rejected because of wrong authentication.");
+               debug2("X11 rejected %d i%d/o%d", c->self, c->istate, c->ostate);
                chan_read_failed(c);
                buffer_clear(&c->input);
                chan_ibuf_empty(c);
@@ -860,16 +1004,17 @@ channel_pre_x11_open(Channel *c, fd_set * readset, fd_set * writeset)
                        chan_write_failed(c);
                else
                        c->type = SSH_CHANNEL_OPEN;
-               debug("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate);
+               debug2("X11 closed %d i%d/o%d", c->self, c->istate, c->ostate);
        }
 }
 
 /* try to decode a socks4 header */
+/* ARGSUSED */
 static int
-channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
+channel_decode_socks4(Channel *c, fd_set *readset, fd_set *writeset)
 {
-       u_char *p, *host;
-       int len, have, i, found;
+       char *p, *host;
+       u_int len, have, i, found, need;
        char username[256];
        struct {
                u_int8_t version;
@@ -885,10 +1030,20 @@ channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
        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 */
@@ -897,7 +1052,7 @@ channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
                        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);
@@ -907,46 +1062,197 @@ channel_decode_socks4(Channel *c, fd_set * readset, fd_set * writeset)
        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);
 
-       debug("channel %d: dynamic request: socks4 host %s port %u command %u",
-           c->self, host, c->host_port, s4_req.command);
+       debug2("channel %d: dynamic request: socks4 host %s port %u command %u",
+           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 */
        s4_rsp.command = 90;                    /* cd: req granted */
        s4_rsp.dest_port = 0;                   /* ignored */
        s4_rsp.dest_addr.s_addr = INADDR_ANY;   /* ignored */
-       buffer_append(&c->output, (char *)&s4_rsp, sizeof(s4_rsp));
+       buffer_append(&c->output, &s4_rsp, sizeof(s4_rsp));
+       return 1;
+}
+
+/* try to decode a socks5 header */
+#define SSH_SOCKS5_AUTHDONE    0x1000
+#define SSH_SOCKS5_NOAUTH      0x00
+#define SSH_SOCKS5_IPV4                0x01
+#define SSH_SOCKS5_DOMAIN      0x03
+#define SSH_SOCKS5_IPV6                0x04
+#define SSH_SOCKS5_CONNECT     0x01
+#define SSH_SOCKS5_SUCCESS     0x00
+
+/* ARGSUSED */
+static int
+channel_decode_socks5(Channel *c, fd_set *readset, fd_set *writeset)
+{
+       struct {
+               u_int8_t version;
+               u_int8_t command;
+               u_int8_t reserved;
+               u_int8_t atyp;
+       } s5_req, s5_rsp;
+       u_int16_t dest_port;
+       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);
+       p = buffer_ptr(&c->input);
+       if (p[0] != 0x05)
+               return -1;
+       have = buffer_len(&c->input);
+       if (!(c->flags & SSH_SOCKS5_AUTHDONE)) {
+               /* format: ver | nmethods | methods */
+               if (have < 2)
+                       return 0;
+               nmethods = p[1];
+               if (have < nmethods + 2)
+                       return 0;
+               /* look for method: "NO AUTHENTICATION REQUIRED" */
+               for (found = 0, i = 2; i < nmethods + 2; i++) {
+                       if (p[i] == SSH_SOCKS5_NOAUTH) {
+                               found = 1;
+                               break;
+                       }
+               }
+               if (!found) {
+                       debug("channel %d: method SSH_SOCKS5_NOAUTH not found",
+                           c->self);
+                       return -1;
+               }
+               buffer_consume(&c->input, nmethods + 2);
+               buffer_put_char(&c->output, 0x05);              /* version */
+               buffer_put_char(&c->output, SSH_SOCKS5_NOAUTH); /* method */
+               FD_SET(c->sock, writeset);
+               c->flags |= SSH_SOCKS5_AUTHDONE;
+               debug2("channel %d: socks5 auth done", c->self);
+               return 0;                               /* need more */
+       }
+       debug2("channel %d: socks5 post auth", c->self);
+       if (have < sizeof(s5_req)+1)
+               return 0;                       /* need more */
+       memcpy(&s5_req, p, sizeof(s5_req));
+       if (s5_req.version != 0x05 ||
+           s5_req.command != SSH_SOCKS5_CONNECT ||
+           s5_req.reserved != 0x00) {
+               debug2("channel %d: only socks5 connect supported", c->self);
+               return -1;
+       }
+       switch (s5_req.atyp){
+       case SSH_SOCKS5_IPV4:
+               addrlen = 4;
+               af = AF_INET;
+               break;
+       case SSH_SOCKS5_DOMAIN:
+               addrlen = p[sizeof(s5_req)];
+               af = -1;
+               break;
+       case SSH_SOCKS5_IPV6:
+               addrlen = 16;
+               af = AF_INET6;
+               break;
+       default:
+               debug2("channel %d: bad socks5 atyp %d", c->self, s5_req.atyp);
+               return -1;
+       }
+       need = sizeof(s5_req) + addrlen + 2;
+       if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
+               need++;
+       if (have < need)
+               return 0;
+       buffer_consume(&c->input, sizeof(s5_req));
+       if (s5_req.atyp == SSH_SOCKS5_DOMAIN)
+               buffer_consume(&c->input, 1);    /* host string length */
+       buffer_get(&c->input, (char *)&dest_addr, addrlen);
+       buffer_get(&c->input, (char *)&dest_port, 2);
+       dest_addr[addrlen] = '\0';
+       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",
+           c->self, c->path, c->host_port, s5_req.command);
+
+       s5_rsp.version = 0x05;
+       s5_rsp.command = SSH_SOCKS5_SUCCESS;
+       s5_rsp.reserved = 0;                    /* ignored */
+       s5_rsp.atyp = SSH_SOCKS5_IPV4;
+       ((struct in_addr *)&dest_addr)->s_addr = INADDR_ANY;
+       dest_port = 0;                          /* ignored */
+
+       buffer_append(&c->output, &s5_rsp, sizeof(s5_rsp));
+       buffer_append(&c->output, &dest_addr, sizeof(struct in_addr));
+       buffer_append(&c->output, &dest_port, sizeof(dest_port));
        return 1;
 }
 
 /* dynamic port forwarding */
 static void
-channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
+channel_pre_dynamic(Channel *c, fd_set *readset, fd_set *writeset)
 {
        u_char *p;
-       int have, ret;
+       u_int have;
+       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. */
-       if (have < 4) {
+       if (have < 3) {
                /* need more */
                FD_SET(c->sock, readset);
                return;
@@ -957,6 +1263,9 @@ channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
        case 0x04:
                ret = channel_decode_socks4(c, readset, writeset);
                break;
+       case 0x05:
+               ret = channel_decode_socks5(c, readset, writeset);
+               break;
        default:
                ret = -1;
                break;
@@ -975,11 +1284,12 @@ channel_pre_dynamic(Channel *c, fd_set * readset, fd_set * writeset)
 }
 
 /* This is our fake X11 server socket. */
+/* ARGSUSED */
 static void
-channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
+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;
@@ -988,9 +1298,9 @@ channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
        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) {
-                       debug("single_connection: closing X11 listener.");
+                       debug2("single_connection: closing X11 listener.");
                        channel_close_fd(&c->sock);
                        chan_mark_dead(c);
                }
@@ -1006,8 +1316,7 @@ channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
 
                nc = channel_new("accepted x11 socket",
                    SSH_CHANNEL_OPENING, newsock, newsock, -1,
-                   c->local_window_max, c->local_maxpacket,
-                   0, xstrdup(buf), 1);
+                   c->local_window_max, c->local_maxpacket, 0, buf, 1);
                if (compat20) {
                        packet_start(SSH2_MSG_CHANNEL_OPEN);
                        packet_put_cstring("x11");
@@ -1017,7 +1326,7 @@ channel_post_x11_listener(Channel *c, fd_set * readset, fd_set * writeset)
                        /* originator ipaddr and port */
                        packet_put_cstring(remote_ipaddr);
                        if (datafellows & SSH_BUG_X11FWD) {
-                               debug("ssh2 x11 bug compat mode");
+                               debug2("ssh2 x11 bug compat mode");
                        } else {
                                packet_put_int(remote_port);
                        }
@@ -1040,7 +1349,7 @@ port_open_helper(Channel *c, char *rtype)
        int direct;
        char buf[1024];
        char *remote_ipaddr = get_peer_ipaddr(c->sock);
-       u_short remote_port = get_peer_port(c->sock);
+       int remote_port = get_peer_port(c->sock);
 
        direct = (strcmp(rtype, "direct-tcpip") == 0);
 
@@ -1070,7 +1379,7 @@ port_open_helper(Channel *c, char *rtype)
                }
                /* originator host and port */
                packet_put_cstring(remote_ipaddr);
-               packet_put_int(remote_port);
+               packet_put_int((u_int)remote_port);
                packet_send();
        } else {
                packet_start(SSH_MSG_PORT_OPEN);
@@ -1085,14 +1394,28 @@ port_open_helper(Channel *c, char *rtype)
        xfree(remote_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));
+}
+
 /*
  * This socket is listening for connections to a forwarded TCP/IP port.
  */
+/* ARGSUSED */
 static void
-channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset)
+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;
@@ -1116,19 +1439,18 @@ channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset)
                }
 
                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;
                }
                set_nodelay(newsock);
-               nc = channel_new(rtype,
-                   nextstate, newsock, newsock, -1,
-                   c->local_window_max, c->local_maxpacket,
-                   0, xstrdup(rtype), 1);
+               nc = channel_new(rtype, nextstate, newsock, newsock, -1,
+                   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) {
                        /*
@@ -1147,27 +1469,26 @@ channel_post_port_listener(Channel *c, fd_set * readset, fd_set * writeset)
  * This is the authentication agent socket listening for connections from
  * clients.
  */
+/* ARGSUSED */
 static void
-channel_post_auth_listener(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_auth_listener(Channel *c, fd_set *readset, fd_set *writeset)
 {
        Channel *nc;
-       char *name;
        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;
                }
-               name = xstrdup("accepted auth socket");
                nc = channel_new("accepted auth socket",
                    SSH_CHANNEL_OPENING, newsock, newsock, -1,
                    c->local_window_max, c->local_maxpacket,
-                   0, name, 1);
+                   0, "accepted auth socket", 1);
                if (compat20) {
                        packet_start(SSH2_MSG_CHANNEL_OPEN);
                        packet_put_cstring("auth-agent@openssh.com");
@@ -1182,10 +1503,11 @@ channel_post_auth_listener(Channel *c, fd_set * readset, fd_set * writeset)
        }
 }
 
+/* ARGSUSED */
 static void
-channel_post_connecting(Channel *c, fd_set * readset, fd_set * writeset)
+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)) {
@@ -1194,7 +1516,9 @@ channel_post_connecting(Channel *c, fd_set * readset, fd_set * 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);
@@ -1208,8 +1532,19 @@ channel_post_connecting(Channel *c, fd_set * readset, fd_set * writeset)
                                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);
@@ -1228,28 +1563,36 @@ channel_post_connecting(Channel *c, fd_set * readset, fd_set * writeset)
        }
 }
 
+/* ARGSUSED */
 static int
-channel_handle_rfd(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_rfd(Channel *c, fd_set *readset, fd_set *writeset)
 {
-       char buf[16*1024];
-       int len;
+       char buf[CHAN_RBUF];
+       int len, force;
 
-       if (c->rfd != -1 &&
-           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))
+               if (len < 0 && (errno == EINTR ||
+                   ((errno == EAGAIN || errno == EWOULDBLOCK) && !force)))
                        return 1;
+#ifndef PTY_ZEROREAD
                if (len <= 0) {
-                       debug("channel %d: read<=0 rfd %d len %d",
+#else
+               if ((!c->isatty && len <= 0) ||
+                   (c->isatty && (len < 0 || (len == 0 && errno != 0)))) {
+#endif
+                       debug2("channel %d: read<=0 rfd %d len %d",
                            c->self, c->rfd, len);
                        if (c->type != SSH_CHANNEL_OPEN) {
-                               debug("channel %d: not open", c->self);
+                               debug2("channel %d: not open", c->self);
                                chan_mark_dead(c);
                                return -1;
                        } else if (compat13) {
                                buffer_clear(&c->output);
                                c->type = SSH_CHANNEL_INPUT_DRAINING;
-                               debug("channel %d: input draining.", c->self);
+                               debug2("channel %d: input draining.", c->self);
                        } else {
                                chan_read_failed(c);
                        }
@@ -1257,20 +1600,24 @@ channel_handle_rfd(Channel *c, fd_set * readset, fd_set * writeset)
                }
                if (c->input_filter != NULL) {
                        if (c->input_filter(c, buf, len) == -1) {
-                               debug("channel %d: filter stops", c->self);
+                               debug2("channel %d: filter stops", c->self);
                                chan_read_failed(c);
                        }
+               } else if (c->datagram) {
+                       buffer_put_string(&c->input, buf, len);
                } else {
                        buffer_append(&c->input, buf, len);
                }
        }
        return 1;
 }
+
+/* ARGSUSED */
 static int
-channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_wfd(Channel *c, fd_set *readset, fd_set *writeset)
 {
        struct termios tio;
-       u_char *data;
+       u_char *data = NULL, *buf;
        u_int dlen;
        int len;
 
@@ -1278,38 +1625,78 @@ channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset)
        if (c->wfd != -1 &&
            FD_ISSET(c->wfd, writeset) &&
            buffer_len(&c->output) > 0) {
-               data = buffer_ptr(&c->output);
-               dlen = buffer_len(&c->output);
-               len = write(c->wfd, data, dlen);
-               if (len < 0 && (errno == EINTR || errno == EAGAIN))
+               if (c->output_filter != NULL) {
+                       if ((buf = c->output_filter(c, &data, &dlen)) == NULL) {
+                               debug2("channel %d: filter stops", c->self);
+                               if (c->type != SSH_CHANNEL_OPEN)
+                                       chan_mark_dead(c);
+                               else
+                                       chan_write_failed(c);
+                               return -1;
+                       }
+               } else if (c->datagram) {
+                       buf = data = buffer_get_string(&c->output, &dlen);
+               } else {
+                       buf = data = buffer_ptr(&c->output);
+                       dlen = buffer_len(&c->output);
+               }
+
+               if (c->datagram) {
+                       /* ignore truncated writes, datagrams might get lost */
+                       c->local_consumed += dlen + 4;
+                       len = write(c->wfd, buf, dlen);
+                       xfree(data);
+                       if (len < 0 && (errno == EINTR || errno == EAGAIN ||
+                           errno == EWOULDBLOCK))
+                               return 1;
+                       if (len <= 0) {
+                               if (c->type != SSH_CHANNEL_OPEN)
+                                       chan_mark_dead(c);
+                               else
+                                       chan_write_failed(c);
+                               return -1;
+                       }
+                       return 1;
+               }
+#ifdef _AIX
+               /* XXX: Later AIX versions can't push as much data to tty */
+               if (compat20 && c->wfd_isatty)
+                       dlen = MIN(dlen, 8*1024);
+#endif
+
+               len = write(c->wfd, buf, dlen);
+               if (len < 0 &&
+                   (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK))
                        return 1;
                if (len <= 0) {
                        if (c->type != SSH_CHANNEL_OPEN) {
-                               debug("channel %d: not open", c->self);
+                               debug2("channel %d: not open", c->self);
                                chan_mark_dead(c);
                                return -1;
                        } else if (compat13) {
                                buffer_clear(&c->output);
-                               debug("channel %d: input draining.", c->self);
+                               debug2("channel %d: input draining.", c->self);
                                c->type = SSH_CHANNEL_INPUT_DRAINING;
                        } else {
                                chan_write_failed(c);
                        }
                        return -1;
                }
-               if (compat20 && c->isatty && dlen >= 1 && data[0] != '\r') {
+#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)) {
                                /*
                                 * Simulate echo to reduce the impact of
                                 * traffic analysis. We need to match the
                                 * size of a SSH2_MSG_CHANNEL_DATA message
-                                * (4 byte channel id + data)
+                                * (4 byte channel id + buf)
                                 */
                                packet_send_ignore(4 + len);
                                packet_send();
                        }
                }
+#endif
                buffer_consume(&c->output, len);
                if (compat20 && len > 0) {
                        c->local_consumed += len;
@@ -1317,10 +1704,11 @@ channel_handle_wfd(Channel *c, fd_set * readset, fd_set * writeset)
        }
        return 1;
 }
+
 static int
-channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset)
+channel_handle_efd(Channel *c, fd_set *readset, fd_set *writeset)
 {
-       char buf[16*1024];
+       char buf[CHAN_RBUF];
        int len;
 
 /** XXX handle drain efd, too */
@@ -1332,7 +1720,8 @@ channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset)
                            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",
@@ -1343,11 +1732,12 @@ channel_handle_efd(Channel *c, fd_set * readset, fd_set * writeset)
                                c->local_consumed += len;
                        }
                } else if (c->extended_usage == CHAN_EXTENDED_READ &&
-                   FD_ISSET(c->efd, readset)) {
+                   (c->detach_close || FD_ISSET(c->efd, readset))) {
                        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))
+                       if (len < 0 && (errno == EINTR || ((errno == EAGAIN ||
+                           errno == EWOULDBLOCK) && !c->detach_close)))
                                return 1;
                        if (len <= 0) {
                                debug2("channel %d: closing read-efd %d",
@@ -1360,28 +1750,68 @@ 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)
 {
        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;
 }
 
 static void
-channel_post_open(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_open(Channel *c, fd_set *readset, fd_set *writeset)
 {
        if (c->delayed)
                return;
@@ -1390,13 +1820,16 @@ channel_post_open(Channel *c, fd_set * readset, fd_set * writeset)
        if (!compat20)
                return;
        channel_handle_efd(c, readset, writeset);
+       channel_handle_ctl(c, readset, writeset);
        channel_check_window(c);
 }
 
+/* ARGSUSED */
 static void
-channel_post_output_drain_13(Channel *c, fd_set * readset, fd_set * writeset)
+channel_post_output_drain_13(Channel *c, fd_set *readset, fd_set *writeset)
 {
        int len;
+
        /* Send buffered output data to the socket. */
        if (FD_ISSET(c->sock, writeset) && buffer_len(&c->output) > 0) {
                len = write(c->sock, buffer_ptr(&c->output),
@@ -1474,6 +1907,7 @@ static void
 channel_handler_init(void)
 {
        int i;
+
        for (i = 0; i < SSH_CHANNEL_MAX_TYPE; i++) {
                channel_pre[i] = NULL;
                channel_post[i] = NULL;
@@ -1493,26 +1927,26 @@ channel_garbage_collect(Channel *c)
        if (c == NULL)
                return;
        if (c->detach_user != NULL) {
-               if (!chan_is_dead(c, 0))
+               if (!chan_is_dead(c, c->detach_close))
                        return;
-               debug("channel %d: gc: notify user", c->self);
+               debug2("channel %d: gc: notify user", c->self);
                c->detach_user(c->self, NULL);
                /* if we still have a callback */
                if (c->detach_user != NULL)
                        return;
-               debug("channel %d: gc: user detached", c->self);
+               debug2("channel %d: gc: user detached", c->self);
        }
        if (!chan_is_dead(c, 1))
                return;
-       debug("channel %d: garbage collecting", c->self);
+       debug2("channel %d: garbage collecting", c->self);
        channel_free(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)
 {
        static int did_init = 0;
-       int i;
+       u_int i;
        Channel *c;
 
        if (!did_init) {
@@ -1535,18 +1969,22 @@ channel_handler(chan_fn *ftab[], fd_set * readset, fd_set * writeset)
  */
 void
 channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
-    int *nallocp, int rekeying)
+    u_int *nallocp, int rekeying)
 {
-       int n;
-       u_int sz;
+       u_int n, sz, nfdset;
 
        n = MAX(*maxfdp, channel_max_fd);
 
-       sz = howmany(n+1, NFDBITS) * sizeof(fd_mask);
+       nfdset = howmany(n+1, NFDBITS);
+       /* Explicitly test here, because xrealloc isn't always called */
+       if (nfdset && SIZE_T_MAX / nfdset < sizeof(fd_mask))
+               fatal("channel_prepare_select: max_fd (%d) is too large", n);
+       sz = nfdset * sizeof(fd_mask);
+
        /* perhaps check sz < nalloc/2 and shrink? */
        if (*readsetp == NULL || sz > *nallocp) {
-               *readsetp = xrealloc(*readsetp, sz);
-               *writesetp = xrealloc(*writesetp, sz);
+               *readsetp = xrealloc(*readsetp, nfdset, sizeof(fd_mask));
+               *writesetp = xrealloc(*writesetp, nfdset, sizeof(fd_mask));
                *nallocp = sz;
        }
        *maxfdp = n;
@@ -1562,19 +2000,19 @@ channel_prepare_select(fd_set **readsetp, fd_set **writesetp, int *maxfdp,
  * events pending.
  */
 void
-channel_after_select(fd_set * readset, fd_set * writeset)
+channel_after_select(fd_set *readset, fd_set *writeset)
 {
        channel_handler(channel_post, readset, writeset);
 }
 
 
 /* If there is data to send to the connection, enqueue some of it now. */
-
-void
+int
 channel_output_poll(void)
 {
-       int len, i;
        Channel *c;
+       u_int i, len;
+       int packet_length = 0;
 
        for (i = 0; i < channels_alloc; i++) {
                c = channels[i];
@@ -1604,6 +2042,22 @@ channel_output_poll(void)
                if ((c->istate == CHAN_INPUT_OPEN ||
                    c->istate == CHAN_INPUT_WAIT_DRAIN) &&
                    (len = buffer_len(&c->input)) > 0) {
+                       if (c->datagram) {
+                               if (len > 0) {
+                                       u_char *data;
+                                       u_int dlen;
+
+                                       data = buffer_get_string(&c->input,
+                                           &dlen);
+                                       packet_start(SSH2_MSG_CHANNEL_DATA);
+                                       packet_put_int(c->remote_id);
+                                       packet_put_string(data, dlen);
+                                       packet_length = packet_send();
+                                       c->remote_window -= dlen + 4;
+                                       xfree(data);
+                               }
+                               continue;
+                       }
                        /*
                         * Send some data for the other side over the secure
                         * connection.
@@ -1628,7 +2082,7 @@ channel_output_poll(void)
                                    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;
                        }
@@ -1641,8 +2095,8 @@ channel_output_poll(void)
                         * hack for extended data: delay EOF if EFD still in use.
                         */
                        if (CHANNEL_EFD_INPUT_ACTIVE(c))
-                               debug2("channel %d: ibuf_empty delayed efd %d/(%d)",
-                                   c->self, c->efd, buffer_len(&c->extended));
+                               debug2("channel %d: ibuf_empty delayed efd %d/(%d)",
+                                   c->self, c->efd, buffer_len(&c->extended));
                        else
                                chan_ibuf_empty(c);
                }
@@ -1652,7 +2106,7 @@ channel_output_poll(void)
                    c->remote_window > 0 &&
                    (len = buffer_len(&c->extended)) > 0 &&
                    c->extended_usage == CHAN_EXTENDED_READ) {
-                       debug2("channel %d: rwin %d elen %d euse %d",
+                       debug2("channel %d: rwin %u elen %u euse %d",
                            c->self, c->remote_window, buffer_len(&c->extended),
                            c->extended_usage);
                        if (len > c->remote_window)
@@ -1663,17 +2117,19 @@ channel_output_poll(void)
                        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);
 }
 
 
 /* -- protocol input */
 
+/* ARGSUSED */
 void
 channel_input_data(int type, u_int32_t seq, void *ctxt)
 {
@@ -1693,38 +2149,50 @@ channel_input_data(int type, u_int32_t seq, void *ctxt)
            c->type != SSH_CHANNEL_X11_OPEN)
                return;
 
-       /* same for protocol 1.5 if output end is no longer open */
-       if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN)
-               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.
+        * For protocol 2 the sending side is reducing its window as it sends
+        * data, so we must 'fake' consumption of the data in order to ensure
+        * that window updates are sent back.  Otherwise the connection might
+        * deadlock.
+        */
+       if (!compat13 && c->ostate != CHAN_OUTPUT_OPEN) {
+               if (compat20) {
+                       c->local_window -= data_len;
+                       c->local_consumed += data_len;
+               }
+               return;
+       }
 
        if (compat20) {
                if (data_len > c->local_maxpacket) {
-                       log("channel %d: rcvd big packet %d, maxpack %d",
+                       logit("channel %d: rcvd big packet %d, maxpack %d",
                            c->self, data_len, c->local_maxpacket);
                }
                if (data_len > c->local_window) {
-                       log("channel %d: rcvd too much data %d, win %d",
+                       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;
        }
+       if (c->datagram)
+               buffer_put_string(&c->output, data, data_len);
+       else
+               buffer_append(&c->output, data, data_len);
        packet_check_eom();
-       buffer_append(&c->output, data, data_len);
-       xfree(data);
 }
 
+/* ARGSUSED */
 void
 channel_input_extended_data(int type, u_int32_t seq, void *ctxt)
 {
        int id;
-       int tcode;
        char *data;
-       u_int data_len;
+       u_int data_len, tcode;
        Channel *c;
 
        /* Get the channel number and verify it. */
@@ -1734,7 +2202,7 @@ channel_input_extended_data(int type, u_int32_t seq, void *ctxt)
        if (c == NULL)
                packet_disconnect("Received extended_data for bad channel %d.", id);
        if (c->type != SSH_CHANNEL_OPEN) {
-               log("channel %d: ext data for non open", id);
+               logit("channel %d: ext data for non open", id);
                return;
        }
        if (c->flags & CHAN_EOF_RCVD) {
@@ -1748,13 +2216,13 @@ channel_input_extended_data(int type, u_int32_t seq, void *ctxt)
        if (c->efd == -1 ||
            c->extended_usage != CHAN_EXTENDED_WRITE ||
            tcode != SSH2_EXTENDED_DATA_STDERR) {
-               log("channel %d: bad ext data", c->self);
+               logit("channel %d: bad ext data", c->self);
                return;
        }
        data = packet_get_string(&data_len);
        packet_check_eom();
        if (data_len > c->local_window) {
-               log("channel %d: rcvd too much extended_data %d, win %d",
+               logit("channel %d: rcvd too much extended_data %d, win %d",
                    c->self, data_len, c->local_window);
                xfree(data);
                return;
@@ -1765,6 +2233,7 @@ channel_input_extended_data(int type, u_int32_t seq, void *ctxt)
        xfree(data);
 }
 
+/* ARGSUSED */
 void
 channel_input_ieof(int type, u_int32_t seq, void *ctxt)
 {
@@ -1788,6 +2257,7 @@ channel_input_ieof(int type, u_int32_t seq, void *ctxt)
 
 }
 
+/* ARGSUSED */
 void
 channel_input_close(int type, u_int32_t seq, void *ctxt)
 {
@@ -1826,6 +2296,7 @@ channel_input_close(int type, u_int32_t seq, void *ctxt)
 }
 
 /* proto version 1.5 overloads CLOSE_CONFIRMATION with OCLOSE */
+/* ARGSUSED */
 void
 channel_input_oclose(int type, u_int32_t seq, void *ctxt)
 {
@@ -1838,6 +2309,7 @@ channel_input_oclose(int type, u_int32_t seq, void *ctxt)
        chan_rcvd_oclose(c);
 }
 
+/* ARGSUSED */
 void
 channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt)
 {
@@ -1854,6 +2326,7 @@ channel_input_close_confirmation(int type, u_int32_t seq, void *ctxt)
        channel_free(c);
 }
 
+/* ARGSUSED */
 void
 channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt)
 {
@@ -1874,12 +2347,12 @@ channel_input_open_confirmation(int type, u_int32_t seq, void *ctxt)
        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, NULL);
+                       c->open_confirm(c->self, c->open_confirm_ctx);
                        debug2("callback done");
                }
-               debug("channel %d: open confirm rwindow %d rmax %d", c->self,
+               debug2("channel %d: open confirm rwindow %u rmax %u", c->self,
                    c->remote_window, c->remote_maxpacket);
        }
        packet_check_eom();
@@ -1901,6 +2374,7 @@ reason2txt(int reason)
        return "unknown reason";
 }
 
+/* ARGSUSED */
 void
 channel_input_open_failure(int type, u_int32_t seq, void *ctxt)
 {
@@ -1920,7 +2394,7 @@ channel_input_open_failure(int type, u_int32_t seq, void *ctxt)
                        msg  = packet_get_string(NULL);
                        lang = packet_get_string(NULL);
                }
-               log("channel %d: open failed: %s%s%s", id,
+               logit("channel %d: open failed: %s%s%s", id,
                    reason2txt(reason), msg ? ": ": "", msg ? msg : "");
                if (msg != NULL)
                        xfree(msg);
@@ -1928,15 +2402,17 @@ channel_input_open_failure(int type, u_int32_t seq, void *ctxt)
                        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 */
 void
 channel_input_window_adjust(int type, u_int32_t seq, void *ctxt)
 {
        Channel *c;
-       int id, adjust;
+       int id;
+       u_int adjust;
 
        if (!compat20)
                return;
@@ -1945,24 +2421,24 @@ channel_input_window_adjust(int type, u_int32_t seq, void *ctxt)
        id = packet_get_int();
        c = channel_lookup(id);
 
-       if (c == NULL || c->type != SSH_CHANNEL_OPEN) {
-               log("Received window adjust for "
-                   "non-open channel %d.", id);
+       if (c == NULL) {
+               logit("Received window adjust for non-open channel %d.", id);
                return;
        }
        adjust = packet_get_int();
        packet_check_eom();
-       debug2("channel %d: rcvd adjust %d", id, adjust);
+       debug2("channel %d: rcvd adjust %u", id, adjust);
        c->remote_window += adjust;
 }
 
+/* ARGSUSED */
 void
 channel_input_port_open(int type, u_int32_t seq, void *ctxt)
 {
        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);
@@ -1974,21 +2450,46 @@ channel_input_port_open(int type, u_int32_t seq, void *ctxt)
                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 */
 
@@ -1998,66 +2499,134 @@ channel_set_af(int af)
        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;
-       int success, sock, on = 1;
+       int sock, r, success = 0, wildcard = 0, is_client;
        struct addrinfo hints, *ai, *aitop;
-       const char *host;
+       const char *host, *addr;
        char ntop[NI_MAXHOST], strport[NI_MAXSERV];
-       struct linger linger;
+       in_port_t *lport_p;
 
-       success = 0;
        host = (type == SSH_CHANNEL_RPORT_LISTENER) ?
            listen_addr : host_to_connect;
+       is_client = (type == SSH_CHANNEL_PORT_LISTENER);
 
        if (host == NULL) {
                error("No forward host name.");
-               return success;
+               return 0;
        }
-       if (strlen(host) > SSH_CHANNEL_PATH_LEN - 1) {
+       if (strlen(host) >= NI_MAXHOST) {
                error("Forward host name too long.");
-               return success;
+               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;
+       }
+
+       debug3("channel_setup_fwd_listener: type %d wildcard %d addr %s",
+           type, wildcard, (addr == NULL) ? "NULL" : addr);
+
        /*
         * getaddrinfo returns a loopback address if the hostname is
         * set to NULL and hints.ai_flags is not AI_PASSIVE
         */
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = IPv4or6;
-       hints.ai_flags = gateway_ports ? AI_PASSIVE : 0;
+       hints.ai_flags = wildcard ? AI_PASSIVE : 0;
        hints.ai_socktype = SOCK_STREAM;
        snprintf(strport, sizeof strport, "%d", listen_port);
-       if (getaddrinfo(NULL, strport, &hints, &aitop) != 0)
-               packet_disconnect("getaddrinfo: fatal error");
-
+       if ((r = getaddrinfo(addr, strport, &hints, &aitop)) != 0) {
+               if (addr == NULL) {
+                       /* This really shouldn't happen */
+                       packet_disconnect("getaddrinfo: fatal error: %s",
+                           ssh_gai_strerror(r));
+               } else {
+                       error("channel_setup_fwd_listener: "
+                           "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");
                        continue;
                }
                /* Create a port to listen for the host. */
-               sock = socket(ai->ai_family, SOCK_STREAM, 0);
+               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
                if (sock < 0) {
                        /* this is no error since kernel may not support ipv6 */
                        verbose("socket: %.100s", strerror(errno));
                        continue;
                }
-               /*
-                * Set socket options.  We would like the socket to disappear
-                * as soon as it has been closed for whatever reason.
-                */
-               setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
-               linger.l_onoff = 1;
-               linger.l_linger = 5;
-               setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
-               debug("Local forwarding listening on %s port %s.", ntop, strport);
+
+               channel_set_reuseaddr(sock);
+
+               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) {
@@ -2071,16 +2640,35 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por
                        continue;
                }
                /* Start listening for connections on the socket. */
-               if (listen(sock, 5) < 0) {
+               if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
                        error("listen: %.100s", strerror(errno));
                        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, xstrdup("port listener"), 1);
-               strlcpy(c->path, host, sizeof(c->path));
+                   0, "port listener", 1);
+               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;
@@ -2092,22 +2680,44 @@ channel_setup_fwd_listener(int type, const char *listen_addr, u_short listen_por
        return success;
 }
 
+int
+channel_cancel_rport_listener(const char *host, u_short port)
+{
+       u_int i;
+       int found = 0;
+
+       for (i = 0; i < channels_alloc; i++) {
+               Channel *c = channels[i];
+
+               if (c != NULL && c->type == SSH_CHANNEL_RPORT_LISTENER &&
+                   strcmp(c->path, host) == 0 && c->listening_port == port) {
+                       debug2("%s: close channel %d", __func__, i);
+                       channel_free(c);
+                       found = 1;
+               }
+       }
+
+       return (found);
+}
+
 /* protocol local port fwd, used by ssh (and sshd in v1) */
 int
-channel_setup_local_fwd_listener(u_short listen_port,
+channel_setup_local_fwd_listener(const char *listen_host, u_short listen_port,
     const char *host_to_connect, u_short port_to_connect, int gateway_ports)
 {
        return channel_setup_fwd_listener(SSH_CHANNEL_PORT_LISTENER,
-           NULL, listen_port, host_to_connect, port_to_connect, gateway_ports);
+           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);
 }
 
 /*
@@ -2115,8 +2725,8 @@ channel_setup_remote_fwd_listener(const char *listen_address,
  * the secure channel to host:port from local side.
  */
 
-void
-channel_request_remote_forwarding(u_short listen_port,
+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;
@@ -2127,7 +2737,21 @@ channel_request_remote_forwarding(u_short listen_port,
 
        /* Send the forward request to the remote side. */
        if (compat20) {
-               const char *address_to_bind = "0.0.0.0";
+               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 */
@@ -2152,7 +2776,6 @@ channel_request_remote_forwarding(u_short listen_port,
                        success = 1;
                        break;
                case SSH_SMSG_FAILURE:
-                       log("Warning: Server denied remote port forwarding.");
                        break;
                default:
                        /* Unknown packet */
@@ -2166,18 +2789,53 @@ channel_request_remote_forwarding(u_short listen_port,
                permitted_opens[num_permitted_opens].listen_port = listen_port;
                num_permitted_opens++;
        }
+       return (success ? 0 : -1);
+}
+
+/*
+ * Request cancellation of remote forwarding of connection host:port from
+ * local side.
+ */
+void
+channel_request_rforward_cancel(const char *host, u_short port)
+{
+       int i;
+
+       if (!compat20)
+               return;
+
+       for (i = 0; i < num_permitted_opens; i++) {
+               if (permitted_opens[i].host_to_connect != NULL &&
+                   permitted_opens[i].listen_port == port)
+                       break;
+       }
+       if (i >= num_permitted_opens) {
+               debug("%s: requested forward not found", __func__);
+               return;
+       }
+       packet_start(SSH2_MSG_GLOBAL_REQUEST);
+       packet_put_cstring("cancel-tcpip-forward");
+       packet_put_char(0);
+       packet_put_cstring(host == NULL ? "" : host);
+       packet_put_int(port);
+       packet_send();
+
+       permitted_opens[i].listen_port = 0;
+       permitted_opens[i].port_to_connect = 0;
+       xfree(permitted_opens[i].host_to_connect);
+       permitted_opens[i].host_to_connect = NULL;
 }
 
 /*
  * This is called after receiving CHANNEL_FORWARDING_REQUEST.  This initates
  * listening for the port, and sends back a success reply (or disconnect
- * message if there was an error).  This never returns if there was an error.
+ * message if there was an error).
  */
-
-void
+int
 channel_input_port_forward_request(int is_root, int gateway_ports)
 {
        u_short port, host_port;
+       int success = 0;
        char *hostname;
 
        /* Get arguments from the packet. */
@@ -2191,14 +2849,21 @@ channel_input_port_forward_request(int is_root, int gateway_ports)
         * privileged port.
         */
        if (port < IPPORT_RESERVED && !is_root)
-               packet_disconnect("Requested forwarding of port %d but user is not root.",
-                                 port);
+               packet_disconnect(
+                   "Requested forwarding of port %d but user is not root.",
+                   port);
+       if (host_port == 0)
+               packet_disconnect("Dynamic forwarding denied.");
 #endif
+
        /* Initiate forwarding */
-       channel_setup_local_fwd_listener(port, hostname, host_port, gateway_ports);
+       success = channel_setup_local_fwd_listener(NULL, port, hostname,
+           host_port, gateway_ports);
 
        /* Free the argument string. */
        xfree(hostname);
+
+       return (success ? 0 : -1);
 }
 
 /*
@@ -2217,7 +2882,7 @@ void
 channel_add_permitted_opens(char *host, int port)
 {
        if (num_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
-               fatal("channel_request_remote_forwarding: too many forwards");
+               fatal("channel_add_permitted_opens: too many forwards");
        debug("allow port forwarding to host %s port %d", host, port);
 
        permitted_opens[num_permitted_opens].host_to_connect = xstrdup(host);
@@ -2227,118 +2892,235 @@ channel_add_permitted_opens(char *host, int port)
        all_opens_permitted = 0;
 }
 
+int
+channel_add_adm_permitted_opens(char *host, int port)
+{
+       if (num_adm_permitted_opens >= SSH_MAX_FORWARDS_PER_DIRECTION)
+               fatal("channel_add_adm_permitted_opens: too many forwards");
+       debug("config allows port forwarding to host %s port %d", host, port);
+
+       permitted_adm_opens[num_adm_permitted_opens].host_to_connect
+            = xstrdup(host);
+       permitted_adm_opens[num_adm_permitted_opens].port_to_connect = port;
+       return ++num_adm_permitted_opens;
+}
+
 void
 channel_clear_permitted_opens(void)
 {
        int i;
 
        for (i = 0; i < num_permitted_opens; i++)
-               xfree(permitted_opens[i].host_to_connect);
+               if (permitted_opens[i].host_to_connect != NULL)
+                       xfree(permitted_opens[i].host_to_connect);
        num_permitted_opens = 0;
+}
+
+void
+channel_clear_adm_permitted_opens(void)
+{
+       int i;
 
+       for (i = 0; i < num_adm_permitted_opens; i++)
+               if (permitted_adm_opens[i].host_to_connect != NULL)
+                       xfree(permitted_adm_opens[i].host_to_connect);
+       num_adm_permitted_opens = 0;
 }
 
+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");
+}
 
-/* return socket to remote host, port */
+/* 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, SOCK_STREAM, 0);
-               if (sock < 0) {
-                       error("socket: %.100s", strerror(errno));
+               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));
                        continue;
                }
-               if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0)
-                       fatal("connect_to: F_SETFL: %s", strerror(errno));
-               if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0 &&
-                   errno != EINPROGRESS) {
-                       error("connect_to %.100s port %s: %.100s", ntop, strport,
+               if (set_nonblock(sock) == -1)
+                       fatal("%s: set_nonblock(%d)", __func__, sock);
+               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++)
-               if (permitted_opens[i].listen_port == listen_port)
+       for (i = 0; i < num_permitted_opens; i++) {
+               if (permitted_opens[i].host_to_connect != NULL &&
+                   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;
+       int i, permit, permit_adm = 1;
 
        permit = all_opens_permitted;
        if (!permit) {
                for (i = 0; i < num_permitted_opens; i++)
-                       if (permitted_opens[i].port_to_connect == port &&
+                       if (permitted_opens[i].host_to_connect != NULL &&
+                           permitted_opens[i].port_to_connect == port &&
                            strcmp(permitted_opens[i].host_to_connect, host) == 0)
                                permit = 1;
+       }
 
+       if (num_adm_permitted_opens > 0) {
+               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 &&
+                           strcmp(permitted_adm_opens[i].host_to_connect, host)
+                           == 0)
+                               permit_adm = 1;
        }
-       if (!permit) {
-               log("Received request to connect to host %.100s port %d, "
+
+       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, ctype, rname);
+}
+
+void
+channel_send_window_changes(void)
+{
+       u_int i;
+       struct winsize ws;
+
+       for (i = 0; i < channels_alloc; i++) {
+               if (channels[i] == NULL || !channels[i]->client_tty ||
+                   channels[i]->type != SSH_CHANNEL_OPEN)
+                       continue;
+               if (ioctl(channels[i]->rfd, TIOCGWINSZ, &ws) < 0)
+                       continue;
+               channel_request_start(i, "window-change", 0);
+               packet_put_int((u_int)ws.ws_col);
+               packet_put_int((u_int)ws.ws_row);
+               packet_put_int((u_int)ws.ws_xpixel);
+               packet_put_int((u_int)ws.ws_ypixel);
+               packet_send();
        }
-       return connect_to(host, port);
 }
 
 /* -- X11 forwarding */
 
 /*
  * Creates an internet domain socket for listening for X11 connections.
- * Returns a suitable display number for the DISPLAY variable, or -1 if
- * an error occurs.
+ * Returns 0 and a suitable display number for the DISPLAY variable
+ * stored in display_numberp , or -1 if an error occurs.
  */
 int
 x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
-    int single_connection)
+    int single_connection, u_int *display_numberp, int **chanids)
 {
        Channel *nc = NULL;
        int display_number, sock;
@@ -2347,6 +3129,9 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
        char strport[NI_MAXSERV];
        int gaierr, n, num_socks = 0, socks[NUM_SOCKS];
 
+       if (chanids == NULL)
+               return -1;
+
        for (display_number = x11_display_offset;
            display_number < MAX_DISPLAYS;
            display_number++) {
@@ -2357,16 +3142,18 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
                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) {
                        if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
                                continue;
-                       sock = socket(ai->ai_family, SOCK_STREAM, 0);
+                       sock = socket(ai->ai_family, ai->ai_socktype,
+                           ai->ai_protocol);
                        if (sock < 0) {
                                if ((errno != EINVAL) && (errno != EAFNOSUPPORT)) {
                                        error("socket: %.100s", strerror(errno));
+                                       freeaddrinfo(aitop);
                                        return -1;
                                } else {
                                        debug("x11_create_display_inet: Socket family %d not supported",
@@ -2374,13 +3161,19 @@ 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 (x11_use_localhost)
+                               channel_set_reuseaddr(sock);
                        if (bind(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
-                               debug("bind port %d: %.100s", port, strerror(errno));
+                               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]);
                                }
@@ -2388,12 +3181,8 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
                                break;
                        }
                        socks[num_socks++] = sock;
-#ifndef DONT_TRY_OTHER_AF
                        if (num_socks == NUM_SOCKS)
                                break;
-#else
-                       break;
-#endif
                }
                freeaddrinfo(aitop);
                if (num_socks > 0)
@@ -2406,7 +3195,7 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
        /* Start listening for connections on the socket. */
        for (n = 0; n < num_socks; n++) {
                sock = socks[n];
-               if (listen(sock, 5) < 0) {
+               if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
                        error("listen: %.100s", strerror(errno));
                        close(sock);
                        return -1;
@@ -2414,21 +3203,32 @@ x11_create_display_inet(int x11_display_offset, int x11_use_localhost,
        }
 
        /* Allocate a channel for each socket. */
+       *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, xstrdup("X11 inet listener"), 1);
+                   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;
        }
+       (*chanids)[n] = -1;
 
        /* Return the display number for the DISPLAY environment variable. */
-       return display_number;
+       *display_numberp = display_number;
+       return (0);
 }
 
 static int
-connect_local_xsocket(u_int dnr)
+connect_local_xsocket_path(const char *pathname)
 {
        int sock;
        struct sockaddr_un addr;
@@ -2438,23 +3238,31 @@ connect_local_xsocket(u_int dnr)
                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);
-       if (connect(sock, (struct sockaddr *) & addr, sizeof(addr)) == 0)
+       strlcpy(addr.sun_path, pathname, sizeof addr.sun_path);
+       if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
                return sock;
        close(sock);
        error("connect %.100s: %.100s", addr.sun_path, strerror(errno));
        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)
 {
-       int display_number, sock = 0;
+       u_int display_number;
        const char *display;
        char buf[1024], *cp;
        struct addrinfo hints, *ai, *aitop;
        char strport[NI_MAXSERV];
-       int gaierr;
+       int gaierr, sock = 0;
 
        /* Try to open a socket for the local X server. */
        display = getenv("DISPLAY");
@@ -2467,6 +3275,17 @@ 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]
@@ -2474,7 +3293,7 @@ x11_connect_display(void)
        if (strncmp(display, "unix:", 5) == 0 ||
            display[0] == ':') {
                /* Connect to the unix domain socket. */
-               if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) {
+               if (sscanf(strrchr(display, ':') + 1, "%u", &display_number) != 1) {
                        error("Could not parse display number from DISPLAY: %.100s",
                            display);
                        return -1;
@@ -2499,7 +3318,7 @@ x11_connect_display(void)
        }
        *cp = 0;
        /* buf now contains the host name.  But first we parse the display number. */
-       if (sscanf(cp + 1, "%d", &display_number) != 1) {
+       if (sscanf(cp + 1, "%u", &display_number) != 1) {
                error("Could not parse display number from DISPLAY: %.100s",
                    display);
                return -1;
@@ -2509,21 +3328,22 @@ x11_connect_display(void)
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = IPv4or6;
        hints.ai_socktype = SOCK_STREAM;
-       snprintf(strport, sizeof strport, "%d", 6000 + display_number);
+       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) {
                /* Create a socket. */
-               sock = socket(ai->ai_family, SOCK_STREAM, 0);
+               sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
                if (sock < 0) {
-                       debug("socket: %.100s", strerror(errno));
+                       debug2("socket: %.100s", strerror(errno));
                        continue;
                }
                /* Connect it to the display. */
                if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
-                       debug("connect %.100s port %d: %.100s", buf,
+                       debug2("connect %.100s port %u: %.100s", buf,
                            6000 + display_number, strerror(errno));
                        close(sock);
                        continue;
@@ -2533,7 +3353,7 @@ x11_connect_display(void)
        }
        freeaddrinfo(aitop);
        if (!ai) {
-               error("connect %.100s port %d: %.100s", buf, 6000 + display_number,
+               error("connect %.100s port %u: %.100s", buf, 6000 + display_number,
                    strerror(errno));
                return -1;
        }
@@ -2547,6 +3367,7 @@ x11_connect_display(void)
  * with either SSH_MSG_OPEN_CONFIRMATION or SSH_MSG_OPEN_FAILURE.
  */
 
+/* ARGSUSED */
 void
 x11_input_open(int type, u_int32_t seq, void *ctxt)
 {
@@ -2575,6 +3396,7 @@ x11_input_open(int type, u_int32_t seq, void *ctxt)
                c->remote_id = remote_id;
                c->force_drain = 1;
        }
+       xfree(remote_host);
        if (c == NULL) {
                /* Send refusal to the remote host. */
                packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE);
@@ -2589,10 +3411,12 @@ x11_input_open(int type, u_int32_t seq, void *ctxt)
 }
 
 /* dummy protocol handler that denies SSH-1 requests (agent/x11) */
+/* ARGSUSED */
 void
 deny_input_open(int type, u_int32_t seq, void *ctxt)
 {
        int rchan = packet_get_int();
+
        switch (type) {
        case SSH_SMSG_AGENT_OPEN:
                error("Warning: ssh server tried agent forwarding.");
@@ -2604,7 +3428,7 @@ deny_input_open(int type, u_int32_t seq, void *ctxt)
                error("deny_input_open: type %d", type);
                break;
        }
-       error("Warning: this is probably a break in attempt by a malicious server.");
+       error("Warning: this is probably a break-in attempt by a malicious server.");
        packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE);
        packet_put_int(rchan);
        packet_send();
@@ -2616,53 +3440,57 @@ deny_input_open(int type, u_int32_t seq, void *ctxt)
  * This should be called in the client only.
  */
 void
-x11_request_forwarding_with_spoofing(int client_session_id,
+x11_request_forwarding_with_spoofing(int client_session_id, const char *disp,
     const char *proto, const char *data)
 {
        u_int data_len = (u_int) strlen(data) / 2;
-       u_int i, value, len;
+       u_int i, value;
        char *new_data;
        int screen_number;
        const char *cp;
-       u_int32_t rand = 0;
+       u_int32_t rnd = 0;
 
-       cp = getenv("DISPLAY");
-       if (cp)
-               cp = strchr(cp, ':');
+       if (x11_saved_display == NULL)
+               x11_saved_display = xstrdup(disp);
+       else if (strcmp(disp, x11_saved_display) != 0) {
+               error("x11_request_forwarding_with_spoofing: different "
+                   "$DISPLAY already forwarded");
+               return;
+       }
+
+       cp = strchr(disp, ':');
        if (cp)
                cp = strchr(cp, '.');
        if (cp)
-               screen_number = atoi(cp + 1);
+               screen_number = (u_int)strtonum(cp + 1, 0, 400, NULL);
        else
                screen_number = 0;
 
-       /* Save protocol name. */
-       x11_saved_proto = xstrdup(proto);
-
-       /*
-        * Extract real authentication data and generate fake data of the
-        * same length.
-        */
-       x11_saved_data = xmalloc(data_len);
-       x11_fake_data = xmalloc(data_len);
-       for (i = 0; i < data_len; i++) {
-               if (sscanf(data + 2 * i, "%2x", &value) != 1)
-                       fatal("x11_request_forwarding: bad authentication data: %.100s", data);
-               if (i % 4 == 0)
-                       rand = arc4random();
-               x11_saved_data[i] = value;
-               x11_fake_data[i] = rand & 0xff;
-               rand >>= 8;
-       }
-       x11_saved_data_len = data_len;
-       x11_fake_data_len = data_len;
+       if (x11_saved_proto == NULL) {
+               /* Save protocol name. */
+               x11_saved_proto = xstrdup(proto);
+               /*
+                * Extract real authentication data and generate fake data
+                * of the same length.
+                */
+               x11_saved_data = xmalloc(data_len);
+               x11_fake_data = xmalloc(data_len);
+               for (i = 0; i < data_len; i++) {
+                       if (sscanf(data + 2 * i, "%2x", &value) != 1)
+                               fatal("x11_request_forwarding: bad "
+                                   "authentication data: %.100s", data);
+                       if (i % 4 == 0)
+                               rnd = arc4random();
+                       x11_saved_data[i] = value;
+                       x11_fake_data[i] = rnd & 0xff;
+                       rnd >>= 8;
+               }
+               x11_saved_data_len = data_len;
+               x11_fake_data_len = data_len;
+       }
 
        /* Convert the fake data into hex. */
-       len = 2 * data_len + 1;
-       new_data = xmalloc(len);
-       for (i = 0; i < data_len; i++)
-               snprintf(new_data + 2 * i, len - 2 * i,
-                   "%02x", (u_char) x11_fake_data[i]);
+       new_data = tohex(x11_fake_data, data_len);
 
        /* Send the request packet. */
        if (compat20) {
@@ -2691,147 +3519,3 @@ auth_request_forwarding(void)
        packet_send();
        packet_write_wait();
 }
-
-/*
- * Returns the name of the forwarded authentication socket.  Returns NULL if
- * there is no forwarded authentication socket.  The returned value points to
- * a static buffer.
- */
-
-char *
-auth_get_socket_name(void)
-{
-       return auth_sock_name;
-}
-
-/* removes the agent forwarding socket */
-
-void
-auth_sock_cleanup_proc(void *_pw)
-{
-       struct passwd *pw = _pw;
-
-       if (auth_sock_name) {
-               temporarily_use_uid(pw);
-               unlink(auth_sock_name);
-               rmdir(auth_sock_dir);
-               auth_sock_name = NULL;
-               restore_uid();
-       }
-}
-
-/*
- * This is called to process SSH_CMSG_AGENT_REQUEST_FORWARDING on the server.
- * This starts forwarding authentication requests.
- */
-
-int
-auth_input_request_forwarding(struct passwd * pw)
-{
-       Channel *nc;
-       int sock;
-       struct sockaddr_un sunaddr;
-
-       if (auth_get_socket_name() != NULL) {
-               error("authentication forwarding requested twice.");
-               return 0;
-       }
-
-       /* Temporarily drop privileged uid for mkdir/bind. */
-       temporarily_use_uid(pw);
-
-       /* Allocate a buffer for the socket name, and format the name. */
-       auth_sock_name = xmalloc(MAXPATHLEN);
-       auth_sock_dir = xmalloc(MAXPATHLEN);
-       strlcpy(auth_sock_dir, "/tmp/ssh-XXXXXXXX", MAXPATHLEN);
-
-       /* Create private directory for socket */
-       if (mkdtemp(auth_sock_dir) == NULL) {
-               packet_send_debug("Agent forwarding disabled: "
-                   "mkdtemp() failed: %.100s", strerror(errno));
-               restore_uid();
-               xfree(auth_sock_name);
-               xfree(auth_sock_dir);
-               auth_sock_name = NULL;
-               auth_sock_dir = NULL;
-               return 0;
-       }
-       snprintf(auth_sock_name, MAXPATHLEN, "%s/agent.%d",
-                auth_sock_dir, (int) getpid());
-
-       /* delete agent socket on fatal() */
-       fatal_add_cleanup(auth_sock_cleanup_proc, pw);
-
-       /* Create the socket. */
-       sock = socket(AF_UNIX, SOCK_STREAM, 0);
-       if (sock < 0)
-               packet_disconnect("socket: %.100s", strerror(errno));
-
-       /* Bind it to the name. */
-       memset(&sunaddr, 0, sizeof(sunaddr));
-       sunaddr.sun_family = AF_UNIX;
-       strlcpy(sunaddr.sun_path, auth_sock_name, sizeof(sunaddr.sun_path));
-
-       if (bind(sock, (struct sockaddr *) & sunaddr, sizeof(sunaddr)) < 0)
-               packet_disconnect("bind: %.100s", strerror(errno));
-
-       /* Restore the privileged uid. */
-       restore_uid();
-
-       /* Start listening on the socket. */
-       if (listen(sock, 5) < 0)
-               packet_disconnect("listen: %.100s", strerror(errno));
-
-       /* Allocate a channel for the authentication agent socket. */
-       nc = channel_new("auth socket",
-           SSH_CHANNEL_AUTH_SOCKET, sock, sock, -1,
-           CHAN_X11_WINDOW_DEFAULT, CHAN_X11_PACKET_DEFAULT,
-           0, xstrdup("auth socket"), 1);
-       strlcpy(nc->path, auth_sock_name, sizeof(nc->path));
-       return 1;
-}
-
-/* This is called to process an SSH_SMSG_AGENT_OPEN message. */
-
-void
-auth_input_open_request(int type, u_int32_t seq, void *ctxt)
-{
-       Channel *c = NULL;
-       int remote_id, sock;
-       char *name;
-
-       /* Read the remote channel number from the message. */
-       remote_id = packet_get_int();
-       packet_check_eom();
-
-       /*
-        * Get a connection to the local authentication agent (this may again
-        * get forwarded).
-        */
-       sock = ssh_get_authentication_socket();
-
-       /*
-        * If we could not connect the agent, send an error message back to
-        * the server. This should never happen unless the agent dies,
-        * because authentication forwarding is only enabled if we have an
-        * agent.
-        */
-       if (sock >= 0) {
-               name = xstrdup("authentication agent connection");
-               c = channel_new("", SSH_CHANNEL_OPEN, sock, sock,
-                   -1, 0, 0, 0, name, 1);
-               c->remote_id = remote_id;
-               c->force_drain = 1;
-       }
-       if (c == NULL) {
-               packet_start(SSH_MSG_CHANNEL_OPEN_FAILURE);
-               packet_put_int(remote_id);
-       } else {
-               /* Send a confirmation to the remote host. */
-               debug("Forwarding authentication connection.");
-               packet_start(SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
-               packet_put_int(remote_id);
-               packet_put_int(c->self);
-       }
-       packet_send();
-}
This page took 0.277219 seconds and 4 git commands to generate.