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