Browse Source

PuTTY snapshot d3a9142d (Allow channels not to close immediately after two EOFs - 2018-10-21)

This includes 554e8f399 (Restore missing Event Log entries from SSH layers), what should fix re-key tests

Source commit: 51a0493c5f4dc6f3474374136d56473e5bbd7867
Martin Prikryl 6 years ago
parent
commit
b294a58f70

+ 9 - 3
source/putty/WINDOWS/winhsock.c

@@ -270,7 +270,7 @@ static const char *sk_handle_socket_error(Socket *s)
     return hs->error;
 }
 
-static char *sk_handle_peer_info(Socket *s)
+static SocketPeerInfo *sk_handle_peer_info(Socket *s)
 {
     HandleSocket *hs = container_of(s, HandleSocket, sock);
     ULONG pid;
@@ -299,8 +299,14 @@ static char *sk_handle_peer_info(Socket *s)
      * to log what we can find out about the client end.
      */
     if (p_GetNamedPipeClientProcessId &&
-        p_GetNamedPipeClientProcessId(hs->send_H, &pid))
-        return dupprintf("process id %lu", (unsigned long)pid);
+        p_GetNamedPipeClientProcessId(hs->send_H, &pid)) {
+        SocketPeerInfo *pi = snew(SocketPeerInfo);
+        pi->addressfamily = ADDRTYPE_LOCAL;
+        pi->addr_text = NULL;
+        pi->port = -1;
+        pi->log_text = dupprintf("process id %lu", (unsigned long)pid);
+        return pi;
+    }
 
     return NULL;
 }

+ 5 - 1
source/putty/agentf.c

@@ -155,7 +155,11 @@ static const struct ChannelVtable agentf_channelvt = {
     agentf_send_eof,
     agentf_set_input_wanted,
     agentf_log_close_msg,
-    chan_no_eager_close,
+    chan_default_want_close,
+    chan_no_exit_status,
+    chan_no_exit_signal,
+    chan_no_exit_signal_numeric,
+    chan_no_request_response,
 };
 
 Channel *agentf_new(SshChannel *c)

+ 2 - 0
source/putty/defs.h

@@ -44,6 +44,7 @@ typedef struct SockAddr SockAddr;
 
 typedef struct Socket Socket;
 typedef struct Plug Plug;
+typedef struct SocketPeerInfo SocketPeerInfo;
 
 typedef struct Backend Backend;
 typedef struct BackendVtable BackendVtable;
@@ -62,6 +63,7 @@ typedef struct Ssh Ssh;
 
 typedef struct Channel Channel;
 typedef struct SshChannel SshChannel;
+typedef struct mainchan mainchan;
 
 typedef struct ssh_sharing_state ssh_sharing_state;
 typedef struct ssh_sharing_connstate ssh_sharing_connstate;

+ 1 - 1
source/putty/errsock.c

@@ -39,7 +39,7 @@ static const char *sk_error_socket_error(Socket *s)
     return es->error;
 }
 
-static char *sk_error_peer_info(Socket *s)
+static SocketPeerInfo *sk_error_peer_info(Socket *s)
 {
     return NULL;
 }

+ 39 - 10
source/putty/logging.c

@@ -209,14 +209,8 @@ void logtraffic(LogContext *ctx, unsigned char c, int logmode)
     }
 }
 
-/*
- * Log an Event Log entry. Used in SSH packet logging mode, to copy
- * the Event Log entries into the same log file as the packet data.
- */
-void logevent(LogContext *ctx, const char *event)
+static void logevent_internal(LogContext *ctx, const char *event)
 {
-    if (!ctx)
-        return;
     if (ctx->logtype == LGTYP_PACKETS || ctx->logtype == LGTYP_SSHRAW) {
         logprintf(ctx, "Event Log: %s\r\n", event);
         logflush(ctx);
@@ -224,21 +218,56 @@ void logevent(LogContext *ctx, const char *event)
     lp_eventlog(ctx->lp, event);
 }
 
+void logevent(LogContext *ctx, const char *event)
+{
+    if (!ctx)
+        return;
+
+    /*
+     * Replace newlines in Event Log messages with spaces. (Sometimes
+     * the same message string is reused for the Event Log and a GUI
+     * dialog box; newlines are sometimes appropriate in the latter,
+     * but never in the former.)
+     */
+    if (strchr(event, '\n') || strchr(event, '\r')) {
+        char *dup = dupstr(event);
+        char *p = dup, *q = dup;
+        while (*p) {
+            if (*p == '\r' || *p == '\n') {
+                do {
+                    p++;
+                } while (*p == '\r' || *p == '\n');
+                *q++ = ' ';
+            } else {
+                *q++ = *p++;
+            }
+        }
+        *q = '\0';
+        logevent_internal(ctx, dup);
+        sfree(dup);
+    } else {
+        logevent_internal(ctx, event);
+    }
+}
+
 void logevent_and_free(LogContext *ctx, char *event)
 {
     logevent(ctx, event);
     sfree(event);
 }
 
+void logeventvf(LogContext *ctx, const char *fmt, va_list ap)
+{
+    logevent_and_free(ctx, dupvprintf(fmt, ap));
+}
+
 void logeventf(LogContext *ctx, const char *fmt, ...)
 {
     va_list ap;
-    char *buf;
 
     va_start(ap, fmt);
-    buf = dupvprintf(fmt, ap);
+    logeventvf(ctx, fmt, ap);
     va_end(ap);
-    logevent_and_free(ctx, buf);
 }
 
 /*

+ 548 - 0
source/putty/mainchan.c

@@ -0,0 +1,548 @@
+/*
+ * SSH main session channel handling.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "ssh.h"
+#include "sshppl.h"
+#include "sshchan.h"
+
+static void mainchan_free(Channel *chan);
+static void mainchan_open_confirmation(Channel *chan);
+static void mainchan_open_failure(Channel *chan, const char *errtext);
+static int mainchan_send(Channel *chan, int is_stderr, const void *, int);
+static void mainchan_send_eof(Channel *chan);
+static void mainchan_set_input_wanted(Channel *chan, int wanted);
+static char *mainchan_log_close_msg(Channel *chan);
+static int mainchan_rcvd_exit_status(Channel *chan, int status);
+static int mainchan_rcvd_exit_signal(
+    Channel *chan, ptrlen signame, int core_dumped, ptrlen msg);
+static int mainchan_rcvd_exit_signal_numeric(
+    Channel *chan, int signum, int core_dumped, ptrlen msg);
+static void mainchan_request_response(Channel *chan, int success);
+
+static const struct ChannelVtable mainchan_channelvt = {
+    mainchan_free,
+    mainchan_open_confirmation,
+    mainchan_open_failure,
+    mainchan_send,
+    mainchan_send_eof,
+    mainchan_set_input_wanted,
+    mainchan_log_close_msg,
+    chan_default_want_close,
+    mainchan_rcvd_exit_status,
+    mainchan_rcvd_exit_signal,
+    mainchan_rcvd_exit_signal_numeric,
+    mainchan_request_response,
+};
+
+typedef enum MainChanType {
+    MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP
+} MainChanType;
+
+typedef struct mainchan {
+    SshChannel *sc;
+    Conf *conf;
+    PacketProtocolLayer *ppl;
+    ConnectionLayer *cl;
+
+    MainChanType type;
+    int is_simple;
+
+    int req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback;
+    int n_req_env, n_env_replies, n_env_fails;
+    int eof_pending, eof_sent, got_pty, ready;
+
+    int term_width, term_height;
+
+    Channel chan;
+} mainchan;
+
+mainchan *mainchan_new(
+    PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+    int term_width, int term_height, int is_simple, SshChannel **sc_out)
+{
+    mainchan *mc;
+
+    if (conf_get_int(conf, CONF_ssh_no_shell))
+        return NULL;                   /* no main channel at all */
+
+    mc = snew(mainchan);
+    memset(mc, 0, sizeof(mainchan));
+    mc->ppl = ppl;
+    mc->cl = cl;
+    mc->conf = conf_copy(conf);
+    mc->term_width = term_width;
+    mc->term_height = term_height;
+    mc->is_simple = is_simple;
+
+    mc->sc = NULL;
+    mc->chan.vt = &mainchan_channelvt;
+    mc->chan.initial_fixed_window_size = 0;
+
+    if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) {
+        const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host);
+        int port = conf_get_int(mc->conf, CONF_ssh_nc_port);
+
+        mc->sc = ssh_lportfwd_open(cl, host, port, "main channel",
+                                   NULL, &mc->chan);
+        mc->type = MAINCHAN_DIRECT_TCPIP;
+    } else {
+        mc->sc = ssh_session_open(cl, &mc->chan);
+        mc->type = MAINCHAN_SESSION;
+    }
+
+    if (sc_out) *sc_out = mc->sc;
+    return mc;
+}
+
+static void mainchan_free(Channel *chan)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    conf_free(mc->conf);
+    sfree(mc);
+}
+
+static void mainchan_try_fallback_command(mainchan *mc);
+static void mainchan_ready(mainchan *mc);
+
+static void mainchan_open_confirmation(Channel *chan)
+{
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    seat_update_specials_menu(mc->ppl->seat);
+    ppl_logevent(("Opened main channel"));
+
+    if (mc->is_simple)
+        sshfwd_hint_channel_is_simple(mc->sc);
+
+    if (mc->type == MAINCHAN_SESSION) {
+	/*
+	 * Send the CHANNEL_REQUESTS for the main session channel.
+         */
+        char *key, *val, *cmd;
+        struct X11Display *x11disp;
+        struct X11FakeAuth *x11auth;
+        int retry_cmd_now = FALSE;
+
+	if (conf_get_int(mc->conf, CONF_x11_forward)) {;
+            char *x11_setup_err;
+            if ((x11disp = x11_setup_display(
+                     conf_get_str(mc->conf, CONF_x11_display),
+                     mc->conf, &x11_setup_err)) == NULL) {
+                ppl_logevent(("X11 forwarding not enabled: unable to"
+                              " initialise X display: %s", x11_setup_err));
+                sfree(x11_setup_err);
+            } else {
+                x11auth = ssh_add_x11_display(
+                    mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp);
+
+                sshfwd_request_x11_forwarding(
+                    mc->sc, TRUE, x11auth->protoname, x11auth->datastring,
+                    x11disp->screennum, FALSE);
+                mc->req_x11 = TRUE;
+            }
+        }
+
+	if (ssh_agent_forwarding_permitted(mc->cl)) {
+            sshfwd_request_agent_forwarding(mc->sc, TRUE);
+            mc->req_agent = TRUE;
+        }
+
+	if (!conf_get_int(mc->conf, CONF_nopty)) {
+            sshfwd_request_pty(
+                mc->sc, TRUE, mc->conf, mc->term_width, mc->term_height);
+            mc->req_pty = TRUE;
+        }
+
+        for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key);
+             val != NULL;
+             val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) {
+            sshfwd_send_env_var(mc->sc, TRUE, key, val);
+            mc->n_req_env++;
+        }
+        if (mc->n_req_env)
+            ppl_logevent(("Sent %d environment variables", mc->n_req_env));
+
+        cmd = conf_get_str(mc->conf, CONF_remote_cmd);
+        if (conf_get_int(mc->conf, CONF_ssh_subsys)) {
+            retry_cmd_now = !sshfwd_start_subsystem(mc->sc, TRUE, cmd);
+        } else if (*cmd) {
+            sshfwd_start_command(mc->sc, TRUE, cmd);
+        } else {
+            sshfwd_start_shell(mc->sc, TRUE);
+        }
+
+        if (retry_cmd_now)
+            mainchan_try_fallback_command(mc);
+        else
+            mc->req_cmd_primary = TRUE;
+
+    } else {
+        ssh_set_ldisc_option(mc->cl, LD_ECHO, TRUE);
+        ssh_set_ldisc_option(mc->cl, LD_EDIT, TRUE);
+        mainchan_ready(mc);
+    }
+}
+
+static void mainchan_try_fallback_command(mainchan *mc)
+{
+    const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2);
+    if (conf_get_int(mc->conf, CONF_ssh_subsys2)) {
+        sshfwd_start_subsystem(mc->sc, TRUE, cmd);
+    } else {
+        sshfwd_start_command(mc->sc, TRUE, cmd);
+    }
+    mc->req_cmd_fallback = TRUE;
+}
+
+static void mainchan_request_response(Channel *chan, int success)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    if (mc->req_x11) {
+        mc->req_x11 = FALSE;
+
+        if (success) {
+            ppl_logevent(("X11 forwarding enabled"));
+            ssh_enable_x_fwd(mc->cl);
+        } else {
+            ppl_logevent(("X11 forwarding refused"));
+        }
+        return;
+    }
+
+    if (mc->req_agent) {
+        mc->req_agent = FALSE;
+
+        if (success) {
+            ppl_logevent(("Agent forwarding enabled"));
+            ssh_enable_agent_fwd(mc->cl);
+        } else {
+            ppl_logevent(("Agent forwarding refused"));
+        }
+        return;
+    }
+
+    if (mc->req_pty) {
+        mc->req_pty = FALSE;
+
+        if (success) {
+            ppl_logevent(("Allocated pty"));
+            mc->got_pty = TRUE;
+        } else {
+            ppl_logevent(("Server refused to allocate pty"));
+            ppl_printf(("Server refused to allocate pty\r\n"));
+            ssh_set_ldisc_option(mc->cl, LD_ECHO, TRUE);
+            ssh_set_ldisc_option(mc->cl, LD_EDIT, TRUE);
+        }
+        return;
+    }
+
+    if (mc->n_env_replies < mc->n_req_env) {
+        int j = mc->n_env_replies++;
+        if (!success) {
+            ppl_logevent(("Server refused to set environment variable %s",
+                          conf_get_str_nthstrkey(mc->conf,
+                                                 CONF_environmt, j)));
+            mc->n_env_fails++;
+        }
+
+        if (mc->n_env_replies == mc->n_req_env) {
+            if (mc->n_env_fails == 0) {
+                ppl_logevent(("All environment variables successfully set"));
+            } else if (mc->n_env_fails == mc->n_req_env) {
+                ppl_logevent(("All environment variables refused"));
+                ppl_printf(("Server refused to set environment "
+                            "variables\r\n"));
+            } else {
+                ppl_printf(("Server refused to set all environment "
+                            "variables\r\n"));
+            }
+        }
+        return;
+    }
+
+    if (mc->req_cmd_primary) {
+        mc->req_cmd_primary = FALSE;
+
+        if (success) {
+            ppl_logevent(("Started a shell/command"));
+            mainchan_ready(mc);
+        } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) {
+            ppl_logevent(("Primary command failed; attempting fallback"));
+            mainchan_try_fallback_command(mc);
+        } else {
+            /*
+             * If there's no remote_cmd2 configured, then we have no
+             * fallback command, so we've run out of options.
+             */
+            ssh_sw_abort(mc->ppl->ssh,
+                         "Server refused to start a shell/command");
+        }
+        return;
+    }
+
+    if (mc->req_cmd_fallback) {
+        mc->req_cmd_fallback = FALSE;
+
+        if (success) {
+            ppl_logevent(("Started a shell/command"));
+            ssh_got_fallback_cmd(mc->ppl->ssh);
+            mainchan_ready(mc);
+        } else {
+            ssh_sw_abort(mc->ppl->ssh,
+                         "Server refused to start a shell/command");
+        }
+        return;
+    }
+}
+
+static void mainchan_ready(mainchan *mc)
+{
+    mc->ready = TRUE;
+
+    ssh_set_wants_user_input(mc->cl, TRUE);
+    ssh_ppl_got_user_input(mc->ppl); /* in case any is already queued */
+
+    /* If an EOF arrived before we were ready, handle it now. */
+    if (mc->eof_pending) {
+        mc->eof_pending = FALSE;
+	mainchan_special_cmd(mc, SS_EOF, 0);
+    }
+
+    ssh_ldisc_update(mc->ppl->ssh);
+    queue_idempotent_callback(&mc->ppl->ic_process_queue);
+}
+
+struct mainchan_open_failure_abort_ctx {
+    Ssh *ssh;
+    char *abort_message;
+};
+
+static void mainchan_open_failure_abort(void *vctx)
+{
+    struct mainchan_open_failure_abort_ctx *ctx =
+        (struct mainchan_open_failure_abort_ctx *)vctx;
+    ssh_sw_abort(
+        ctx->ssh, "Server refused to open main channel: %s",
+        ctx->abort_message);
+    sfree(ctx->abort_message);
+    sfree(ctx);
+}
+
+static void mainchan_open_failure(Channel *chan, const char *errtext)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+
+    struct mainchan_open_failure_abort_ctx *ctx =
+        snew(struct mainchan_open_failure_abort_ctx);
+
+    ctx->ssh = mc->ppl->ssh;
+    ctx->abort_message = dupstr(errtext);
+    queue_toplevel_callback(mainchan_open_failure_abort, ctx);
+}
+
+static int mainchan_send(Channel *chan, int is_stderr,
+                         const void *data, int length)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    return seat_output(mc->ppl->seat, is_stderr, data, length);
+}
+
+static void mainchan_send_eof(Channel *chan)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    if (!mc->eof_sent & (seat_eof(mc->ppl->seat) || mc->got_pty)) {
+        /*
+         * Either seat_eof told us that the front end wants us to
+         * close the outgoing side of the connection as soon as we see
+         * EOF from the far end, or else we've unilaterally decided to
+         * do that because we've allocated a remote pty and hence EOF
+         * isn't a particularly meaningful concept.
+         */
+        sshfwd_write_eof(mc->sc);
+        ppl_logevent(("Sent EOF message"));
+    }
+    mc->eof_sent = TRUE;
+    ssh_set_wants_user_input(mc->cl, FALSE); /* now stop reading from stdin */
+}
+
+static void mainchan_set_input_wanted(Channel *chan, int wanted)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+
+    /*
+     * This is the main channel of the SSH session, i.e. the one tied
+     * to the standard input (or GUI) of the primary SSH client user
+     * interface. So ssh->send_ok is how we control whether we're
+     * reading from that input.
+     */
+    ssh_set_wants_user_input(mc->cl, wanted);
+}
+
+static char *mainchan_log_close_msg(Channel *chan)
+{
+    return dupstr("Main session channel closed");
+}
+
+static int mainchan_rcvd_exit_status(Channel *chan, int status)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    ssh_got_exitcode(mc->ppl->ssh, status);
+    ppl_logevent(("Session sent command exit status %d", status));
+    return TRUE;
+}
+
+static void mainchan_log_exit_signal_common(
+    mainchan *mc, const char *sigdesc,
+    int core_dumped, ptrlen msg)
+{
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+
+    const char *core_msg = core_dumped ? " (core dumped)" : "";
+    const char *msg_pre = (msg.len ? " (" : "");
+    const char *msg_post = (msg.len ? ")" : "");
+    ppl_logevent(("Session exited on %s%s%s%.*s%s",
+                  sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post));
+}
+
+static int mainchan_rcvd_exit_signal(
+    Channel *chan, ptrlen signame, int core_dumped, ptrlen msg)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    int exitcode;
+    char *signame_str;
+
+    /*
+     * Translate the signal description back into a locally meaningful
+     * number, or 128 if the string didn't match any we recognise.
+     */
+    exitcode = 128;
+
+    #define SIGNAL_SUB(s) \
+        if (ptrlen_eq_string(signame, #s))      \
+            exitcode = 128 + SIG ## s;
+    #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s)
+    #define SIGNALS_LOCAL_ONLY
+    #include "sshsignals.h"
+    #undef SIGNAL_SUB
+    #undef SIGNAL_MAIN
+    #undef SIGNALS_LOCAL_ONLY
+
+    ssh_got_exitcode(mc->ppl->ssh, exitcode);
+    if (exitcode == 128)
+        signame_str = dupprintf("unrecognised signal \"%.*s\"",
+                                PTRLEN_PRINTF(signame));
+    else
+        signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame));
+    mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg);
+    sfree(signame_str);
+    return TRUE;
+}
+
+static int mainchan_rcvd_exit_signal_numeric(
+    Channel *chan, int signum, int core_dumped, ptrlen msg)
+{
+    assert(chan->vt == &mainchan_channelvt);
+    mainchan *mc = container_of(chan, mainchan, chan);
+    char *signum_str;
+
+    ssh_got_exitcode(mc->ppl->ssh, 128 + signum);
+    signum_str = dupprintf("signal %d", signum);
+    mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg);
+    sfree(signum_str);
+    return TRUE;
+}
+
+void mainchan_get_specials(
+    mainchan *mc, add_special_fn_t add_special, void *ctx)
+{
+    /* FIXME: this _does_ depend on whether these services are supported */
+
+    add_special(ctx, "Break", SS_BRK, 0);
+
+    #define SIGNAL_MAIN(name, desc) \
+    add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0);
+    #define SIGNAL_SUB(name)
+    #include "sshsignals.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    add_special(ctx, "More signals", SS_SUBMENU, 0);
+
+    #define SIGNAL_MAIN(name, desc)
+    #define SIGNAL_SUB(name) \
+    add_special(ctx, "SIG" #name, SS_SIG ## name, 0);
+    #include "sshsignals.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    add_special(ctx, NULL, SS_EXITMENU, 0);
+}
+
+static const char *ssh_signal_lookup(SessionSpecialCode code)
+{
+    #define SIGNAL_SUB(name) \
+    if (code == SS_SIG ## name) return #name;
+    #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name)
+    #include "sshsignals.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
+
+    /* If none of those clauses matched, fail lookup. */
+    return NULL;
+}
+
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg)
+{
+    PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */
+    const char *signame;
+
+    if (code == SS_EOF) {
+        if (!mc->ready) {
+            /*
+             * Buffer the EOF to send as soon as the main channel is
+             * fully set up.
+             */
+            mc->eof_pending = TRUE;
+        } else if (!mc->eof_sent) {
+            sshfwd_write_eof(mc->sc);
+            mc->eof_sent = TRUE;
+        }
+    } else if (code == SS_BRK) {
+        sshfwd_send_serial_break(
+            mc->sc, FALSE, 0 /* default break length */);
+    } else if ((signame = ssh_signal_lookup(code)) != NULL) {
+        /* It's a signal. */
+        sshfwd_send_signal(mc->sc, FALSE, signame);
+        ppl_logevent(("Sent signal SIG%s", signame));
+    }
+}
+
+void mainchan_terminal_size(mainchan *mc, int width, int height)
+{
+    mc->term_width = width;
+    mc->term_height = height;
+
+    if (mc->req_pty || mc->got_pty)
+        sshfwd_send_terminal_size_change(mc->sc, width, height);
+}

