From 6c682a5410ab3bd1819d2a04478297181d1acaac Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@unixuser.org>
Date: Wed, 16 Dec 2009 08:46:06 +0900
Subject: [PATCH 1/3] Add callback-based API for publickey auth.

---
 include/libssh2.h |   13 ++++
 src/userauth.c    |  196 ++++++++++++++++++++++++++++++++++++++---------------
 2 files changed, 154 insertions(+), 55 deletions(-)

diff --git a/include/libssh2.h b/include/libssh2.h
index 807f6a2..f771eca 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -178,6 +178,11 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE
     unsigned int length;
 } LIBSSH2_USERAUTH_KBDINT_RESPONSE;
 
+/* 'publickey' authentication callback */
+#define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \
+  int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \
+           const unsigned char *data, size_t data_len, void **abstract)
+
 /* 'keyboard-interactive' authentication callback */
 #define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) \
  void name_(const char* name, int name_len, const char* instruction, \
@@ -437,6 +442,14 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
                                          (privatekey), (passphrase))
 
 LIBSSH2_API int
+libssh2_userauth_publickey(LIBSSH2_SESSION *session,
+                           const char *username,
+                           const unsigned char *pubkeydata,
+                           size_t pubkeydata_len,
+                           LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                           void **abstract);
+
+LIBSSH2_API int
 libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
                                        const char *username,
                                        unsigned int username_len,
diff --git a/src/userauth.c b/src/userauth.c
index ffd1db1..1bf333e 100644
--- a/src/userauth.c
+++ b/src/userauth.c
@@ -615,6 +615,45 @@ file_read_privatekey(LIBSSH2_SESSION * session,
     return 0;
 }
 
+struct privkey_file {
+    const char *filename;
+    const char *passphrase;
+};
+
+static int
+sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
+              const unsigned char *data, size_t data_len, void **abstract)
+{
+    struct privkey_file *privkey_file = (struct privkey_file *) (*abstract);
+    const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
+    void *hostkey_abstract;
+    struct iovec datavec;
+
+    if (file_read_privatekey(session, &privkeyobj, &hostkey_abstract,
+                             session->userauth_pblc_method,
+                             session->userauth_pblc_method_len,
+                             privkey_file->filename,
+                             privkey_file->passphrase)) {
+        return -1;
+    }
+
+    datavec.iov_base = (unsigned char *)data;
+    datavec.iov_len = data_len;
+
+    if (privkeyobj->signv(session, sig, sig_len, 1, &datavec,
+                          &hostkey_abstract)) {
+        if (privkeyobj->dtor) {
+            privkeyobj->dtor(session, abstract);
+        }
+        return -1;
+    }
+
+    if (privkeyobj->dtor) {
+        privkeyobj->dtor(session, &hostkey_abstract);
+    }
+    return 0;
+}
+
 
 
 /* userauth_hostbased_fromfile
@@ -887,19 +926,15 @@ libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
 
 
 
-/*
- * userauth_publickey_fromfile
- * Authenticate using a keypair found in the named files
- */
 static int
