From a3039648ec084e333dfbd1d78573cc01a393512f Mon Sep 17 00:00:00 2001
From: Daiki Ueno <ueno@unixuser.org>
Date: Wed, 16 Dec 2009 08:49:57 +0900
Subject: [PATCH 2/3] Add ssh-agent API.

---
 Makefile.inc      |    2 +-
 configure.ac      |    1 +
 include/libssh2.h |   93 +++++++
 src/agent.c       |  750 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 845 insertions(+), 1 deletions(-)
 create mode 100644 src/agent.c

diff --git a/Makefile.inc b/Makefile.inc
index 53e7a51..47a6b7a 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -1,5 +1,5 @@
 CSOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \
  packet.c publickey.c scp.c session.c sftp.c userauth.c transport.c \
- version.c knownhost.c openssl.c libgcrypt.c pem.c
+ version.c knownhost.c agent.c openssl.c libgcrypt.c pem.c
 
 HHEADERS = libssh2_priv.h openssl.h libgcrypt.h transport.h channel.h comp.h mac.h misc.h
diff --git a/configure.ac b/configure.ac
index 44fbe99..a7a7cbd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -206,6 +206,7 @@ AC_HELP_STRING([--disable-hidden-symbols],[Leave all symbols with default visibi
 AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h sys/uio.h])
 AC_CHECK_HEADERS([sys/select.h sys/socket.h sys/ioctl.h sys/time.h])
 AC_CHECK_HEADERS([arpa/inet.h netinet/in.h])
+AC_CHECK_HEADERS([sys/un.h])
 
 case $host in
   *-*-cygwin* | *-*-cegcc*)
diff --git a/include/libssh2.h b/include/libssh2.h
index f771eca..9be60f4 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -247,6 +247,7 @@ typedef struct _LIBSSH2_SESSION                     LIBSSH2_SESSION;
 typedef struct _LIBSSH2_CHANNEL                     LIBSSH2_CHANNEL;
 typedef struct _LIBSSH2_LISTENER                    LIBSSH2_LISTENER;
 typedef struct _LIBSSH2_KNOWNHOSTS                  LIBSSH2_KNOWNHOSTS;
