Add an option to disable SSH-2 banners.
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 029c78a..0a485da 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -13,6 +13,7 @@
 #include "tree234.h"
 #include "ssh.h"
 #ifndef NO_GSSAPI
+#include "sshgssc.h"
 #include "sshgss.h"
 #endif
 
@@ -589,6 +590,17 @@ struct ssh_channel {
      * A channel is completely finished with when all four bits are set.
      */
     int closes;
+
+    /*
+     * This flag indicates that a close is pending on the outgoing
+     * side of the channel: that is, wherever we're getting the data
+     * for this channel has sent us some data followed by EOF. We
+     * can't actually close the channel until we've finished sending
+     * the data, so we set this flag instead to remind us to
+     * initiate the closing process once our buffer is clear.
+     */
+    int pending_close;
+
     /*
      * True if this channel is causing the underlying connection to be
      * throttled.
@@ -929,6 +941,13 @@ struct ssh_tag {
      * Fully qualified host name, which we need if doing GSSAPI.
      */
     char *fullhostname;
+
+#ifndef NO_GSSAPI
+    /*
+     * GSSAPI libraries for this session.
+     */
+    struct ssh_gss_liblist *gsslibs;
+#endif
 };
 
 #define logevent(s) logevent(ssh->frontend, s)
@@ -4152,7 +4171,7 @@ void sshfwd_close(struct ssh_channel *c)
     if (ssh->state == SSH_STATE_CLOSED)
        return;
 
-    if (c && !c->closes) {
+    if (!c->closes) {
        /*
         * If halfopen is true, we have sent
         * CHANNEL_OPEN for this channel, but it hasn't even been
@@ -4164,14 +4183,42 @@ void sshfwd_close(struct ssh_channel *c)
            if (ssh->version == 1) {
                send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
                            PKT_END);
+               c->closes = 1;                 /* sent MSG_CLOSE */
            } else {
-               struct Packet *pktout;
-               pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
-               ssh2_pkt_adduint32(pktout, c->remoteid);
-               ssh2_pkt_send(ssh, pktout);
+               int bytes_to_send = bufchain_size(&c->v.v2.outbuffer);
+               if (bytes_to_send > 0) {
+                   /*
+                    * If we still have unsent data in our outgoing
+                    * buffer for this channel, we can't actually
+                    * initiate a close operation yet or that data
+                    * will be lost. Instead, set the pending_close
+                    * flag so that when we do clear the buffer
+                    * we'll start closing the channel.
+                    */
+                   char logmsg[160] = {'\0'};
+                   sprintf(
+                           logmsg,
+                           "Forwarded port pending to be closed : "
+                           "%d bytes remaining",
+                           bytes_to_send);
+                   logevent(logmsg);
+
+                   c->pending_close = TRUE;
+               } else {
+                   /*
+                    * No locally buffered data, so we can send the
+                    * close message immediately.
+                    */
+                   struct Packet *pktout;
+                   pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+                   ssh2_pkt_adduint32(pktout, c->remoteid);
+                   ssh2_pkt_send(ssh, pktout);
+                   c->closes = 1;                     /* sent MSG_CLOSE */
+                   logevent("Nothing left to send, closing channel");
+               }
            }
        }
-       c->closes = 1;                 /* sent MSG_CLOSE */
+
        if (c->type == CHAN_X11) {
            c->u.x11.s = NULL;
            logevent("Forwarded X11 connection terminated");
@@ -4310,6 +4357,7 @@ static void ssh_rportfwd_succfail(Ssh ssh, struct Packet *pktin, void *ctx)
 
        rpf = del234(ssh->rportfwds, pf);
        assert(rpf == pf);
+       pf->pfrec->remote = NULL;
        free_rportfwd(pf);
     }
 }
@@ -4486,6 +4534,8 @@ static void ssh_setup_portfwd(Ssh ssh, const Config *cfg)
            logeventf(ssh, "Cancelling %s", message);
            sfree(message);
 
