Blame channels/sshagent/client/sshagent_main.c

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