Added Pageant, a first-attempt PuTTY authentication agent
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 7511e08..dd8ffd5 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -16,7 +16,7 @@
 #endif
 
 #define logevent(s) { logevent(s); \
-                      if (IS_SCP && (scp_flags & SCP_VERBOSE) != 0) \
+                      if (!(flags & FLAG_CONNECTION) && (flags & FLAG_VERBOSE)) \
                       fprintf(stderr, "%s\n", s); }
 
 #define SSH1_MSG_DISCONNECT    1
 #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
@@ -160,10 +173,10 @@ static struct ssh_compress *cscomp = NULL;
 static struct ssh_compress *sccomp = NULL;
 static struct ssh_kex *kex = NULL;
 static struct ssh_hostkey *hostkey = NULL;
-int scp_flags = 0;
 int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
 
 static char *savedhost;
+static int ssh_send_ok;
 
 static enum {
     SSH_STATE_BEFORE_SIZE,
@@ -177,11 +190,9 @@ static int size_needed = FALSE;
 static void s_write (char *buf, int len) {
     while (len > 0) {
        int i = send (s, buf, len, 0);
-       if (IS_SCP) {
-           noise_ultralight(i);
-           if (i <= 0)
-               fatalbox("Lost connection while sending");
-       }
+        noise_ultralight(i);
+        if (i <= 0)
+            fatalbox("Lost connection while sending");
        if (i > 0)
            len -= i, buf += i;
     }
@@ -191,8 +202,7 @@ static int s_read (char *buf, int len) {
     int ret = 0;
     while (len > 0) {
        int i = recv (s, buf, len, 0);
-       if (IS_SCP)
-           noise_ultralight(i);
+        noise_ultralight(i);
        if (i > 0)
            len -= i, buf += i, ret += i;
        else
@@ -202,10 +212,11 @@ static int s_read (char *buf, int len) {
 }
 
 static void c_write (char *buf, int len) {
-    if (IS_SCP) {
-       if (len > 0 && buf[len-1] == '\n') len--;
-       if (len > 0 && buf[len-1] == '\r') len--;
-       if (len > 0) { fwrite(buf, len, 1, stderr); fputc('\n', stderr); }
+    if (!(flags & FLAG_CONNECTION)) {
+        int i;
+        for (i = 0; i < len; i++)
+            if (buf[i] != '\r')
+                fputc(buf[i], stderr);
        return;
     }
     while (len--) 
@@ -224,6 +235,7 @@ struct Packet {
 static struct Packet pktin = { 0, 0, NULL, NULL, 0 };
 static struct Packet pktout = { 0, 0, NULL, NULL, 0 };
 
+static int ssh_version;
 static void (*ssh_protocol)(unsigned char *in, int inlen, int ispkt);
 static void ssh1_protocol(unsigned char *in, int inlen, int ispkt);
 static void ssh2_protocol(unsigned char *in, int inlen, int ispkt);
@@ -309,7 +321,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");
@@ -518,7 +531,6 @@ static void send_packet(int pkttype, ...)
     unsigned long argint;
     int pktlen, argtype, arglen;
     Bignum bn;
-    int i;
 
     pktlen = 0;
     va_start(args, pkttype);
@@ -544,10 +556,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);
@@ -586,18 +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--;
-            *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;
        }
     }
@@ -731,13 +729,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);
 }
 
@@ -958,7 +951,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.
          */
@@ -975,6 +972,7 @@ static int do_ssh_init(void) {
         logevent("Using SSH protocol version 2");
         s_write(vstring, strlen(vstring));
         ssh_protocol = ssh2_protocol;
+        ssh_version = 2;
         s_rdpkt = ssh2_rdpkt;
     } else {
         /*
@@ -988,8 +986,10 @@ static int do_ssh_init(void) {
         logevent("Using SSH protocol version 1");
         s_write(vstring, strlen(vstring));
         ssh_protocol = ssh1_protocol;
+        ssh_version = 1;
         s_rdpkt = ssh1_rdpkt;
     }
+    ssh_send_ok = 0;
     return 1;
 }
 
@@ -1133,7 +1133,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 (!IS_SCP && !*cfg.username) {
+       if ((flags & FLAG_CONNECTION) && !*cfg.username) {
            c_write("login as: ", 10);
            while (pos >= 0) {
                crWaitUntil(!ispkt);
@@ -1173,9 +1173,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
            char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-           if (!IS_SCP) {
+            if (flags & FLAG_VERBOSE) {
                sprintf(stuff, "Sent username \"%s\".\r\n", username);
-               c_write(stuff, strlen(stuff));
+                c_write(stuff, strlen(stuff));
            }
        }
 
@@ -1198,13 +1198,106 @@ 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;
 
-       if (IS_SCP) {
+       if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD && !FLAG_WINDOWED) {
            char prompt[200];
            sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
            if (!ssh_get_password(prompt, password, sizeof(password))) {
@@ -1239,12 +1332,34 @@ 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) {
-                c_write("Trying public key authentication.\r\n", 35);
+                if (flags & FLAG_VERBOSE)
+                    c_write("Trying public key authentication.\r\n", 35);
                 if (!rsakey_encrypted(cfg.keyfile)) {
-                    c_write("No passphrase required.\r\n", 25);
+                    if (flags & FLAG_VERBOSE)
+                        c_write("No passphrase required.\r\n", 25);
                     goto tryauth;
                 }
                 c_write("passphrase: ", 12);
@@ -1313,7 +1428,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
             crWaitUntil(ispkt);
             if (pktin.type == SSH1_SMSG_FAILURE) {
-                c_write("Server refused our public key.\r\n", 32);
+                if (flags & FLAG_VERBOSE)
+                    c_write("Server refused our public key.\r\n", 32);
                 continue;              /* go and try password */
             }
             if (pktin.type != SSH1_SMSG_AUTH_RSA_CHALLENGE)
@@ -1337,7 +1453,9 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
             crWaitUntil(ispkt);
             if (pktin.type == SSH1_SMSG_FAILURE) {
-                c_write("Failed to authenticate with our public key.\r\n", 45);
+                if (flags & FLAG_VERBOSE)
+                    c_write("Failed to authenticate with our public key.\r\n",
+                            45);
                 continue;              /* go and try password */
             } else if (pktin.type != SSH1_SMSG_SUCCESS) {
                 fatalbox("Bizarre response to RSA authentication response");
@@ -1351,7 +1469,8 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        memset(password, 0, strlen(password));
        crWaitUntil(ispkt);
        if (pktin.type == SSH1_SMSG_FAILURE) {
-           c_write("Access denied\r\n", 15);
+            if (flags & FLAG_VERBOSE)
+                c_write("Access denied\r\n", 15);
            logevent("Authentication refused");
        } else if (pktin.type == SSH1_MSG_DISCONNECT) {
            logevent("Received disconnect request");
@@ -1395,13 +1514,17 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt) {
        logevent("Allocated pty");
     }
 
-    send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
+    if (*cfg.remote_cmd)
+        send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cfg.remote_cmd, PKT_END);
+    else
+        send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
     logevent("Started session");
 
     ssh_state = SSH_STATE_SESSION;
     if (size_needed)
        ssh_size();
 
+    ssh_send_ok = 1;
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1741,11 +1864,15 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 }
 
 /*
+ * SSH2: remote identifier for the main session channel.
+ */
+static unsigned long ssh_remote_channel;
+
+/*
  * Handle the SSH2 userauth and connection layers.
  */
 static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
 {
-    static unsigned long their_channel;
     static unsigned long remote_winsize;
     static unsigned long remote_maxpkt;
 
@@ -1775,7 +1902,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        static int pos = 0;
        static char c;
 
-       if (!IS_SCP && !*cfg.username) {
+       if ((flags & FLAG_CONNECTION) && !*cfg.username) {
            c_write("login as: ", 10);
            while (pos >= 0) {
                crWaitUntilV(!ispkt);
@@ -1815,13 +1942,13 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            char stuff[200];
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
-           if (!IS_SCP) {
+           if (flags & FLAG_VERBOSE) {
                sprintf(stuff, "Using username \"%s\".\r\n", username);
                c_write(stuff, strlen(stuff));
            }
        }
 
-       if (IS_SCP) {
+       if (!(flags & FLAG_WINDOWED)) {
            char prompt[200];
            sprintf(prompt, "%s@%s's password: ", cfg.username, savedhost);
            if (!ssh_get_password(prompt, password, sizeof(password))) {
@@ -1906,7 +2033,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     if (ssh2_pkt_getuint32() != 100) {
         fatalbox("Server's channel confirmation cited wrong channel");
     }
-    their_channel = ssh2_pkt_getuint32();
+    ssh_remote_channel = ssh2_pkt_getuint32();
     remote_winsize = ssh2_pkt_getuint32();
     remote_maxpkt = ssh2_pkt_getuint32();
     logevent("Opened channel for session");
@@ -1915,7 +2042,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      * Now allocate a pty for the session.
      */
     ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-    ssh2_pkt_adduint32(their_channel); /* recipient channel */
+    ssh2_pkt_adduint32(ssh_remote_channel); /* recipient channel */
     ssh2_pkt_addstring("pty-req");
     ssh2_pkt_addbool(1);               /* want reply */
     ssh2_pkt_addstring(cfg.termtype);
@@ -1944,7 +2071,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
      * Start a shell.
      */
     ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
-    ssh2_pkt_adduint32(their_channel); /* recipient channel */
+    ssh2_pkt_adduint32(ssh_remote_channel); /* recipient channel */
     ssh2_pkt_addstring("shell");
     ssh2_pkt_addbool(1);               /* want reply */
     ssh2_pkt_send();
@@ -1963,6 +2090,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     /*
      * Transfer data!
      */
+    ssh_send_ok = 1;
     while (1) {
        crReturnV;
        if (ispkt) {
@@ -1991,7 +2119,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
        } else {
             /* FIXME: for now, ignore window size */
             ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
-            ssh2_pkt_adduint32(their_channel);
+            ssh2_pkt_adduint32(ssh_remote_channel);
             ssh2_pkt_addstring_start();
             ssh2_pkt_addstring_data(in, inlen);
             ssh2_pkt_send();
@@ -2033,7 +2161,7 @@ static char *ssh_init (HWND hwnd, char *host, int port, char **realhost) {
     if (!do_ssh_init())
        return "Protocol initialisation error";
 
-    if (WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR)
+    if (hwnd && WSAAsyncSelect (s, hwnd, WM_NETEVENT, FD_READ | FD_CLOSE) == SOCKET_ERROR)
        switch (WSAGetLastError()) {
          case WSAENETDOWN: return "Network is down";
          default: return "WSAAsyncSelect(): unknown error";
@@ -2117,10 +2245,22 @@ static void ssh_size(void) {
 }
 
 /*
- * (Send Telnet special codes)
+ * Send Telnet special codes. TS_EOF is useful for `plink', so you
+ * can send an EOF and collect resulting output (e.g. `plink
+ * hostname sort').
  */
 static void ssh_special (Telnet_Special code) {
-    /* do nothing */
+    if (code == TS_EOF) {
+        if (ssh_version = 1) {
+            send_packet(SSH1_CMSG_EOF, PKT_END);
+        } else {
+            ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
+            ssh2_pkt_adduint32(ssh_remote_channel);
+            ssh2_pkt_send();
+        }
+    } else {
+        /* do nothing */
+    }
 }
 
 
@@ -2134,8 +2274,6 @@ static void get_packet(void)
     long to_read;
     int len;
 
-    assert(IS_SCP);
-
     p = NULL;
     len = 0;
 
@@ -2164,8 +2302,6 @@ int ssh_scp_recv(unsigned char *buf, int len)
     static unsigned char *pending_input_ptr;
     int to_read = len;
 
-    assert(IS_SCP);
-
     if (pending_input_len >= to_read) {
        memcpy(buf, pending_input_ptr, to_read);
        pending_input_ptr += to_read;
@@ -2229,7 +2365,6 @@ int ssh_scp_recv(unsigned char *buf, int len)
  */
 void ssh_scp_send(unsigned char *buf, int len)
 {
-    assert(IS_SCP);
     if (s == INVALID_SOCKET)
        return;
     send_packet(SSH1_CMSG_STDIN_DATA,
@@ -2242,7 +2377,6 @@ void ssh_scp_send(unsigned char *buf, int len)
  */
 void ssh_scp_send_eof(void)
 {
-    assert(IS_SCP);
     if (s == INVALID_SOCKET)
        return;
     send_packet(SSH1_CMSG_EOF, PKT_END);
@@ -2258,8 +2392,6 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost)
 {
     char buf[160], *p;
 
-    assert(IS_SCP);
-
 #ifdef MSCRYPTOAPI
     if (crypto_startup() == 0)
        return "Microsoft high encryption pack not installed!";
@@ -2295,11 +2427,16 @@ char *ssh_scp_init(char *host, int port, char *cmd, char **realhost)
     return NULL;
 }
 
+static SOCKET ssh_socket(void) { return s; }
+
+static int ssh_sendok(void) { return ssh_send_ok; }
 
 Backend ssh_backend = {
     ssh_init,
     ssh_msg,
     ssh_send,
     ssh_size,
-    ssh_special
+    ssh_special,
+    ssh_socket,
+    ssh_sendok
 };