Blame channels/sshagent/client/sshagent_main.c

Packit Service fa4841
/**
Packit Service fa4841
 * FreeRDP: A Remote Desktop Protocol Implementation
Packit Service fa4841
 * SSH Agent Virtual Channel Extension
Packit Service fa4841
 *
Packit Service fa4841
 * Copyright 2013 Christian Hofstaedtler
Packit Service fa4841
 * Copyright 2015 Thincast Technologies GmbH
Packit Service fa4841
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
Packit Service fa4841
 * Copyright 2017 Ben Cohen
Packit Service fa4841
 *
Packit Service fa4841
 * Licensed under the Apache License, Version 2.0 (the "License");
Packit Service fa4841
 * you may not use this file except in compliance with the License.
Packit Service fa4841
 * You may obtain a copy of the License at
Packit Service fa4841
 *
Packit Service fa4841
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit Service fa4841
 *
Packit Service fa4841
 * Unless required by applicable law or agreed to in writing, software
Packit Service fa4841
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit Service fa4841
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit Service fa4841
 * See the License for the specific language governing permissions and
Packit Service fa4841
 * limitations under the License.
Packit Service fa4841
 */
Packit Service fa4841
Packit Service fa4841
/*
Packit Service fa4841
 * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent
Packit Service fa4841
 *
Packit Service fa4841
 * This relays data to and from an ssh-agent program equivalent running on the
Packit Service fa4841
 * RDP server to an ssh-agent running locally.  Unlike the normal ssh-agent,
Packit Service fa4841
 * which sends data over an SSH channel, the data is send over an RDP dynamic
Packit Service fa4841
 * virtual channel.
Packit Service fa4841
 *
Packit Service fa4841
 * protocol specification:
Packit Service fa4841
 *     Forward data verbatim over RDP dynamic virtual channel named "sshagent"
Packit Service fa4841
 *     between a ssh client on the xrdp server and the real ssh-agent where
Packit Service fa4841
 *     the RDP client is running.  Each connection by a separate client to
Packit Service fa4841
 *     xrdp-ssh-agent gets a separate DVC invocation.
Packit Service fa4841
 */
Packit Service fa4841
Packit Service fa4841
#ifdef HAVE_CONFIG_H
Packit Service fa4841
#include "config.h"
Packit Service fa4841
#endif
Packit Service fa4841
Packit Service fa4841
#include <stdio.h>
Packit Service fa4841
#include <stdlib.h>
Packit Service fa4841
#include <sys/types.h>
Packit Service fa4841
#include <sys/socket.h>
Packit Service fa4841
#include <sys/un.h>
Packit Service fa4841
#include <pwd.h>
Packit Service fa4841
#include <unistd.h>
Packit Service fa4841
#include <errno.h>
Packit Service fa4841
Packit Service fa4841
#include <winpr/crt.h>
Packit Service fa4841
#include <winpr/synch.h>
Packit Service fa4841
#include <winpr/thread.h>
Packit Service fa4841
#include <winpr/stream.h>
Packit Service fa4841
Packit Service fa4841
#include "sshagent_main.h"
Packit Service fa4841
#include <freerdp/channels/log.h>
Packit Service fa4841
Packit Service fa4841
#define TAG CHANNELS_TAG("sshagent.client")
Packit Service fa4841
Packit Service fa4841
typedef struct _SSHAGENT_LISTENER_CALLBACK SSHAGENT_LISTENER_CALLBACK;
Packit Service fa4841
struct _SSHAGENT_LISTENER_CALLBACK
Packit Service fa4841
{
Packit Service fa4841
	IWTSListenerCallback iface;
Packit Service fa4841
Packit Service fa4841
	IWTSPlugin* plugin;
Packit Service fa4841
	IWTSVirtualChannelManager* channel_mgr;
Packit Service fa4841
Packit Service fa4841
	rdpContext* rdpcontext;
Packit Service fa4841
	const char* agent_uds_path;
Packit Service fa4841
};
Packit Service fa4841
Packit Service fa4841
typedef struct _SSHAGENT_CHANNEL_CALLBACK SSHAGENT_CHANNEL_CALLBACK;
Packit Service fa4841
struct _SSHAGENT_CHANNEL_CALLBACK
Packit Service fa4841
{
Packit Service fa4841
	IWTSVirtualChannelCallback iface;
Packit Service fa4841
Packit Service fa4841
	IWTSPlugin* plugin;
Packit Service fa4841
	IWTSVirtualChannelManager* channel_mgr;
Packit Service fa4841
	IWTSVirtualChannel* channel;
Packit Service fa4841
Packit Service fa4841
	rdpContext* rdpcontext;
Packit Service fa4841
	int agent_fd;
Packit Service fa4841
	HANDLE thread;
Packit Service fa4841
	CRITICAL_SECTION lock;
Packit Service fa4841
};
Packit Service fa4841
Packit Service fa4841
typedef struct _SSHAGENT_PLUGIN SSHAGENT_PLUGIN;
Packit Service fa4841
struct _SSHAGENT_PLUGIN
Packit Service fa4841
{
Packit Service fa4841
	IWTSPlugin iface;
Packit Service fa4841
Packit Service fa4841
	SSHAGENT_LISTENER_CALLBACK* listener_callback;
Packit Service fa4841
Packit Service fa4841
	rdpContext* rdpcontext;
Packit Service fa4841
};
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Function to open the connection to the sshagent
Packit Service fa4841
 *
