38#include <freerdp/config.h>
43#include <sys/socket.h>
50#include <winpr/assert.h>
51#include <winpr/synch.h>
52#include <winpr/thread.h>
53#include <winpr/stream.h>
55#include "sshagent_main.h"
57#include <freerdp/freerdp.h>
58#include <freerdp/client/channels.h>
59#include <freerdp/channels/log.h>
61#define TAG CHANNELS_TAG("sshagent.client")
65 IWTSListenerCallback iface;
68 IWTSVirtualChannelManager* channel_mgr;
70 rdpContext* rdpcontext;
71 const char* agent_uds_path;
72} SSHAGENT_LISTENER_CALLBACK;
78 rdpContext* rdpcontext;
82} SSHAGENT_CHANNEL_CALLBACK;
88 SSHAGENT_LISTENER_CALLBACK* listener_callback;
90 rdpContext* rdpcontext;
99static int connect_to_sshagent(
const char* udspath)
101 WINPR_ASSERT(udspath);
103 int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0);
107 WLog_ERR(TAG,
"Can't open Unix domain socket!");
111 struct sockaddr_un addr = WINPR_C_ARRAY_INIT;
113 addr.sun_family = AF_UNIX;
115 strncpy(addr.sun_path, udspath,
sizeof(addr.sun_path) - 1);
117 int rc = connect(agent_fd, (
struct sockaddr*)&addr,
sizeof(addr));
121 WLog_ERR(TAG,
"Can't connect to Unix domain socket \"%s\"!", udspath);
135static DWORD WINAPI sshagent_read_thread(LPVOID data)
137 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data;
138 WINPR_ASSERT(callback);
140 BYTE buffer[4096] = WINPR_C_ARRAY_INIT;
142 UINT status = CHANNEL_RC_OK;
146 const ssize_t bytes_read = read(callback->agent_fd, buffer,
sizeof(buffer));
153 else if (bytes_read < 0)
157 WLog_ERR(TAG,
"Error reading from sshagent, errno=%d", errno);
158 status = ERROR_READ_FAULT;
162 else if ((
size_t)bytes_read > ULONG_MAX)
164 status = ERROR_READ_FAULT;
170 IWTSVirtualChannel* channel = callback->generic.channel;
171 status = channel->Write(channel, (ULONG)bytes_read, buffer,
nullptr);
173 if (status != CHANNEL_RC_OK)
180 close(callback->agent_fd);
182 if (status != CHANNEL_RC_OK)
183 setChannelError(callback->rdpcontext, status,
"sshagent_read_thread reported an error");
194static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback,
wStream* data)
196 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
197 WINPR_ASSERT(callback);
199 BYTE* pBuffer = Stream_Pointer(data);
200 size_t cbSize = Stream_GetRemainingLength(data);
203 size_t bytes_to_write = cbSize;
206 while (bytes_to_write > 0)
208 const ssize_t bytes_written = write(callback->agent_fd, pos, bytes_to_write);
210 if (bytes_written < 0)
214 WLog_ERR(TAG,
"Error writing to sshagent, errno=%d", errno);
215 return ERROR_WRITE_FAULT;
220 bytes_to_write -= WINPR_ASSERTING_INT_CAST(
size_t, bytes_written);
221 pos += bytes_written;
226 Stream_Seek(data, cbSize);
227 return CHANNEL_RC_OK;
235static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback)
237 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
238 WINPR_ASSERT(callback);
241 shutdown(callback->agent_fd, SHUT_RDWR);
242 EnterCriticalSection(&callback->lock);
244 if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED)
246 UINT error = GetLastError();
247 WLog_ERR(TAG,
"WaitForSingleObject failed with error %" PRIu32
"!", error);
251 (void)CloseHandle(callback->thread);
252 LeaveCriticalSection(&callback->lock);
253 DeleteCriticalSection(&callback->lock);
255 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)callback->generic.plugin;
256 WINPR_ASSERT(sshagent);
257 sshagent->connected = FALSE;
259 return CHANNEL_RC_OK;
268static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
269 IWTSVirtualChannel* pChannel, BYTE* Data,
271 IWTSVirtualChannelCallback** ppCallback)
274 SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback;
276 WINPR_UNUSED(pbAccept);
278 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)listener_callback->plugin;
279 WINPR_ASSERT(sshagent);
281 if (sshagent->connected)
283 WLog_ERR(TAG,
"Channel already connected, terminating.");
284 return ERROR_DEVICE_ALREADY_ATTACHED;
287 SSHAGENT_CHANNEL_CALLBACK* callback =
288 (SSHAGENT_CHANNEL_CALLBACK*)calloc(1,
sizeof(SSHAGENT_CHANNEL_CALLBACK));
292 WLog_ERR(TAG,
"calloc failed!");
293 return CHANNEL_RC_NO_MEMORY;
298 callback->agent_fd = connect_to_sshagent(listener_callback->agent_uds_path);
300 if (callback->agent_fd == -1)
303 return CHANNEL_RC_INITIALIZATION_ERROR;
306 InitializeCriticalSection(&callback->lock);
309 generic->iface.OnDataReceived = sshagent_on_data_received;
310 generic->iface.OnClose = sshagent_on_close;
311 generic->plugin = listener_callback->plugin;
312 generic->channel_mgr = listener_callback->channel_mgr;
313 generic->channel = pChannel;
314 callback->rdpcontext = listener_callback->rdpcontext;
315 callback->thread = CreateThread(
nullptr, 0, sshagent_read_thread, (
void*)callback, 0,
nullptr);
317 if (!callback->thread)
319 WLog_ERR(TAG,
"CreateThread failed!");
320 DeleteCriticalSection(&callback->lock);
322 return CHANNEL_RC_INITIALIZATION_ERROR;
325 *ppCallback = &callback->generic.iface;
326 sshagent->connected = TRUE;
327 return CHANNEL_RC_OK;
335static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
337 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
338 WINPR_ASSERT(sshagent);
339 WINPR_ASSERT(pChannelMgr);
341 sshagent->listener_callback =
342 (SSHAGENT_LISTENER_CALLBACK*)calloc(1,
sizeof(SSHAGENT_LISTENER_CALLBACK));
344 if (!sshagent->listener_callback)
346 WLog_ERR(TAG,
"calloc failed!");
347 return CHANNEL_RC_NO_MEMORY;
350 sshagent->listener_callback->rdpcontext = sshagent->rdpcontext;
351 sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection;
352 sshagent->listener_callback->plugin = pPlugin;
353 sshagent->listener_callback->channel_mgr = pChannelMgr;
355 sshagent->listener_callback->agent_uds_path = getenv(
"SSH_AUTH_SOCK");
357 if (sshagent->listener_callback->agent_uds_path ==
nullptr)
359 WLog_ERR(TAG,
"Environment variable $SSH_AUTH_SOCK undefined!");
360 free(sshagent->listener_callback);
361 sshagent->listener_callback =
nullptr;
362 return CHANNEL_RC_INITIALIZATION_ERROR;
365 return pChannelMgr->CreateListener(pChannelMgr,
"SSHAGENT", 0,
366 (IWTSListenerCallback*)sshagent->listener_callback,
nullptr);
374static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin)
376 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
378 return CHANNEL_RC_OK;
386FREERDP_ENTRY_POINT(UINT VCAPITYPE sshagent_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
388 UINT status = CHANNEL_RC_OK;
390 WINPR_ASSERT(pEntryPoints);
392 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints,
"sshagent");
396 sshagent = (SSHAGENT_PLUGIN*)calloc(1,
sizeof(SSHAGENT_PLUGIN));
400 WLog_ERR(TAG,
"calloc failed!");
401 return CHANNEL_RC_NO_MEMORY;
404 sshagent->iface.Initialize = sshagent_plugin_initialize;
405 sshagent->iface.Connected =
nullptr;
406 sshagent->iface.Disconnected =
nullptr;
407 sshagent->iface.Terminated = sshagent_plugin_terminated;
408 sshagent->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
409 status = pEntryPoints->RegisterPlugin(pEntryPoints,
"sshagent", &sshagent->iface);