+ 38 - 1
source/putty/misc.c

@@ -1243,12 +1243,40 @@ ptrlen make_ptrlen(const void *ptr, size_t len)
     return pl;
 }
 
+ptrlen ptrlen_from_asciz(const char *str)
+{
+    return make_ptrlen(str, strlen(str));
+}
+
+ptrlen ptrlen_from_strbuf(strbuf *sb)
+{
+    return make_ptrlen(sb->u, sb->len);
+}
+
 int ptrlen_eq_string(ptrlen pl, const char *str)
 {
     size_t len = strlen(str);
     return (pl.len == len && !memcmp(pl.ptr, str, len));
 }
 
+int ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2)
+{
+    return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len));
+}
+
+int ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail)
+{
+    if (whole.len >= prefix.len &&
+        !memcmp(whole.ptr, prefix.ptr, prefix.len)) {
+        if (tail) {
+            tail->ptr = (const char *)whole.ptr + prefix.len;
+            tail->len = whole.len - prefix.len;
+        }
+        return TRUE;
+    }
+    return FALSE;
+}
+
 char *mkstr(ptrlen pl)
 {
     char *p = snewn(pl.len + 1, char);
@@ -1386,5 +1414,14 @@ int nullseat_is_always_utf8(Seat *seat) { return TRUE; }
 void nullseat_echoedit_update(Seat *seat, int echoing, int editing) {}
 const char *nullseat_get_x_display(Seat *seat) { return NULL; }
 int nullseat_get_windowid(Seat *seat, long *id_out) { return FALSE; }
-int nullseat_get_char_cell_size(
+int nullseat_get_window_pixel_size(
     Seat *seat, int *width, int *height) { return FALSE; }
+
+void sk_free_peer_info(SocketPeerInfo *pi)
+{
+    if (pi) {
+        sfree((char *)pi->addr_text);
+        sfree((char *)pi->log_text);
+        sfree(pi);
+    }
+}

+ 4 - 0
source/putty/misc.h

@@ -97,7 +97,11 @@ struct tm ltime(void);
 int nullstrcmp(const char *a, const char *b);
 
 ptrlen make_ptrlen(const void *ptr, size_t len);
+ptrlen ptrlen_from_asciz(const char *str);
+ptrlen ptrlen_from_strbuf(strbuf *sb);
 int ptrlen_eq_string(ptrlen pl, const char *str);
+int ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2);
+int ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
 char *mkstr(ptrlen pl);
 int string_length_for_printf(size_t);
 /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */

+ 44 - 4
source/putty/network.h

@@ -35,7 +35,7 @@ struct SocketVtable {
     void (*set_frozen) (Socket *s, int is_frozen);
     /* ignored by tcp, but vital for ssl */
     const char *(*socket_error) (Socket *s);
-    char *(*peer_info) (Socket *s);
+    SocketPeerInfo *(*peer_info) (Socket *s);
 };
 
 typedef union { void *p; int i; } accept_ctx_t;
@@ -194,12 +194,52 @@ const char *sk_addr_error(SockAddr *addr);
 #define sk_set_frozen(s, is_frozen) (((s)->vt->set_frozen) (s, is_frozen))
 
 /*
- * Return a (dynamically allocated) string giving some information
- * about the other end of the socket, suitable for putting in log
- * files. May be NULL if nothing is available at all.
+ * Return a structure giving some information about the other end of
+ * the socket. May be NULL, if nothing is available at all. If it is
+ * not NULL, then it is dynamically allocated, and should be freed by
+ * a call to sk_free_peer_info(). See below for the definition.
  */
 #define sk_peer_info(s) (((s)->vt->peer_info) (s))
 
+/*
+ * The structure returned from sk_peer_info, and a function to free
+ * one (in misc.c).
+ */
+struct SocketPeerInfo {
+    int addressfamily;
+
+    /*
+     * Text form of the IPv4 or IPv6 address of the other end of the
+     * socket, if available, in the standard text representation.
+     */
+    const char *addr_text;
+
+    /*
+     * Binary form of the same address. Filled in if and only if
+     * addr_text is not NULL. You can tell which branch of the union
+     * is used by examining 'addressfamily'.
+     */
+    union {
+        unsigned char ipv6[16];
+        unsigned char ipv4[4];
+    } addr_bin;
+
+    /*
+     * Remote port number, or -1 if not available.
+     */
+    int port;
+
+    /*
+     * Free-form text suitable for putting in log messages. For IP
+     * sockets, repeats the address and port information from above.
+     * But it can be completely different, e.g. for Unix-domain
+     * sockets it gives information about the uid, gid and pid of the
+     * connecting process.
+     */
+    const char *log_text;
+};
+void sk_free_peer_info(SocketPeerInfo *pi);
+
 /*
  * Simple wrapper on getservbyname(), needed by ssh.c. Returns the
  * port number, in host byte order (suitable for printf and so on).

+ 17 - 12
source/putty/portfwd.c

@@ -120,12 +120,12 @@ static void pfd_closing(Plug *plug, const char *error_msg, int error_code,
          * Socket error. Slam the connection instantly shut.
          */
         if (pf->c) {
-            sshfwd_unclean_close(pf->c, error_msg);
+            sshfwd_initiate_close(pf->c, error_msg);
         } else {
             /*
              * We might not have an SSH channel, if a socket error
              * occurred during SOCKS negotiation. If not, we must
-             * clean ourself up without sshfwd_unclean_close's call
+             * clean ourself up without sshfwd_initiate_close's call
              * back to pfd_close.
              */
             pfd_close(pf);
@@ -153,18 +153,18 @@ static SshChannel *wrap_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
     Socket *s, Channel *chan)
 {
-    char *peerinfo, *description;
+    SocketPeerInfo *pi;
+    char *description;
     SshChannel *toret;
 
-    peerinfo = sk_peer_info(s);
-    if (peerinfo) {
-        description = dupprintf("forwarding from %s", peerinfo);
-        sfree(peerinfo);
+    pi = sk_peer_info(s);
+    if (pi && pi->log_text) {
+        description = dupprintf("forwarding from %s", pi->log_text);
     } else {
         description = dupstr("forwarding");
     }
-
-    toret = ssh_lportfwd_open(cl, hostname, port, description, chan);
+    toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan);
+    sk_free_peer_info(pi);
 
     sfree(description);
     return toret;
@@ -450,7 +450,11 @@ static const struct ChannelVtable PortForwarding_channelvt = {
     pfd_send_eof,
     pfd_set_input_wanted,
     pfd_log_close_msg,
-    chan_no_eager_close,
+    chan_default_want_close,
+    chan_no_exit_status,
+    chan_no_exit_signal,
+    chan_no_exit_signal_numeric,
+    chan_no_request_response,
 };
 
 /*
@@ -516,8 +520,9 @@ static const PlugVtable PortListener_plugvt = {
  * On success, returns NULL and fills in *pl_ret. On error, returns a
  * dynamically allocated error message string.
  */
-static char *pfl_listen(char *desthost, int destport, char *srcaddr,
-                        int port, ConnectionLayer *cl, Conf *conf,
+static char *pfl_listen(const char *desthost, int destport,
+                        const char *srcaddr, int port,
+                        ConnectionLayer *cl, Conf *conf,
                         struct PortListener **pl_ret, int address_family)
 {
     const char *err;

+ 27 - 10
source/putty/putty.h

@@ -211,10 +211,16 @@ typedef enum {
 
     /*
      * Send a POSIX-style signal. (Useful in SSH and also pterm.)
+     *
+     * We use the master list in sshsignals.h to define these enum
+     * values, which will come out looking like names of the form
+     * SS_SIGABRT, SS_SIGINT etc.
      */
-    SS_SIGABRT, SS_SIGALRM, SS_SIGFPE,  SS_SIGHUP,  SS_SIGILL,
-    SS_SIGINT,  SS_SIGKILL, SS_SIGPIPE, SS_SIGQUIT, SS_SIGSEGV,
-    SS_SIGTERM, SS_SIGUSR1, SS_SIGUSR2,
+    #define SIGNAL_MAIN(name, text) SS_SIG ## name,
+    #define SIGNAL_SUB(name) SS_SIG ## name,
+    #include "sshsignals.h"
+    #undef SIGNAL_MAIN
+    #undef SIGNAL_SUB
 
     /*
      * These aren't really special commands, but they appear in the
@@ -236,6 +242,10 @@ struct SessionSpecial {
     int arg;
 };
 
+/* Needed by both sshchan.h and sshppl.h */
+typedef void (*add_special_fn_t)(
+    void *ctx, const char *text, SessionSpecialCode code, int arg);
+
 typedef enum {
     MBT_NOTHING,
     MBT_LEFT, MBT_MIDDLE, MBT_RIGHT,   /* `raw' button designations */
@@ -357,7 +367,8 @@ enum {
      * Line discipline options which the backend might try to control.
      */
     LD_EDIT,			       /* local line editing */
-    LD_ECHO			       /* local echo */
+    LD_ECHO,                           /* local echo */
+    LD_N_OPTIONS
 };
 
 enum {
@@ -472,7 +483,11 @@ enum {
      * host name has already been resolved or will be resolved at
      * the proxy end.
      */
-    ADDRTYPE_UNSPEC, ADDRTYPE_IPV4, ADDRTYPE_IPV6, ADDRTYPE_NAME
+    ADDRTYPE_UNSPEC,
+    ADDRTYPE_IPV4,
+    ADDRTYPE_IPV6,
+    ADDRTYPE_LOCAL,    /* e.g. Unix domain socket, or Windows named pipe */
+    ADDRTYPE_NAME      /* SockAddr storing an unresolved host name */
 };
 
 struct Backend {
@@ -892,12 +907,12 @@ struct SeatVtable {
     int (*get_windowid)(Seat *seat, long *id_out);
 
     /*
-     * Return the pixel size of a terminal character cell. If the
+     * Return the size of the terminal window in pixels. If the
      * concept is meaningless or the information is unavailable,
      * return FALSE; otherwise fill in the output pointers and return
      * TRUE.
      */
-    int (*get_char_cell_size)(Seat *seat, int *width, int *height);
+    int (*get_window_pixel_size)(Seat *seat, int *width, int *height);
 };
 
 #define seat_output(seat, is_stderr, data, len) \
@@ -928,8 +943,8 @@ struct SeatVtable {
     ((seat)->vt->get_x_display(seat))
 #define seat_get_windowid(seat, out) \
     ((seat)->vt->get_windowid(seat, out))
-#define seat_get_char_cell_size(seat, width, height) \
-    ((seat)->vt->get_char_cell_size(seat, width, height))
+#define seat_get_window_pixel_size(seat, width, height) \
+    ((seat)->vt->get_window_pixel_size(seat, width, height))
 
 /* Unlike the seat's actual method, the public entry point
  * seat_connection_fatal is a wrapper function with a printf-like API,
@@ -972,7 +987,7 @@ int nullseat_is_always_utf8(Seat *seat);
 void nullseat_echoedit_update(Seat *seat, int echoing, int editing);
 const char *nullseat_get_x_display(Seat *seat);
 int nullseat_get_windowid(Seat *seat, long *id_out);
-int nullseat_get_char_cell_size(Seat *seat, int *width, int *height);
+int nullseat_get_window_pixel_size(Seat *seat, int *width, int *height);
 
 /*
  * Seat functions provided by the platform's console-application
@@ -1484,6 +1499,8 @@ void logtraffic(LogContext *logctx, unsigned char c, int logmode);
 void logflush(LogContext *logctx);
 void logevent(LogContext *logctx, const char *event);
 void logeventf(LogContext *logctx, const char *fmt, ...);
+void logeventvf(LogContext *logctx, const char *fmt, va_list ap);
+
 /*
  * Pass a dynamically allocated string to logevent and immediately
  * free it. Intended for use by wrapper macros which pass the return

+ 1 - 0
source/putty/ssh.c

@@ -142,6 +142,7 @@ static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl)
     ppl->user_input = &ssh->user_input;
     ppl->seat = ssh->seat;
     ppl->ssh = ssh;
+    ppl->logctx = ssh->logctx;
     ppl->remote_bugs = ssh->remote_bugs;
 }
 

+ 88 - 8
source/putty/ssh.h

@@ -186,6 +186,9 @@ void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
                              int protomajor, int protominor,
                              const void *initial_data, int initial_len);
 
+struct X11Display;
+struct X11FakeAuth;
+
 /* Structure definition centralised here because the SSH-1 and SSH-2
  * connection layers both use it. But the client module (portfwd.c)
  * should not try to look inside here. */
@@ -213,7 +216,15 @@ struct ConnectionLayerVtable {
      * PortFwdManager */
     SshChannel *(*lportfwd_open)(
         ConnectionLayer *cl, const char *hostname, int port,
-        const char *org, Channel *chan);
+        const char *description, const SocketPeerInfo *peerinfo,
+        Channel *chan);
+
+    /* Initiate opening of a 'session'-type channel */
+    SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan);
+
+    /* Add an X11 display for ordinary X forwarding */
+    struct X11FakeAuth *(*add_x11_display)(
+        ConnectionLayer *cl, int authtype, struct X11Display *x11disp);
 
     /* Add and remove X11 displays for connection sharing downstreams */
     struct X11FakeAuth *(*add_sharing_x11_display)(
@@ -263,6 +274,20 @@ struct ConnectionLayerVtable {
     /* Ask the connection layer about its current preference for
      * line-discipline options. */
     int (*ldisc_option)(ConnectionLayer *cl, int option);
+
+    /* Communicate _to_ the connection layer (from the main session
+     * channel) what its preference for line-discipline options is. */
+    void (*set_ldisc_option)(ConnectionLayer *cl, int option, int value);
+
+    /* Communicate to the connection layer whether X and agent
+     * forwarding were successfully enabled (for purposes of
+     * knowing whether to accept subsequent channel-opens). */
+    void (*enable_x_fwd)(ConnectionLayer *cl);
+    void (*enable_agent_fwd)(ConnectionLayer *cl);
+
+    /* Communicate to the connection layer whether the main session
+     * channel currently wants user input. */
+    void (*set_wants_user_input)(ConnectionLayer *cl, int wanted);
 };
 
 struct ConnectionLayer {
@@ -273,8 +298,12 @@ struct ConnectionLayer {
 #define ssh_rportfwd_alloc(cl, sh, sp, dh, dp, af, ld, pfr, share) \
     ((cl)->vt->rportfwd_alloc(cl, sh, sp, dh, dp, af, ld, pfr, share))
 #define ssh_rportfwd_remove(cl, rpf) ((cl)->vt->rportfwd_remove(cl, rpf))
-#define ssh_lportfwd_open(cl, h, p, org, chan) \
-    ((cl)->vt->lportfwd_open(cl, h, p, org, chan))
+#define ssh_lportfwd_open(cl, h, p, desc, pi, chan) \
+    ((cl)->vt->lportfwd_open(cl, h, p, desc, pi, chan))
+#define ssh_session_open(cl, chan) \
+    ((cl)->vt->session_open(cl, chan))
+#define ssh_add_x11_display(cl, auth, disp) \
+    ((cl)->vt->add_x11_display(cl, auth, disp))
 #define ssh_add_sharing_x11_display(cl, auth, cs, ch)   \
     ((cl)->vt->add_sharing_x11_display(cl, auth, cs, ch))
 #define ssh_remove_sharing_x11_display(cl, fa)   \
@@ -298,6 +327,12 @@ struct ConnectionLayer {
 #define ssh_throttle_all_channels(cl, throttled) \
     ((cl)->vt->throttle_all_channels(cl, throttled))
 #define ssh_ldisc_option(cl, option) ((cl)->vt->ldisc_option(cl, option))
+#define ssh_set_ldisc_option(cl, opt, val) \
+    ((cl)->vt->set_ldisc_option(cl, opt, val))
+#define ssh_enable_x_fwd(cl) ((cl)->vt->enable_x_fwd(cl))
+#define ssh_enable_agent_fwd(cl) ((cl)->vt->enable_agent_fwd(cl))
+#define ssh_set_wants_user_input(cl, wanted) \
+    ((cl)->vt->set_wants_user_input(cl, wanted))
 
 /* Exports from portfwd.c */
 PortFwdManager *portfwdmgr_new(ConnectionLayer *cl);
@@ -1359,6 +1394,44 @@ enum {
 
 #define SSH2_EXTENDED_DATA_STDERR                 1	/* 0x1 */
 
+enum {
+    /* TTY modes with opcodes defined consistently in the SSH specs. */
+    #define TTYMODE_CHAR(name, val, index) SSH_TTYMODE_##name = val,
+    #define TTYMODE_FLAG(name, val, field, mask) SSH_TTYMODE_##name = val,
+    #include "sshttymodes.h"
+    #undef TTYMODE_CHAR
+    #undef TTYMODE_FLAG
+
+    /* Modes encoded differently between SSH-1 and SSH-2, for which we
+     * make up our own dummy opcodes to avoid confusion. */
+    TTYMODE_dummy = 255,
+    TTYMODE_ISPEED, TTYMODE_OSPEED,
+
+    /* Limiting value that we can use as an array bound below */
+    TTYMODE_LIMIT,
+
+    /* The real opcodes for terminal speeds. */
+    TTYMODE_ISPEED_SSH1 = 192,
+    TTYMODE_OSPEED_SSH1 = 193,
+    TTYMODE_ISPEED_SSH2 = 128,
+    TTYMODE_OSPEED_SSH2 = 129,
+
+    /* And the opcode that ends a list. */
+    TTYMODE_END_OF_LIST = 0
+};
+
+struct ssh_ttymodes {
+    /* A boolean per mode, indicating whether it's set. */
+    int have_mode[TTYMODE_LIMIT];
+
+    /* The actual value for each mode. */
+    unsigned mode_val[TTYMODE_LIMIT];
+};
+
+struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf);
+void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,
+                              struct ssh_ttymodes modes);
+
 const char *ssh1_pkt_type(int type);
 const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type);
 int ssh2_pkt_type_code_valid(unsigned type);
@@ -1395,11 +1468,6 @@ enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) };
 enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_REAL_ENUM) };
 #undef TMP_DECLARE_REAL_ENUM
 
-/* Shared function that writes tty modes into a pty request */
-void write_ttymodes_to_packet_from_conf(
-    BinarySink *bs, Seat *seat, Conf *conf,
-    int ssh_version, int ospeed, int ispeed);
-
 /* Shared system for allocating local SSH channel ids. Expects to be
  * passed a tree full of structs that have a field called 'localid' of
  * type unsigned, and will check that! */
@@ -1412,6 +1480,18 @@ int first_in_commasep_string(char const *needle, char const *haystack,
                              int haylen);
 int in_commasep_string(char const *needle, char const *haystack, int haylen);
 void add_to_commasep(strbuf *buf, const char *data);
+int get_commasep_word(ptrlen *list, ptrlen *word);
 
 int verify_ssh_manual_host_key(
     Conf *conf, const char *fingerprint, ssh_key *key);
+
+typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache;
+ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void);
+void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc);
+void ssh_transient_hostkey_cache_add(
+    ssh_transient_hostkey_cache *thc, ssh_key *key);
+int ssh_transient_hostkey_cache_verify(
+    ssh_transient_hostkey_cache *thc, ssh_key *key);
+int ssh_transient_hostkey_cache_has(
+    ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg);
+int ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc);

+ 391 - 196
source/putty/ssh1connection.c

@@ -26,12 +26,20 @@ struct ssh1_connection_state {
 
     tree234 *channels;		       /* indexed by local id */
 
+    /* In SSH-1, the main session doesn't take the form of a 'channel'
+     * according to the wire protocol. But we want to use the same API
+     * for it, so we define an SshChannel here - but one that uses a
+     * separate vtable from the usual one, so it doesn't map to a
+     * struct ssh1_channel as all the others do. */
+    SshChannel mainchan_sc;
+    Channel *mainchan_chan;            /* the other end of mainchan_sc */
+    mainchan *mainchan;                /* and its subtype */
+
     int got_pty;
-    int echoedit;
-    int ospeed, ispeed;
+    int ldisc_opts[LD_N_OPTIONS];
     int stdout_throttling;
-    int session_ready;
-    int session_eof_pending, session_eof_sent, session_terminated;
+    int want_user_input;
+    int session_terminated;
     int term_width, term_height, term_width_orig, term_height_orig;
 
     int X11_fwd_enabled;
@@ -101,18 +109,27 @@ static void ssh1_rportfwd_remove(
     ConnectionLayer *cl, struct ssh_rportfwd *rpf);
 static SshChannel *ssh1_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
-    const char *org, Channel *chan);
+    const char *description, const SocketPeerInfo *pi, Channel *chan);
+static SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan);
+static struct X11FakeAuth *ssh1_add_x11_display(
+    ConnectionLayer *cl, int authtype, struct X11Display *disp);
 static int ssh1_agent_forwarding_permitted(ConnectionLayer *cl);
 static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height);
 static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize);
 static int ssh1_stdin_backlog(ConnectionLayer *cl);
 static void ssh1_throttle_all_channels(ConnectionLayer *cl, int throttled);
 static int ssh1_ldisc_option(ConnectionLayer *cl, int option);