+           /* epf->remote or epf->local may be NULL if setting up a
+            * forwarding failed. */
            if (epf->remote) {
                struct ssh_rportfwd *rpf = epf->remote;
                struct Packet *pktout;
@@ -4695,6 +4745,7 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin)
            c->halfopen = FALSE;
            c->localid = alloc_channel_id(ssh);
            c->closes = 0;
+           c->pending_close = FALSE;
            c->throttling_conn = 0;
            c->type = CHAN_X11; /* identify channel type */
            add234(ssh->channels, c);
@@ -4724,6 +4775,7 @@ static void ssh1_smsg_agent_open(Ssh ssh, struct Packet *pktin)
        c->halfopen = FALSE;
        c->localid = alloc_channel_id(ssh);
        c->closes = 0;
+       c->pending_close = FALSE;
        c->throttling_conn = 0;
        c->type = CHAN_AGENT;   /* identify channel type */
        c->u.a.lensofar = 0;
@@ -4778,6 +4830,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin)
            c->halfopen = FALSE;
            c->localid = alloc_channel_id(ssh);
            c->closes = 0;
+           c->pending_close = FALSE;
            c->throttling_conn = 0;
            c->type = CHAN_SOCKDATA;    /* identify channel type */
            add234(ssh->channels, c);
@@ -6344,7 +6397,7 @@ static int ssh2_try_send(struct ssh_channel *c)
     return bufchain_size(&c->v.v2.outbuffer);
 }
 
-static void ssh2_try_send_and_unthrottle(struct ssh_channel *c)
+static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
 {
     int bufsize;
     if (c->closes)
@@ -6368,6 +6421,19 @@ static void ssh2_try_send_and_unthrottle(struct ssh_channel *c)
            break;
        }
     }
