diff --git a/channels/sshagent/CMakeLists.txt b/channels/sshagent/CMakeLists.txt new file mode 100644 index 000000000..f3fa34e33 --- /dev/null +++ b/channels/sshagent/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("sshagent") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/sshagent/ChannelOptions.cmake b/channels/sshagent/ChannelOptions.cmake new file mode 100644 index 000000000..083d8d54c --- /dev/null +++ b/channels/sshagent/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT OFF) + +define_channel_options(NAME "sshagent" TYPE "dynamic" + DESCRIPTION "SSH Agent Forwarding Extension" + SPECIFICATIONS "" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/sshagent/client/CMakeLists.txt b/channels/sshagent/client/CMakeLists.txt new file mode 100644 index 000000000..7feea9668 --- /dev/null +++ b/channels/sshagent/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("sshagent") + +set(${MODULE_PREFIX}_SRCS + sshagent_main.c + sshagent_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${CMAKE_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +target_link_libraries(${MODULE_NAME} winpr) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/sshagent/client/sshagent_main.c b/channels/sshagent/client/sshagent_main.c new file mode 100644 index 000000000..1c95210c7 --- /dev/null +++ b/channels/sshagent/client/sshagent_main.c @@ -0,0 +1,404 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent + * + * This relays data to and from an ssh-agent program equivalent running on the + * RDP server to an ssh-agent running locally. Unlike the normal ssh-agent, + * which sends data over an SSH channel, the data is send over an RDP dynamic + * virtual channel. + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sshagent_main.h" +#include + +#define TAG CHANNELS_TAG("sshagent.client") + +typedef struct _SSHAGENT_LISTENER_CALLBACK SSHAGENT_LISTENER_CALLBACK; +struct _SSHAGENT_LISTENER_CALLBACK +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + + rdpContext* rdpcontext; + const char *agent_uds_path; +}; + +typedef struct _SSHAGENT_CHANNEL_CALLBACK SSHAGENT_CHANNEL_CALLBACK; +struct _SSHAGENT_CHANNEL_CALLBACK +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; + + rdpContext* rdpcontext; + int agent_fd; + HANDLE thread; + CRITICAL_SECTION lock; +}; + +typedef struct _SSHAGENT_PLUGIN SSHAGENT_PLUGIN; +struct _SSHAGENT_PLUGIN +{ + IWTSPlugin iface; + + SSHAGENT_LISTENER_CALLBACK* listener_callback; + + rdpContext* rdpcontext; +}; + + +/** + * Function to open the connection to the sshagent + * + * @return The fd on success, otherwise -1 + */ +static int connect_to_sshagent(const char *udspath) +{ + int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (agent_fd == -1) + { + WLog_ERR(TAG, "Can't open Unix domain socket!"); + return -1; + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1); + int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr)); + if (rc != 0) + { + WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", + udspath); + close(agent_fd); + return -1; + } + + return agent_fd; +} + + +/** + * Entry point for thread to read from the ssh-agent socket and forward + * the data to RDP + * + * @return NULL + */ +static void *sshagent_read_thread(void *data) +{ + SSHAGENT_CHANNEL_CALLBACK *callback = (SSHAGENT_CHANNEL_CALLBACK *)data; + BYTE buffer[4096]; + int going = 1; + UINT status = CHANNEL_RC_OK; + + while (going) + { + int bytes_read = read(callback->agent_fd, + buffer, + sizeof(buffer)); + + if (bytes_read == 0) + { + /* Socket closed cleanly at other end */ + going = 0; + } + else if (bytes_read < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, + "Error reading from sshagent, errno=%d", + errno); + status = ERROR_READ_FAULT; + going = 0; + } + } + else + { + /* Something read: forward to virtual channel */ + status = callback->channel->Write(callback->channel, + bytes_read, + buffer, + NULL); + if (status != CHANNEL_RC_OK) + { + going = 0; + } + } + } + + close(callback->agent_fd); + + if (status != CHANNEL_RC_OK) + setChannelError(callback->rdpcontext, status, + "sshagent_read_thread reported an error"); + + ExitThread(0); + return NULL; +} + +/** + * Callback for data received from the RDP server; forward this to ssh-agent + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *data) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*) pChannelCallback; + BYTE* pBuffer = Stream_Pointer(data); + UINT32 cbSize = Stream_GetRemainingLength(data); + BYTE *pos = pBuffer; + + /* Forward what we have received to the ssh agent */ + UINT32 bytes_to_write = cbSize; + errno = 0; + while (bytes_to_write > 0) + { + int bytes_written = write(callback->agent_fd, pos, + bytes_to_write); + if (bytes_written < 0) + { + if (errno != EINTR) + { + WLog_ERR(TAG, + "Error writing to sshagent, errno=%d", + errno); + return ERROR_WRITE_FAULT; + } + } + else + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + } + + /* Consume stream */ + Stream_Seek(data, cbSize); + + return CHANNEL_RC_OK; +} + +/** + * Callback for when the virtual channel is closed + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*) pChannelCallback; + + /* Call shutdown() to wake up the read() in sshagent_read_thread(). */ + shutdown(callback->agent_fd, SHUT_RDWR); + + EnterCriticalSection(&callback->lock); + if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED) + { + UINT error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"!", error); + return error; + } + + CloseHandle(callback->thread); + DeleteCriticalSection(&callback->lock); + + free(callback); + + return CHANNEL_RC_OK; +} + + +/** + * Callback for when a new virtual channel is opened + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + SSHAGENT_CHANNEL_CALLBACK* callback; + SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*) pListenerCallback; + + callback = (SSHAGENT_CHANNEL_CALLBACK*) calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + /* Now open a connection to the local ssh-agent. Do this for each + * connection to the plugin in case we mess up the agent session. */ + callback->agent_fd + = connect_to_sshagent(listener_callback->agent_uds_path); + if (callback->agent_fd == -1) + { + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + InitializeCriticalSection(&callback->lock); + + callback->iface.OnDataReceived = sshagent_on_data_received; + callback->iface.OnClose = sshagent_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + callback->rdpcontext = listener_callback->rdpcontext; + + callback->thread + = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE) sshagent_read_thread, + (void*) callback, + 0, + NULL); + if (!callback->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + *ppCallback = (IWTSVirtualChannelCallback*) callback; + + return CHANNEL_RC_OK; +} + +/** + * Callback for when the plugin is initialised + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*) pPlugin; + + sshagent->listener_callback = (SSHAGENT_LISTENER_CALLBACK*) calloc(1, sizeof(SSHAGENT_LISTENER_CALLBACK)); + + if (!sshagent->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->listener_callback->rdpcontext = sshagent->rdpcontext; + sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection; + sshagent->listener_callback->plugin = pPlugin; + sshagent->listener_callback->channel_mgr = pChannelMgr; + + sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK"); + if (sshagent->listener_callback->agent_uds_path == NULL) + { + WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!"); + return CHANNEL_RC_INITIALIZATION_ERROR; + } + + return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0, + (IWTSListenerCallback*) sshagent->listener_callback, NULL); +} + +/** + * Callback for when the plugin is terminated + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin) +{ + SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*) pPlugin; + + free(sshagent); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry sshagent_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Main entry point for sshagent DVC plugin + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = CHANNEL_RC_OK; + SSHAGENT_PLUGIN* sshagent; + + sshagent = (SSHAGENT_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "sshagent"); + + if (!sshagent) + { + sshagent = (SSHAGENT_PLUGIN*) calloc(1, sizeof(SSHAGENT_PLUGIN)); + + if (!sshagent) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + sshagent->iface.Initialize = sshagent_plugin_initialize; + sshagent->iface.Connected = NULL; + sshagent->iface.Disconnected = NULL; + sshagent->iface.Terminated = sshagent_plugin_terminated; + sshagent->rdpcontext = ((freerdp*)((rdpSettings*) pEntryPoints->GetRdpSettings( + pEntryPoints))->instance)->context; + + status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", (IWTSPlugin*) sshagent); + } + + return status; +} + +/* vim: set sw=8:ts=8:noet: */ diff --git a/channels/sshagent/client/sshagent_main.h b/channels/sshagent/client/sshagent_main.h new file mode 100644 index 000000000..fc1ac150d --- /dev/null +++ b/channels/sshagent/client/sshagent_main.h @@ -0,0 +1,42 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2013 Christian Hofstaedtler + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SSHAGENT_MAIN_H +#define SSHAGENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("sshagent.client") +#ifdef WITH_DEBUG_SSHAGENT +#define DEBUG_SSHAGENT(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_SSHAGENT(...) do { } while (0) +#endif + +#endif /* SSHAGENT_MAIN_H */ + diff --git a/channels/sshagent/server/CMakeLists.txt b/channels/sshagent/server/CMakeLists.txt new file mode 100644 index 000000000..9d6299584 --- /dev/null +++ b/channels/sshagent/server/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2012 Marc-Andre Moreau +# Copyright 2017 Ben Cohen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("sshagent") + +set(${MODULE_PREFIX}_SRCS + sshagent_main.c) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + + + +target_link_libraries(${MODULE_NAME} freerdp) + + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/sshagent/server/sshagent_main.c b/channels/sshagent/server/sshagent_main.c new file mode 100644 index 000000000..2cf3a9108 --- /dev/null +++ b/channels/sshagent/server/sshagent_main.c @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2012-2013 Jay Sorg + * Copyright 2012-2013 Laxmikant Rashinkar + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Portions are from OpenSSH, under the following license: + * + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * The authentication agent program. + * + * 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". + * + * Copyright (c) 2000, 2001 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. + */ + +/* + * xrdp-ssh-agent.c: program to forward ssh-agent protocol from xrdp session + * + * This performs the equivalent function of ssh-agent on a server you connect + * to via ssh, but the ssh-agent protocol is over an RDP dynamic virtual + * channel and not an SSH channel. + * + * This will print out variables to set in your environment (specifically, + * $SSH_AUTH_SOCK) for ssh clients to find the agent's socket, then it will + * run in the background. This is suitable to run just as you would run the + * normal ssh-agent, e.g. in your Xsession or /etc/xrdp/startwm.sh. + * + * Your RDP client needs to be running a compatible client-side plugin + * that can see a local ssh-agent. + * + * usage (from within an xrdp session): + * xrdp-ssh-agent + * + * build instructions: + * gcc xrdp-ssh-agent.c -o xrdp-ssh-agent -L./.libs -lxrdpapi -Wall + * + * protocol specification: + * Forward data verbatim over RDP dynamic virtual channel named "sshagent" + * between a ssh client on the xrdp server and the real ssh-agent where + * the RDP client is running. Each connection by a separate client to + * xrdp-ssh-agent gets a separate DVC invocation. + */ + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#ifdef __WIN32__ +#include +#endif + +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _PATH_DEVNULL "/dev/null" + +char socket_name[PATH_MAX]; +char socket_dir[PATH_MAX]; +static int sa_uds_fd = -1; +static int is_going = 1; + + +/* Make a template filename for mk[sd]temp() */ +/* This is from mktemp_proto() in misc.c from openssh */ +void +mktemp_proto(char *s, size_t len) +{ + const char *tmpdir; + int r; + + if ((tmpdir = getenv("TMPDIR")) != NULL) { + r = snprintf(s, len, "%s/ssh-XXXXXXXXXXXX", tmpdir); + if (r > 0 && (size_t)r < len) + return; + } + r = snprintf(s, len, "/tmp/ssh-XXXXXXXXXXXX"); + if (r < 0 || (size_t)r >= len) + { + fprintf(stderr, "%s: template string too short", __func__); + exit(1); + } +} + + +/* This uses parts of main() in ssh-agent.c from openssh */ +static void +setup_ssh_agent(struct sockaddr_un *addr) +{ + int rc; + + /* Create private directory for agent socket */ + mktemp_proto(socket_dir, sizeof(socket_dir)); + if (mkdtemp(socket_dir) == NULL) { + perror("mkdtemp: private socket dir"); + exit(1); + } + snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir, + (long)getpid()); + + /* Create unix domain socket */ + unlink(socket_name); + + sa_uds_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sa_uds_fd == -1) + { + fprintf(stderr, "sshagent: socket creation failed"); + exit(2); + } + + memset(addr, 0, sizeof(struct sockaddr_un)); + addr->sun_family = AF_UNIX; + strncpy(addr->sun_path, socket_name, sizeof(addr->sun_path)); + addr->sun_path[sizeof(addr->sun_path) - 1] = 0; + + /* Create with privileges rw------- so other users can't access the UDS */ + mode_t umask_sav = umask(0177); + rc = bind(sa_uds_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_un)); + if (rc != 0) + { + fprintf(stderr, "sshagent: bind failed"); + close(sa_uds_fd); + unlink(socket_name); + exit(3); + } + umask(umask_sav); + + rc = listen(sa_uds_fd, /* backlog = */ 5); + if (rc != 0) + { + fprintf(stderr, "listen failed\n"); + close(sa_uds_fd); + unlink(socket_name); + exit(1); + } + + /* Now fork: the child becomes the ssh-agent daemon and the parent prints + * out the pid and socket name. */ + pid_t pid = fork(); + if (pid == -1) + { + perror("fork"); + exit(1); + } + else if (pid != 0) + { + /* Parent */ + close(sa_uds_fd); + printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n", socket_name); + printf("SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", pid); + printf("echo Agent pid %d;\n", pid); + exit(0); + } + + /* Child */ + + if (setsid() == -1) + { + fprintf(stderr, "setsid failed"); + exit(1); + } + + (void)chdir("/"); + int devnullfd; + if ((devnullfd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + /* XXX might close listen socket */ + (void)dup2(devnullfd, STDIN_FILENO); + (void)dup2(devnullfd, STDOUT_FILENO); + (void)dup2(devnullfd, STDERR_FILENO); + if (devnullfd > 2) + close(devnullfd); + } + + /* deny core dumps, since memory contains unencrypted private keys */ + struct rlimit rlim; + rlim.rlim_cur = rlim.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) { + fprintf(stderr, "setrlimit RLIMIT_CORE: %s", strerror(errno)); + exit(1); + } +} + + +static void +handle_connection(int client_fd) +{ + int rdp_fd = -1; + int rc; + void *channel = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, + "SSHAGENT", + WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED); + if (channel == NULL) + { + fprintf(stderr, "WTSVirtualChannelOpenEx() failed\n"); + } + + unsigned int retlen; + int *retdata; + rc = WTSVirtualChannelQuery(channel, + WTSVirtualFileHandle, + (void **)&retdata, + &retlen); + if (!rc) + { + fprintf(stderr, "WTSVirtualChannelQuery() failed\n"); + } + if (retlen != sizeof(rdp_fd)) + { + fprintf(stderr, "WTSVirtualChannelQuery() returned wrong length %d\n", + retlen); + } + rdp_fd = *retdata; + + int client_going = 1; + while (client_going) + { + /* Wait for data from RDP or the client */ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(client_fd, &readfds); + FD_SET(rdp_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + if (FD_ISSET(rdp_fd, &readfds)) + { + /* Read from RDP and write to the client */ + char buffer[4096]; + unsigned int bytes_to_write; + rc = WTSVirtualChannelRead(channel, + /* TimeOut = */ 5000, + buffer, + sizeof(buffer), + &bytes_to_write); + if (rc == 1) + { + char *pos = buffer; + errno = 0; + while (bytes_to_write > 0) + { + int bytes_written = send(client_fd, pos, bytes_to_write, 0); + + if (bytes_written > 0) + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + else if (bytes_written == 0) + { + fprintf(stderr, "send() returned 0!\n"); + } + else if (errno != EINTR) + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + else + { + /* Error */ + fprintf(stderr, "WTSVirtualChannelRead() failed: %d\n", errno); + client_going = 0; + } + } + + if (FD_ISSET(client_fd, &readfds)) + { + /* Read from the client and write to RDP */ + char buffer[4096]; + ssize_t bytes_to_write = recv(client_fd, buffer, sizeof(buffer), 0); + if (bytes_to_write > 0) + { + char *pos = buffer; + while (bytes_to_write > 0) + { + unsigned int bytes_written; + int rc = WTSVirtualChannelWrite(channel, + pos, + bytes_to_write, + &bytes_written); + if (rc == 0) + { + fprintf(stderr, "WTSVirtualChannelWrite() failed: %d\n", + errno); + client_going = 0; + } + else + { + bytes_to_write -= bytes_written; + pos += bytes_written; + } + } + } + else if (bytes_to_write == 0) + { + /* Client has closed connection */ + client_going = 0; + } + else + { + /* Error */ + fprintf(stderr, "Error %d on recv\n", errno); + client_going = 0; + } + } + } + WTSVirtualChannelClose(channel); +} + + +int +main(int argc, char **argv) +{ + /* Setup the Unix domain socket and daemon process */ + struct sockaddr_un addr; + setup_ssh_agent(&addr); + + /* Wait for a client to connect to the socket */ + while (is_going) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sa_uds_fd, &readfds); + select(FD_SETSIZE, &readfds, NULL, NULL, NULL); + + /* If something connected then get it... + * (You can test this using "socat - UNIX-CONNECT:".) */ + if (FD_ISSET(sa_uds_fd, &readfds)) + { + socklen_t addrsize = sizeof(addr); + int client_fd = accept(sa_uds_fd, + (struct sockaddr*)&addr, + &addrsize); + handle_connection(client_fd); + close(client_fd); + } + } + + close(sa_uds_fd); + unlink(socket_name); + + return 0; +} + +/* vim: set sw=4:ts=4:et: */ diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 4b328b6f4..950162840 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -179,6 +179,7 @@ static COMMAND_LINE_ARGUMENT_A args[] = { "sound", COMMAND_LINE_VALUE_OPTIONAL, "[sys:,][dev:,][format:,][rate:,][channel:,][latency:,][quality:]", NULL, NULL, -1, "audio", "Audio output (sound)" }, { "span", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "Span screen over multiple monitors" }, { "spn-class", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, NULL, "SPN authentication service class" }, + { "ssh-agent", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "ssh-agent", "SSH Agent forwarding channel" }, { "t", COMMAND_LINE_VALUE_REQUIRED, "", NULL, NULL, -1, "title", "Window title" }, { "themes", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL, "Enable themes" }, { "tls-ciphers", COMMAND_LINE_VALUE_REQUIRED, "netmon|ma|ciphers", NULL, NULL, -1, NULL, "Allowed TLS ciphers" }, @@ -928,6 +929,10 @@ static int freerdp_client_command_line_post_filter(void* context, { settings->SupportEchoChannel = TRUE; } + CommandLineSwitchCase(arg, "ssh-agent") + { + settings->SupportSSHAgentChannel = TRUE; + } CommandLineSwitchCase(arg, "disp") { settings->SupportDisplayControl = TRUE; @@ -2904,6 +2909,17 @@ BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) return FALSE; } + if (settings->SupportSSHAgentChannel) + { + char* p[1]; + int count; + count = 1; + p[0] = "sshagent"; + + if (!freerdp_client_add_dynamic_channel(settings, count, p)) + return FALSE; + } + if (settings->SupportDisplayControl) { char* p[1]; diff --git a/include/freerdp/client/sshagent.h b/include/freerdp/client/sshagent.h new file mode 100644 index 000000000..64ada334a --- /dev/null +++ b/include/freerdp/client/sshagent.h @@ -0,0 +1,87 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * SSH Agent Virtual Channel Extension + * + * Copyright 2017 Ben Cohen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CLIENT_SSHAGENT_H +#define FREERDP_CHANNEL_CLIENT_SSHAGENT_H + +#include <freerdp/types.h> + +#include <freerdp/message.h> +#include <freerdp/channels/cliprdr.h> +#include <freerdp/freerdp.h> + +typedef struct _sshagent_client_context +{ + int ProtocolVersion; + int MaxConnections; +} SSHAgentClientContext; + + +/* + * The channel is defined by the sshagent channel in xrdp as follows. + * + * Server to client commands + * ------------------------- + * + * Capabilities (at start of channel stream): + * + * INT32 SA_TAG_CAPABILITY + * INT32 SSHAGENT_CHAN_PROT_VERSION := 1 + * INT32 SSHAGENT_MAX_CONNECTIONS + * + * Open connection: + * + * INT32 SA_TAG_OPEN + * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1) + * + * Send data: + * + * INT32 SA_TAG_WRITE + * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1) + * INT32 Data length + * DATA ... + * + * Close connection: + * + * INT32 SA_TAG_CLOSE + * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1) + * + * Client to server commands + * ------------------------- + * + * Capabilities (in reply to server capabilities): + * + * INT32 SA_TAG_CAPABILITY + * INT32 SSHAGENT_CHAN_PROT_VERSION := 1 + * INT32 SSHAGENT_MAX_CONNECTIONS + * + * Send data: + * + * INT32 SA_TAG_WRITE + * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1) + * INT32 Data length + * DATA ... + * + * Close connection (abnormal): + * + * INT32 SA_TAG_CLOSE + * INT32 Connection id (0, ..., SSHAGENT_MAX_CONNECTIONS - 1) + */ + +#endif /* FREERDP_CHANNEL_CLIENT_SSHAGENT_H */ diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index d6af94a99..2f378dea7 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -809,6 +809,7 @@ typedef struct _RDPDR_PARALLEL RDPDR_PARALLEL; #define FreeRDP_SupportEchoChannel 5184 #define FreeRDP_SupportDisplayControl 5185 #define FreeRDP_SupportGeometryTracking 5186 +#define FreeRDP_SupportSSHAgentChannel 5187 /** * FreeRDP Settings Data Structure @@ -1422,7 +1423,8 @@ struct rdp_settings ALIGN64 BOOL SupportEchoChannel; /* 5184 */ ALIGN64 BOOL SupportDisplayControl; /* 5185 */ ALIGN64 BOOL SupportGeometryTracking; /* 5186 */ - UINT64 padding5312[5312 - 5187]; /* 5187 */ + ALIGN64 BOOL SupportSSHAgentChannel; /* 5187 */ + UINT64 padding5312[5312 - 5188]; /* 5188 */ /** * WARNING: End of ABI stable zone!