+static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, int value);
+static void ssh1_enable_x_fwd(ConnectionLayer *cl);
+static void ssh1_enable_agent_fwd(ConnectionLayer *cl);
+static void ssh1_set_wants_user_input(ConnectionLayer *cl, int wanted);
 
 static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
     ssh1_rportfwd_alloc,
     ssh1_rportfwd_remove,
     ssh1_lportfwd_open,
+    ssh1_session_open,
+    ssh1_add_x11_display,
     NULL /* add_sharing_x11_display */,
     NULL /* remove_sharing_x11_display */,
     NULL /* send_packet_from_downstream */,
@@ -126,6 +143,10 @@ static const struct ConnectionLayerVtable ssh1_connlayer_vtable = {
     ssh1_stdin_backlog,
     ssh1_throttle_all_channels,
     ssh1_ldisc_option,
+    ssh1_set_ldisc_option,
+    ssh1_enable_x_fwd,
+    ssh1_enable_agent_fwd,
+    ssh1_set_wants_user_input,
 };
 
 struct ssh1_channel {
@@ -174,18 +195,78 @@ struct ssh1_channel {
 
 static int ssh1channel_write(SshChannel *c, const void *buf, int len);
 static void ssh1channel_write_eof(SshChannel *c);
-static void ssh1channel_unclean_close(SshChannel *c, const char *err);
+static void ssh1channel_initiate_close(SshChannel *c, const char *err);
 static void ssh1channel_unthrottle(SshChannel *c, int bufsize);
 static Conf *ssh1channel_get_conf(SshChannel *c);
+static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ }
 
 static const struct SshChannelVtable ssh1channel_vtable = {
     ssh1channel_write,
     ssh1channel_write_eof,
-    ssh1channel_unclean_close,
+    ssh1channel_initiate_close,
     ssh1channel_unthrottle,
     ssh1channel_get_conf,
+    ssh1channel_window_override_removed,
+    NULL /* x11_sharing_handover is only used by SSH-2 connection sharing */,
+    NULL /* request_x11_forwarding */,
+    NULL /* request_agent_forwarding */,
+    NULL /* request_pty */,
+    NULL /* send_env_var */,
+    NULL /* start_shell */,
+    NULL /* start_command */,
+    NULL /* start_subsystem */,
+    NULL /* send_serial_break */,
+    NULL /* send_signal */,
+    NULL /* send_terminal_size_change */,
+    NULL /* hint_channel_is_simple */,
+};
+
+static void ssh1mainchan_request_x11_forwarding(
+    SshChannel *c, int want_reply, const char *authproto,
+    const char *authdata, int screen_number, int oneshot);
+static void ssh1mainchan_request_agent_forwarding(
+    SshChannel *c, int want_reply);
+static void ssh1mainchan_request_pty(
+    SshChannel *c, int want_reply, Conf *conf, int w, int h);
+static int ssh1mainchan_send_env_var(
+    SshChannel *c, int want_reply, const char *var, const char *value);
+static void ssh1mainchan_start_shell(
+    SshChannel *c, int want_reply);
+static void ssh1mainchan_start_command(
+    SshChannel *c, int want_reply, const char *command);
+static int ssh1mainchan_start_subsystem(
+    SshChannel *c, int want_reply, const char *subsystem);
+static int ssh1mainchan_send_env_var(
+    SshChannel *c, int want_reply, const char *var, const char *value);
+static int ssh1mainchan_send_serial_break(
+    SshChannel *c, int want_reply, int length);
+static int ssh1mainchan_send_signal(
+    SshChannel *c, int want_reply, const char *signame);
+static void ssh1mainchan_send_terminal_size_change(
+    SshChannel *c, int w, int h);
+static void ssh1mainchan_hint_channel_is_simple(SshChannel *c);
+static int ssh1mainchan_write(SshChannel *sc, const void *data, int len);
+static void ssh1mainchan_write_eof(SshChannel *sc);
+
+static const struct SshChannelVtable ssh1mainchan_vtable = {
+    ssh1mainchan_write,
+    ssh1mainchan_write_eof,
+    NULL /* unclean_close */,
+    NULL /* unthrottle */,
+    NULL /* get_conf */,
     NULL /* window_override_removed is only used by SSH-2 sharing */,
     NULL /* x11_sharing_handover, likewise */,
+    ssh1mainchan_request_x11_forwarding,
+    ssh1mainchan_request_agent_forwarding,
+    ssh1mainchan_request_pty,
+    ssh1mainchan_send_env_var,
+    ssh1mainchan_start_shell,
+    ssh1mainchan_start_command,
+    ssh1mainchan_start_subsystem,
+    ssh1mainchan_send_serial_break,
+    ssh1mainchan_send_signal,
+    ssh1mainchan_send_terminal_size_change,
+    ssh1mainchan_hint_channel_is_simple,
 };
 
 static void ssh1_channel_init(struct ssh1_channel *c);
@@ -198,24 +279,60 @@ static void ssh1_channel_check_close(struct ssh1_channel *c);
 static int ssh1_check_termination(struct ssh1_connection_state *s);
 
 typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s,
-                                PktIn *pktin, void *ctx);
+                                int success, void *ctx);
 struct outstanding_succfail {
     sf_handler_fn_t handler;
     void *ctx;
     struct outstanding_succfail *next;
+
+    /*
+     * The 'trivial' flag is set if this handler is in response to a
+     * request for which the SSH-1 protocol doesn't actually specify a
+     * response packet. The client of this system (mainchan.c) will
+     * expect to get an acknowledgment regardless, so we arrange to
+     * send that ack immediately after the rest of the queue empties.
+     */
+    int trivial;
 };
