Pageant now accepts an initial key list on the command line
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 7e02bac..613b23e 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -5,6 +5,7 @@
 #include <winsock.h>
 
 #include "putty.h"
+#include "tree234.h"
 #include "ssh.h"
 #include "scp.h"
 
 #define SSH1_SMSG_STDERR_DATA  18
 #define SSH1_CMSG_EOF          19
 #define SSH1_SMSG_EXIT_STATUS  20
+#define SSH1_MSG_CHANNEL_OPEN_CONFIRMATION  21
+#define SSH1_MSG_CHANNEL_OPEN_FAILURE   22
+#define SSH1_MSG_CHANNEL_DATA   23
+#define SSH1_MSG_CHANNEL_CLOSE  24
+#define SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION 25
+#define SSH1_CMSG_AGENT_REQUEST_FORWARDING  30
+#define SSH1_SMSG_AGENT_OPEN    31
 #define SSH1_CMSG_EXIT_CONFIRMATION    33
 #define SSH1_MSG_IGNORE                32
 #define SSH1_MSG_DEBUG         36
 #define SSH1_CMSG_AUTH_TIS     39
 #define SSH1_SMSG_AUTH_TIS_CHALLENGE   40
 #define SSH1_CMSG_AUTH_TIS_RESPONSE    41
+#define SSH1_CMSG_AUTH_CCARD   70
+#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71
+#define SSH1_CMSG_AUTH_CCARD_RESPONSE  72
 
 #define SSH1_AUTH_TIS          5
+#define SSH1_AUTH_CCARD                16
+
+#define SSH_AGENTC_REQUEST_RSA_IDENTITIES    1
+#define SSH_AGENT_RSA_IDENTITIES_ANSWER      2
+#define SSH_AGENTC_RSA_CHALLENGE             3
+#define SSH_AGENT_RSA_RESPONSE               4
+#define SSH_AGENT_FAILURE                    5
+#define SSH_AGENT_SUCCESS                    6
+#define SSH_AGENTC_ADD_RSA_IDENTITY          7
+#define SSH_AGENTC_REMOVE_RSA_IDENTITY       8
 
 #define SSH2_MSG_DISCONNECT             1
 #define SSH2_MSG_IGNORE                 2
@@ -133,7 +154,7 @@ struct ssh_hostkey *hostkey_algs[] = { &ssh_dss };
 
 extern struct ssh_mac ssh_sha1;
 
-SHA_State exhash;
+static SHA_State exhash;
 
 static void nullmac_key(unsigned char *key) { }
 static void nullmac_generate(unsigned char *blk, int len, unsigned long seq) { }
@@ -163,6 +184,38 @@ static struct ssh_hostkey *hostkey = NULL;
 int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
 
 static char *savedhost;
+static int ssh_send_ok;
+
+/*
+ * 2-3-4 tree storing channels.
+ */
+struct ssh_channel {
+    int remoteid, localid;
+    int type;
+    int closes;
+    union {
+        struct ssh_agent_channel {
+            unsigned char *message;
+            unsigned char msglen[4];
+            int lensofar, totallen;
+        } a;
+    } u;
+};
+static tree234 *ssh_channels;           /* indexed by local id */
+static int ssh_channelcmp(void *av, void *bv) {
+    struct ssh_channel *a = (struct ssh_channel *)av;
+    struct ssh_channel *b = (struct ssh_channel *)bv;
+    if (a->localid < b->localid) return -1;
+    if (a->localid > b->localid) return +1;
+    return 0;
+}
+static int ssh_channelfind(void *av, void *bv) {
+    int *a = (int *)av;
+    struct ssh_channel *b = (struct ssh_channel *)bv;
+    if (*a < b->localid) return -1;
+    if (*a > b->localid) return +1;
+    return 0;
+}
 
 static enum {
     SSH_STATE_BEFORE_SIZE,
@@ -307,7 +360,8 @@ next_packet:
     if (pktin.type == SSH1_SMSG_STDOUT_DATA ||
         pktin.type == SSH1_SMSG_STDERR_DATA ||
         pktin.type == SSH1_MSG_DEBUG ||
-        pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
+        pktin.type == SSH1_SMSG_AUTH_TIS_CHALLENGE ||
+        pktin.type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
        long strlen = GET_32BIT(pktin.body);
        if (strlen + 4 != pktin.length)
            fatalbox("Received data packet with bogus string length");
@@ -516,7 +570,6 @@ static void send_packet(int pkttype, ...)
     unsigned long argint;
     int pktlen, argtype, arglen;
     Bignum bn;
-    int i;
 
     pktlen = 0;
     va_start(args, pkttype);
@@ -542,10 +595,7 @@ static void send_packet(int pkttype, ...)
            break;
          case PKT_BIGNUM:
            bn = va_arg(args, Bignum);
-            i = 16 * bn[0] - 1;
-            while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 )
-                i--;
-            pktlen += 2 + (i+7)/8;
+            pktlen += ssh1_bignum_length(bn);
            break;
          default:
            assert(0);
@@ -584,18 +634,7 @@ static void send_packet(int pkttype, ...)
            break;
          case PKT_BIGNUM:
            bn = va_arg(args, Bignum);
-            i = 16 * bn[0] - 1;
-            while ( i > 0 && (bn[i/16+1] >> (i%16)) == 0 )
-                i--;
-            *p++ = (i >> 8) & 0xFF;
-            *p++ = i & 0xFF;
-            i = (i + 7) / 8;
-            while (i-- > 0) {
-                if (i % 2)
-                    *p++ = bn[i/2+1] >> 8;
-                else
-                    *p++ = bn[i/2+1] & 0xFF;
-            }
+            p += ssh1_write_bignum(p, bn);
            break;
        }
     }
