summaryrefslogtreecommitdiff
path: root/libs/libssh2/src/agent.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libssh2/src/agent.c')
-rw-r--r--libs/libssh2/src/agent.c811
1 files changed, 811 insertions, 0 deletions
diff --git a/libs/libssh2/src/agent.c b/libs/libssh2/src/agent.c
new file mode 100644
index 0000000000..c2ba422b65
--- /dev/null
+++ b/libs/libssh2/src/agent.c
@@ -0,0 +1,811 @@
+/*
+ * Copyright (c) 2009 by Daiki Ueno
+ * Copyright (C) 2010-2014 by Daniel Stenberg
+ * 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
+#include "userauth.h"
+#include "session.h"
+
+/* 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" */
+
+ libssh2_socket_t 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 s_un;
+
+ path = getenv("SSH_AUTH_SOCK");
+ if (!path)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_USE,
+ "no auth sock variable");
+
+ agent->fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (agent->fd < 0)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_SOCKET,
+ "failed creating socket");
+
+ s_un.sun_family = AF_UNIX;
+ strncpy (s_un.sun_path, path, sizeof s_un.sun_path);
+ s_un.sun_path[sizeof(s_un.sun_path)-1]=0; /* make sure there's a trailing
+ zero */
+ if (connect(agent->fd, (struct sockaddr*)(&s_un), sizeof s_un) != 0) {
+ close (agent->fd);
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "failed connecting with agent");
+ }
+
+ return LIBSSH2_ERROR_NONE;
+}
+
+static int
+agent_transact_unix(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx)
+{
+ unsigned char buf[4];
+ int rc;
+
+ /* Send the length of the request */
+ if (transctx->state == agent_NB_state_request_created) {
+ _libssh2_htonu32(buf, transctx->request_len);
+ rc = LIBSSH2_SEND_FD(agent->session, agent->fd, buf, sizeof buf, 0);
+ if (rc == -EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ else if (rc < 0)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND,
+ "agent send failed");
+ transctx->state = agent_NB_state_request_length_sent;
+ }
+
+ /* Send the request body */
+ if (transctx->state == agent_NB_state_request_length_sent) {
+ rc = LIBSSH2_SEND_FD(agent->session, agent->fd, transctx->request,
+ transctx->request_len, 0);
+ if (rc == -EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ else if (rc < 0)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND,
+ "agent send failed");
+ transctx->state = agent_NB_state_request_sent;
+ }
+
+ /* Receive the length of a response */
+ if (transctx->state == agent_NB_state_request_sent) {
+ rc = LIBSSH2_RECV_FD(agent->session, agent->fd, buf, sizeof buf, 0);
+ if (rc < 0) {
+ if (rc == -EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_RECV,
+ "agent recv failed");
+ }
+ transctx->response_len = _libssh2_ntohu32(buf);
+ 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 = LIBSSH2_RECV_FD(agent->session, agent->fd, transctx->response,
+ transctx->response_len, 0);
+ if (rc < 0) {
+ if (rc == -EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_SEND,
+ "agent recv failed");
+ }
+ transctx->state = agent_NB_state_response_received;
+ }
+
+ return 0;
+}
+
+static int
+agent_disconnect_unix(LIBSSH2_AGENT *agent)
+{
+ int ret;
+ ret = close(agent->fd);
+ if(ret != -1)
+ agent->fd = LIBSSH2_INVALID_SOCKET;
+ else
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_DISCONNECT,
+ "failed closing the agent socket");
+ return LIBSSH2_ERROR_NONE;
+}
+
+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 _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "failed connecting agent");
+ agent->fd = 0; /* Mark as the connection has been established */
+ return LIBSSH2_ERROR_NONE;
+}
+
+static int
+agent_transact_pageant(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx)
+{
+ HWND hwnd;
+ char mapname[23];
+ HANDLE filemap;
+ unsigned char *p;
+ unsigned char *p2;
+ int id;
+ COPYDATASTRUCT cds;
+
+ if (!transctx || 4 + transctx->request_len > PAGEANT_MAX_MSGLEN)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_INVAL,
+ "illegal input");
+
+ hwnd = FindWindow("Pageant", "Pageant");
+ if (!hwnd)
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "found no pageant");
+
+ 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 _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "failed setting up pageant filemap");
+
+ p2 = p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0);
+ if (p == NULL || p2 == NULL) {
+ CloseHandle(filemap);
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "failed to open pageant filemap for writing");
+ }
+
+ _libssh2_store_str(&p2, (const char *)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->session, LIBSSH2_ERROR_AGENT_PROTOCOL,
+ "agent setup fail");
+ }
+ transctx->response = LIBSSH2_ALLOC(agent->session,
+ transctx->response_len);
+ if (!transctx->response) {
+ UnmapViewOfFile(p);
+ CloseHandle(filemap);
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_ALLOC,
+ "agent malloc");
+ }
+ memcpy(transctx->response, p + 4, transctx->response_len);
+ }
+
+ UnmapViewOfFile(p);
+ CloseHandle(filemap);
+ return 0;
+}
+
+static int
+agent_disconnect_pageant(LIBSSH2_AGENT *agent)
+{
+ agent->fd = LIBSSH2_INVALID_SOCKET;
+ 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, 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(session, LIBSSH2_ERROR_ALLOC,
+ "out of memory");
+
+ *s++ = SSH2_AGENTC_SIGN_REQUEST;
+ /* key blob */
+ _libssh2_store_str(&s, (const char *)identity->external.blob,
+ identity->external.blob_len);
+ /* data */
+ _libssh2_store_str(&s, (const char *)data, data_len);
+
+ /* flags */
+ _libssh2_store_u32(&s, 0);
+
+ 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(session, LIBSSH2_ERROR_BAD_USE,
+ "illegal request");
+
+ if (!agent->ops)
+ /* if no agent has been connected, bail out */
+ return _libssh2_error(session, LIBSSH2_ERROR_BAD_USE,
+ "agent not connected");
+
+ 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 _libssh2_error(session, rc, "agent sign failure");
+}
+
+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;
+ unsigned char c = SSH2_AGENTC_REQUEST_IDENTITIES;
+
+ /* Create a request to list identities */
+ if (transctx->state == agent_NB_state_init) {
+ 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(agent->session, LIBSSH2_ERROR_BAD_USE,
+ "illegal agent request");
+
+ if (!agent->ops)
+ /* if no agent has been connected, bail out */
+ return _libssh2_error(agent->session, LIBSSH2_ERROR_BAD_USE,
+ "agent not connected");
+
+ 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;
+
+ /* Read the length of the blob */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ identity = LIBSSH2_ALLOC(agent->session, sizeof *identity);
+ if (!identity) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ 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;
+ LIBSSH2_FREE(agent->session, identity);
+ goto error;
+ }
+
+ identity->external.blob = LIBSSH2_ALLOC(agent->session,
+ identity->external.blob_len);
+ if (!identity->external.blob) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ LIBSSH2_FREE(agent->session, identity);
+ 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;
+ LIBSSH2_FREE(agent->session, identity->external.blob);
+ LIBSSH2_FREE(agent->session, identity);
+ goto error;
+ }
+ comment_len = _libssh2_ntohu32(s);
+ s += 4;
+
+ /* Read the comment */
+ len -= comment_len;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ LIBSSH2_FREE(agent->session, identity->external.blob);
+ LIBSSH2_FREE(agent->session, identity);
+ goto error;
+ }
+
+ identity->external.comment = LIBSSH2_ALLOC(agent->session,
+ comment_len + 1);
+ if (!identity->external.comment) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ LIBSSH2_FREE(agent->session, identity->external.blob);
+ LIBSSH2_FREE(agent->session, identity);
+ 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 _libssh2_error(agent->session, rc,
+ "agent list id failed");
+}
+
+static void
+agent_free_identities(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_list_init(&agent->head);
+}
+
+#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_CALLOC(session, sizeof *agent);
+ if (!agent) {
+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate space for agent connection");
+ return NULL;
+ }
+ agent->fd = LIBSSH2_INVALID_SOCKET;
+ agent->session = session;
+ _libssh2_list_init(&agent->head);
+
+ return agent;
+}
+
+/*
+ * libssh2_agent_connect()
+ *
+ * Connect to an ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+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);
+ /* Abondon the last fetched identities */
+ agent_free_identities(agent);
+ 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 pointer 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;
+ int rc;
+
+ if (agent->session->userauth_pblc_state == libssh2_NB_state_idle) {
+ memset(&agent->transctx, 0, sizeof agent->transctx);
+ agent->identity = identity->node;
+ }
+
+ BLOCK_ADJUST(rc, agent->session,
+ _libssh2_userauth_publickey(agent->session, username,
+ strlen(username),
+ identity->blob,
+ identity->blob_len,
+ agent_sign,
+ &abstract));
+ return rc;
+}
+
+/*
+ * libssh2_agent_disconnect()
+ *
+ * Close a connection to an ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_disconnect(LIBSSH2_AGENT *agent)
+{
+ if (agent->ops && agent->fd != LIBSSH2_INVALID_SOCKET)
+ return agent->ops->disconnect(agent);
+ return 0;
+}
+
+/*
+ * libssh2_agent_free()
+ *
+ * Free an ssh-agent handle. This function also frees the internal
+ * collection of public keys.
+ */
+LIBSSH2_API void
+libssh2_agent_free(LIBSSH2_AGENT *agent) {
+ /* Allow connection freeing when the socket has lost its connection */
+ if (agent->fd != LIBSSH2_INVALID_SOCKET) {
+ libssh2_agent_disconnect(agent);
+ }
+ agent_free_identities(agent);
+ LIBSSH2_FREE(agent->session, agent);
+}