+
+static void ssh1_connection_process_trivial_succfails(void *vs);
+
 static void ssh1_queue_succfail_handler(
-    struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx)
+    struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx,
+    int trivial)
 {
     struct outstanding_succfail *osf =
         snew(struct outstanding_succfail);
     osf->handler = handler;
     osf->ctx = ctx;
+    osf->trivial = trivial;
     if (s->succfail_tail)
         s->succfail_tail->next = osf;
     else
         s->succfail_head = osf;
     s->succfail_tail = osf;
+
+    /* In case this one was trivial and the queue was already empty,
+     * we should make sure we run the handler promptly, and the
+     * easiest way is to queue it anyway and then run a trivials pass
+     * by callback. */
+    queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s);
+}
+
+static void ssh1_connection_process_succfail(
+    struct ssh1_connection_state *s, int success)
+{
+    struct outstanding_succfail *prevhead = s->succfail_head;
+    s->succfail_head = s->succfail_head->next;
+    prevhead->handler(s, success, prevhead->ctx);
+    sfree(prevhead);
+}
+
+static void ssh1_connection_process_trivial_succfails(void *vs)
+{
+    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs;
+    while (s->succfail_head && s->succfail_head->trivial)
+        ssh1_connection_process_succfail(s, TRUE);
 }
 
 static int ssh1_channelcmp(void *av, void *bv)
@@ -298,6 +415,8 @@ static void ssh1_connection_free(PacketProtocolLayer *ppl)
     freetree234(s->rportfwds);
     portfwdmgr_free(s->portfwdmgr);
 
+    delete_callbacks_for_context(s);
+
     sfree(s);
 }
 
@@ -346,12 +465,10 @@ static int ssh1_connection_filter_queue(struct ssh1_connection_state *s)
                 return TRUE;
             }
 
-            s->succfail_head->handler(s, pktin, s->succfail_head->ctx);
-            {
-                struct outstanding_succfail *tmp = s->succfail_head;
-                s->succfail_head = s->succfail_head->next;
-                sfree(tmp);
-            }
+            ssh1_connection_process_succfail(
+                s, pktin->type == SSH1_SMSG_SUCCESS);
+            queue_toplevel_callback(
+                ssh1_connection_process_trivial_succfails, s);
 
             pq_pop(s->ppl.in_pq);
             break;
@@ -621,153 +738,23 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
     struct ssh1_connection_state *s =
         container_of(ppl, struct ssh1_connection_state, ppl);
     PktIn *pktin;
-    PktOut *pktout;
 
     if (ssh1_connection_filter_queue(s)) /* no matter why we were called */
         return;
 
     crBegin(s->crState);
 
-    if (ssh_agent_forwarding_permitted(&s->cl)) {
-	ppl_logevent(("Requesting agent forwarding"));
-        pktout = ssh_bpp_new_pktout(s->ppl.bpp,
-                                    SSH1_CMSG_AGENT_REQUEST_FORWARDING);
-        pq_push(s->ppl.out_pq, pktout);
-        crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
-	if (pktin->type == SSH1_SMSG_SUCCESS) {
-	    ppl_logevent(("Agent forwarding enabled"));
-	    s->agent_fwd_enabled = TRUE;
-	} else if (pktin->type == SSH1_SMSG_FAILURE) {
-	    ppl_logevent(("Agent forwarding refused"));
-	} else {
-            ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
-                            " in response to agent forwarding request, "
-                            "type %d (%s)", pktin->type,
-                            ssh1_pkt_type(pktin->type));
-            return;
-	}
-    }
-
-    if (conf_get_int(s->conf, CONF_x11_forward)) {
-        char *x11_setup_err;
-
-        s->x11disp =
-            x11_setup_display(conf_get_str(s->conf, CONF_x11_display),
-                              s->conf, &x11_setup_err);
-        if (!s->x11disp) {
-            ppl_logevent(("X11 forwarding not enabled: unable to"
-                          " initialise X display: %s", x11_setup_err));
-            sfree(x11_setup_err);
-        } else {
-            s->x11auth = x11_invent_fake_auth
-                (s->x11authtree, conf_get_int(s->conf, CONF_x11_auth));
-            s->x11auth->disp = s->x11disp;
-
-            ppl_logevent(("Requesting X11 forwarding"));
-            pktout = ssh_bpp_new_pktout(
-                s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
-            put_stringz(pktout, s->x11auth->protoname);
-            put_stringz(pktout, s->x11auth->datastring);
-            if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
-                put_uint32(pktout, s->x11disp->screennum);
-            pq_push(s->ppl.out_pq, pktout);
-            crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
-            if (pktin->type == SSH1_SMSG_SUCCESS) {
-                ppl_logevent(("X11 forwarding enabled"));
-                s->X11_fwd_enabled = TRUE;
-            } else if (pktin->type == SSH1_SMSG_FAILURE) {
-                ppl_logevent(("X11 forwarding refused"));
-            } else {
-                ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
-                                " in response to X11 forwarding request, "
-                                "type %d (%s)", pktin->type,
-                                ssh1_pkt_type(pktin->type));
-                return;
-            }
-        }
-    }
-
     portfwdmgr_config(s->portfwdmgr, s->conf);
     s->portfwdmgr_configured = TRUE;
 
