Merge pull request #4122 from ben-cohen/sshagent

Forward ssh-agent data between ssh-agent and RDP
This commit is contained in:
akallabeth
2017-11-13 10:01:01 +01:00
committed by GitHub
10 changed files with 1059 additions and 1 deletions

View File

@@ -0,0 +1,27 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
# 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()

View File

@@ -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})

View File

@@ -0,0 +1,34 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
# 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")

View File

@@ -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 <martin.haimberger@thincast.com>
* 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pwd.h>
#include <unistd.h>
#include <errno.h>
#include <winpr/crt.h>
#include <winpr/synch.h>
#include <winpr/thread.h>
#include <winpr/stream.h>
#include "sshagent_main.h"
#include <freerdp/channels/log.h>
#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: */

View File

@@ -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 <winpr/stream.h>
#include <freerdp/svc.h>
#include <freerdp/addin.h>
#include <freerdp/channels/log.h>
#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 */

View File

@@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
# 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")

View File

@@ -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 <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, 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 <config.h>
#endif
#ifdef __WIN32__
#include <mstsapi.h>
#endif
#include <freerdp/channels/wtsvc.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#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:<udspath>".) */
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: */

View File

@@ -179,6 +179,7 @@ static COMMAND_LINE_ARGUMENT_A args[] =
{ "sound", COMMAND_LINE_VALUE_OPTIONAL, "[sys:<sys>,][dev:<dev>,][format:<format>,][rate:<rate>,][channel:<channel>,][latency:<latency>,][quality:<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, "<service-class>", 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, "<title>", 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];

View File

@@ -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 */

View File

@@ -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!