@@ -729,13 +768,8 @@ static int ssh_versioncmp(char *a, char *b) {
 #include <stdio.h>
 void sha_string(SHA_State *s, void *str, int len) {
     unsigned char lenblk[4];
-static FILE *fp;
     PUT_32BIT(lenblk, len);
-if (!fp) fp = fopen("h:\\statham\\windows\\putty\\data","wb");
-fwrite(lenblk, 4, 1, fp);
     SHA_Bytes(s, lenblk, 4);
-fwrite(str, len, 1, fp);
-fflush(fp);
     SHA_Bytes(s, str, len);
 }
 
@@ -956,7 +990,11 @@ static int do_ssh_init(void) {
     vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
 
-    if (ssh_versioncmp(version, "2.0" /* FIXME: "1.99" */ ) >= 0) {
+    /*
+     * Server version "1.99" means we can choose whether we use v1
+     * or v2 protocol. Choice is based on cfg.sshprot.
+     */
+    if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
         /*
          * This is a v2 server. Begin v2 protocol.
          */
@@ -990,6 +1028,7 @@ static int do_ssh_init(void) {
         ssh_version = 1;
         s_rdpkt = ssh1_rdpkt;
     }
+    ssh_send_ok = 0;
     return 1;
 }
 
@@ -1133,7 +1172,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static char username[100];
        static int pos = 0;
        static char c;
-       if (!(flags & FLAG_CONNECTION) && !*cfg.username) {
+       if ((flags & FLAG_CONNECTION) && !*cfg.username) {
            c_write("login as: ", 10);
            while (pos >= 0) {
                crWaitUntil(!ispkt);
@@ -1198,9 +1237,102 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
         static int pwpkt_type;
         /*
          * Show password prompt, having first obtained it via a TIS
-         * exchange if we're doing TIS authentication.
+         * or CryptoCard exchange if we're doing TIS or CryptoCard
+         * authentication.
          */
         pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
+        if (agent_exists()) {
+            /*
+             * Attempt RSA authentication using Pageant.
+             */
+            static unsigned char request[5], *response, *p;
+            static int responselen;
+            static int i, nkeys;
+            static int authed = FALSE;
+            void *r;
+
+            logevent("Pageant is running. Requesting keys.");
+
+            /* Request the keys held by the agent. */
+            PUT_32BIT(request, 1);
+            request[4] = SSH_AGENTC_REQUEST_RSA_IDENTITIES;
+            agent_query(request, 5, &r, &responselen);
+            response = (unsigned char *)r;
+            if (response) {
+                p = response + 5;
+                nkeys = GET_32BIT(p); p += 4;
+                { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+                    logevent(buf); }
+                for (i = 0; i < nkeys; i++) {
+                    static struct RSAKey key;
+                    static Bignum challenge;
+
+                    { char buf[64]; sprintf(buf, "Trying Pageant key #%d", i);
+                        logevent(buf); }
+                    p += 4;
+                    p += ssh1_read_bignum(p, &key.exponent);
+                    p += ssh1_read_bignum(p, &key.modulus);
+                    send_packet(SSH1_CMSG_AUTH_RSA,
+                                PKT_BIGNUM, key.modulus, PKT_END);
+                    crWaitUntil(ispkt);
+                    if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
+                        logevent("Key refused");
+                        continue;
+                    }
+                    logevent("Received RSA challenge");
+                    ssh1_read_bignum(pktin.body, &challenge);
+                    {
+                        char *agentreq, *q, *ret;
+                        int len, retlen;
+                        len = 1 + 4;   /* message type, bit count */
+                        len += ssh1_bignum_length(key.exponent);
+                        len += ssh1_bignum_length(key.modulus);
+                        len += ssh1_bignum_length(challenge);
+                        len += 16;     /* session id */
+                        len += 4;      /* response format */
+                        agentreq = malloc(4 + len);
+                        PUT_32BIT(agentreq, len);
+                        q = agentreq + 4;
+                        *q++ = SSH_AGENTC_RSA_CHALLENGE;
+                        PUT_32BIT(q, ssh1_bignum_bitcount(key.modulus));
+                        q += 4;
+                        q += ssh1_write_bignum(q, key.exponent);
+                        q += ssh1_write_bignum(q, key.modulus);
+                        q += ssh1_write_bignum(q, challenge);
+                        memcpy(q, session_id, 16); q += 16;
+                        PUT_32BIT(q, 1);   /* response format */
+                        agent_query(agentreq, len+4, &ret, &retlen);
+                        free(agentreq);
+                        if (ret) {
+                            if (ret[4] == SSH_AGENT_RSA_RESPONSE) {
+                                logevent("Sending Pageant's response");
+                                send_packet(SSH1_CMSG_AUTH_RSA_RESPONSE,
+                                            PKT_DATA, ret+5, 16, PKT_END);
+                                free(ret);
+                                crWaitUntil(ispkt);
+                                if (pktin.type == SSH1_SMSG_SUCCESS) {
+                                    logevent("Pageant's response accepted");
+                                    authed = TRUE;
+                                } else
+                                    logevent("Pageant's response not accepted");
+                            } else {
+                                logevent("Pageant failed to answer challenge");
+                                free(ret);
+                            }
+                        } else {
+                            logevent("No reply received from Pageant");
+                        }
+                    }
+                    freebn(key.exponent);
+                    freebn(key.modulus);
+                    freebn(challenge);
+                    if (authed)
+                        break;
+                }
+            }
+            if (authed)
+                break;
+        }
         if (*cfg.keyfile && !tried_publickey)
             pwpkt_type = SSH1_CMSG_AUTH_RSA;
 
@@ -1239,6 +1371,26 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
                     c_write(pktin.body+4, challengelen);
                 }
             }
+            if (pktin.type == SSH1_SMSG_FAILURE &&
+                cfg.try_tis_auth &&
+                (supported_auths_mask & (1<<SSH1_AUTH_CCARD))) {
+                pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
+                logevent("Requested CryptoCard authentication");
+                send_packet(SSH1_CMSG_AUTH_CCARD, PKT_END);
+                crWaitUntil(ispkt);
+                if (pktin.type != SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
+                    logevent("CryptoCard authentication declined");
+                    c_write("CryptoCard authentication refused.\r\n", 29);
+                } else {
+                    int challengelen = ((pktin.body[0] << 24) |
+                                        (pktin.body[1] << 16) |
+                                        (pktin.body[2] << 8) |
+                                        (pktin.body[3]));
+                    logevent("Received CryptoCard challenge");
+                    c_write(pktin.body+4, challengelen);
+                    c_write("\r\nResponse : ", 13);
+                }
+            }
             if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD)
                 c_write("password: ", 10);
             if (pwpkt_type == SSH1_CMSG_AUTH_RSA) {
@@ -1384,6 +1536,18 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
     if (ssh_state == SSH_STATE_CLOSED)
         crReturnV;
 
+    if (1 /* FIXME: agent exists && agent forwarding configured */ ) {
+        logevent("Requesting agent forwarding");
+        send_packet(SSH1_CMSG_AGENT_REQUEST_FORWARDING, PKT_END);
+        do { crReturnV; } while (!ispkt);
+        if (pktin.type != SSH1_SMSG_SUCCESS && pktin.type != SSH1_SMSG_FAILURE) {
+            fatalbox("Protocol confusion");
+        } else if (pktin.type == SSH1_SMSG_FAILURE) {
+            logevent("Agent forwarding refused");
+        } else
+            logevent("Agent forwarding enabled");
+    }
+
     if (!cfg.nopty) {
        send_packet(SSH1_CMSG_REQUEST_PTY,
                    PKT_STR, cfg.termtype,
@@ -1411,6 +1575,8 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
     if (size_needed)
        ssh_size();
 
+    ssh_send_ok = 1;
+    ssh_channels = newtree234(ssh_channelcmp);
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1421,6 +1587,94 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
            } else if (pktin.type == SSH1_MSG_DISCONNECT) {
                 ssh_state = SSH_STATE_CLOSED;
                logevent("Received disconnect request");
+            } else if (pktin.type == SSH1_SMSG_AGENT_OPEN) {
+                /* Remote side is trying to open a channel to talk to our
+                 * agent. Give them back a local channel number. */
+                int i = 1;
+                struct ssh_channel *c;
+                enum234 e;
+                for (c = first234(ssh_channels, &e); c; c = next234(&e)) {
+                    if (c->localid > i)
+                        break;         /* found a free number */
+                    i = c->localid + 1;
+                }
+                c = malloc(sizeof(struct ssh_channel));
+                c->remoteid = GET_32BIT(pktin.body);
+                c->localid = i;
+                c->type = SSH1_SMSG_AGENT_OPEN;   /* identify channel type */
+                add234(ssh_channels, c);
+                send_packet(SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
+                            PKT_INT, c->remoteid, PKT_INT, c->localid,
+                            PKT_END);
+            } else if (pktin.type == SSH1_MSG_CHANNEL_CLOSE ||
+                       pktin.type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION) {
+                /* Remote side closes a channel. */
+                int i = GET_32BIT(pktin.body);
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (c) {
+                    int closetype;
+                    closetype = (pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
+                    send_packet(pktin.type, PKT_INT, c->remoteid, PKT_END);
+                    c->closes |= closetype;
+                    if (c->closes == 3) {
+                        del234(ssh_channels, c);
+                        free(c);
+                    }
+                }
+            } else if (pktin.type == SSH1_MSG_CHANNEL_DATA) {
+                /* Data sent down one of our channels. */
+                int i = GET_32BIT(pktin.body);
+                int len = GET_32BIT(pktin.body+4);
+                unsigned char *p = pktin.body+8;
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (c) {
+                    switch(c->type) {
+                      case SSH1_SMSG_AGENT_OPEN:
+                        /* Data for an agent message. Buffer it. */
+                        while (len > 0) {
+                            if (c->u.a.lensofar < 4) {
+                                int l = min(4 - c->u.a.lensofar, len);
+                                memcpy(c->u.a.msglen + c->u.a.lensofar, p, l);
+                                p += l; len -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == 4) {
+                                c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
+                                c->u.a.message = malloc(c->u.a.totallen);
+                                memcpy(c->u.a.message, c->u.a.msglen, 4);
+                            }
+                            if (c->u.a.lensofar >= 4 && len > 0) {
+                                int l = min(c->u.a.totallen - c->u.a.lensofar, len);
+                                memcpy(c->u.a.message + c->u.a.lensofar, p, l);
+                                p += l; len -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == c->u.a.totallen) {
+                                void *reply, *sentreply;
+                                int replylen;
+                                agent_query(c->u.a.message, c->u.a.totallen,
+                                            &reply, &replylen);
+                                if (reply)
+                                    sentreply = reply;
+                                else {
+                                    /* Fake SSH_AGENT_FAILURE. */
+                                    sentreply = "\0\0\0\1\5";
+                                    replylen = 5;
+                                }
+                                send_packet(SSH1_MSG_CHANNEL_DATA,
+                                            PKT_INT, c->remoteid,
+                                            PKT_INT, replylen,
+                                            PKT_DATA, sentreply, replylen,
+                                            PKT_END);
+                                if (reply)
+                                    free(reply);
+                                free(c->u.a.message);
+                                c->u.a.lensofar = 0;
+                            }
+                        }
+                        break;
+                    }
+                }                
            } else if (pktin.type == SSH1_SMSG_SUCCESS) {
                /* may be from EXEC_SHELL on some servers */
            } else if (pktin.type == SSH1_SMSG_FAILURE) {
@@ -1976,6 +2230,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     /*
      * Transfer data!
      */
+    ssh_send_ok = 1;
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -2312,7 +2567,9 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost)
     return NULL;
 }
 
-SOCKET ssh_socket(void) { return s; }
+static SOCKET ssh_socket(void) { return s; }
+
+static int ssh_sendok(void) { return ssh_send_ok; }
 
 Backend ssh_backend = {
     ssh_init,
@@ -2320,5 +2577,6 @@ Backend ssh_backend = {
     ssh_send,
     ssh_size,
     ssh_special,
-    ssh_socket
+    ssh_socket,
+    ssh_sendok
 };