-userauth_publickey_fromfile(LIBSSH2_SESSION *session,
-                            const char *username,
-                            unsigned int username_len,
-                            const char *publickey,
-                            const char *privatekey,
-                            const char *passphrase)
+userauth_publickey(LIBSSH2_SESSION *session,
+                   const char *username,
+                   unsigned int username_len,
+                   const unsigned char *pubkeydata,
+                   unsigned long pubkeydata_len,
+                   LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                   void *abstract)
 {
-    unsigned long pubkeydata_len = 0;
     unsigned char reply_codes[4] =
         { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE,
           SSH_MSG_USERAUTH_PK_OK, 0
@@ -907,17 +942,20 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     int rc;
 
     if (session->userauth_pblc_state == libssh2_NB_state_idle) {
-        unsigned char *pubkeydata;
-
         /* Zero the whole thing out */
         memset(&session->userauth_pblc_packet_requirev_state, 0,
                sizeof(session->userauth_pblc_packet_requirev_state));
 
-        if (file_read_publickey(session, &session->userauth_pblc_method,
-                                &session->userauth_pblc_method_len,
-                                &pubkeydata, &pubkeydata_len,publickey)) {
+        session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata);
+        session->userauth_pblc_method =
+            LIBSSH2_ALLOC(session, session->userauth_pblc_method_len);
+        if (!session->userauth_pblc_method) {
+            libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+                          "Unable to allocate memory for public key data", 0);
             return -1;
         }
+        memcpy(session->userauth_pblc_method, pubkeydata + 4,
+               session->userauth_pblc_method_len);
 
         /*
          * 45 = packet_type(1) + username_len(4) + servicename_len(4) +
@@ -942,7 +980,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         if (!session->userauth_pblc_packet) {
             LIBSSH2_FREE(session, session->userauth_pblc_method);
             session->userauth_pblc_method = NULL;
-            LIBSSH2_FREE(session, pubkeydata);
             return -1;
         }
 
@@ -977,7 +1014,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         session->userauth_pblc_s += 4;
         memcpy(session->userauth_pblc_s, pubkeydata, pubkeydata_len);
         session->userauth_pblc_s += pubkeydata_len;
-        LIBSSH2_FREE(session, pubkeydata);
 
         _libssh2_debug(session, LIBSSH2_TRACE_AUTH,
                        "Attempting publickey authentication");
@@ -1005,13 +1041,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     }
 
     if (session->userauth_pblc_state == libssh2_NB_state_sent) {
-        const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
-        void *abstract;
-        unsigned char buf[5];
-        struct iovec datavec[4];
-        unsigned char *sig;
-        unsigned long sig_len;
-
         rc = _libssh2_packet_requirev(session, reply_codes,
                                       &session->userauth_pblc_data,
                                       &session->userauth_pblc_data_len, 0,
@@ -1065,44 +1094,45 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         LIBSSH2_FREE(session, session->userauth_pblc_data);
         session->userauth_pblc_data = NULL;
 
-        if (file_read_privatekey(session, &privkeyobj, &abstract,
-                                 session->userauth_pblc_method,
-                                 session->userauth_pblc_method_len,
-                                 privatekey, passphrase)) {
-            LIBSSH2_FREE(session, session->userauth_pblc_method);
-            session->userauth_pblc_method = NULL;
-            LIBSSH2_FREE(session, session->userauth_pblc_packet);
-            session->userauth_pblc_packet = NULL;
-            session->userauth_pblc_state = libssh2_NB_state_idle;
-            return -1;
-        }
-
         *session->userauth_pblc_b = 0x01;
+        session->userauth_pblc_state = libssh2_NB_state_sent1;
+    }
 
-        _libssh2_htonu32(buf, session->session_id_len);
-        datavec[0].iov_base = buf;
-        datavec[0].iov_len = 4;
-        datavec[1].iov_base = session->session_id;
-        datavec[1].iov_len = session->session_id_len;
-        datavec[2].iov_base = session->userauth_pblc_packet;
-        datavec[2].iov_len = session->userauth_pblc_packet_len;
+    if (session->userauth_pblc_state == libssh2_NB_state_sent1) {
+        unsigned char *buf, *s;
+        unsigned char *sig;
+        unsigned long sig_len;
 
-        if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) {
+        s = buf = LIBSSH2_ALLOC(session, 4 + session->session_id_len
+                                + session->userauth_pblc_packet_len);
+        if (!buf) {
+            libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+                          "Unable to allocate memory for userauth-publickey "
+                          "signed data", 0);
+            return -1;
+        }
+
+        _libssh2_htonu32(s, session->session_id_len);
+        s += 4;
+        memcpy (s, session->session_id, session->session_id_len);
+        s += session->session_id_len;
+        memcpy (s, session->userauth_pblc_packet,
+                session->userauth_pblc_packet_len);
+        s += session->userauth_pblc_packet_len;
+
+        rc = sign_callback(session, &sig, &sig_len, buf, s - buf, abstract);
+        LIBSSH2_FREE(session, buf);
+        if (rc == PACKET_EAGAIN) {
+            return rc;
+        } else if (rc) {
             LIBSSH2_FREE(session, session->userauth_pblc_method);
             session->userauth_pblc_method = NULL;
             LIBSSH2_FREE(session, session->userauth_pblc_packet);
             session->userauth_pblc_packet = NULL;
-            if (privkeyobj->dtor) {
-                privkeyobj->dtor(session, &abstract);
-            }
             session->userauth_pblc_state = libssh2_NB_state_idle;
             return -1;
         }
 
-        if (privkeyobj->dtor) {
-            privkeyobj->dtor(session, &abstract);
-        }
-
         /*
          * If this function was restarted, pubkeydata_len might still be 0
          * which will cause an unnecessary but harmless realloc here.
@@ -1157,10 +1187,10 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         _libssh2_debug(session, LIBSSH2_TRACE_AUTH,
                        "Attempting publickey authentication -- phase 2");
 
-        session->userauth_pblc_state = libssh2_NB_state_sent1;
+        session->userauth_pblc_state = libssh2_NB_state_sent2;
     }
 
-    if (session->userauth_pblc_state == libssh2_NB_state_sent1) {
+    if (session->userauth_pblc_state == libssh2_NB_state_sent2) {
         rc = _libssh2_transport_write(session, session->userauth_pblc_packet,
                                       session->userauth_pblc_s -
                                       session->userauth_pblc_packet);
@@ -1177,7 +1207,7 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
         LIBSSH2_FREE(session, session->userauth_pblc_packet);
         session->userauth_pblc_packet = NULL;
 
-        session->userauth_pblc_state = libssh2_NB_state_sent2;
+        session->userauth_pblc_state = libssh2_NB_state_sent3;
     }
 
     /* PK_OK is no longer valid */
@@ -1216,6 +1246,43 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
     return -1;
 }
 
+/*
+ * userauth_publickey_fromfile
+ * Authenticate using a keypair found in the named files
+ */
+static int
+userauth_publickey_fromfile(LIBSSH2_SESSION *session,
+                            const char *username,
+                            unsigned int username_len,
+                            const char *publickey,
+                            const char *privatekey,
+                            const char *passphrase)
+{
+    unsigned char *pubkeydata = NULL;
+    unsigned long pubkeydata_len = 0;
+    struct privkey_file privkey_file;
+    void *abstract = &privkey_file;
+    int rc;
+
+    privkey_file.filename = privatekey;
+    privkey_file.passphrase = passphrase;
+
+    if (session->userauth_pblc_state == libssh2_NB_state_idle) {
+        if (file_read_publickey(session, &session->userauth_pblc_method,
+                                &session->userauth_pblc_method_len,
+                                &pubkeydata, &pubkeydata_len, publickey)) {
+            return -1;
+        }
+    }
+
+    rc = userauth_publickey(session, username, username_len,
+                            pubkeydata, pubkeydata_len,
+                            sign_fromfile, &abstract);
+    LIBSSH2_FREE(session, pubkeydata);
+
+    return rc;
+}
+
 /* libssh2_userauth_publickey_fromfile_ex
  * Authenticate using a keypair found in the named files
  */
@@ -1235,6 +1302,25 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
     return rc;
 }
 
+/* libssh2_userauth_publickey_ex
+ * Authenticate using an external callback function
+ */
+LIBSSH2_API int
+libssh2_userauth_publickey(LIBSSH2_SESSION *session,
+                           const char *user,
+                           const unsigned char *pubkeydata,
+                           size_t pubkeydata_len,
+                           LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+                           void **abstract)
+{
+    int rc;
+    BLOCK_ADJUST(rc, session,
+                 userauth_publickey(session, user, strlen(user),
+                                    pubkeydata, pubkeydata_len,
+                                    sign_callback, abstract));
+    return rc;
+}
+
 
 
 /*
-- 
1.6.5.4