-    if (!conf_get_int(s->conf, CONF_nopty)) {
-	/* Unpick the terminal-speed string. */
-	/* XXX perhaps we should allow no speeds to be sent. */
-	s->ospeed = 38400; s->ispeed = 38400; /* last-resort defaults */
-	sscanf(conf_get_str(s->conf, CONF_termspeed), "%d,%d",
-               &s->ospeed, &s->ispeed);
-	/* Send the pty request. */
-	pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY);
-	put_stringz(pktout, conf_get_str(s->conf, CONF_termtype));
-	put_uint32(pktout, s->term_height);
-	put_uint32(pktout, s->term_width);
-        s->term_width_orig = s->term_width;
-        s->term_height_orig = s->term_height;
-	put_uint32(pktout, 0); /* width in pixels */
-	put_uint32(pktout, 0); /* height in pixels */
-        write_ttymodes_to_packet_from_conf(
-            BinarySink_UPCAST(pktout), s->ppl.seat, s->conf,
-            1, s->ospeed, s->ispeed);
-        pq_push(s->ppl.out_pq, pktout);
-        crMaybeWaitUntilV((pktin = ssh1_connection_pop(s)) != NULL);
-	if (pktin->type == SSH1_SMSG_SUCCESS) {
-            ppl_logevent(("Allocated pty (ospeed %dbps, ispeed %dbps)",
-                          s->ospeed, s->ispeed));
-            s->got_pty = TRUE;
-	} else if (pktin->type == SSH1_SMSG_FAILURE) {
-	    ppl_printf(("Server refused to allocate pty\r\n"));
-	    s->echoedit = TRUE;
-	} else {
-            ssh_proto_error(s->ppl.ssh, "Unexpected packet received"
-                            " in response to pty request, "
-                            "type %d (%s)", pktin->type,
-                            ssh1_pkt_type(pktin->type));
-            crStopV;
-        }
-    } else {
-	s->echoedit = TRUE;
-    }
-
     /*
-     * Start the shell or command.
-     * 
-     * Special case: if the first-choice command is an SSH-2
-     * subsystem (hence not usable here) and the second choice
-     * exists, we fall straight back to that.
+     * Start up the main session, by telling mainchan.c to do it all
+     * just as it would in SSH-2, and translating those concepts to
+     * SSH-1's non-channel-shaped idea of the main session.
      */
-    {
-	char *cmd = conf_get_str(s->conf, CONF_remote_cmd);
-	
-	if (conf_get_int(s->conf, CONF_ssh_subsys) &&
-	    conf_get_str(s->conf, CONF_remote_cmd2)) {
-	    cmd = conf_get_str(s->conf, CONF_remote_cmd2);
-	    ssh_got_fallback_cmd(s->ppl.ssh);
-	}
-	if (*cmd) {
-            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
-            put_stringz(pktout, cmd);
-        } else {
-            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
-        }
-        pq_push(s->ppl.out_pq, pktout);
-	ppl_logevent(("Started session"));
-    }
-
-    s->session_ready = TRUE;
-    ssh_ppl_got_user_input(&s->ppl); /* in case any input is already queued */
-
-    /* If an EOF or a window-size change arrived before we were ready
-     * to handle either one, handle them now. */
-    if (s->session_eof_pending) {
-        ssh_ppl_special_cmd(&s->ppl, SS_EOF, 0);
-        s->session_eof_pending = FALSE;
-    }
-    if (s->term_width_orig != s->term_width ||
-        s->term_height_orig != s->term_height)
-	ssh_terminal_size(&s->cl, s->term_width, s->term_height);
-
-    ssh_ldisc_update(s->ppl.ssh);
-    s->finished_setup = TRUE;
+    s->mainchan = mainchan_new(
+        &s->ppl, &s->cl, s->conf, s->term_width, s->term_height,
+        FALSE /* is_simple */, NULL);
 
     while (1) {
 
@@ -783,17 +770,6 @@ static void ssh1_connection_process_queue(PacketProtocolLayer *ppl)
                             ssh1_pkt_type(pktin->type));
             return;
 	}
-        while (bufchain_size(s->ppl.user_input) > 0) {
-            void *data;
-            int len;
-            bufchain_prefix(s->ppl.user_input, &data, &len);
-            if (len > 512)
-                len = 512;
-            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
-            put_string(pktout, data, len);
-            pq_push(s->ppl.out_pq, pktout);
-            bufchain_consume(s->ppl.user_input, len);
-        }
 	crReturnV;
     }
 
@@ -938,6 +914,7 @@ static void ssh1_channel_init(struct ssh1_channel *c)
     c->pending_eof = FALSE;
     c->throttling_conn = FALSE;
     c->sc.vt = &ssh1channel_vtable;
+    c->sc.cl = &s->cl;
     c->localid = alloc_channel_id(s->channels, struct ssh1_channel);
     add234(s->channels, c);
 }
@@ -960,12 +937,12 @@ static void ssh1channel_write_eof(SshChannel *sc)
     ssh1_channel_try_eof(c);
 }
 
-static void ssh1channel_unclean_close(SshChannel *sc, const char *err)
+static void ssh1channel_initiate_close(SshChannel *sc, const char *err)
 {
     struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc);
     char *reason;
 
-    reason = dupprintf("due to local error: %s", err);
+    reason = err ? dupprintf("due to local error: %s", err) : NULL;
     ssh1_channel_close_local(c, reason);
     sfree(reason);
     c->pending_eof = FALSE;   /* this will confuse a zombie channel */
@@ -1006,9 +983,200 @@ static int ssh1channel_write(SshChannel *sc, const void *buf, int len)
     return 0;
 }
 
+static struct X11FakeAuth *ssh1_add_x11_display(
+    ConnectionLayer *cl, int authtype, struct X11Display *disp)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+    struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype);
+    auth->disp = disp;
+    return auth;
+}
+
+static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s,
+                                            int success, void *ctx)
+{
+    chan_request_response(s->mainchan_chan, success);
+}
+
+static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s,
+                                              int success, void *ctx)
+{
+}
+
+static void ssh1mainchan_queue_response(struct ssh1_connection_state *s,
+                                        int want_reply, int trivial)
+{
+    sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply :
+                               ssh1mainchan_succfail_nowantreply);
+    ssh1_queue_succfail_handler(s, handler, NULL, trivial);
+}
+
+static void ssh1mainchan_request_x11_forwarding(
+    SshChannel *sc, int want_reply, const char *authproto,
+    const char *authdata, int screen_number, int oneshot)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING);
+    put_stringz(pktout, authproto);
+    put_stringz(pktout, authdata);
+    if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER)
+        put_uint32(pktout, screen_number);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static void ssh1mainchan_request_agent_forwarding(
+    SshChannel *sc, int want_reply)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(
+        s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static void ssh1mainchan_request_pty(
+    SshChannel *sc, int want_reply, Conf *conf, int w, int h)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY);
+    put_stringz(pktout, conf_get_str(s->conf, CONF_termtype));
+    put_uint32(pktout, h);
+    put_uint32(pktout, w);
+    put_uint32(pktout, 0); /* width in pixels */
+    put_uint32(pktout, 0); /* height in pixels */
+    write_ttymodes_to_packet(
+        BinarySink_UPCAST(pktout), 1,
+        get_ttymodes_from_conf(s->ppl.seat, conf));
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, FALSE);
+}
+
+static int ssh1mainchan_send_env_var(
+    SshChannel *sc, int want_reply, const char *var, const char *value)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_start_shell(
+    SshChannel *sc, int want_reply)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, TRUE);
+}
+
+static void ssh1mainchan_start_command(
+    SshChannel *sc, int want_reply, const char *command)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD);
+    put_stringz(pktout, command);
+    pq_push(s->ppl.out_pq, pktout);
+
+    ssh1mainchan_queue_response(s, want_reply, TRUE);
+}
+
+static int ssh1mainchan_start_subsystem(
+    SshChannel *sc, int want_reply, const char *subsystem)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static int ssh1mainchan_send_serial_break(
+    SshChannel *sc, int want_reply, int length)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static int ssh1mainchan_send_signal(
+    SshChannel *sc, int want_reply, const char *signame)
+{
+    return FALSE;              /* SSH-1 doesn't support this at all */
+}
+
+static void ssh1mainchan_send_terminal_size_change(SshChannel *sc, int w, int h)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
+    put_uint32(pktout, h);
+    put_uint32(pktout, w);
+    put_uint32(pktout, 0); /* width in pixels */
+    put_uint32(pktout, 0); /* height in pixels */
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+static void ssh1mainchan_hint_channel_is_simple(SshChannel *c)
+{
+}
+
+static int ssh1mainchan_write(SshChannel *sc, const void *data, int len)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA);
+    put_string(pktout, data, len);
+    pq_push(s->ppl.out_pq, pktout);
+
+    return 0;
+}
+
+static void ssh1mainchan_write_eof(SshChannel *sc)
+{
+    struct ssh1_connection_state *s =
+        container_of(sc, struct ssh1_connection_state, mainchan_sc);
+    PktOut *pktout;
+
+    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
+    pq_push(s->ppl.out_pq, pktout);
+}
+
+static void ssh1_session_confirm_callback(void *vctx)
+{
+    struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx;
+    chan_open_confirmation(s->mainchan_chan);
+}
+
+static SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+    s->mainchan_sc.cl = &s->cl;
+    s->mainchan_sc.vt = &ssh1mainchan_vtable;
+    s->mainchan_chan = chan;
+    queue_toplevel_callback(ssh1_session_confirm_callback, s);
+    return &s->mainchan_sc;
+}
+
 static SshChannel *ssh1_lportfwd_open(
     ConnectionLayer *cl, const char *hostname, int port,
-    const char *org, Channel *chan)
+    const char *description, const SocketPeerInfo *pi, Channel *chan)
 {
     struct ssh1_connection_state *s =
         container_of(cl, struct ssh1_connection_state, cl);
@@ -1021,7 +1189,8 @@ static SshChannel *ssh1_lportfwd_open(
     c->halfopen = TRUE;
     c->chan = chan;
 
-    ppl_logevent(("Opening connection to %s:%d for %s", hostname, port, org));
+    ppl_logevent(("Opening connection to %s:%d for %s",
+                  hostname, port, description));
 
     pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN);
     put_uint32(pktout, c->localid);
@@ -1035,12 +1204,12 @@ static SshChannel *ssh1_lportfwd_open(
 }
 
 static void ssh1_rportfwd_response(struct ssh1_connection_state *s,
-                                   PktIn *pktin, void *ctx)
+                                   int success, void *ctx)
 {
     PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
     struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx;
 
-    if (pktin->type == SSH1_SMSG_SUCCESS) {
+    if (success) {
 	ppl_logevent(("Remote port forwarding from %s enabled",
                       rpf->log_description));
     } else {
@@ -1084,7 +1253,7 @@ static struct ssh_rportfwd *ssh1_rportfwd_alloc(
     put_uint32(pktout, rpf->dport);
     pq_push(s->ppl.out_pq, pktout);
 
-    ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf);
+    ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, FALSE);
 
     return rpf;
 }
@@ -1117,19 +1286,8 @@ static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl,
             put_stringz(pktout, "");
             pq_push(s->ppl.out_pq, pktout);
         }
-    } else if (code == SS_EOF) {
-	if (!s->session_ready) {
-	    /*
-	     * Buffer the EOF to send as soon as the main session is
-	     * fully set up.
-	     */
-            s->session_eof_pending = TRUE;
-	} else if (!s->session_eof_sent) {
-	    pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF);
-            pq_push(s->ppl.out_pq, pktout);
-            ppl_logevent(("Sent EOF message"));
-            s->session_eof_sent = TRUE;
-	}
+    } else if (s->mainchan) {
+        mainchan_special_cmd(s->mainchan, code, arg);
     }
 }
 
@@ -1140,15 +1298,8 @@ static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height)
 
     s->term_width = width;
     s->term_height = height;
-
-    if (s->session_ready) {
-        PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE);
-        put_uint32(pktout, s->term_height);
-        put_uint32(pktout, s->term_width);
-        put_uint32(pktout, 0);
-        put_uint32(pktout, 0);
-	pq_push(s->ppl.out_pq, pktout);
-    }
+    if (s->mainchan)
+        mainchan_terminal_size(s->mainchan, width, height);
 }
 
 static void ssh1_stdout_unthrottle(ConnectionLayer *cl, int bufsize)