+
+    /*
+     * If we've emptied the channel's output buffer and there's a
+     * pending close event, start the channel-closing procedure.
+     */
+    if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) {
+       struct Packet *pktout;
+       pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+       ssh2_pkt_adduint32(pktout, c->remoteid);
+       ssh2_pkt_send(ssh, pktout);
+       c->closes = 1;
+       c->pending_close = FALSE;
+    }
 }
 
 /*
@@ -6378,6 +6444,7 @@ static void ssh2_channel_init(struct ssh_channel *c)
     Ssh ssh = c->ssh;
     c->localid = alloc_channel_id(ssh);
     c->closes = 0;
+    c->pending_close = FALSE;
     c->throttling_conn = FALSE;
     c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
        ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
@@ -6561,7 +6628,7 @@ static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
        return;
     if (!c->closes) {
        c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
-       ssh2_try_send_and_unthrottle(c);
+       ssh2_try_send_and_unthrottle(ssh, c);
     }
 }
 
@@ -7127,12 +7194,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 }
 
 /*
- * Buffer banner messages for later display at some convenient point.
+ * Buffer banner messages for later display at some convenient point,
+ * if we're going to display them.
  */
 static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
 {
     /* Arbitrary limit to prevent unbounded inflation of buffer */
-    if (bufchain_size(&ssh->banner) <= 131072) {
+    if (ssh->cfg.ssh_show_banner &&
+       bufchain_size(&ssh->banner) <= 131072) {
        char *banner = NULL;
        int size = 0;
        ssh_pkt_getstring(pktin, &banner, &size);
@@ -7209,6 +7278,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        int num_env, env_left, env_ok;
        struct Packet *pktout;
 #ifndef NO_GSSAPI
+       struct ssh_gss_library *gsslib;
        Ssh_gss_ctx gss_ctx;
        Ssh_gss_buf gss_buf;
        Ssh_gss_buf gss_rcvtok, gss_sndtok;
@@ -7477,6 +7547,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        }
 
        while (1) {
+           char *methods = NULL;
+           int methlen = 0;
+
            /*
             * Wait for the result of the last authentication request.
             */
@@ -7526,8 +7599,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * helpfully try next.
             */
            if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
-               char *methods;
-               int methlen;
                ssh_pkt_getstring(pktin, &methods, &methlen);
                if (!ssh2_pkt_getbool(pktin)) {
                    /*
@@ -7583,10 +7654,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    in_commasep_string("password", methods, methlen);
                s->can_keyb_inter = ssh->cfg.try_ki_auth &&
                    in_commasep_string("keyboard-interactive", methods, methlen);
-#ifndef NO_GSSAPI              
+#ifndef NO_GSSAPI
+               if (!ssh->gsslibs)
+                   ssh->gsslibs = ssh_gss_setup(&ssh->cfg);
                s->can_gssapi = ssh->cfg.try_gssapi_auth &&
-                 in_commasep_string("gssapi-with-mic", methods, methlen) &&
-                 ssh_gss_init();
+                   in_commasep_string("gssapi-with-mic", methods, methlen) &&
+                   ssh->gsslibs->nlibraries > 0;
 #endif
            }
 
@@ -7929,6 +8002,35 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->gotit = TRUE;
                ssh->pkt_actx = SSH2_PKTCTX_GSSAPI;
 
+               /*
+                * Pick the highest GSS library on the preference
+                * list.
+                */
+               {
+                   int i, j;
+                   s->gsslib = NULL;
+                   for (i = 0; i < ngsslibs; i++) {
+                       int want_id = ssh->cfg.ssh_gsslist[i];
+                       for (j = 0; j < ssh->gsslibs->nlibraries; j++)
+                           if (ssh->gsslibs->libraries[j].id == want_id) {
+                               s->gsslib = &ssh->gsslibs->libraries[j];
+                               goto got_gsslib;   /* double break */
+                           }
+                   }
+                   got_gsslib:
+                   /*
+                    * We always expect to have found something in
+                    * the above loop: we only came here if there
+                    * was at least one viable GSS library, and the
+                    * preference list should always mention
+                    * everything and only change the order.
+                    */
+                   assert(s->gsslib);
+               }
+
+               if (s->gsslib->gsslogmsg)
+                   logevent(s->gsslib->gsslogmsg);
+
                /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
                ssh2_pkt_addstring(s->pktout, s->username);
@@ -7936,7 +8038,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                ssh2_pkt_addstring(s->pktout, "gssapi-with-mic");
 
                /* add mechanism info */
-               ssh_gss_indicate_mech(&s->gss_buf);
+               s->gsslib->indicate_mech(s->gsslib, &s->gss_buf);
 
                /* number of GSSAPI mechanisms */
                ssh2_pkt_adduint32(s->pktout,1);
@@ -7972,8 +8074,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
 
                /* now start running */
-               s->gss_stat = ssh_gss_import_name(ssh->fullhostname,
-                                                 &s->gss_srv_name);
+               s->gss_stat = s->gsslib->import_name(s->gsslib,
+                                                    ssh->fullhostname,
+                                                    &s->gss_srv_name);
                if (s->gss_stat != SSH_GSS_OK) {
                    if (s->gss_stat == SSH_GSS_BAD_HOST_NAME)
                        logevent("GSSAPI import name failed - Bad service name");
@@ -7983,11 +8086,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
 
                /* fetch TGT into GSS engine */
-               s->gss_stat = ssh_gss_acquire_cred(&s->gss_ctx);
+               s->gss_stat = s->gsslib->acquire_cred(s->gsslib, &s->gss_ctx);
 
                if (s->gss_stat != SSH_GSS_OK) {
                    logevent("GSSAPI authentication failed to get credentials");
-                   ssh_gss_release_name(&s->gss_srv_name);
+                   s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
                    continue;
                }
 
@@ -7997,17 +8100,20 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
                /* now enter the loop */
                do {
-                   s->gss_stat = ssh_gss_init_sec_context(&s->gss_ctx,
-                                                          s->gss_srv_name,
-                                                          ssh->cfg.gssapifwd,
-                                                          &s->gss_rcvtok,
-                                                          &s->gss_sndtok);
+                   s->gss_stat = s->gsslib->init_sec_context
+                       (s->gsslib,
+                        &s->gss_ctx,
+                        s->gss_srv_name,
+                        ssh->cfg.gssapifwd,
+                        &s->gss_rcvtok,
+                        &s->gss_sndtok);
 
                    if (s->gss_stat!=SSH_GSS_S_COMPLETE &&
                        s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) {
                        logevent("GSSAPI authentication initialisation failed");
 
-                       if (ssh_gss_display_status(s->gss_ctx,&s->gss_buf) == SSH_GSS_OK) {
+                       if (s->gsslib->display_status(s->gsslib, s->gss_ctx,
+                                                     &s->gss_buf) == SSH_GSS_OK) {
                            logevent(s->gss_buf.value);
                            sfree(s->gss_buf.value);
                        }
@@ -8024,7 +8130,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                        ssh_pkt_addstring_start(s->pktout);
                        ssh_pkt_addstring_data(s->pktout,s->gss_sndtok.value,s->gss_sndtok.length);
                        ssh2_pkt_send(ssh, s->pktout);
-                       ssh_gss_free_tok(&s->gss_sndtok);
+                       s->gsslib->free_tok(s->gsslib, &s->gss_sndtok);
                    }
 
                    if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) {
@@ -8041,8 +8147,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED);
 
                if (s->gss_stat != SSH_GSS_OK) {
-                   ssh_gss_release_name(&s->gss_srv_name);
-                   ssh_gss_release_cred(&s->gss_ctx);
+                   s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+                   s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
                    continue;
                }
                logevent("GSSAPI authentication loop finished OK");
@@ -8061,17 +8167,17 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                s->gss_buf.value = (char *)s->pktout->data + micoffset;
                s->gss_buf.length = s->pktout->length - micoffset;
 
-               ssh_gss_get_mic(s->gss_ctx, &s->gss_buf, &mic);
+               s->gsslib->get_mic(s->gsslib, s->gss_ctx, &s->gss_buf, &mic);
                s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_GSSAPI_MIC);
                ssh_pkt_addstring_start(s->pktout);
                ssh_pkt_addstring_data(s->pktout, mic.value, mic.length);
                ssh2_pkt_send(ssh, s->pktout);
-               ssh_gss_free_mic(&mic);
+               s->gsslib->free_mic(s->gsslib, &mic);
 
                s->gotit = FALSE;
 
-               ssh_gss_release_name(&s->gss_srv_name);
-               ssh_gss_release_cred(&s->gss_ctx);
+               s->gsslib->release_name(s->gsslib, &s->gss_srv_name);
+               s->gsslib->release_cred(s->gsslib, &s->gss_ctx);
                continue;
 #endif
            } else if (s->can_keyb_inter && !s->kbd_inter_refused) {
@@ -8457,11 +8563,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                sfree(s->password);
 
            } else {
+               char *str = dupprintf("No supported authentication methods available"
+                                     " (server sent: %.*s)",
+                                     methlen, methods);
 
-               ssh_disconnect(ssh, NULL,
+               ssh_disconnect(ssh, str,
                               "No supported authentication methods available",
                               SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
                               FALSE);
+               sfree(str);
+
                crStopV;
 
            }
@@ -8900,7 +9011,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             * Try to send data on all channels if we can.
             */
            for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
-               ssh2_try_send_and_unthrottle(c);
+               ssh2_try_send_and_unthrottle(ssh, c);
        }
     }
 
@@ -9182,6 +9293,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->max_data_size = parse_blocksize(ssh->cfg.ssh_rekey_data);
     ssh->kex_in_progress = FALSE;
 
+#ifndef NO_GSSAPI
+    ssh->gsslibs = NULL;
+#endif
+
     p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
     if (p != NULL)
        return p;
@@ -9254,7 +9369,7 @@ static void ssh_free(void *handle)
 
     if (ssh->rportfwds) {
        while ((pf = delpos234(ssh->rportfwds, 0)) != NULL)
-           sfree(pf);
+           free_rportfwd(pf);
        freetree234(ssh->rportfwds);
        ssh->rportfwds = NULL;
     }
@@ -9278,6 +9393,10 @@ static void ssh_free(void *handle)
     if (ssh->pinger)
        pinger_free(ssh->pinger);
     bufchain_clear(&ssh->queued_incoming_data);
+#ifndef NO_GSSAPI
+    if (ssh->gsslibs)
+       ssh_gss_cleanup(ssh->gsslibs);
+#endif
     sfree(ssh);
 
     random_unref();