FreeRDP
Loading...
Searching...
No Matches
rdpewa_main.c
1
19#include <freerdp/config.h>
20
21#include <stdio.h>
22#include <stdlib.h>
23
24#include <winpr/crt.h>
25#include <winpr/assert.h>
26#include <winpr/stream.h>
27#include <winpr/wlog.h>
28#include <winpr/print.h>
29#include <winpr/thread.h>
30#include <winpr/synch.h>
31
32#include "rdpewa_main.h"
33#include "rdpewa_cbor.h"
34#include "rdpewa_fido.h"
35#include <rdpewa-common.h>
36
37#include <freerdp/client/channels.h>
38#include <freerdp/channels/log.h>
39#include <freerdp/channels/rdpewa.h>
40
41#define TAG CHANNELS_TAG("rdpewa.client")
42
44typedef struct
45{
46 rdpContext* rdpContext;
47 RDPEWA_REQUEST request;
48 IWTSVirtualChannel* channel;
49} RDPEWA_ASYNC_WORK;
50
51static DWORD WINAPI rdpewa_async_webauthn_thread(LPVOID arg)
52{
53 RDPEWA_ASYNC_WORK* work = (RDPEWA_ASYNC_WORK*)arg;
54 wStream* s = nullptr;
55
56 switch (work->request.command)
57 {
58 case CTAPCBOR_RPC_COMMAND_WEB_AUTHN:
59 s = rdpewa_fido_webauthn(work->rdpContext, &work->request);
60 break;
61 default:
62 break;
63 }
64
65 if (s)
66 {
67 const size_t len = Stream_GetPosition(s);
68 WLog_DBG(TAG, "Async: sending %" PRIuz " byte response for command %" PRIu32, len,
69 work->request.command);
70 winpr_HexDump(TAG, WLOG_TRACE, Stream_Buffer(s), len > 512 ? 512 : len);
71
72 if (!freerdp_shall_disconnect_context(work->rdpContext))
73 {
74 UINT status =
75 work->channel->Write(work->channel, (ULONG)len, Stream_Buffer(s), nullptr);
76 if (status != CHANNEL_RC_OK)
77 WLog_ERR(TAG, "Async: Write failed with 0x%08" PRIx32, status);
78 }
79 }
80 else
81 {
82 WLog_ERR(TAG, "Async: FIDO operation failed for command %" PRIu32, work->request.command);
83 }
84
85 Stream_Free(s, TRUE);
86 free(work->request.request);
87 free(work);
88 return 0;
89}
90
96static UINT rdpewa_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
97{
98 RDPEWA_CHANNEL_CALLBACK* ecb = (RDPEWA_CHANNEL_CALLBACK*)pChannelCallback;
99 GENERIC_CHANNEL_CALLBACK* callback = &ecb->base;
100 const BYTE* pBuffer = Stream_ConstPointer(data);
101 const size_t cbSize = Stream_GetRemainingLength(data);
102 wStream* response = nullptr;
103 RDPEWA_REQUEST request = WINPR_C_ARRAY_INIT;
104
105 WINPR_ASSERT(callback);
106 WINPR_ASSERT(callback->channel);
107 WINPR_ASSERT(callback->channel->Write);
108
109 WLog_DBG(TAG, "Received %" PRIuz " bytes from server", cbSize);
110 winpr_HexDump(TAG, WLOG_TRACE, pBuffer, cbSize > 2048 ? 2048 : cbSize);
111
112 if (!rdpewa_cbor_decode_request(pBuffer, cbSize, &request))
113 {
114 WLog_ERR(TAG, "Failed to decode CBOR request");
115 free(request.request);
116 return ERROR_INVALID_DATA;
117 }
118
119 WLog_DBG(TAG,
120 "Received command %" PRIu32 " flags 0x%08" PRIx32 " requestLen=%" PRIuz
121 " timeout=%" PRIu32,
122 request.command, request.flags, request.requestLen, request.timeout);
123
124 switch (request.command)
125 {
126 case CTAPCBOR_RPC_COMMAND_WEB_AUTHN:
127 {
128 /* Run FIDO operations on a worker thread to avoid blocking
129 * the DVC callback thread (which would freeze RDP rendering). */
130 RDPEWA_PLUGIN* rdpewa = (RDPEWA_PLUGIN*)callback->plugin;
131 RDPEWA_ASYNC_WORK* work = calloc(1, sizeof(*work));
132 if (!work)
133 {
134 free(request.request);
135 return ERROR_INTERNAL_ERROR;
136 }
137 work->rdpContext = rdpewa->rdp_context;
138 work->request = request;
139 work->channel = callback->channel;
140 /* Transfer ownership of request.request to the worker */
141 request.request = nullptr;
142
143 /* Wait for any previous in-flight operation to finish before
144 * starting a new one, to avoid use-after-free on the channel. */
145 if (ecb->workerThread)
146 {
147 WaitForSingleObject(ecb->workerThread, INFINITE);
148 CloseHandle(ecb->workerThread);
149 ecb->workerThread = nullptr;
150 }
151
152 ecb->workerThread =
153 CreateThread(nullptr, 0, rdpewa_async_webauthn_thread, work, 0, nullptr);
154 if (!ecb->workerThread)
155 {
156 free(work->request.request);
157 free(work);
158 return ERROR_INTERNAL_ERROR;
159 }
160 return CHANNEL_RC_OK;
161 }
162
163 case CTAPCBOR_RPC_COMMAND_IUVPAA:
164 response = rdpewa_fido_is_uvpaa();
165 break;
166
167 case CTAPCBOR_RPC_COMMAND_CANCEL_CUR_OP:
168 /* Cancel is best-effort; we don't track in-flight operations yet */
169 response = rdpewa_cbor_encode_hresult_response(S_OK);
170 break;
171
172 case CTAPCBOR_RPC_COMMAND_API_VERSION:
173 response = rdpewa_fido_api_version();
174 break;
175
176 case CTAPCBOR_RPC_COMMAND_GET_CREDENTIALS:
177 {
178 RDPEWA_PLUGIN* rdpewa = (RDPEWA_PLUGIN*)callback->plugin;
179 response = rdpewa_fido_get_credentials(rdpewa->rdp_context, request.rpId);
180 break;
181 }
182
183 case CTAPCBOR_RPC_COMMAND_GET_AUTHENTICATOR_LIST:
184 response = rdpewa_fido_get_authenticator_list();
185 break;
186
187 default:
188 WLog_WARN(TAG, "Unsupported command %" PRIu32, request.command);
189 response = rdpewa_cbor_encode_hresult_response(E_NOTIMPL);
190 break;
191 }
192
193 if (!response)
194 {
195 free(request.request);
196 return ERROR_INTERNAL_ERROR;
197 }
198
199 const size_t responseLen = Stream_GetPosition(response);
200 WLog_DBG(TAG, "Sending %" PRIuz " byte response for command %" PRIu32, responseLen,
201 request.command);
202 winpr_HexDump(TAG, WLOG_TRACE, Stream_Buffer(response), responseLen > 512 ? 512 : responseLen);
203 UINT status = callback->channel->Write(callback->channel, (ULONG)responseLen,
204 Stream_Buffer(response), nullptr);
205 if (status != CHANNEL_RC_OK)
206 WLog_ERR(TAG, "Write failed with 0x%08" PRIx32, status);
207 Stream_Free(response, TRUE);
208 free(request.request);
209 return status;
210}
211
217static UINT rdpewa_on_open(IWTSVirtualChannelCallback* pChannelCallback)
218{
219 WINPR_UNUSED(pChannelCallback);
220 return CHANNEL_RC_OK;
221}
222
228static UINT rdpewa_on_close(IWTSVirtualChannelCallback* pChannelCallback)
229{
230 RDPEWA_CHANNEL_CALLBACK* ecb = (RDPEWA_CHANNEL_CALLBACK*)pChannelCallback;
231
232 /* Wait for any in-flight FIDO operation to complete */
233 if (ecb->workerThread)
234 {
235 WLog_DBG(TAG, "Waiting for worker thread to finish...");
236 WaitForSingleObject(ecb->workerThread, INFINITE);
237 CloseHandle(ecb->workerThread);
238 ecb->workerThread = nullptr;
239 }
240
241 free(ecb);
242
243 return CHANNEL_RC_OK;
244}
245
246static const IWTSVirtualChannelCallback rdpewa_callbacks = { rdpewa_on_data_received,
247 rdpewa_on_open, rdpewa_on_close,
248 nullptr };
249
250static UINT rdpewa_init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext,
251 rdpSettings* settings)
252{
253 WINPR_ASSERT(base);
254 WINPR_UNUSED(settings);
255
256 RDPEWA_PLUGIN* rdpewa = (RDPEWA_PLUGIN*)base;
257 rdpewa->rdp_context = rcontext;
258 return CHANNEL_RC_OK;
259}
260
266FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpewa_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
267{
268 return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEWA_DVC_CHANNEL_NAME,
269 sizeof(RDPEWA_PLUGIN), sizeof(RDPEWA_CHANNEL_CALLBACK),
270 &rdpewa_callbacks, rdpewa_init_plugin_cb, nullptr);
271}
Per-channel callback with async work tracking.
Definition rdpewa_main.h:40
Decoded MS-RDPEWA request message.
Definition rdpewa_cbor.h:29