diff --git a/COPYING b/COPYING index 68c8d369..4852a889 100644 --- a/COPYING +++ b/COPYING @@ -4,6 +4,8 @@ * Copyright (c) 2007 Eli Fant * Copyright (c) 2009-2019 Daniel Stenberg * Copyright (C) 2008, 2009 Simon Josefsson + * Copyright (c) 2000 Markus Friedl + * Copyright (c) 2015 Microsoft Corp. * All rights reserved. * * Redistribution and use in source and binary forms, diff --git a/Makefile.inc b/Makefile.inc index ff8e6efa..87d26bbd 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -1,7 +1,7 @@ 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 agent.c $(CRYPTO_CSOURCES) pem.c keepalive.c global.c \ - blowfish.c bcrypt_pbkdf.c + blowfish.c bcrypt_pbkdf.c agent_win.c HHEADERS = libssh2_priv.h $(CRYPTO_HHEADERS) transport.h channel.h comp.h \ mac.h misc.h packet.h userauth.h session.h sftp.h crypto.h blf.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa0812be..eee1a80d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -176,6 +176,7 @@ include(GNUInstallDirs) set(SOURCES ${CRYPTO_SOURCES} agent.c + agent_win.c blf.h bcrypt_pbkdf.c blowfish.c diff --git a/src/agent.c b/src/agent.c index 3fbf310d..85c3e34a 100644 --- a/src/agent.c +++ b/src/agent.c @@ -38,6 +38,7 @@ */ #include "libssh2_priv.h" +#include "agent.h" #include "misc.h" #include #ifdef HAVE_SYS_UN_H @@ -50,6 +51,9 @@ #endif #include "userauth.h" #include "session.h" +#ifdef WIN32 +#include +#endif /* Requests from client to agent for protocol 1 key operations */ #define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 @@ -90,58 +94,6 @@ #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 */ - - char *identity_agent_path; /* Path to a custom identity agent socket */ -}; - #ifdef PF_UNIX static int agent_connect_unix(LIBSSH2_AGENT *agent) @@ -404,6 +356,7 @@ static struct { } supported_backends[] = { #ifdef WIN32 {"Pageant", &agent_ops_pageant}, + {"OpenSSH", &agent_ops_openssh}, #endif /* WIN32 */ #ifdef PF_UNIX {"Unix", &agent_ops_unix}, @@ -441,6 +394,7 @@ agent_sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, _libssh2_store_u32(&s, 0); transctx->request_len = s - transctx->request; + transctx->send_recv_total = 0; transctx->state = agent_NB_state_request_created; } @@ -541,6 +495,7 @@ agent_list_identities(LIBSSH2_AGENT *agent) 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; } @@ -717,6 +672,12 @@ libssh2_agent_init(LIBSSH2_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; } diff --git a/src/agent.h b/src/agent.h new file mode 100644 index 00000000..dfac0715 --- /dev/null +++ b/src/agent.h @@ -0,0 +1,112 @@ +#ifndef __LIBSSH2_AGENT_H +#define __LIBSSH2_AGENT_H +/* + * 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 "session.h" +#ifdef WIN32 +#include +#endif + +/* 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; + size_t send_recv_total; +} *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 */ + + char *identity_agent_path; /* Path to a custom identity agent socket */ + +#ifdef WIN32 + OVERLAPPED overlapped; + HANDLE pipe; + BOOL pending_io; +#endif +}; + +#ifdef WIN32 +extern struct agent_ops agent_ops_openssh; +#endif + +#endif /* __LIBSSH2_AGENT_H */ diff --git a/src/agent_win.c b/src/agent_win.c new file mode 100644 index 00000000..a1605a95 --- /dev/null +++ b/src/agent_win.c @@ -0,0 +1,361 @@ +/* + * 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 "agent.h" +#include "misc.h" +#include +#ifdef HAVE_SYS_UN_H +#include +#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 +#endif + +#ifdef WIN32 +/* Code to talk to OpenSSH was taken and modified from the Win32 port of + * Portable OpenSSH by the PowerShell team. Commit + * 8ab565c53f3619d6a1f5ac229e212cad8a52852c of + * https://github.com/PowerShell/openssh-portable.git was used as the base, + * specificaly the following files: + * + * - contrib\win32\win32compat\fileio.c + * - Structure of agent_connect_openssh from ssh_get_authentication_socket + * - Structure of agent_transact_openssh from ssh_request_reply + * - contrib\win32\win32compat\wmain_common.c + * - Windows equivalent functions for common Unix functions, inlined into + * this implementation + * - fileio_connect replacing connect + * - fileio_read replacing read + * - fileio_write replacing write + * - fileio_close replacing close + * + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Functions for connecting the local authentication agent. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + * + * SSH2 implementation, + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + * Copyright (c) 2015 Microsoft Corp. + * All rights reserved + * + * Microsoft openssh win32 port + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +#define WIN32_OPENSSH_AGENT_SOCK "\\\\.\\pipe\\openssh-ssh-agent" + +static int +agent_connect_openssh(LIBSSH2_AGENT *agent) +{ + int ret = LIBSSH2_ERROR_NONE; + const char *path; + HANDLE pipe = INVALID_HANDLE_VALUE; + HANDLE event = NULL; + + path = agent->identity_agent_path; + if(!path) { + path = getenv("SSH_AUTH_SOCK"); + if(!path) + path = WIN32_OPENSSH_AGENT_SOCK; + } + + for(;;) { + pipe = CreateFileA( + path, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + /* Non-blocking mode for agent connections is not implemented at + * the point this was implemented. The code for Win32 OpenSSH + * should support non-blocking IO, but the code calling it doesn't + * support it as of yet. + * When non-blocking IO is implemented for the surrounding code, + * uncomment the following line to enable support within the Win32 + * OpenSSH code. + */ + /* FILE_FLAG_OVERLAPPED | */ + SECURITY_SQOS_PRESENT | + SECURITY_IDENTIFICATION, + NULL + ); + + if(pipe != INVALID_HANDLE_VALUE) + break; + if(GetLastError() != ERROR_PIPE_BUSY) + break; + + /* Wait up to 1 second for a pipe instance to become available */ + if(!WaitNamedPipeA(path, 1000)) + break; + } + + if(pipe == INVALID_HANDLE_VALUE) { + ret = _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "unable to connect to agent pipe"); + goto cleanup; + } + + if(SetHandleInformation(pipe, HANDLE_FLAG_INHERIT, 0) == FALSE) { + ret = _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "unable to set handle information of agent pipe"); + goto cleanup; + } + + event = CreateEventA(NULL, TRUE, FALSE, NULL); + if(event == NULL) { + ret = _libssh2_error(agent->session, LIBSSH2_ERROR_AGENT_PROTOCOL, + "unable to create async I/O event"); + goto cleanup; + } + + agent->pipe = pipe; + pipe = INVALID_HANDLE_VALUE; + agent->overlapped.hEvent = event; + event = NULL; + agent->fd = 0; /* Mark as the connection has been established */ + +cleanup: + if(event != NULL) + CloseHandle(event); + if(pipe != INVALID_HANDLE_VALUE) + CloseHandle(pipe); + return ret; +} + +#define RECV_SEND_ALL(func, agent, buffer, length, total) \ + DWORD bytes_transfered; \ + BOOL ret; \ + DWORD err; \ + int rc; \ + \ + while(*total < length) { \ + if(!agent->pending_io) \ + ret = func(agent->pipe, (char *)buffer + *total, \ + (DWORD)(length - *total), &bytes_transfered, \ + &agent->overlapped); \ + else \ + ret = GetOverlappedResult(agent->pipe, &agent->overlapped, \ + &bytes_transfered, FALSE); \ + \ + *total += bytes_transfered; \ + if(!ret) { \ + err = GetLastError(); \ + if((!agent->pending_io && ERROR_IO_PENDING == err) \ + || (agent->pending_io && ERROR_IO_INCOMPLETE == err)) { \ + agent->pending_io = TRUE; \ + return LIBSSH2_ERROR_EAGAIN; \ + } \ + \ + return LIBSSH2_ERROR_SOCKET_NONE; \ + } \ + agent->pending_io = FALSE; \ + } \ + \ + rc = (int)*total; \ + *total = 0; \ + return rc; + +static int +win32_openssh_send_all(LIBSSH2_AGENT *agent, void *buffer, size_t length, + size_t *send_recv_total) +{ + RECV_SEND_ALL(WriteFile, agent, buffer, length, send_recv_total) +} + +static int +win32_openssh_recv_all(LIBSSH2_AGENT *agent, void *buffer, size_t length, + size_t *send_recv_total) +{ + RECV_SEND_ALL(ReadFile, agent, buffer, length, send_recv_total) +} + +#undef RECV_SEND_ALL + +static int +agent_transact_openssh(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, (uint32_t)transctx->request_len); + rc = win32_openssh_send_all(agent, buf, sizeof buf, + &transctx->send_recv_total); + if(rc == LIBSSH2_ERROR_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 = win32_openssh_send_all(agent, transctx->request, + transctx->request_len, + &transctx->send_recv_total); + if(rc == LIBSSH2_ERROR_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 the body */ + if(transctx->state == agent_NB_state_request_sent) { + rc = win32_openssh_recv_all(agent, buf, sizeof buf, + &transctx->send_recv_total); + if(rc == LIBSSH2_ERROR_EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + else if(rc < 0) + 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 = win32_openssh_recv_all(agent, transctx->response, + transctx->response_len, + &transctx->send_recv_total); + if(rc == LIBSSH2_ERROR_EAGAIN) + return LIBSSH2_ERROR_EAGAIN; + else if(rc < 0) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_RECV, + "agent recv failed"); + transctx->state = agent_NB_state_response_received; + } + + return LIBSSH2_ERROR_NONE; +} + +static int +agent_disconnect_openssh(LIBSSH2_AGENT *agent) +{ + if(!CancelIo(agent->pipe)) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "failed to cancel pending IO of agent pipe"); + if(!CloseHandle(agent->overlapped.hEvent)) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "failed to close handle to async I/O event"); + agent->overlapped.hEvent = NULL; + /* let queued APCs (if any) drain */ + SleepEx(0, TRUE); + if(!CloseHandle(agent->pipe)) + return _libssh2_error(agent->session, LIBSSH2_ERROR_SOCKET_DISCONNECT, + "failed to close handle to agent pipe"); + + agent->pipe = INVALID_HANDLE_VALUE; + agent->fd = LIBSSH2_INVALID_SOCKET; + + return LIBSSH2_ERROR_NONE; +} + +struct agent_ops agent_ops_openssh = { + agent_connect_openssh, + agent_transact_openssh, + agent_disconnect_openssh +}; +#endif /* WIN32 */