FreeRDP
Loading...
Searching...
No Matches
sshagent_main.c
1
23/*
24 * sshagent_main.c: DVC plugin to forward queries from RDP to the ssh-agent
25 *
26 * This relays data to and from an ssh-agent program equivalent running on the
27 * RDP server to an ssh-agent running locally. Unlike the normal ssh-agent,
28 * which sends data over an SSH channel, the data is send over an RDP dynamic
29 * virtual channel.
30 *
31 * protocol specification:
32 * Forward data verbatim over RDP dynamic virtual channel named "sshagent"
33 * between a ssh client on the xrdp server and the real ssh-agent where
34 * the RDP client is running. Each connection by a separate client to
35 * xrdp-ssh-agent gets a separate DVC invocation.
36 */
37
38#include <freerdp/config.h>
39
40#include <stdio.h>
41#include <stdlib.h>
42#include <sys/types.h>
43#include <sys/socket.h>
44#include <sys/un.h>
45#include <pwd.h>
46#include <unistd.h>
47#include <errno.h>
48
49#include <winpr/crt.h>
50#include <winpr/assert.h>
51#include <winpr/synch.h>
52#include <winpr/thread.h>
53#include <winpr/stream.h>
54
55#include "sshagent_main.h"
56
57#include <freerdp/freerdp.h>
58#include <freerdp/client/channels.h>
59#include <freerdp/channels/log.h>
60
61#define TAG CHANNELS_TAG("sshagent.client")
62
63typedef struct
64{
65 IWTSListenerCallback iface;
66
67 IWTSPlugin* plugin;
68 IWTSVirtualChannelManager* channel_mgr;
69
70 rdpContext* rdpcontext;
71 const char* agent_uds_path;
72} SSHAGENT_LISTENER_CALLBACK;
73
74typedef struct
75{
77
78 rdpContext* rdpcontext;
79 int agent_fd;
80 HANDLE thread;
82} SSHAGENT_CHANNEL_CALLBACK;
83
84typedef struct
85{
86 IWTSPlugin iface;
87
88 SSHAGENT_LISTENER_CALLBACK* listener_callback;
89
90 rdpContext* rdpcontext;
91} SSHAGENT_PLUGIN;
92
98static int connect_to_sshagent(const char* udspath)
99{
100 WINPR_ASSERT(udspath);
101
102 int agent_fd = socket(AF_UNIX, SOCK_STREAM, 0);
103
104 if (agent_fd == -1)
105 {
106 WLog_ERR(TAG, "Can't open Unix domain socket!");
107 return -1;
108 }
109
110 struct sockaddr_un addr = { 0 };
111
112 addr.sun_family = AF_UNIX;
113
114 strncpy(addr.sun_path, udspath, sizeof(addr.sun_path) - 1);
115
116 int rc = connect(agent_fd, (struct sockaddr*)&addr, sizeof(addr));
117
118 if (rc != 0)
119 {
120 WLog_ERR(TAG, "Can't connect to Unix domain socket \"%s\"!", udspath);
121 close(agent_fd);
122 return -1;
123 }
124
125 return agent_fd;
126}
127
134static DWORD WINAPI sshagent_read_thread(LPVOID data)
135{
136 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)data;
137 WINPR_ASSERT(callback);
138
139 BYTE buffer[4096] = { 0 };
140 int going = 1;
141 UINT status = CHANNEL_RC_OK;
142
143 while (going)
144 {
145 const ssize_t bytes_read = read(callback->agent_fd, buffer, sizeof(buffer));
146
147 if (bytes_read == 0)
148 {
149 /* Socket closed cleanly at other end */
150 going = 0;
151 }
152 else if (bytes_read < 0)
153 {
154 if (errno != EINTR)
155 {
156 WLog_ERR(TAG, "Error reading from sshagent, errno=%d", errno);
157 status = ERROR_READ_FAULT;
158 going = 0;
159 }
160 }
161 else if ((size_t)bytes_read > ULONG_MAX)
162 {
163 status = ERROR_READ_FAULT;
164 going = 0;
165 }
166 else
167 {
168 /* Something read: forward to virtual channel */
169 IWTSVirtualChannel* channel = callback->generic.channel;
170 status = channel->Write(channel, (ULONG)bytes_read, buffer, NULL);
171
172 if (status != CHANNEL_RC_OK)
173 {
174 going = 0;
175 }
176 }
177 }
178
179 close(callback->agent_fd);
180
181 if (status != CHANNEL_RC_OK)
182 setChannelError(callback->rdpcontext, status, "sshagent_read_thread reported an error");
183
184 ExitThread(status);
185 return status;
186}
187
193static UINT sshagent_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
194{
195 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
196 WINPR_ASSERT(callback);
197
198 BYTE* pBuffer = Stream_Pointer(data);
199 size_t cbSize = Stream_GetRemainingLength(data);
200 BYTE* pos = pBuffer;
201 /* Forward what we have received to the ssh agent */
202 size_t bytes_to_write = cbSize;
203 errno = 0;
204
205 while (bytes_to_write > 0)
206 {
207 const ssize_t bytes_written = write(callback->agent_fd, pos, bytes_to_write);
208
209 if (bytes_written < 0)
210 {
211 if (errno != EINTR)
212 {
213 WLog_ERR(TAG, "Error writing to sshagent, errno=%d", errno);
214 return ERROR_WRITE_FAULT;
215 }
216 }
217 else
218 {
219 bytes_to_write -= WINPR_ASSERTING_INT_CAST(size_t, bytes_written);
220 pos += bytes_written;
221 }
222 }
223
224 /* Consume stream */
225 Stream_Seek(data, cbSize);
226 return CHANNEL_RC_OK;
227}
228
234static UINT sshagent_on_close(IWTSVirtualChannelCallback* pChannelCallback)
235{
236 SSHAGENT_CHANNEL_CALLBACK* callback = (SSHAGENT_CHANNEL_CALLBACK*)pChannelCallback;
237 WINPR_ASSERT(callback);
238
239 /* Call shutdown() to wake up the read() in sshagent_read_thread(). */
240 shutdown(callback->agent_fd, SHUT_RDWR);
241 EnterCriticalSection(&callback->lock);
242
243 if (WaitForSingleObject(callback->thread, INFINITE) == WAIT_FAILED)
244 {
245 UINT error = GetLastError();
246 WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
247 return error;
248 }
249
250 (void)CloseHandle(callback->thread);
251 LeaveCriticalSection(&callback->lock);
252 DeleteCriticalSection(&callback->lock);
253 free(callback);
254 return CHANNEL_RC_OK;
255}
256
262// NOLINTBEGIN(readability-non-const-parameter)
263static UINT sshagent_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
264 IWTSVirtualChannel* pChannel, BYTE* Data,
265 BOOL* pbAccept,
266 IWTSVirtualChannelCallback** ppCallback)
267// NOLINTEND(readability-non-const-parameter)
268{
269 SSHAGENT_LISTENER_CALLBACK* listener_callback = (SSHAGENT_LISTENER_CALLBACK*)pListenerCallback;
270 WINPR_UNUSED(Data);
271 WINPR_UNUSED(pbAccept);
272
273 SSHAGENT_CHANNEL_CALLBACK* callback =
274 (SSHAGENT_CHANNEL_CALLBACK*)calloc(1, sizeof(SSHAGENT_CHANNEL_CALLBACK));
275
276 if (!callback)
277 {
278 WLog_ERR(TAG, "calloc failed!");
279 return CHANNEL_RC_NO_MEMORY;
280 }
281
282 /* Now open a connection to the local ssh-agent. Do this for each
283 * connection to the plugin in case we mess up the agent session. */
284 callback->agent_fd = connect_to_sshagent(listener_callback->agent_uds_path);
285
286 if (callback->agent_fd == -1)
287 {
288 free(callback);
289 return CHANNEL_RC_INITIALIZATION_ERROR;
290 }
291
292 InitializeCriticalSection(&callback->lock);
293
294 GENERIC_CHANNEL_CALLBACK* generic = &callback->generic;
295 generic->iface.OnDataReceived = sshagent_on_data_received;
296 generic->iface.OnClose = sshagent_on_close;
297 generic->plugin = listener_callback->plugin;
298 generic->channel_mgr = listener_callback->channel_mgr;
299 generic->channel = pChannel;
300 callback->rdpcontext = listener_callback->rdpcontext;
301 callback->thread = CreateThread(NULL, 0, sshagent_read_thread, (void*)callback, 0, NULL);
302
303 if (!callback->thread)
304 {
305 WLog_ERR(TAG, "CreateThread failed!");
306 DeleteCriticalSection(&callback->lock);
307 free(callback);
308 return CHANNEL_RC_INITIALIZATION_ERROR;
309 }
310
311 *ppCallback = (IWTSVirtualChannelCallback*)callback;
312 return CHANNEL_RC_OK;
313}
314
320static UINT sshagent_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
321{
322 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
323 WINPR_ASSERT(sshagent);
324 WINPR_ASSERT(pChannelMgr);
325
326 sshagent->listener_callback =
327 (SSHAGENT_LISTENER_CALLBACK*)calloc(1, sizeof(SSHAGENT_LISTENER_CALLBACK));
328
329 if (!sshagent->listener_callback)
330 {
331 WLog_ERR(TAG, "calloc failed!");
332 return CHANNEL_RC_NO_MEMORY;
333 }
334
335 sshagent->listener_callback->rdpcontext = sshagent->rdpcontext;
336 sshagent->listener_callback->iface.OnNewChannelConnection = sshagent_on_new_channel_connection;
337 sshagent->listener_callback->plugin = pPlugin;
338 sshagent->listener_callback->channel_mgr = pChannelMgr;
339 // NOLINTNEXTLINE(concurrency-mt-unsafe)
340 sshagent->listener_callback->agent_uds_path = getenv("SSH_AUTH_SOCK");
341
342 if (sshagent->listener_callback->agent_uds_path == NULL)
343 {
344 WLog_ERR(TAG, "Environment variable $SSH_AUTH_SOCK undefined!");
345 free(sshagent->listener_callback);
346 sshagent->listener_callback = NULL;
347 return CHANNEL_RC_INITIALIZATION_ERROR;
348 }
349
350 return pChannelMgr->CreateListener(pChannelMgr, "SSHAGENT", 0,
351 (IWTSListenerCallback*)sshagent->listener_callback, NULL);
352}
353
359static UINT sshagent_plugin_terminated(IWTSPlugin* pPlugin)
360{
361 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pPlugin;
362 free(sshagent);
363 return CHANNEL_RC_OK;
364}
365
371FREERDP_ENTRY_POINT(UINT VCAPITYPE sshagent_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
372{
373 UINT status = CHANNEL_RC_OK;
374
375 WINPR_ASSERT(pEntryPoints);
376
377 SSHAGENT_PLUGIN* sshagent = (SSHAGENT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "sshagent");
378
379 if (!sshagent)
380 {
381 sshagent = (SSHAGENT_PLUGIN*)calloc(1, sizeof(SSHAGENT_PLUGIN));
382
383 if (!sshagent)
384 {
385 WLog_ERR(TAG, "calloc failed!");
386 return CHANNEL_RC_NO_MEMORY;
387 }
388
389 sshagent->iface.Initialize = sshagent_plugin_initialize;
390 sshagent->iface.Connected = NULL;
391 sshagent->iface.Disconnected = NULL;
392 sshagent->iface.Terminated = sshagent_plugin_terminated;
393 sshagent->rdpcontext = pEntryPoints->GetRdpContext(pEntryPoints);
394 status = pEntryPoints->RegisterPlugin(pEntryPoints, "sshagent", &sshagent->iface);
395 }
396
397 return status;
398}