Packit Service fa4841
 * @return The fd on success, otherwise -1
Packit Service fa4841
 */
Packit Service fa4841
static int connect_to_sshagent(const char* udspath)
Packit Service fa4841
{
Packit Service fa4841
	int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0);
Packit Service fa4841
Packit Service fa4841
	if (agent_fd == -1)
Packit Service fa4841
	{
Packit Service fa4841
		WLog_ERR(TAG, "Can't open Unix domain socket!");
Packit Service fa4841
		return -1;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	struct sockaddr_un addr;
Packit Service fa4841
Packit Service fa4841
	memset(&addr, 0, sizeof(addr));
Packit Service fa4841
Packit Service fa4841
	addr.sun_family = AF_UNIX;
Packit Service fa4841
Packit Service fa4841
	strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1);
Packit Service fa4841
Packit Service fa4841
	int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr));
Packit Service fa4841
Packit Service fa4841
	if (rc != 0)
Packit Service fa4841
	{
Packit Service b1ea74
		WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", udspath);
Packit Service fa4841
		close(agent_fd);
Packit Service fa4841
		return -1;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	return agent_fd;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Entry point for thread to read from the ssh-agent socket and forward
Packit Service fa4841
 * the data to RDP
Packit Service fa4841
 *
Packit Service fa4841
 * @return NULL
Packit Service fa4841
 */
Packit Service fa4841
static DWORD WINAPI sshagent_read_thread(LPVOID data)
Packit Service fa4841
{
Packit Service fa4841
	SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data;
Packit Service fa4841
	BYTE buffer[4096];
Packit Service fa4841
	int going = 1;
Packit Service fa4841
	UINT status = CHANNEL_RC_OK;
Packit Service fa4841
Packit Service fa4841
	while (going)
Packit Service fa4841
	{
Packit Service b1ea74
		int bytes_read = read(callback->agent_fd, buffer, sizeof(buffer));
Packit Service fa4841
Packit Service fa4841
		if (bytes_read == 0)
Packit Service fa4841
		{
Packit Service fa4841
			/* Socket closed cleanly at other end */
Packit Service fa4841
			going = 0;
Packit Service fa4841
		}
Packit Service fa4841
		else if (bytes_read < 0)
Packit Service fa4841
		{
Packit Service fa4841
			if (errno != EINTR)
Packit Service fa4841
			{
Packit Service b1ea74
				WLog_ERR(TAG, "Error reading from sshagent, errno=%d", errno);
Packit Service fa4841
				status = ERROR_READ_FAULT;
Packit Service fa4841
				going = 0;
Packit Service fa4841
			}
Packit Service fa4841
		}
Packit Service fa4841
		else
Packit Service fa4841
		{
Packit Service fa4841
			/* Something read: forward to virtual channel */
Packit Service b1ea74
			status = callback->channel->Write(callback->channel, bytes_read, buffer, NULL);
Packit Service fa4841
Packit Service fa4841
			if (status != CHANNEL_RC_OK)
Packit Service fa4841
			{
Packit Service fa4841
				going = 0;
Packit Service fa4841
			}
Packit Service fa4841
		}
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	close(callback->agent_fd);
Packit Service fa4841
Packit Service fa4841
	if (status != CHANNEL_RC_OK)
Packit Service b1ea74
		setChannelError(callback->rdpcontext, status, "sshagent_read_thread reported an error");
Packit Service fa4841
Packit Service b1ea74
	ExitThread(status);
Packit Service b1ea74
	return status;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Callback for data received from the RDP server; forward this to ssh-agent
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
Packit Service fa4841
{
Packit Service b1ea74
	SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
Packit Service fa4841
	BYTE* pBuffer = Stream_Pointer(data);
Packit Service fa4841
	UINT32 cbSize = Stream_GetRemainingLength(data);
Packit Service fa4841
	BYTE* pos = pBuffer;
Packit Service fa4841
	/* Forward what we have received to the ssh agent */
Packit Service fa4841
	UINT32 bytes_to_write = cbSize;
Packit Service fa4841
	errno = 0;
Packit Service fa4841
Packit Service fa4841
	while (bytes_to_write > 0)
Packit Service fa4841
	{
Packit Service b1ea74
		int bytes_written = write(callback->agent_fd, pos, bytes_to_write);
Packit Service fa4841
Packit Service fa4841
		if (bytes_written < 0)
Packit Service fa4841
		{
Packit Service fa4841
			if (errno != EINTR)
Packit Service fa4841
			{
Packit Service b1ea74
				WLog_ERR(TAG, "Error writing to sshagent, errno=%d", errno);
Packit Service fa4841
				return ERROR_WRITE_FAULT;
Packit Service fa4841
			}
Packit Service fa4841
		}
Packit Service fa4841
		else
Packit Service fa4841
		{
Packit Service fa4841
			bytes_to_write -= bytes_written;
Packit Service fa4841
			pos += bytes_written;
Packit Service fa4841
		}
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	/* Consume stream */
Packit Service fa4841
	Stream_Seek(data, cbSize);
Packit Service fa4841
	return CHANNEL_RC_OK;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Callback for when the virtual channel is closed
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback)
Packit Service fa4841
{
Packit Service b1ea74
	SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
Packit Service fa4841
	/* Call shutdown() to wake up the read() in sshagent_read_thread(). */
Packit Service fa4841
	shutdown(callback->agent_fd, SHUT_RDWR);
Packit Service fa4841
	EnterCriticalSection(&callback->lock);
Packit Service fa4841
Packit Service fa4841
	if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED)
Packit Service fa4841
	{
Packit Service fa4841
		UINT error = GetLastError();
Packit Service b1ea74
		WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
Packit Service fa4841
		return error;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	CloseHandle(callback->thread);
Packit Service fa4841
	LeaveCriticalSection(&callback->lock);
Packit Service fa4841
	DeleteCriticalSection(&callback->lock);
Packit Service fa4841
	free(callback);
Packit Service fa4841
	return CHANNEL_RC_OK;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Callback for when a new virtual channel is opened
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
Packit Service b1ea74
                                               IWTSVirtualChannel* pChannel, BYTE* Data,
Packit Service b1ea74
                                               BOOL* pbAccept,
Packit Service b1ea74
                                               IWTSVirtualChannelCallback** ppCallback)
Packit Service fa4841
{
Packit Service fa4841
	SSHAGENT_CHANNEL_CALLBACK* callback;
Packit Service b1ea74
	SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback;
Packit Service b1ea74
	callback = (SSHAGENT_CHANNEL_CALLBACK*)calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK));
Packit Service fa4841
Packit Service fa4841
	if (!callback)
Packit Service fa4841
	{
Packit Service fa4841
		WLog_ERR(TAG, "calloc failed!");
Packit Service fa4841
		return CHANNEL_RC_NO_MEMORY;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	/* Now open a connection to the local ssh-agent.  Do this for each
Packit Service fa4841
	 * connection to the plugin in case we mess up the agent session. */
Packit Service b1ea74
	callback->agent_fd = connect_to_sshagent(listener_callback->agent_uds_path);
Packit Service fa4841
Packit Service fa4841
	if (callback->agent_fd == -1)
Packit Service fa4841
	{
Packit Service fa4841
		free(callback);
Packit Service fa4841
		return CHANNEL_RC_INITIALIZATION_ERROR;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	InitializeCriticalSection(&callback->lock);
Packit Service fa4841
	callback->iface.OnDataReceived = sshagent_on_data_received;
Packit Service fa4841
	callback->iface.OnClose = sshagent_on_close;
Packit Service fa4841
	callback->plugin = listener_callback->plugin;
Packit Service fa4841
	callback->channel_mgr = listener_callback->channel_mgr;
Packit Service fa4841
	callback->channel = pChannel;
Packit Service fa4841
	callback->rdpcontext = listener_callback->rdpcontext;
Packit Service b1ea74
	callback->thread = CreateThread(NULL, 0, sshagent_read_thread, (void*)callback, 0, NULL);
Packit Service fa4841
Packit Service fa4841
	if (!callback->thread)
Packit Service fa4841
	{
Packit Service fa4841
		WLog_ERR(TAG, "CreateThread failed!");
Packit Service fa4841
		DeleteCriticalSection(&callback->lock);
Packit Service fa4841
		free(callback);
Packit Service fa4841
		return CHANNEL_RC_INITIALIZATION_ERROR;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service b1ea74
	*ppCallback = (IWTSVirtualChannelCallback*)callback;
Packit Service fa4841
	return CHANNEL_RC_OK;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Callback for when the plugin is initialised
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
Packit Service fa4841
{
Packit Service b1ea74
	SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
Packit Service b1ea74
	sshagent->listener_callback =
Packit Service b1ea74
	    (SSHAGENT_LISTENER_CALLBACK*)calloc(1, sizeof(SSHAGENT_LISTENER_CALLBACK));
Packit Service fa4841
Packit Service fa4841
	if (!sshagent->listener_callback)
Packit Service fa4841
	{
Packit Service fa4841
		WLog_ERR(TAG, "calloc failed!");
Packit Service fa4841
		return CHANNEL_RC_NO_MEMORY;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	sshagent->listener_callback->rdpcontext = sshagent->rdpcontext;
Packit Service fa4841
	sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection;
Packit Service fa4841
	sshagent->listener_callback->plugin = pPlugin;
Packit Service fa4841
	sshagent->listener_callback->channel_mgr = pChannelMgr;
Packit Service fa4841
	sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK");
Packit Service fa4841
Packit Service fa4841
	if (sshagent->listener_callback->agent_uds_path == NULL)
Packit Service fa4841
	{
Packit Service fa4841
		WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!");
Packit Service fa4841
		free(sshagent->listener_callback);
Packit Service fa4841
		sshagent->listener_callback = NULL;
Packit Service fa4841
		return CHANNEL_RC_INITIALIZATION_ERROR;
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0,
Packit Service b1ea74
	                                   (IWTSListenerCallback*)sshagent->listener_callback, NULL);
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Callback for when the plugin is terminated
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin)
Packit Service fa4841
{
Packit Service b1ea74
	SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
Packit Service fa4841
	free(sshagent);
Packit Service fa4841
	return CHANNEL_RC_OK;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
#ifdef BUILTIN_CHANNELS
Packit Service b1ea74
#define DVCPluginEntry sshagent_DVCPluginEntry
Packit Service fa4841
#else
Packit Service b1ea74
#define DVCPluginEntry FREERDP_API DVCPluginEntry
Packit Service fa4841
#endif
Packit Service fa4841
Packit Service fa4841
/**
Packit Service fa4841
 * Main entry point for sshagent DVC plugin
Packit Service fa4841
 *
Packit Service fa4841
 * @return 0 on success, otherwise a Win32 error code
Packit Service fa4841
 */
Packit Service fa4841
UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
Packit Service fa4841
{
Packit Service fa4841
	UINT status = CHANNEL_RC_OK;
Packit Service fa4841
	SSHAGENT_PLUGIN* sshagent;
Packit Service b1ea74
	sshagent = (SSHAGENT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "sshagent");
Packit Service fa4841
Packit Service fa4841
	if (!sshagent)
Packit Service fa4841
	{
Packit Service b1ea74
		sshagent = (SSHAGENT_PLUGIN*)calloc(1, sizeof(SSHAGENT_PLUGIN));
Packit Service fa4841
Packit Service fa4841
		if (!sshagent)
Packit Service fa4841
		{
Packit Service fa4841
			WLog_ERR(TAG, "calloc failed!");
Packit Service fa4841
			return CHANNEL_RC_NO_MEMORY;
Packit Service fa4841
		}
Packit Service fa4841
Packit Service fa4841
		sshagent->iface.Initialize = sshagent_plugin_initialize;
Packit Service fa4841
		sshagent->iface.Connected = NULL;
Packit Service fa4841
		sshagent->iface.Disconnected = NULL;
Packit Service fa4841
		sshagent->iface.Terminated = sshagent_plugin_terminated;
Packit Service b1ea74
		sshagent->rdpcontext =
Packit Service b1ea74
		    ((freerdp*)((rdpSettings*)pEntryPoints->GetRdpSettings(pEntryPoints))->instance)
Packit Service b1ea74
		        ->context;
Packit Service b1ea74
		status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", (IWTSPlugin*)sshagent);
Packit Service fa4841
	}
Packit Service fa4841
Packit Service fa4841
	return status;
Packit Service fa4841
}
Packit Service fa4841
Packit Service fa4841
/* vim: set sw=8:ts=8:noet: */