+typedef struct _LIBSSH2_AGENT                       LIBSSH2_AGENT;
 
 typedef struct _LIBSSH2_POLLFD {
     unsigned char type; /* LIBSSH2_POLLFD_* below */
@@ -364,6 +365,7 @@ typedef struct _LIBSSH2_POLLFD {
 #define LIBSSH2_ERROR_BAD_USE                   -39
 #define LIBSSH2_ERROR_COMPRESS                  -40
 #define LIBSSH2_ERROR_OUT_OF_BOUNDARY           -41
+#define LIBSSH2_ERROR_AGENT_PROTOCOL            -42
 
 /* Session API */
 LIBSSH2_API LIBSSH2_SESSION *
@@ -883,6 +885,97 @@ libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts,
                       struct libssh2_knownhost **store,
                       struct libssh2_knownhost *prev);
 
+#define HAVE_LIBSSH2_AGENT_API 0x010202 /* since 1.2.2 */
+
+struct libssh2_agent_publickey {
+    unsigned int magic;              /* magic stored by the library */
+    void *node;	    /* handle to the internal representation of key */
+    unsigned char *blob;           /* public key blob */
+    size_t blob_len;               /* length of the public key blob */
+    char *comment;                 /* comment in printable format */
+};
+
+/*
+ * libssh2_agent_init
+ *
+ * Init an ssh-agent handle. Returns the pointer to the handle.
+ *
+ */
+LIBSSH2_API LIBSSH2_AGENT *
+libssh2_agent_init(LIBSSH2_SESSION *session);
+
+/*
+ * libssh2_agent_connect()
+ *
+ * Connect to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_connect(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_list_identities()
+ *
+ * Request ssh-agent to list identities.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_list_identities(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_get_identity()
+ *
+ * Traverse the internal list of public keys. Pass NULL to 'prev' to get
+ * the first one. Or pass a poiner to the previously returned one to get the
+ * next.
+ *
+ * Returns:
+ * 0 if a fine public key was stored in 'store'
+ * 1 if end of public keys
+ * [negative] on errors
+ */
+LIBSSH2_API int
+libssh2_agent_get_identity(LIBSSH2_AGENT *agent,
+			   struct libssh2_agent_publickey **store,
+			   struct libssh2_agent_publickey *prev);
+
+/*
+ * libssh2_agent_userauth()
+ *
+ * Do publickey user authentication with the help of ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_userauth(LIBSSH2_AGENT *agent,
+		       const char *username,
+		       struct libssh2_agent_publickey *identity);
+
+/*
+ * libssh2_agent_disconnect()
+ *
+ * Close a connection to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_disconnect(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_free()
+ *
+ * Free a connection to an ssh-agent.  This function also frees the
+ * internal list of public keys.
+ */
+LIBSSH2_API int
+libssh2_agent_free(LIBSSH2_AGENT *agent);
+
 /* NOTE NOTE NOTE
    libssh2_trace() has no function in builds that aren't built with debug
    enabled
diff --git a/src/agent.c b/src/agent.c
new file mode 100644
index 0000000..5a7b8fb
--- /dev/null
+++ b/src/agent.c
@@ -0,0 +1,750 @@
+/*
+ * Copyright (c) 2009 by Daiki Ueno
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ *   Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution.
+ *
+ *   Neither the name of the copyright holder nor the names
+ *   of any other contributors may be used to endorse or
+ *   promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#include "libssh2_priv.h"
+#include "misc.h"
+#include <errno.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#else
+/* Use the existence of sys/un.h as a test if Unix domain socket is
+   supported.  winsock*.h define PF_UNIX/AF_UNIX but do not actually
+   support them. */
+#undef PF_UNIX
+#endif
+
+/* Requests from client to agent for protocol 1 key operations */
+#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1
+#define SSH_AGENTC_RSA_CHALLENGE 3
+#define SSH_AGENTC_ADD_RSA_IDENTITY 7
+#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8
+#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9
+#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24
+
+/* Requests from client to agent for protocol 2 key operations */
+#define SSH2_AGENTC_REQUEST_IDENTITIES 11
+#define SSH2_AGENTC_SIGN_REQUEST 13
+#define SSH2_AGENTC_ADD_IDENTITY 17
+#define SSH2_AGENTC_REMOVE_IDENTITY 18
+#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
+#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25
+
+/* Key-type independent requests from client to agent */
+#define SSH_AGENTC_ADD_SMARTCARD_KEY 20
+#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21
+#define SSH_AGENTC_LOCK 22
+#define SSH_AGENTC_UNLOCK 23
+#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26
+
+/* Generic replies from agent to client */
+#define SSH_AGENT_FAILURE 5
+#define SSH_AGENT_SUCCESS 6
+
+/* Replies from agent to client for protocol 1 key operations */
+#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2
+#define SSH_AGENT_RSA_RESPONSE 4
+
+/* Replies from agent to client for protocol 2 key operations */
+#define SSH2_AGENT_IDENTITIES_ANSWER 12
+#define SSH2_AGENT_SIGN_RESPONSE 14
+
+/* Key constraint identifiers */
+#define SSH_AGENT_CONSTRAIN_LIFETIME 1
+#define SSH_AGENT_CONSTRAIN_CONFIRM 2
+
+/* non-blocking mode on agent connection is not yet implemented, but
+   for future use. */
+typedef enum {
+    agent_NB_state_init = 0,
+    agent_NB_state_request_created,
+    agent_NB_state_request_length_sent,
+    agent_NB_state_request_sent,
+    agent_NB_state_response_length_received,
+    agent_NB_state_response_received
+} agent_nonblocking_states;
+
+typedef struct agent_transaction_ctx {
+    unsigned char *request;
+    size_t request_len;
+    unsigned char *response;
+    size_t response_len;
+    agent_nonblocking_states state;
+} *agent_transaction_ctx_t;
+
+typedef int (*agent_connect_func)(LIBSSH2_AGENT *agent);
+typedef int (*agent_transact_func)(LIBSSH2_AGENT *agent,
+                                   agent_transaction_ctx_t transctx);
+typedef int (*agent_disconnect_func)(LIBSSH2_AGENT *agent);
+
+struct agent_publickey {
+    struct list_node node;
+
+    /* this is the struct we expose externally */
+    struct libssh2_agent_publickey external;
+};
+
+struct agent_ops {
+    agent_connect_func connect;
+    agent_transact_func transact;
+    agent_disconnect_func disconnect;
+};
+
+struct _LIBSSH2_AGENT
+{
+    LIBSSH2_SESSION *session;  /* the session this "belongs to" */
+
+    int fd;
+
+    struct agent_ops *ops;
+
+    struct agent_transaction_ctx transctx;
+    struct agent_publickey *identity;
+    struct list_head head;              /* list of public keys */
+};
+
+#ifdef PF_UNIX
+static int
+agent_connect_unix(LIBSSH2_AGENT *agent)
+{
+    const char *path;
+    struct sockaddr_un sun;
+
+    path = getenv("SSH_AUTH_SOCK");
+    if (!path) {
+        return -1;
+    }
+
+    agent->fd = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (agent->fd < -1) {
+        return -1;
+    }
+
+    sun.sun_family = AF_UNIX;
+    strncpy (sun.sun_path, path, sizeof sun.sun_path);
+    if (connect(agent->fd, (struct sockaddr*)(&sun), sizeof sun) != 0) {
+        close (agent->fd);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+agent_transact_unix(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx)
+{
+    unsigned char buf[4], *s;
+    int rc;
+
+    /* Send the length of the request */
+    if (transctx->state == agent_NB_state_request_created) {
+        _libssh2_htonu32(buf, transctx->request_len);
+        rc = send(agent->fd, buf, sizeof buf, 0);
+        if (rc < 0) {
+            if (errno == EAGAIN)
+                return LIBSSH2_ERROR_EAGAIN;
+            return -1;
+        }
+        transctx->state = agent_NB_state_request_length_sent;
+    }
+
+    /* Send the request body */
+    if (transctx->state == agent_NB_state_request_length_sent) {
+        rc = send(agent->fd, transctx->request,
+                  transctx->request_len, 0);
+        if (rc < 0) {
+            if (errno == EAGAIN)
+                return LIBSSH2_ERROR_EAGAIN;
+            return -1;
+        }
+        transctx->state = agent_NB_state_request_sent;
+    }
+
+    /* Receive the length of a response */
+    if (transctx->state == agent_NB_state_request_sent) {
+        rc = recv(agent->fd, buf, sizeof buf, 0);
+        if (rc < 0) {
+            if (errno == EAGAIN)
+                return LIBSSH2_ERROR_EAGAIN;
+            return -1;
+        }
+        transctx->response_len = _libssh2_ntohu32(buf);
+        s = transctx->response = LIBSSH2_ALLOC(agent->session,
+                                                  transctx->response_len);
+        if (!transctx->response) {
+            return LIBSSH2_ERROR_ALLOC;
+        }
+        transctx->state = agent_NB_state_response_length_received;
+    }
+
+    /* Receive the response body */
+    if (transctx->state == agent_NB_state_response_length_received) {
+        rc = recv(agent->fd, transctx->response, transctx->response_len, 0);
+        if (rc < 0) {
+            if (errno == EAGAIN)
+                return LIBSSH2_ERROR_EAGAIN;
+            return -1;
+        }
+        transctx->state = agent_NB_state_response_received;
+    }
+
+    return 0;
+}
+
+static int
+agent_disconnect_unix(LIBSSH2_AGENT *agent)
+{
+    return close(agent->fd);
+}
+
+struct agent_ops agent_ops_unix = {
+    agent_connect_unix,
+    agent_transact_unix,
+    agent_disconnect_unix
+};
+#endif  /* PF_UNIX */
+
+#ifdef WIN32
+/* Code to talk to Pageant was taken from PuTTY.
+ *
+ * Portions copyright Robert de Bath, Joris van Rantwijk, Delian
+ * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas
+ * Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa,
+ * Markus Kuhn, Colin Watson, and CORE SDI S.A.
+ */
+#define PAGEANT_COPYDATA_ID 0x804e50ba   /* random goop */
+#define PAGEANT_MAX_MSGLEN  8192
+
+static int
+agent_connect_pageant(LIBSSH2_AGENT *agent)
+{
+    HWND hwnd;
+    hwnd = FindWindow("Pageant", "Pageant");
+    if (!hwnd)
+	return -1;
+    return 0;
+}
+
+static int
+agent_transact_pageant(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx)
+{
+    HWND hwnd;
+    char mapname[23];
+    HANDLE filemap;
+    unsigned char *p;
+    int id;
+    COPYDATASTRUCT cds;
+
+    if (!transctx || 4 + transctx->request_len > PAGEANT_MAX_MSGLEN) {
+        return LIBSSH2_ERROR_INVAL;
+    }
+    hwnd = FindWindow("Pageant", "Pageant");
+    if (!hwnd) {
+	return -1;
+    }
+    sprintf(mapname, "PageantRequest%08x", (unsigned)GetCurrentThreadId());
+    filemap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
+				0, PAGEANT_MAX_MSGLEN, mapname);
+    if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) {
+	return -1;
+    }
+    p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+    _libssh2_htonu32(p, transctx->request_len);
+    memcpy(p + 4, transctx->request, transctx->request_len);
+    cds.dwData = PAGEANT_COPYDATA_ID;
+    cds.cbData = 1 + strlen(mapname);
+    cds.lpData = mapname;
+
+    id = SendMessage(hwnd, WM_COPYDATA, (WPARAM) NULL, (LPARAM) &cds);
+    if (id > 0) {
+	transctx->response_len = _libssh2_ntohu32(p);
+        if (transctx->response_len > PAGEANT_MAX_MSGLEN) {
+            UnmapViewOfFile(p);
+            CloseHandle(filemap);
+            return LIBSSH2_ERROR_AGENT_PROTOCOL;
+        }
+	transctx->response = LIBSSH2_ALLOC(agent->session,
+                                           transctx->response_len);
+	if (!transctx->response) {
+            UnmapViewOfFile(p);
+            CloseHandle(filemap);
+            return LIBSSH2_ERROR_ALLOC;
+        }
+        memcpy(transctx->response, p + 4, transctx->response_len);
+    }
+
+    UnmapViewOfFile(p);
+    CloseHandle(filemap);
+    return 0;
+}
+
+static int
+agent_disconnect_pageant(LIBSSH2_AGENT *agent)
+{
+    (void)agent;
+    return 0;
+}
+
+struct agent_ops agent_ops_pageant = {
+    agent_connect_pageant,
+    agent_transact_pageant,
+    agent_disconnect_pageant
+};
+#endif  /* WIN32 */
+
+static struct {
+    const char *name;
+    struct agent_ops *ops;
+} supported_backends[] = {
+#ifdef WIN32
+    {"Pageant", &agent_ops_pageant},
+#endif  /* WIN32 */
+#ifdef PF_UNIX
+    {"Unix", &agent_ops_unix},
+#endif  /* PF_UNIX */
+    {NULL}
+};
+
+static int
+agent_sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
+           const unsigned char *data, size_t data_len, void **abstract)
+{
+    LIBSSH2_AGENT *agent = (LIBSSH2_AGENT *) (*abstract);
+    agent_transaction_ctx_t transctx = &agent->transctx;
+    struct agent_publickey *identity = agent->identity;
+    ssize_t len = 1 + 4 + identity->external.blob_len + 4 + data_len + 4;
+    ssize_t method_len;
+    unsigned char *s;
+    int rc;
+
+    /* Create a request to sign the data */
+    if (transctx->state == agent_NB_state_init) {
+        s = transctx->request = LIBSSH2_ALLOC(session, len);
+        if (!transctx->request) {
+            return LIBSSH2_ERROR_ALLOC;
+        }
+
+        *s++ = SSH2_AGENTC_SIGN_REQUEST;
+        /* key blob */
+        _libssh2_htonu32(s, identity->external.blob_len);
+        s += 4;
+         memcpy(s, identity->external.blob, identity->external.blob_len);
+        s += identity->external.blob_len;
+        /* data */
+        _libssh2_htonu32(s, data_len);
+        s += 4;
+        memcpy(s, data, data_len);
+        s += data_len;
+        /* flags */
+        _libssh2_htonu32(s, 0);
+        s += 4;
+        transctx->request_len = s - transctx->request;
+        transctx->state = agent_NB_state_request_created;
+    }
+
+    /* Make sure to be re-called as a result of EAGAIN. */
+    if (*transctx->request != SSH2_AGENTC_SIGN_REQUEST) {
+        return LIBSSH2_ERROR_BAD_USE;
+    }
+
+    rc = agent->ops->transact(agent, transctx);
+    if (rc) {
+        goto error;
+    }
+    LIBSSH2_FREE(session, transctx->request);
+    transctx->request = NULL;
+
+    len = transctx->response_len;
+    s = transctx->response;
+    len--;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    if (*s != SSH2_AGENT_SIGN_RESPONSE) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    s++;
+
+    /* Skip the entire length of the signature */
+    len -= 4;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    s += 4;
+
+    /* Skip signing method */
+    len -= 4;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    method_len = _libssh2_ntohu32(s);
+    s += 4;
+    len -= method_len;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    s += method_len;
+
+    /* Read the signature */
+    len -= 4;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    *sig_len = _libssh2_ntohu32(s);
+    s += 4;
+    len -= *sig_len;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+
+    *sig = LIBSSH2_ALLOC(session, *sig_len);
+    if (!*sig) {
+        rc = LIBSSH2_ERROR_ALLOC;
+        goto error;
+    }
+    memcpy(*sig, s, *sig_len);
+
+  error:
+    LIBSSH2_FREE(session, transctx->request);
+    transctx->request = NULL;
+
+    LIBSSH2_FREE(session, transctx->response);
+    transctx->response = NULL;
+
+    return rc;
+}
+
+static int
+agent_list_identities(LIBSSH2_AGENT *agent)
+{
+    agent_transaction_ctx_t transctx = &agent->transctx;
+    ssize_t len, num_identities;
+    unsigned char *s;
+    int rc;
+
+    /* Create a request to list identities */
+    if (transctx->state == agent_NB_state_init) {
+      unsigned char c = SSH2_AGENTC_REQUEST_IDENTITIES;
+      transctx->request = &c;
+      transctx->request_len = 1;
+      transctx->state = agent_NB_state_request_created;
+    }
+
+    /* Make sure to be re-called as a result of EAGAIN. */
+    if (*transctx->request != SSH2_AGENTC_REQUEST_IDENTITIES) {
+        return LIBSSH2_ERROR_BAD_USE;
+    }
+
+    rc = agent->ops->transact(agent, transctx);
+    if (rc) {
+        goto error;
+    }
+    transctx->request = NULL;
+
+    len = transctx->response_len;
+    s = transctx->response;
+    len--;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    if (*s != SSH2_AGENT_IDENTITIES_ANSWER) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    s++;
+
+    /* Read the length of identities */
+    len -= 4;
+    if (len < 0) {
+        rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+        goto error;
+    }
+    num_identities = _libssh2_ntohu32(s);
+    s += 4;
+
+    while (num_identities--) {
+        struct agent_publickey *identity;
+        ssize_t comment_len;
+
+        identity = LIBSSH2_ALLOC(agent->session, sizeof *identity);
+        if (!identity) {
+            rc = LIBSSH2_ERROR_ALLOC;
+            goto error;
+        }
+
+        /* Read the length of the blob */
+        len -= 4;
+        if (len < 0) {
+            rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+            goto error;
+        }
+        identity->external.blob_len = _libssh2_ntohu32(s);
+        s += 4;
+
+        /* Read the blob */
+        len -= identity->external.blob_len;
+        if (len < 0) {
+            rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+            goto error;
+        }
+        identity->external.blob = LIBSSH2_ALLOC(agent->session,
+                                                identity->external.blob_len);
+        if (!identity->external.blob) {
+            rc = LIBSSH2_ERROR_ALLOC;
+            goto error;
+        }
+        memcpy(identity->external.blob, s, identity->external.blob_len);
+        s += identity->external.blob_len;
+
+        /* Read the length of the comment */
+        len -= 4;
+        if (len < 0) {
+            rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+            goto error;
+        }
+        comment_len = _libssh2_ntohu32(s);
+        s += 4;
+
+        /* Read the comment */
+        len -= comment_len;
+        if (len < 0) {
+            rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+            goto error;
+        }
+        identity->external.comment = LIBSSH2_ALLOC(agent->session,
+                                                   comment_len + 1);
+        if (!identity->external.comment) {
+            rc = LIBSSH2_ERROR_ALLOC;
+            goto error;
+        }
+        identity->external.comment[comment_len] = '\0';
+        memcpy(identity->external.comment, s, comment_len);
+        s += comment_len;
+
+        _libssh2_list_add(&agent->head, &identity->node);
+    }
+ error:
+    LIBSSH2_FREE(agent->session, transctx->response);
+    transctx->response = NULL;
+
+    return rc;
+}
+
+#define AGENT_PUBLICKEY_MAGIC 0x3bdefed2
+/*
+ * agent_publickey_to_external()
+ *
+ * Copies data from the internal to the external representation struct.
+ *
+ */
+static struct libssh2_agent_publickey *
+agent_publickey_to_external(struct agent_publickey *node)
+{
+    struct libssh2_agent_publickey *ext = &node->external;
+
+    ext->magic = AGENT_PUBLICKEY_MAGIC;
+    ext->node = node;
+
+    return ext;
+}
+
+/*
+ * libssh2_agent_init
+ *
+ * Init an ssh-agent handle. Returns the pointer to the handle.
+ *
+ */
+LIBSSH2_API LIBSSH2_AGENT *
+libssh2_agent_init(LIBSSH2_SESSION *session)
+{
+    LIBSSH2_AGENT *agent;
+
+    agent = LIBSSH2_ALLOC(session, sizeof *agent);
+    if (!agent) {
+        libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+                      "Unable to allocate space for agent connection", 0);
+        return NULL;
+    }
+    memset(agent, 0, sizeof *agent);
+    agent->session = session;
+
+    return agent;
+}
+
+/*
+ * libssh2_agent_connect()
+ *
+ * Connect to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_connect(LIBSSH2_AGENT *agent)
+{
+    int i, rc = -1;
+    for (i = 0; supported_backends[i].name; i++) {
+        agent->ops = supported_backends[i].ops;
+        rc = agent->ops->connect(agent);
+        if (!rc)
+            return 0;
+    }
+    return rc;
+}
+
+/*
+ * libssh2_agent_list_identities()
+ *
+ * Request ssh-agent to list identities.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_list_identities(LIBSSH2_AGENT *agent)
+{
+    memset(&agent->transctx, 0, sizeof agent->transctx);
+    return agent_list_identities(agent);
+}
+
+/*
+ * libssh2_agent_get_identity()
+ *
+ * Traverse the internal list of public keys. Pass NULL to 'prev' to get
+ * the first one. Or pass a poiner to the previously returned one to get the
+ * next.
+ *
+ * Returns:
+ * 0 if a fine public key was stored in 'store'
+ * 1 if end of public keys
+ * [negative] on errors
+ */
+LIBSSH2_API int
+libssh2_agent_get_identity(LIBSSH2_AGENT *agent,
+                           struct libssh2_agent_publickey **ext,
+                           struct libssh2_agent_publickey *oprev)
+{
+    struct agent_publickey *node;
+    if (oprev && oprev->node) {
+        /* we have a starting point */
+        struct agent_publickey *prev = oprev->node;
+
+        /* get the next node in the list */
+        node = _libssh2_list_next(&prev->node);
+    }
+    else
+        node = _libssh2_list_first(&agent->head);
+
+    if (!node)
+        /* no (more) node */
+        return 1;
+
+    *ext = agent_publickey_to_external(node);
+
+    return 0;
+}
+
+/*
+ * libssh2_agent_userauth()
+ *
+ * Do publickey user authentication with the help of ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_userauth(LIBSSH2_AGENT *agent,
+                       const char *username,
+                       struct libssh2_agent_publickey *identity)
+{
+    void *abstract = agent;
+
+    if (agent->session->userauth_pblc_state == libssh2_NB_state_idle) {
+        memset(&agent->transctx, 0, sizeof agent->transctx);
+        agent->identity = identity->node;
+    }
+    return libssh2_userauth_publickey(agent->session, username,
+				      identity->blob,
+				      identity->blob_len,
+				      agent_sign,
+				      &abstract);
+}
+
+/*
+ * libssh2_agent_disconnect()
+ *
+ * Close a connection to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_disconnect(LIBSSH2_AGENT *agent)
+{
+    if (agent->ops)
+        return agent->ops->disconnect(agent);
+    return 0;
+}
+
+/*
+ * libssh2_agent_free()
+ *
+ * Free a connection to an ssh-agent.  This function also frees the
+ * internal list of public keys.
+ */
+LIBSSH2_API int
+libssh2_agent_free(LIBSSH2_AGENT *agent) {
+    struct agent_publickey *node;
+    struct agent_publickey *next;
+
+    for (node = _libssh2_list_first(&agent->head); node; node = next) {
+        next = _libssh2_list_next(&node->node);
+        LIBSSH2_FREE(agent->session, node->external.blob);
+        LIBSSH2_FREE(agent->session, node->external.comment);
+        LIBSSH2_FREE(agent->session, node);
+    }
+    LIBSSH2_FREE(agent->session, agent);
+    return 0;
+}
-- 
1.6.5.4