@@ -1183,23 +1334,67 @@ static int ssh1_ldisc_option(ConnectionLayer *cl, int option)
     struct ssh1_connection_state *s =
         container_of(cl, struct ssh1_connection_state, cl);
 
-    /* We always return the same value for LD_ECHO and LD_EDIT */
-    return s->echoedit;
+    return s->ldisc_opts[option];
+}
+
+static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, int value)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+
+    s->ldisc_opts[option] = value;
+}
+
+static void ssh1_enable_x_fwd(ConnectionLayer *cl)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+
+    s->X11_fwd_enabled = TRUE;
+}
+
+static void ssh1_enable_agent_fwd(ConnectionLayer *cl)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+
+    s->agent_fwd_enabled = TRUE;
+}
+
+static void ssh1_set_wants_user_input(ConnectionLayer *cl, int wanted)
+{
+    struct ssh1_connection_state *s =
+        container_of(cl, struct ssh1_connection_state, cl);
+
+    s->want_user_input = wanted;
+    s->finished_setup = TRUE;
 }
 
 static int ssh1_connection_want_user_input(PacketProtocolLayer *ppl)
 {
     struct ssh1_connection_state *s =
         container_of(ppl, struct ssh1_connection_state, ppl);
-    return s->session_ready && !s->session_eof_sent;
+
+    return s->want_user_input;
 }
 
 static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl)
 {
     struct ssh1_connection_state *s =
         container_of(ppl, struct ssh1_connection_state, ppl);
-    if (s->session_ready && !s->session_eof_sent)
-        queue_idempotent_callback(&s->ppl.ic_process_queue);
+
+    while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) {
+        /*
+         * Add user input to the main channel's buffer.
+         */
+        void *data;
+        int len;
+        bufchain_prefix(s->ppl.user_input, &data, &len);
+        if (len > 512)
+            len = 512;
+        sshfwd_write(&s->mainchan_sc, data, len);
+        bufchain_consume(s->ppl.user_input, len);
+    }
 }
 
 static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf)

+ 1 - 0
source/putty/ssh1login.c

@@ -132,6 +132,7 @@ int ssh1_common_filter_queue(PacketProtocolLayer *ppl)
             ssh_remote_error(ppl->ssh,
                              "Server sent disconnect message:\n\"%.*s\"",
                              PTRLEN_PRINTF(msg));
+            pq_pop(ppl->in_pq);
             return TRUE;               /* indicate that we've been freed */
 
           case SSH1_MSG_DEBUG:

File diff suppressed because it is too large
+ 193 - 636
source/putty/ssh2connection.c


+ 124 - 0
source/putty/ssh2transhk.c

@@ -0,0 +1,124 @@
+/*
+ * Data structure managing host keys in sessions based on GSSAPI KEX.
+ *
+ * In a session we started with a GSSAPI key exchange, the concept of
+ * 'host key' has completely different lifetime and security semantics
+ * from the usual ones. Per RFC 4462 section 2.1, we assume that any
+ * host key delivered to us in the course of a GSSAPI key exchange is
+ * _solely_ there to use as a transient fallback within the same
+ * session, if at the time of a subsequent rekey the GSS credentials
+ * are temporarily invalid and so a non-GSS KEX method has to be used.
+ *
+ * In particular, in a GSS-based SSH deployment, host keys may not
+ * even _be_ persistent identities for the server; it would be
+ * legitimate for a server to generate a fresh one routinely if it
+ * wanted to, like SSH-1 server keys.
+ *
+ * So, in this mode, we never touch the persistent host key cache at
+ * all, either to check keys against it _or_ to store keys in it.
+ * Instead, we maintain an in-memory cache of host keys that have been
+ * mentioned in GSS key exchanges within this particular session, and
+ * we permit precisely those host keys in non-GSS rekeys.
+ */
+
+#include <assert.h>
+
+#include "putty.h"
+#include "ssh.h"
+
+struct ssh_transient_hostkey_cache {
+    tree234 *cache;
+};
+
+struct ssh_transient_hostkey_cache_entry {
+    const ssh_keyalg *alg;
+    strbuf *pub_blob;
+};
+
+static int ssh_transient_hostkey_cache_cmp(void *av, void *bv)
+{
+    const struct ssh_transient_hostkey_cache_entry
+        *a = (const struct ssh_transient_hostkey_cache_entry *)av,
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(a->alg->ssh_id, b->alg->ssh_id);
+}
+
+static int ssh_transient_hostkey_cache_find(void *av, void *bv)
+{
+    const ssh_keyalg *aalg = (const ssh_keyalg *)av;
+    const struct ssh_transient_hostkey_cache_entry
+        *b = (const struct ssh_transient_hostkey_cache_entry *)bv;
+    return strcmp(aalg->ssh_id, b->alg->ssh_id);
+}
+
+ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void)
+{
+    ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache);
+    thc->cache = newtree234(ssh_transient_hostkey_cache_cmp);
+    return thc;
+}
+
+void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    while ((ent = delpos234(thc->cache, 0)) != NULL) {
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+    freetree234(thc->cache);
+}
+
+void ssh_transient_hostkey_cache_add(
+    ssh_transient_hostkey_cache *thc, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent, *retd;
+
+    if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        strbuf_free(ent->pub_blob);
+        sfree(ent);
+    }
+
+    ent = snew(struct ssh_transient_hostkey_cache_entry);
+    ent->alg = ssh_key_alg(key);
+    ent->pub_blob = strbuf_new();
+    ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob));
+    retd = add234(thc->cache, ent);
+    assert(retd == ent);
+}
+
+int ssh_transient_hostkey_cache_verify(
+    ssh_transient_hostkey_cache *thc, ssh_key *key)
+{
+    struct ssh_transient_hostkey_cache_entry *ent;
+    int toret = FALSE;
+
+    if ((ent = find234(thc->cache, (void *)ssh_key_alg(key),
+                       ssh_transient_hostkey_cache_find)) != NULL) {
+        strbuf *this_blob = strbuf_new();
+        ssh_key_public_blob(key, BinarySink_UPCAST(this_blob));
+
+        if (this_blob->len == ent->pub_blob->len &&
+            !memcmp(this_blob->s, ent->pub_blob->s,
+                    this_blob->len))
+            toret = TRUE;
+
+        strbuf_free(this_blob);
+    }
+
+    return toret;
+}
+
+int ssh_transient_hostkey_cache_has(
+    ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg)
+{
+    struct ssh_transient_hostkey_cache_entry *ent =
+        find234(thc->cache, (void *)alg,
+                ssh_transient_hostkey_cache_find);
+    return ent != NULL;
+}
+
+int ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc)
+{
+    return count234(thc->cache) > 0;
+}

File diff suppressed because it is too large
+ 705 - 556
source/putty/ssh2transport.c


+ 2 - 2
source/putty/ssh2userauth.c

@@ -867,8 +867,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                                  BinarySink_UPCAST(sigblob));
                     strbuf_free(sigdata);
                     ssh2_userauth_add_sigblob(
-                        s, s->pktout, make_ptrlen(pkblob->s, pkblob->len),
-                        make_ptrlen(sigblob->s, sigblob->len));
+                        s, s->pktout, ptrlen_from_strbuf(pkblob),
+                        ptrlen_from_strbuf(sigblob));
                     strbuf_free(pkblob);
                     strbuf_free(sigblob);
 

+ 3 - 1
source/putty/sshbn.c

@@ -1588,7 +1588,9 @@ Bignum BinarySource_get_mp_ssh1(BinarySource *src)
         return bignum_from_long(0);
     } else {
         Bignum toret = bignum_from_bytes(bytes.ptr, bytes.len);
-        if (bignum_bitcount(toret) != bitc) {
+        /* SSH-1.5 spec says that it's OK for the prefix uint16 to be
+         * _greater_ than the actual number of bits */
+        if (bignum_bitcount(toret) > bitc) {
             src->err = BSE_INVALID;
             freebn(toret);
             toret = bignum_from_long(0);

+ 0 - 5
source/putty/sshbpp.h

@@ -56,11 +56,6 @@ BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx);
 void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp,
                          const struct ssh1_cipheralg *cipher,
                          const void *session_key);
-/* requested_compression() notifies the SSH-1 BPP that we've just sent
- * a request to enable compression, which means that on receiving the
- * next SSH1_SMSG_SUCCESS or SSH1_SMSG_FAILURE message, it should set
- * up zlib compression if it was SUCCESS. */
-void ssh1_bpp_requested_compression(BinaryPacketProtocol *bpp);
 
 /* Helper routine which does common BPP initialisation, e.g. setting
  * up in_pq and out_pq, and initialising input_consumer. */

+ 107 - 3
source/putty/sshchan.h

@@ -26,6 +26,19 @@ struct ChannelVtable {
     char *(*log_close_msg)(Channel *);
 
     int (*want_close)(Channel *, int sent_local_eof, int rcvd_remote_eof);
+
+    /* A method for every channel request we know of. All of these
+     * return TRUE for success or FALSE for failure. */
+    int (*rcvd_exit_status)(Channel *, int status);
+    int (*rcvd_exit_signal)(
+        Channel *chan, ptrlen signame, int core_dumped, ptrlen msg);
+    int (*rcvd_exit_signal_numeric)(
+        Channel *chan, int signum, int core_dumped, ptrlen msg);
+
+    /* A method for signalling success/failure responses to channel
+     * requests initiated from the SshChannel vtable with want_reply
+     * true. */
+    void (*request_response)(Channel *, int success);
 };
 
 struct Channel {
@@ -42,6 +55,14 @@ struct Channel {
     ((ch)->vt->set_input_wanted(ch, wanted))
 #define chan_log_close_msg(ch) ((ch)->vt->log_close_msg(ch))
 #define chan_want_close(ch, leof, reof) ((ch)->vt->want_close(ch, leof, reof))
+#define chan_rcvd_exit_status(ch, status) \
+    ((ch)->vt->rcvd_exit_status(ch, status))
+#define chan_rcvd_exit_signal(ch, sig, core, msg)   \
+    ((ch)->vt->rcvd_exit_signal(ch, sig, core, msg))
+#define chan_rcvd_exit_signal_numeric(ch, sig, core, msg)   \
+    ((ch)->vt->rcvd_exit_signal_numeric(ch, sig, core, msg))
+#define chan_request_response(ch, success)   \
+    ((ch)->vt->request_response(ch, success))
 
 /*
  * Reusable methods you can put in vtables to give default handling of
@@ -54,7 +75,15 @@ void chan_remotely_opened_failure(Channel *chan, const char *errtext);
 
 /* want_close for any channel that wants the default behaviour of not
  * closing until both directions have had an EOF */
-int chan_no_eager_close(Channel *, int, int);
+int chan_default_want_close(Channel *, int, int);
+
+/* default implementations that refuse all the channel requests */
+int chan_no_exit_status(Channel *, int);
+int chan_no_exit_signal(Channel *, ptrlen, int, ptrlen);
+int chan_no_exit_signal_numeric(Channel *, int, int, ptrlen);
+
+/* default implementation that never expects to receive a response */
+void chan_no_request_response(Channel *, int);
 
 /*
  * Constructor for a trivial do-nothing implementation of
@@ -77,7 +106,7 @@ Channel *zombiechan_new(void);
 struct SshChannelVtable {
     int (*write)(SshChannel *c, const void *, int);
     void (*write_eof)(SshChannel *c);
-    void (*unclean_close)(SshChannel *c, const char *err);
+    void (*initiate_close)(SshChannel *c, const char *err);
     void (*unthrottle)(SshChannel *c, int bufsize);
     Conf *(*get_conf)(SshChannel *c);
     void (*window_override_removed)(SshChannel *c);
@@ -87,19 +116,94 @@ struct SshChannelVtable {
                                  const char *peer_addr, int peer_port,
                                  int endian, int protomajor, int protominor,
                                  const void *initial_data, int initial_len);
+
+    /*
+     * All the outgoing channel requests we support. Each one has a
+     * want_reply flag, which will cause a callback to
+     * chan_request_response when the result is available.
+     *
+     * The ones that return 'int' use it to indicate that the SSH
+     * protocol in use doesn't support this request at all.
+     *
+     * (It's also intentional that not all of them have a want_reply
+     * flag: the ones that don't are because SSH-1 has no method for
+     * signalling success or failure of that request, or because we
+     * wouldn't do anything usefully different with the reply in any
+     * case.)
+     */
+    void (*request_x11_forwarding)(
+        SshChannel *c, int want_reply, const char *authproto,
+        const char *authdata, int screen_number, int oneshot);
+    void (*request_agent_forwarding)(
+        SshChannel *c, int want_reply);
+    void (*request_pty)(
+        SshChannel *c, int want_reply, Conf *conf, int w, int h);
+    int (*send_env_var)(
+        SshChannel *c, int want_reply, const char *var, const char *value);
+    void (*start_shell)(
+        SshChannel *c, int want_reply);
+    void (*start_command)(
+        SshChannel *c, int want_reply, const char *command);
+    int (*start_subsystem)(
+        SshChannel *c, int want_reply, const char *subsystem);
+    int (*send_serial_break)(
+        SshChannel *c, int want_reply, int length); /* length=0 for default */
+    int (*send_signal)(
+        SshChannel *c, int want_reply, const char *signame);
+    void (*send_terminal_size_change)(
+        SshChannel *c, int w, int h);
+    void (*hint_channel_is_simple)(SshChannel *c);
 };
 
 struct SshChannel {
     const struct SshChannelVtable *vt;
+    ConnectionLayer *cl;
 };
 
 #define sshfwd_write(c, buf, len) ((c)->vt->write(c, buf, len))
 #define sshfwd_write_eof(c) ((c)->vt->write_eof(c))
-#define sshfwd_unclean_close(c, err) ((c)->vt->unclean_close(c, err))
+#define sshfwd_initiate_close(c, err) ((c)->vt->initiate_close(c, err))
 #define sshfwd_unthrottle(c, bufsize) ((c)->vt->unthrottle(c, bufsize))
 #define sshfwd_get_conf(c) ((c)->vt->get_conf(c))
 #define sshfwd_window_override_removed(c) ((c)->vt->window_override_removed(c))
 #define sshfwd_x11_sharing_handover(c, cs, ch, pa, pp, e, pmaj, pmin, d, l) \
     ((c)->vt->x11_sharing_handover(c, cs, ch, pa, pp, e, pmaj, pmin, d, l))
+#define sshfwd_request_x11_forwarding(c, wr, ap, ad, scr, oneshot) \
+    ((c)->vt->request_x11_forwarding(c, wr, ap, ad, scr, oneshot))
+#define sshfwd_request_agent_forwarding(c, wr) \
+    ((c)->vt->request_agent_forwarding(c, wr))
+#define sshfwd_request_pty(c, wr, conf, w, h) \
+    ((c)->vt->request_pty(c, wr, conf, w, h))
+#define sshfwd_send_env_var(c, wr, var, value) \
+    ((c)->vt->send_env_var(c, wr, var, value))
+#define sshfwd_start_shell(c, wr) \
+    ((c)->vt->start_shell(c, wr))
+#define sshfwd_start_command(c, wr, cmd) \
+    ((c)->vt->start_command(c, wr, cmd))
+#define sshfwd_start_subsystem(c, wr, subsys) \
+    ((c)->vt->start_subsystem(c, wr, subsys))
+#define sshfwd_send_serial_break(c, wr, length) \
+    ((c)->vt->send_serial_break(c, wr, length))
+#define sshfwd_send_signal(c, wr, sig) \
+    ((c)->vt->send_signal(c, wr, sig))
+#define sshfwd_send_terminal_size_change(c, w, h) \
+    ((c)->vt->send_terminal_size_change(c, w, h))
+#define sshfwd_hint_channel_is_simple(c) \
+    ((c)->vt->hint_channel_is_simple(c))
+
+/* ----------------------------------------------------------------------
+ * The 'main' or primary channel of the SSH connection is special,
+ * because it's the one that's connected directly to parts of the
+ * frontend such as the terminal and the specials menu. So it exposes
+ * a richer API.
+ */
+
+mainchan *mainchan_new(
+    PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf,
+    int term_width, int term_height, int is_simple, SshChannel **sc_out);
+void mainchan_get_specials(
+    mainchan *mc, add_special_fn_t add_special, void *ctx);
+void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg);
+void mainchan_terminal_size(mainchan *mc, int width, int height);
 
 #endif /* PUTTY_SSHCHAN_H */

+ 133 - 106
source/putty/sshcommon.c

@@ -277,6 +277,10 @@ static const struct ChannelVtable zombiechan_channelvt = {
     zombiechan_set_input_wanted,
     zombiechan_log_close_msg,
     zombiechan_want_close,
+    chan_no_exit_status,
+    chan_no_exit_signal,
+    chan_no_exit_signal_numeric,
+    chan_no_request_response,
 };
 
 Channel *zombiechan_new(void)
@@ -335,107 +339,80 @@ void chan_remotely_opened_failure(Channel *chan, const char *errtext)
     assert(0 && "this channel type should never receive OPEN_FAILURE");
 }
 
