Milestone 5: deliver embedded RDP sessions and lifecycle hardening
This commit is contained in:
27
third_party/FreeRDP/channels/sshagent/client/CMakeLists.txt
vendored
Normal file
27
third_party/FreeRDP/channels/sshagent/client/CMakeLists.txt
vendored
Normal 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_client("sshagent")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS sshagent_main.c sshagent_main.h)
|
||||
|
||||
set(${MODULE_PREFIX}_LIBS winpr)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
|
||||
398
third_party/FreeRDP/channels/sshagent/client/sshagent_main.c
vendored
Normal file
398
third_party/FreeRDP/channels/sshagent/client/sshagent_main.c
vendored
Normal file
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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/assert.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include "sshagent_main.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/client/channels.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("sshagent.client")
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSListenerCallback iface;
|
||||
|
||||
IWTSPlugin* plugin;
|
||||
IWTSVirtualChannelManager* channel_mgr;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
const char* agent_uds_path;
|
||||
} SSHAGENT_LISTENER_CALLBACK;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GENERIC_CHANNEL_CALLBACK generic;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
int agent_fd;
|
||||
HANDLE thread;
|
||||
CRITICAL_SECTION lock;
|
||||
} SSHAGENT_CHANNEL_CALLBACK;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
IWTSPlugin iface;
|
||||
|
||||
SSHAGENT_LISTENER_CALLBACK* listener_callback;
|
||||
|
||||
rdpContext* rdpcontext;
|
||||
} SSHAGENT_PLUGIN;
|
||||
|
||||
/**
|
||||
* Function to open the connection to the sshagent
|
||||
*
|
||||
* @return The fd on success, otherwise -1
|
||||
*/
|
||||
static int connect_to_sshagent(const char* udspath)
|
||||
{
|
||||
WINPR_ASSERT(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 = WINPR_C_ARRAY_INIT;
|
||||
|
||||
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 0
|
||||
*/
|
||||
static DWORD WINAPI sshagent_read_thread(LPVOID data)
|
||||
{
|
||||
SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data;
|
||||
WINPR_ASSERT(callback);
|
||||
|
||||
BYTE buffer[4096] = WINPR_C_ARRAY_INIT;
|
||||
int going = 1;
|
||||
UINT status = CHANNEL_RC_OK;
|
||||
|
||||
while (going)
|
||||
{
|
||||
const ssize_t 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 if ((size_t)bytes_read > ULONG_MAX)
|
||||
{
|
||||
status = ERROR_READ_FAULT;
|
||||
going = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Something read: forward to virtual channel */
|
||||
IWTSVirtualChannel* channel = callback->generic.channel;
|
||||
status = channel->Write(channel, (ULONG)bytes_read, buffer, nullptr);
|
||||
|
||||
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(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
WINPR_ASSERT(callback);
|
||||
|
||||
BYTE* pBuffer = Stream_Pointer(data);
|
||||
size_t cbSize = Stream_GetRemainingLength(data);
|
||||
BYTE* pos = pBuffer;
|
||||
/* Forward what we have received to the ssh agent */
|
||||
size_t bytes_to_write = cbSize;
|
||||
errno = 0;
|
||||
|
||||
while (bytes_to_write > 0)
|
||||
{
|
||||
const ssize_t 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 -= WINPR_ASSERTING_INT_CAST(size_t, 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;
|
||||
WINPR_ASSERT(callback);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
(void)CloseHandle(callback->thread);
|
||||
LeaveCriticalSection(&callback->lock);
|
||||
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
|
||||
*/
|
||||
// NOLINTBEGIN(readability-non-const-parameter)
|
||||
static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data,
|
||||
BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
// NOLINTEND(readability-non-const-parameter)
|
||||
{
|
||||
SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback;
|
||||
WINPR_UNUSED(Data);
|
||||
WINPR_UNUSED(pbAccept);
|
||||
|
||||
SSHAGENT_CHANNEL_CALLBACK* 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)
|
||||
{
|
||||
free(callback);
|
||||
return CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
}
|
||||
|
||||
InitializeCriticalSection(&callback->lock);
|
||||
|
||||
GENERIC_CHANNEL_CALLBACK* generic = &callback->generic;
|
||||
generic->iface.OnDataReceived = sshagent_on_data_received;
|
||||
generic->iface.OnClose = sshagent_on_close;
|
||||
generic->plugin = listener_callback->plugin;
|
||||
generic->channel_mgr = listener_callback->channel_mgr;
|
||||
generic->channel = pChannel;
|
||||
callback->rdpcontext = listener_callback->rdpcontext;
|
||||
callback->thread = CreateThread(nullptr, 0, sshagent_read_thread, (void*)callback, 0, nullptr);
|
||||
|
||||
if (!callback->thread)
|
||||
{
|
||||
WLog_ERR(TAG, "CreateThread failed!");
|
||||
DeleteCriticalSection(&callback->lock);
|
||||
free(callback);
|
||||
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;
|
||||
WINPR_ASSERT(sshagent);
|
||||
WINPR_ASSERT(pChannelMgr);
|
||||
|
||||
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;
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK");
|
||||
|
||||
if (sshagent->listener_callback->agent_uds_path == nullptr)
|
||||
{
|
||||
WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!");
|
||||
free(sshagent->listener_callback);
|
||||
sshagent->listener_callback = nullptr;
|
||||
return CHANNEL_RC_INITIALIZATION_ERROR;
|
||||
}
|
||||
|
||||
return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0,
|
||||
(IWTSListenerCallback*)sshagent->listener_callback, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point for sshagent DVC plugin
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
FREERDP_ENTRY_POINT(UINT VCAPITYPE sshagent_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
|
||||
{
|
||||
UINT status = CHANNEL_RC_OK;
|
||||
|
||||
WINPR_ASSERT(pEntryPoints);
|
||||
|
||||
SSHAGENT_PLUGIN* 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 = nullptr;
|
||||
sshagent->iface.Disconnected = nullptr;
|
||||
sshagent->iface.Terminated = sshagent_plugin_terminated;
|
||||
sshagent->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
|
||||
status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", &sshagent->iface);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
42
third_party/FreeRDP/channels/sshagent/client/sshagent_main.h
vendored
Normal file
42
third_party/FreeRDP/channels/sshagent/client/sshagent_main.h
vendored
Normal 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
|
||||
|
||||
#include <freerdp/config.h>
|
||||
|
||||
#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 */
|
||||
Reference in New Issue
Block a user