FreeRDP
Loading...
Searching...
No Matches
pf_channel_smartcard.c
1
21#include <winpr/assert.h>
22#include <winpr/string.h>
23
24#include <winpr/smartcard.h>
25#include <winpr/pool.h>
26
27#include <freerdp/server/proxy/proxy_log.h>
28#include <freerdp/emulate/scard/smartcard_emulate.h>
29#include <freerdp/channels/scard.h>
30#include <freerdp/channels/rdpdr.h>
31#include <freerdp/utils/rdpdr_utils.h>
32
33#include <freerdp/utils/smartcard_operations.h>
34#include <freerdp/utils/smartcard_call.h>
35
36#include "pf_channel_smartcard.h"
37#include "pf_channel_rdpdr.h"
38
39#define TAG PROXY_TAG("channel.scard")
40
41#define SCARD_SVC_CHANNEL_NAME "SCARD"
42
43typedef struct
44{
46 scard_call_context* callctx;
47 wArrayList* workObjects;
48} pf_channel_client_context;
49
50typedef struct
51{
53 wStream* out;
54 pClientContext* pc;
55 wLog* log;
56 pf_scard_send_fkt_t send_fkt;
57} pf_channel_client_queue_element;
58
59WINPR_ATTR_NODISCARD
60static pf_channel_client_context* scard_get_client_context(pClientContext* pc)
61{
62 pf_channel_client_context* scard = nullptr;
63
64 WINPR_ASSERT(pc);
65 WINPR_ASSERT(pc->interceptContextMap);
66
67 scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
68 if (!scard)
69 WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME);
70 return scard;
71}
72
73WINPR_ATTR_NODISCARD
74static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op,
75 NTSTATUS ioStatus)
76{
77 UINT16 component = 0;
78 UINT16 packetid = 0;
79 UINT32 dID = 0;
80 UINT32 cID = 0;
81 size_t pos = 0;
82
83 WINPR_ASSERT(op);
84 WINPR_ASSERT(out);
85
86 pos = Stream_GetPosition(out);
87 Stream_ResetPosition(out);
88 if (!Stream_CheckAndLogRequiredLength(TAG, out, 16))
89 return FALSE;
90
91 Stream_Read_UINT16(out, component);
92 Stream_Read_UINT16(out, packetid);
93
94 Stream_Read_UINT32(out, dID);
95 Stream_Read_UINT32(out, cID);
96
97 WINPR_ASSERT(component == RDPDR_CTYP_CORE);
98 WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION);
99 WINPR_ASSERT(dID == op->deviceID);
100 WINPR_ASSERT(cID == op->completionID);
101
102 Stream_Write_INT32(out, ioStatus);
103 return Stream_SetPosition(out, pos);
104}
105
106struct thread_arg
107{
108 pf_channel_client_context* scard;
109 pf_channel_client_queue_element* e;
110};
111
112static void queue_free(void* obj);
113
114WINPR_ATTR_MALLOC(queue_free, 1)
115WINPR_ATTR_NODISCARD
116static void* queue_copy(const void* obj);
117
118static VOID irp_thread(WINPR_ATTR_UNUSED PTP_CALLBACK_INSTANCE Instance, PVOID Context,
119 PTP_WORK Work)
120{
121 struct thread_arg* arg = Context;
122 pf_channel_client_context* scard = arg->scard;
123 {
124 NTSTATUS ioStatus = 0;
125 LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus,
126 &arg->e->op);
127 if (rc == CHANNEL_RC_OK)
128 {
129 if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus))
130 arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out);
131 }
132 }
133 queue_free(arg->e);
134 free(arg);
135 ArrayList_Remove(scard->workObjects, Work);
136}
137
138WINPR_ATTR_NODISCARD
139static BOOL start_irp_thread(pf_channel_client_context* scard,
140 const pf_channel_client_queue_element* e)
141{
142 PTP_WORK work = nullptr;
143 struct thread_arg* arg = calloc(1, sizeof(struct thread_arg));
144 if (!arg)
145 return FALSE;
146 arg->scard = scard;
147 arg->e = queue_copy(e);
148 if (!arg->e)
149 goto fail;
150
151 work = CreateThreadpoolWork(irp_thread, arg, nullptr);
152 if (!work)
153 goto fail;
154 if (!ArrayList_Append(scard->workObjects, work))
155 goto fail;
156 SubmitThreadpoolWork(work);
157
158 return TRUE;
159
160fail:
161 if (work)
162 CloseThreadpoolWork(work);
163 if (arg)
164 queue_free(arg->e);
165 free(arg);
166 return FALSE;
167}
168
169BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out,
170 pf_scard_send_fkt_t send_fkt)
171{
172 BOOL rc = FALSE;
173 LONG status = 0;
174 UINT32 FileId = 0;
175 UINT32 CompletionId = 0;
176 NTSTATUS ioStatus = 0;
177 pf_channel_client_queue_element e = WINPR_C_ARRAY_INIT;
178 pf_channel_client_context* scard = scard_get_client_context(pc);
179
180 WINPR_ASSERT(log);
181 WINPR_ASSERT(send_fkt);
182 WINPR_ASSERT(s);
183
184 if (!scard)
185 return FALSE;
186
187 e.log = log;
188 e.pc = pc;
189 e.out = out;
190 e.send_fkt = send_fkt;
191
192 /* Skip IRP header */
193 if (!Stream_CheckAndLogRequiredLength(TAG, s, 20))
194 return FALSE;
195 else
196 {
197 const uint32_t DeviceId = Stream_Get_UINT32(s); /* DeviceId (4 bytes) */
198 FileId = Stream_Get_UINT32(s); /* FileId (4 bytes) */
199 CompletionId = Stream_Get_UINT32(s); /* CompletionId (4 bytes) */
200 const uint32_t MajorFunction = Stream_Get_UINT32(s); /* MajorFunction (4 bytes) */
201 const uint32_t MinorFunction = Stream_Get_UINT32(s); /* MinorFunction (4 bytes) */
202
203 if (MajorFunction != IRP_MJ_DEVICE_CONTROL)
204 {
205 WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %s [0x%08" PRIx32 "]",
206 SCARD_SVC_CHANNEL_NAME, rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL),
207 rdpdr_irp_string(MajorFunction), MinorFunction);
208 return FALSE;
209 }
210 e.op.completionID = CompletionId;
211 e.op.deviceID = DeviceId;
212
213 if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0))
214 return FALSE;
215 }
216
217 status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op);
218 if (status != 0)
219 goto fail;
220
221 switch (e.op.ioControlCode)
222 {
223 case SCARD_IOCTL_LISTREADERGROUPSA:
224 case SCARD_IOCTL_LISTREADERGROUPSW:
225 case SCARD_IOCTL_LISTREADERSA:
226 case SCARD_IOCTL_LISTREADERSW:
227 case SCARD_IOCTL_LOCATECARDSA:
228 case SCARD_IOCTL_LOCATECARDSW:
229 case SCARD_IOCTL_LOCATECARDSBYATRA:
230 case SCARD_IOCTL_LOCATECARDSBYATRW:
231 case SCARD_IOCTL_GETSTATUSCHANGEA:
232 case SCARD_IOCTL_GETSTATUSCHANGEW:
233 case SCARD_IOCTL_CONNECTA:
234 case SCARD_IOCTL_CONNECTW:
235 case SCARD_IOCTL_RECONNECT:
236 case SCARD_IOCTL_DISCONNECT:
237 case SCARD_IOCTL_BEGINTRANSACTION:
238 case SCARD_IOCTL_ENDTRANSACTION:
239 case SCARD_IOCTL_STATE:
240 case SCARD_IOCTL_STATUSA:
241 case SCARD_IOCTL_STATUSW:
242 case SCARD_IOCTL_TRANSMIT:
243 case SCARD_IOCTL_CONTROL:
244 case SCARD_IOCTL_GETATTRIB:
245 case SCARD_IOCTL_SETATTRIB:
246 if (!start_irp_thread(scard, &e))
247 goto fail;
248 return TRUE;
249
250 default:
251 status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op);
252 if (status != 0)
253 goto fail;
254 if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus))
255 goto fail;
256 break;
257 }
258
259 rc = send_fkt(log, pc, out) == CHANNEL_RC_OK;
260
261fail:
262 smartcard_operation_free(&e.op, FALSE);
263 return rc;
264}
265
266BOOL pf_channel_smartcard_server_handle(WINPR_ATTR_UNUSED pServerContext* ps,
267 WINPR_ATTR_UNUSED wStream* s)
268{
269 WLog_ERR(TAG, "TODO: unimplemented");
270 return TRUE;
271}
272
273static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset)
274{
275 WINPR_ASSERT(scard);
276 if (scard->callctx)
277 smartcard_call_context_signal_stop(scard->callctx, FALSE);
278
279 if (scard->workObjects)
280 {
281 while (ArrayList_Count(scard->workObjects) > 0)
282 {
283 PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0);
284 if (!work)
285 continue;
286 WaitForThreadpoolWorkCallbacks(work, TRUE);
287 }
288 }
289
290 if (scard->callctx)
291 smartcard_call_context_signal_stop(scard->callctx, reset);
292}
293
294static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base)
295{
296 pf_channel_client_context* entry = (pf_channel_client_context*)base;
297 if (!entry)
298 return;
299
300 /* Set the stop event.
301 * All threads waiting in blocking operations will abort at the next
302 * available polling slot */
303 channel_stop_and_wait(entry, FALSE);
304 ArrayList_Free(entry->workObjects);
305
306 smartcard_call_context_free(entry->callctx);
307 free(entry);
308}
309
310static void queue_free(void* obj)
311{
312 pf_channel_client_queue_element* element = obj;
313 if (!element)
314 return;
315 smartcard_operation_free(&element->op, FALSE);
316 Stream_Free(element->out, TRUE);
317 free(element);
318}
319
320WINPR_ATTR_MALLOC(queue_free, 1)
321WINPR_ATTR_NODISCARD
322static void* queue_copy(const void* obj)
323{
324 const pf_channel_client_queue_element* other = obj;
325 pf_channel_client_queue_element* copy = nullptr;
326 if (!other)
327 return nullptr;
328 copy = calloc(1, sizeof(pf_channel_client_queue_element));
329 if (!copy)
330 return nullptr;
331
332 *copy = *other;
333 copy->out = Stream_New(nullptr, Stream_Capacity(other->out));
334 if (!copy->out)
335 goto fail;
336 Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out));
337 return copy;
338fail:
339 queue_free(copy);
340 return nullptr;
341}
342
343static void work_object_free(void* arg)
344{
345 PTP_WORK work = arg;
346 CloseThreadpoolWork(work);
347}
348
349BOOL pf_channel_smartcard_client_new(pClientContext* pc)
350{
351 WINPR_ASSERT(pc);
352 WINPR_ASSERT(pc->interceptContextMap);
353
354 pf_channel_client_context* scard = calloc(1, sizeof(pf_channel_client_context));
355 if (!scard)
356 return FALSE;
357 scard->base.free = pf_channel_scard_client_context_free;
358 scard->callctx = smartcard_call_context_new(pc->context.settings);
359 if (!scard->callctx)
360 goto fail;
361
362 scard->workObjects = ArrayList_New(TRUE);
363 if (!scard->workObjects)
364 goto fail;
365
366 {
367 wObject* obj = ArrayList_Object(scard->workObjects);
368 WINPR_ASSERT(obj);
369 obj->fnObjectFree = work_object_free;
370 }
371
372 if (!HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard))
373 goto fail;
374
375 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert takes ownership of rdpdr
376 return TRUE;
377fail:
378 pf_channel_scard_client_context_free(&scard->base);
379 return FALSE;
380}
381
382void pf_channel_smartcard_client_free(pClientContext* pc)
383{
384 WINPR_ASSERT(pc);
385 WINPR_ASSERT(pc->interceptContextMap);
386 HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME);
387}
388
389BOOL pf_channel_smartcard_client_emulate(pClientContext* pc)
390{
391 pf_channel_client_context* scard = scard_get_client_context(pc);
392 if (!scard)
393 return FALSE;
394 return smartcard_call_is_configured(scard->callctx);
395}
396
397BOOL pf_channel_smartcard_client_reset(pClientContext* pc)
398{
399 pf_channel_client_context* scard = scard_get_client_context(pc);
400 if (!scard)
401 return TRUE;
402
403 channel_stop_and_wait(scard, TRUE);
404 return TRUE;
405}
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:58