/* | |
* Copyright (c) 2009 by Daiki Ueno | |
* Copyright (C) 2010-2021 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 "agent.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" | |
#ifdef WIN32 | |
#include <stdlib.h> | |
#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 | |
/* Signature request methods */ | |
#define SSH_AGENT_RSA_SHA2_256 2 | |
#define SSH_AGENT_RSA_SHA2_512 4 | |
#ifdef PF_UNIX | |
static int | |
agent_connect_unix(LIBSSH2_AGENT *agent) | |
{ | |
const char *path; | |
struct sockaddr_un s_un; | |
path = agent->identity_agent_path; | |
if(!path) { | |
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; | |
} | |
#define RECV_SEND_ALL(func, socket, buffer, length, flags, abstract) \ | |
int rc; \ | |
size_t finished = 0; \ | |
\ | |
while(finished < length) { \ | |
rc = func(socket, \ | |
(char *)buffer + finished, length - finished, \ | |
flags, abstract); \ | |
if(rc < 0) \ | |
return rc; \ | |
\ | |
finished += rc; \ | |
} \ | |
\ | |
return finished; | |
static ssize_t _send_all(LIBSSH2_SEND_FUNC(func), libssh2_socket_t socket, | |
const void *buffer, size_t length, | |
int flags, void **abstract) | |
{ | |
RECV_SEND_ALL(func, socket, buffer, length, flags, abstract); | |
} | |
static ssize_t _recv_all(LIBSSH2_RECV_FUNC(func), libssh2_socket_t socket, | |
void *buffer, size_t length, | |
int flags, void **abstract) | |
{ | |
RECV_SEND_ALL(func, socket, buffer, length, flags, abstract); | |
} | |
#undef RECV_SEND_ALL | |
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 = _send_all(agent->session->send, agent->fd, | |
buf, sizeof buf, 0, &agent->session->abstract); | |
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 = _send_all(agent->session->send, agent->fd, transctx->request, | |
transctx->request_len, 0, &agent->session->abstract); | |
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 = _recv_all(agent->session->recv, agent->fd, | |
buf, sizeof buf, 0, &agent->session->abstract); | |
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 = _recv_all(agent->session->recv, agent->fd, transctx->response, | |
transctx->response_len, 0, &agent->session->abstract); | |
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 = FindWindowA("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 = FindWindowA("Pageant", "Pageant"); | |
if(!hwnd) | |
return _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, | |
"found no pageant"); | |
snprintf(mapname, sizeof(mapname), | |
"PageantRequest%08x%c", (unsigned)GetCurrentThreadId(), '\0'); | |
filemap = CreateFileMappingA(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}, | |
{"OpenSSH", &agent_ops_openssh}, | |
#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; | |
unsigned char *method_name = NULL; | |
uint32_t sign_flags = 0; | |
/* 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 */ | |
if(session->userauth_pblc_method_len > 0 && | |
session->userauth_pblc_method) { | |
if(session->userauth_pblc_method_len == 12 && | |
!memcmp(session->userauth_pblc_method, "rsa-sha2-512", 12)) { | |
sign_flags = SSH_AGENT_RSA_SHA2_512; | |
} | |
else if(session->userauth_pblc_method_len == 12 && | |
!memcmp(session->userauth_pblc_method, "rsa-sha2-256", 12)) { | |
sign_flags = SSH_AGENT_RSA_SHA2_256; | |
} | |
} | |
_libssh2_store_u32(&s, sign_flags); | |
transctx->request_len = s - transctx->request; | |
transctx->send_recv_total = 0; | |
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; | |
} | |
/* method name */ | |
method_name = LIBSSH2_ALLOC(session, method_len); | |
if(!method_name) { | |
rc = LIBSSH2_ERROR_ALLOC; | |
goto error; | |
} | |
memcpy(method_name, s, method_len); | |
s += method_len; | |
/* check to see if we match requested */ | |
if((size_t)method_len != session->userauth_pblc_method_len || | |
memcmp(method_name, session->userauth_pblc_method, method_len)) { | |
_libssh2_debug(session, | |
LIBSSH2_TRACE_KEX, | |
"Agent sign method %.*s", | |
method_len, method_name); | |
rc = LIBSSH2_ERROR_ALGO_UNSUPPORTED; | |
goto error; | |
} | |
/* 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: | |
if(method_name) | |
LIBSSH2_FREE(session, method_name); | |
LIBSSH2_FREE(session, transctx->request); | |
transctx->request = NULL; | |
LIBSSH2_FREE(session, transctx->response); | |
transctx->response = NULL; | |
transctx->state = agent_NB_state_init; | |
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->send_recv_total = 0; | |
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) { | |
LIBSSH2_FREE(agent->session, transctx->response); | |
transctx->response = NULL; | |
return rc; | |
} | |
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; | |
size_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; | |
if(comment_len > (size_t)len) { | |
rc = LIBSSH2_ERROR_AGENT_PROTOCOL; | |
LIBSSH2_FREE(agent->session, identity->external.blob); | |
LIBSSH2_FREE(agent->session, identity); | |
goto error; | |
} | |
/* Read the comment */ | |
len -= comment_len; | |
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; | |
agent->identity_agent_path = NULL; | |
_libssh2_list_init(&agent->head); | |
#ifdef WIN32 | |
agent->pipe = INVALID_HANDLE_VALUE; | |
memset(&agent->overlapped, 0, sizeof(OVERLAPPED)); | |
agent->pending_io = FALSE; | |
#endif | |
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); | |
/* Abandon 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); | |
} | |
if(agent->identity_agent_path != NULL) | |
LIBSSH2_FREE(agent->session, agent->identity_agent_path); | |
agent_free_identities(agent); | |
LIBSSH2_FREE(agent->session, agent); | |
} | |
/* | |
* libssh2_agent_set_identity_path() | |
* | |
* Allows a custom agent socket path beyond SSH_AUTH_SOCK env | |
* | |
*/ | |
LIBSSH2_API void | |
libssh2_agent_set_identity_path(LIBSSH2_AGENT *agent, const char *path) | |
{ | |
if(agent->identity_agent_path) { | |
LIBSSH2_FREE(agent->session, agent->identity_agent_path); | |
agent->identity_agent_path = NULL; | |
} | |
if(path) { | |
size_t path_len = strlen(path); | |
if(path_len < SIZE_MAX - 1) { | |
char *path_buf = LIBSSH2_ALLOC(agent->session, path_len + 1); | |
memcpy(path_buf, path, path_len); | |
path_buf[path_len] = '\0'; | |
agent->identity_agent_path = path_buf; | |
} | |
} | |
} | |
/* | |
* libssh2_agent_get_identity_path() | |
* | |
* Returns the custom agent socket path if set | |
* | |
*/ | |
LIBSSH2_API const char *libssh2_agent_get_identity_path(LIBSSH2_AGENT *agent) | |
{ | |
return agent->identity_agent_path; | |
} |