| /* |
| * 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); |
| } |