-int chan_no_eager_close(Channel *chan, int sent_local_eof, int rcvd_remote_eof)
+int chan_default_want_close(
+    Channel *chan, int sent_local_eof, int rcvd_remote_eof)
 {
-    return FALSE;     /* default: never proactively ask for a close */
+    /*
+     * Default close policy: we start initiating the CHANNEL_CLOSE
+     * procedure as soon as both sides of the channel have seen EOF.
+     */
+    return sent_local_eof && rcvd_remote_eof;
+}
+
+int chan_no_exit_status(Channel *chan, int status)
+{
+    return FALSE;
+}
+
+int chan_no_exit_signal(
+    Channel *chan, ptrlen signame, int core_dumped, ptrlen msg)
+{
+    return FALSE;
+}
+
+int chan_no_exit_signal_numeric(
+    Channel *chan, int signum, int core_dumped, ptrlen msg)
+{
+    return FALSE;
+}
+
+void chan_no_request_response(Channel *chan, int success)
+{
+    assert(0 && "this channel type should never send a want-reply request");
 }
 
 /* ----------------------------------------------------------------------
- * Common routine to marshal tty modes into an SSH packet.
+ * Common routines for handling SSH tty modes.
  */
 
-void write_ttymodes_to_packet_from_conf(
-    BinarySink *bs, Seat *seat, Conf *conf,
-    int ssh_version, int ospeed, int ispeed)
+static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version)
 {
-    int i;
+    switch (our_opcode) {
+      case TTYMODE_ISPEED:
+        return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2;
+      case TTYMODE_OSPEED:
+        return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2;
+      default:
+        return our_opcode;
+    }
+}
 
-    /*
-     * Codes for terminal modes.
-     * Most of these are the same in SSH-1 and SSH-2.
-     * This list is derived from RFC 4254 and
-     * SSH-1 RFC-1.2.31.
-     */
-    static const struct ssh_ttymode {
+struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf)
+{
+    struct ssh_ttymodes modes;
+    size_t i;
+
+    static const struct mode_name_type {
         const char *mode;
         int opcode;
-        enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
-    } ssh_ttymodes[] = {
-        /* "V" prefix discarded for special characters relative to SSH specs */
-        { "INTR",      1, TTY_OP_CHAR },
-        { "QUIT",      2, TTY_OP_CHAR },
-        { "ERASE",     3, TTY_OP_CHAR },
-        { "KILL",      4, TTY_OP_CHAR },
-        { "EOF",       5, TTY_OP_CHAR },
-        { "EOL",       6, TTY_OP_CHAR },
-        { "EOL2",      7, TTY_OP_CHAR },
-        { "START",     8, TTY_OP_CHAR },
-        { "STOP",      9, TTY_OP_CHAR },
-        { "SUSP",     10, TTY_OP_CHAR },
-        { "DSUSP",    11, TTY_OP_CHAR },
-        { "REPRINT",  12, TTY_OP_CHAR },
-        { "WERASE",   13, TTY_OP_CHAR },
-        { "LNEXT",    14, TTY_OP_CHAR },
-        { "FLUSH",    15, TTY_OP_CHAR },
-        { "SWTCH",    16, TTY_OP_CHAR },
-        { "STATUS",   17, TTY_OP_CHAR },
-        { "DISCARD",  18, TTY_OP_CHAR },
-        { "IGNPAR",   30, TTY_OP_BOOL },
-        { "PARMRK",   31, TTY_OP_BOOL },
-        { "INPCK",    32, TTY_OP_BOOL },
-        { "ISTRIP",   33, TTY_OP_BOOL },
-        { "INLCR",    34, TTY_OP_BOOL },
-        { "IGNCR",    35, TTY_OP_BOOL },
-        { "ICRNL",    36, TTY_OP_BOOL },
-        { "IUCLC",    37, TTY_OP_BOOL },
-        { "IXON",     38, TTY_OP_BOOL },
-        { "IXANY",    39, TTY_OP_BOOL },
-        { "IXOFF",    40, TTY_OP_BOOL },
-        { "IMAXBEL",  41, TTY_OP_BOOL },
-        { "IUTF8",    42, TTY_OP_BOOL },
-        { "ISIG",     50, TTY_OP_BOOL },
-        { "ICANON",   51, TTY_OP_BOOL },
-        { "XCASE",    52, TTY_OP_BOOL },
-        { "ECHO",     53, TTY_OP_BOOL },
-        { "ECHOE",    54, TTY_OP_BOOL },
-        { "ECHOK",    55, TTY_OP_BOOL },
-        { "ECHONL",   56, TTY_OP_BOOL },
-        { "NOFLSH",   57, TTY_OP_BOOL },
-        { "TOSTOP",   58, TTY_OP_BOOL },
-        { "IEXTEN",   59, TTY_OP_BOOL },
-        { "ECHOCTL",  60, TTY_OP_BOOL },
-        { "ECHOKE",   61, TTY_OP_BOOL },
-        { "PENDIN",   62, TTY_OP_BOOL }, /* XXX is this a real mode? */
-        { "OPOST",    70, TTY_OP_BOOL },
-        { "OLCUC",    71, TTY_OP_BOOL },
-        { "ONLCR",    72, TTY_OP_BOOL },
-        { "OCRNL",    73, TTY_OP_BOOL },
-        { "ONOCR",    74, TTY_OP_BOOL },
-        { "ONLRET",   75, TTY_OP_BOOL },
-        { "CS7",      90, TTY_OP_BOOL },
-        { "CS8",      91, TTY_OP_BOOL },
-        { "PARENB",   92, TTY_OP_BOOL },
-        { "PARODD",   93, TTY_OP_BOOL }
+        enum { TYPE_CHAR, TYPE_BOOL } type;
+    } modes_names_types[] = {
+        #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR },
+        #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL },
+        #include "sshttymodes.h"
+        #undef TTYMODE_CHAR
+        #undef TTYMODE_FLAG
     };
 
-    /* Miscellaneous other tty-related constants. */
-    enum {
-        /* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
-        SSH1_TTY_OP_ISPEED = 192,
-        SSH1_TTY_OP_OSPEED = 193,
-        SSH2_TTY_OP_ISPEED = 128,
-        SSH2_TTY_OP_OSPEED = 129,
-
-        SSH_TTY_OP_END = 0
-    };
+    memset(&modes, 0, sizeof(modes));
 
-    for (i = 0; i < lenof(ssh_ttymodes); i++) {
-        const struct ssh_ttymode *mode = ssh_ttymodes + i;
+    for (i = 0; i < lenof(modes_names_types); i++) {
+        const struct mode_name_type *mode = &modes_names_types[i];
         const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);
         char *to_free = NULL;
 
-        /* Every mode known to the current version of the code should be
-         * mentioned; this was ensured when settings were loaded. */
+        if (!sval)
+            sval = "N";                /* just in case */
 
         /*
          * sval[0] can be
@@ -462,7 +439,7 @@ void write_ttymodes_to_packet_from_conf(
             unsigned ival = 0;
 
             switch (mode->type) {
-              case TTY_OP_CHAR:
+              case TYPE_CHAR:
                 if (*sval) {
                     char *next = NULL;
                     /* We know ctrlparse won't write to the string, so
@@ -474,7 +451,7 @@ void write_ttymodes_to_packet_from_conf(
                     ival = 255; /* special value meaning "don't set" */
                 }
                 break;
-              case TTY_OP_BOOL:
+              case TYPE_BOOL:
                 if (stricmp(sval, "yes") == 0 ||
                     stricmp(sval, "on") == 0 ||
                     stricmp(sval, "true") == 0 ||
@@ -492,30 +469,48 @@ void write_ttymodes_to_packet_from_conf(
                 assert(0 && "Bad mode->type");
             }
 
-            /*
-             * And write it into the output packet. The parameter
-             * value is formatted as a byte in SSH-1, but a uint32
-             * in SSH-2.
-             */
-            put_byte(bs, mode->opcode);
-            if (ssh_version == 1)
-                put_byte(bs, ival);
-            else
-                put_uint32(bs, ival);
+            modes.have_mode[mode->opcode] = TRUE;
+            modes.mode_val[mode->opcode] = ival;
         }
 
         sfree(to_free);
     }
 
-    /*
-     * Finish off with the terminal speeds (which are formatted as
-     * uint32 in both protocol versions) and the end marker.
-     */
-    put_byte(bs, ssh_version == 1 ? SSH1_TTY_OP_ISPEED : SSH2_TTY_OP_ISPEED);
-    put_uint32(bs, ispeed);
-    put_byte(bs, ssh_version == 1 ? SSH1_TTY_OP_OSPEED : SSH2_TTY_OP_OSPEED);
-    put_uint32(bs, ospeed);
-    put_byte(bs, SSH_TTY_OP_END);
+    {
+        unsigned ospeed, ispeed;
+
+        /* Unpick the terminal-speed config string. */
+        ospeed = ispeed = 38400;           /* last-resort defaults */
+        sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed);
+        /* Currently we unconditionally set these */
+        modes.have_mode[TTYMODE_ISPEED] = TRUE;
+        modes.mode_val[TTYMODE_ISPEED] = ispeed;
+        modes.have_mode[TTYMODE_OSPEED] = TRUE;
+        modes.mode_val[TTYMODE_OSPEED] = ospeed;
+    }
+
+    return modes;
+}
+
+void write_ttymodes_to_packet(BinarySink *bs, int ssh_version,
+                              struct ssh_ttymodes modes)
+{
+    unsigned i;
+
+    for (i = 0; i < TTYMODE_LIMIT; i++) {
+        if (modes.have_mode[i]) {
+            unsigned val = modes.mode_val[i];
+            unsigned opcode = real_ttymode_opcode(i, ssh_version);
+
+            put_byte(bs, opcode);
+            if (ssh_version == 1 && opcode >= 1 && opcode <= 127)
+                put_byte(bs, val);
+            else
+                put_uint32(bs, val);
+        }
+    }
+
+    put_byte(bs, TTYMODE_END_OF_LIST);
 }
 
 /* ----------------------------------------------------------------------
@@ -606,6 +601,38 @@ void add_to_commasep(strbuf *buf, const char *data)
     put_data(buf, data, strlen(data));
 }
 
+int get_commasep_word(ptrlen *list, ptrlen *word)
+{
+    const char *comma;
+
+    /*
+     * Discard empty list elements, should there be any, because we
+     * never want to return one as if it was a real string. (This
+     * introduces a mild tolerance of badly formatted data in lists we
+     * receive, but I think that's acceptable.)
+     */
+    while (list->len > 0 && *(const char *)list->ptr == ',') {
+        list->ptr = (const char *)list->ptr + 1;
+        list->len--;
+    }
+
+    if (!list->len)
+        return FALSE;
+
+    comma = memchr(list->ptr, ',', list->len);
+    if (!comma) {
+        *word = *list;
+        list->len = 0;
+    } else {
+        size_t wordlen = comma - (const char *)list->ptr;
+        word->ptr = list->ptr;
+        word->len = wordlen;
+        list->ptr = (const char *)list->ptr + wordlen + 1;
+        list->len -= wordlen + 1;
+    }
+    return TRUE;
+}
+
 /* ----------------------------------------------------------------------
  * Functions for translating SSH packet type codes into their symbolic
  * string names.

+ 0 - 2
source/putty/sshppl.h

@@ -8,8 +8,6 @@
 #define PUTTY_SSHPPL_H
 
 typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin);
-typedef void (*add_special_fn_t)(
-    void *ctx, const char *text, SessionSpecialCode code, int arg);
 
 struct PacketProtocolLayerVtable {
     void (*free)(PacketProtocolLayer *); 

+ 3 - 3
source/putty/sshpubk.c

@@ -584,7 +584,7 @@ const ssh_keyalg *find_pubkey_alg_len(ptrlen name)
 
 const ssh_keyalg *find_pubkey_alg(const char *name)
 {
-    return find_pubkey_alg_len(make_ptrlen(name, strlen(name)));
+    return find_pubkey_alg_len(ptrlen_from_asciz(name));
 }
 
 struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
@@ -798,8 +798,8 @@ struct ssh2_userkey *ssh2_load_userkey(const Filename *filename,
     ret = snew(struct ssh2_userkey);
     ret->comment = comment;
     ret->key = ssh_key_new_priv(
-        alg, make_ptrlen(public_blob->u, public_blob->len),
-        make_ptrlen(private_blob->u, private_blob->len));
+        alg, ptrlen_from_strbuf(public_blob),
+        ptrlen_from_strbuf(private_blob));
     if (!ret->key) {
 	sfree(ret);
 	ret = NULL;

+ 4 - 4
source/putty/sshshare.c

@@ -1914,7 +1914,7 @@ static int share_listen_accepting(Plug *plug,
         plug, struct ssh_sharing_state, plug);
     struct ssh_sharing_connstate *cs;
     const char *err;
-    char *peerinfo;
+    SocketPeerInfo *peerinfo;
 
     /*
      * A new downstream has connected to us.
@@ -1959,9 +1959,9 @@ static int share_listen_accepting(Plug *plug,
 
     peerinfo = sk_peer_info(cs->sock);
     log_downstream(cs, "connected%s%s",
-                   peerinfo ? " from " : "", peerinfo ? peerinfo : "");
-
-    sfree(peerinfo);
+                   (peerinfo && peerinfo->log_text ? " from " : ""),
+                   (peerinfo && peerinfo->log_text ? peerinfo->log_text : ""));
+    sk_free_peer_info(peerinfo);
 
     return 0;
 }

+ 53 - 0
source/putty/sshsignals.h

@@ -0,0 +1,53 @@
+/*
+ * List of signal names known to SSH, indicating whether PuTTY's UI
+ * for special session commands likes to put them in the main specials
+ * menu or in a submenu (and if the former, what title they have).
+ *
+ * This is a separate header file rather than my usual style of a
+ * parametric list macro, because in this case I need to be able to
+ * #ifdef out each mode in case it's not defined on a particular
+ * target system.
+ *
+ * If you want only the locally defined signals, #define
+ * SIGNALS_LOCAL_ONLY before including this header.
+ */
+
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT
+SIGNAL_MAIN(INT, "Interrupt")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM
+SIGNAL_MAIN(TERM, "Terminate")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL
+SIGNAL_MAIN(KILL, "Kill")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT
+SIGNAL_MAIN(QUIT, "Quit")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP
+SIGNAL_MAIN(HUP, "Hangup")
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT
+SIGNAL_SUB(ABRT)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM
+SIGNAL_SUB(ALRM)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE
+SIGNAL_SUB(FPE)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL
+SIGNAL_SUB(ILL)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE
+SIGNAL_SUB(PIPE)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV
+SIGNAL_SUB(SEGV)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1
+SIGNAL_SUB(USR1)
+#endif
+#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2
+SIGNAL_SUB(USR2)
+#endif

+ 179 - 0
source/putty/sshttymodes.h

@@ -0,0 +1,179 @@
+/*
+ * List of SSH terminal modes, indicating whether SSH types them as
+ * char or boolean, and if they're boolean, which POSIX flags field of
+ * a termios structure they appear in, and what bit mask removes them
+ * (e.g. CS7 and CS8 aren't single bits).
+ *
+ * Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux
+ * termios manpage for flags not specified by POSIX.
+ *
+ * This is a separate header file rather than my usual style of a
+ * parametric list macro, because in this case I need to be able to
+ * #ifdef out each mode in case it's not defined on a particular
+ * target system.
+ *
+ * If you want only the locally defined modes, #define
+ * TTYMODES_LOCAL_ONLY before including this header.
+ */
+#if !defined TTYMODES_LOCAL_ONLY || defined VINTR
+TTYMODE_CHAR(INTR, 1, VINTR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT
+TTYMODE_CHAR(QUIT, 2, VQUIT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VERASE
+TTYMODE_CHAR(ERASE, 3, VERASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VKILL
+TTYMODE_CHAR(KILL, 4, VKILL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOF
+TTYMODE_CHAR(EOF, 5, VEOF)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOL
+TTYMODE_CHAR(EOL, 6, VEOL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2
+TTYMODE_CHAR(EOL2, 7, VEOL2)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTART
+TTYMODE_CHAR(START, 8, VSTART)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP
+TTYMODE_CHAR(STOP, 9, VSTOP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP
+TTYMODE_CHAR(SUSP, 10, VSUSP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP
+TTYMODE_CHAR(DSUSP, 11, VDSUSP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT
+TTYMODE_CHAR(REPRINT, 12, VREPRINT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE
+TTYMODE_CHAR(WERASE, 13, VWERASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT
+TTYMODE_CHAR(LNEXT, 14, VLNEXT)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH
+TTYMODE_CHAR(FLUSH, 15, VFLUSH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH
+TTYMODE_CHAR(SWTCH, 16, VSWTCH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS
+TTYMODE_CHAR(STATUS, 17, VSTATUS)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD
+TTYMODE_CHAR(DISCARD, 18, VDISCARD)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR
+TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK
+TTYMODE_FLAG(PARMRK, 31, i, PARMRK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined INPCK
+TTYMODE_FLAG(INPCK, 32, i, INPCK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP
+TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined INLCR
+TTYMODE_FLAG(INLCR, 34, i, INLCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR
+TTYMODE_FLAG(IGNCR, 35, i, IGNCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL
+TTYMODE_FLAG(ICRNL, 36, i, ICRNL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC
+TTYMODE_FLAG(IUCLC, 37, i, IUCLC)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXON
+TTYMODE_FLAG(IXON, 38, i, IXON)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXANY
+TTYMODE_FLAG(IXANY, 39, i, IXANY)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF
+TTYMODE_FLAG(IXOFF, 40, i, IXOFF)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL
+TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8
+TTYMODE_FLAG(IUTF8, 42, i, IUTF8)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ISIG
+TTYMODE_FLAG(ISIG, 50, l, ISIG)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ICANON
+TTYMODE_FLAG(ICANON, 51, l, ICANON)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined XCASE
+TTYMODE_FLAG(XCASE, 52, l, XCASE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHO
+TTYMODE_FLAG(ECHO, 53, l, ECHO)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE
+TTYMODE_FLAG(ECHOE, 54, l, ECHOE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK
+TTYMODE_FLAG(ECHOK, 55, l, ECHOK)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL
+TTYMODE_FLAG(ECHONL, 56, l, ECHONL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH
+TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP
+TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN
+TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL
+TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE
+TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN
+TTYMODE_FLAG(PENDIN, 62, l, PENDIN)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OPOST
+TTYMODE_FLAG(OPOST, 70, o, OPOST)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC
+TTYMODE_FLAG(OLCUC, 71, o, OLCUC)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR
+TTYMODE_FLAG(ONLCR, 72, o, ONLCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL
+TTYMODE_FLAG(OCRNL, 73, o, OCRNL)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR
+TTYMODE_FLAG(ONOCR, 74, o, ONOCR)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET
+TTYMODE_FLAG(ONLRET, 75, o, ONLRET)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined CS7
+TTYMODE_FLAG(CS7, 90, c, CSIZE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined CS8
+TTYMODE_FLAG(CS8, 91, c, CSIZE)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARENB
+TTYMODE_FLAG(PARENB, 92, c, PARENB)
+#endif
+#if !defined TTYMODES_LOCAL_ONLY || defined PARODD
+TTYMODE_FLAG(PARODD, 93, c, PARODD)
+#endif

+ 28 - 11
source/putty/windows/winnet.c

@@ -904,7 +904,7 @@ static int sk_net_write_oob(Socket *s, const void *data, int len);
 static void sk_net_write_eof(Socket *s);
 static void sk_net_set_frozen(Socket *s, int is_frozen);
 static const char *sk_net_socket_error(Socket *s);
-static char *sk_net_peer_info(Socket *s);
+static SocketPeerInfo *sk_net_peer_info(Socket *s);
 
 extern char *do_select(SOCKET skt, int startup);
 
@@ -1763,7 +1763,7 @@ static const char *sk_net_socket_error(Socket *sock)
     return s->error;
 }
 
-static char *sk_net_peer_info(Socket *sock)
+static SocketPeerInfo *sk_net_peer_info(Socket *sock)
 {
     NetSocket *s = container_of(sock, NetSocket, sock);
 #ifdef NO_IPV6
@@ -1773,26 +1773,43 @@ static char *sk_net_peer_info(Socket *sock)
     char buf[INET6_ADDRSTRLEN];
 #endif
     int addrlen = sizeof(addr);
+    SocketPeerInfo *pi;
 
     if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
         return NULL;
 
+    pi = snew(SocketPeerInfo);
+    pi->addressfamily = ADDRTYPE_UNSPEC;
+    pi->addr_text = NULL;
+    pi->port = -1;
+    pi->log_text = NULL;
+
     if (((struct sockaddr *)&addr)->sa_family == AF_INET) {
-        return dupprintf
-            ("%s:%d",
-             p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr),
-             (int)p_ntohs(((struct sockaddr_in *)&addr)->sin_port));
+        pi->addressfamily = ADDRTYPE_IPV4;
+        memcpy(pi->addr_bin.ipv4, &((struct sockaddr_in *)&addr)->sin_addr, 4);
+        pi->port = p_ntohs(((struct sockaddr_in *)&addr)->sin_port);
+        pi->addr_text = dupstr(
+            p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr));
+        pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port);
+
 #ifndef NO_IPV6
     } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) {
-        return dupprintf
-            ("[%s]:%d",
-             p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr,
-                         buf, sizeof(buf)),
-             (int)p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port));
+        pi->addressfamily = ADDRTYPE_IPV6;
+        memcpy(pi->addr_bin.ipv6,
+               &((struct sockaddr_in6 *)&addr)->sin6_addr, 16);
+        pi->port = p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
+        pi->addr_text = dupstr(
+            p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr,
+                        buf, sizeof(buf)));
+        pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port);
+
 #endif
     } else {
+        sfree(pi);
         return NULL;
     }
+
+    return pi;
 }
 
 static void sk_net_set_frozen(Socket *sock, int is_frozen)

+ 6 - 2
source/putty/x11fwd.c

@@ -662,7 +662,7 @@ static void x11_closing(Plug *plug, const char *error_msg, int error_code,
          * Whether we did that or not, now we slam the connection
          * shut.
          */
-        sshfwd_unclean_close(xconn->c, error_msg);
+        sshfwd_initiate_close(xconn->c, error_msg);
     } else {
         /*
          * Ordinary EOF received on socket. Send an EOF on the SSH
@@ -730,7 +730,11 @@ static const struct ChannelVtable X11Connection_channelvt = {
     x11_send_eof,
     x11_set_input_wanted,
     x11_log_close_msg,
-    chan_no_eager_close,
+    chan_default_want_close,
+    chan_no_exit_status,
+    chan_no_exit_signal,
+    chan_no_exit_signal_numeric,
+    chan_no_request_response,
 };
 
 /*

Some files were not shown because too many files changed in this diff