diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /server/proxy/channels/pf_channel_smartcard.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/proxy/channels/pf_channel_smartcard.c')
-rw-r--r-- | server/proxy/channels/pf_channel_smartcard.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/server/proxy/channels/pf_channel_smartcard.c b/server/proxy/channels/pf_channel_smartcard.c new file mode 100644 index 0000000..1d2bdfe --- /dev/null +++ b/server/proxy/channels/pf_channel_smartcard.c @@ -0,0 +1,397 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * FreeRDP Proxy Server + * + * Copyright 2021 Armin Novak <armin.novak@thincast.com> + * Copyright 2021 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> +#include <winpr/string.h> + +#include <winpr/smartcard.h> +#include <winpr/pool.h> + +#include <freerdp/server/proxy/proxy_log.h> +#include <freerdp/emulate/scard/smartcard_emulate.h> +#include <freerdp/channels/scard.h> +#include <freerdp/channels/rdpdr.h> +#include <freerdp/utils/rdpdr_utils.h> + +#include <freerdp/utils/smartcard_operations.h> +#include <freerdp/utils/smartcard_call.h> + +#include "pf_channel_smartcard.h" +#include "pf_channel_rdpdr.h" + +#define TAG PROXY_TAG("channel.scard") + +#define SCARD_SVC_CHANNEL_NAME "SCARD" + +typedef struct +{ + InterceptContextMapEntry base; + scard_call_context* callctx; + PTP_POOL ThreadPool; + TP_CALLBACK_ENVIRON ThreadPoolEnv; + wArrayList* workObjects; +} pf_channel_client_context; + +typedef struct +{ + SMARTCARD_OPERATION op; + wStream* out; + pClientContext* pc; + wLog* log; + pf_scard_send_fkt_t send_fkt; +} pf_channel_client_queue_element; + +static pf_channel_client_context* scard_get_client_context(pClientContext* pc) +{ + pf_channel_client_context* scard = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = HashTable_GetItemValue(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); + if (!scard) + WLog_WARN(TAG, "[%s] missing in pc->interceptContextMap", SCARD_SVC_CHANNEL_NAME); + return scard; +} + +static BOOL pf_channel_client_write_iostatus(wStream* out, const SMARTCARD_OPERATION* op, + UINT32 ioStatus) +{ + UINT16 component = 0; + UINT16 packetid = 0; + UINT32 dID = 0; + UINT32 cID = 0; + size_t pos = 0; + + WINPR_ASSERT(op); + WINPR_ASSERT(out); + + pos = Stream_GetPosition(out); + Stream_SetPosition(out, 0); + if (!Stream_CheckAndLogRequiredLength(TAG, out, 16)) + return FALSE; + + Stream_Read_UINT16(out, component); + Stream_Read_UINT16(out, packetid); + + Stream_Read_UINT32(out, dID); + Stream_Read_UINT32(out, cID); + + WINPR_ASSERT(component == RDPDR_CTYP_CORE); + WINPR_ASSERT(packetid == PAKID_CORE_DEVICE_IOCOMPLETION); + WINPR_ASSERT(dID == op->deviceID); + WINPR_ASSERT(cID == op->completionID); + + Stream_Write_UINT32(out, ioStatus); + Stream_SetPosition(out, pos); + return TRUE; +} + +struct thread_arg +{ + pf_channel_client_context* scard; + pf_channel_client_queue_element* e; +}; + +static void queue_free(void* obj); +static void* queue_copy(const void* obj); + +static VOID irp_thread(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) +{ + struct thread_arg* arg = Context; + pf_channel_client_context* scard = arg->scard; + { + UINT32 ioStatus = 0; + LONG rc = smartcard_irp_device_control_call(arg->scard->callctx, arg->e->out, &ioStatus, + &arg->e->op); + if (rc == CHANNEL_RC_OK) + { + if (pf_channel_client_write_iostatus(arg->e->out, &arg->e->op, ioStatus)) + arg->e->send_fkt(arg->e->log, arg->e->pc, arg->e->out); + } + } + queue_free(arg->e); + free(arg); + ArrayList_Remove(scard->workObjects, Work); +} + +static BOOL start_irp_thread(pf_channel_client_context* scard, + const pf_channel_client_queue_element* e) +{ + PTP_WORK work = NULL; + struct thread_arg* arg = calloc(1, sizeof(struct thread_arg)); + if (!arg) + return FALSE; + arg->scard = scard; + arg->e = queue_copy(e); + if (!arg->e) + goto fail; + + work = CreateThreadpoolWork(irp_thread, arg, &scard->ThreadPoolEnv); + if (!work) + goto fail; + ArrayList_Append(scard->workObjects, work); + SubmitThreadpoolWork(work); + + return TRUE; + +fail: + if (arg) + queue_free(arg->e); + free(arg); + return FALSE; +} + +BOOL pf_channel_smartcard_client_handle(wLog* log, pClientContext* pc, wStream* s, wStream* out, + pf_scard_send_fkt_t send_fkt) +{ + BOOL rc = FALSE; + LONG status = 0; + UINT32 FileId = 0; + UINT32 CompletionId = 0; + UINT32 ioStatus = 0; + pf_channel_client_queue_element e = { 0 }; + pf_channel_client_context* scard = scard_get_client_context(pc); + + WINPR_ASSERT(log); + WINPR_ASSERT(send_fkt); + WINPR_ASSERT(s); + + if (!scard) + return FALSE; + + e.log = log; + e.pc = pc; + e.out = out; + e.send_fkt = send_fkt; + + /* Skip IRP header */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) + return FALSE; + else + { + UINT32 DeviceId = 0; + UINT32 MajorFunction = 0; + UINT32 MinorFunction = 0; + + Stream_Read_UINT32(s, DeviceId); /* DeviceId (4 bytes) */ + Stream_Read_UINT32(s, FileId); /* FileId (4 bytes) */ + Stream_Read_UINT32(s, CompletionId); /* CompletionId (4 bytes) */ + Stream_Read_UINT32(s, MajorFunction); /* MajorFunction (4 bytes) */ + Stream_Read_UINT32(s, MinorFunction); /* MinorFunction (4 bytes) */ + + if (MajorFunction != IRP_MJ_DEVICE_CONTROL) + { + WLog_WARN(TAG, "[%s] Invalid IRP received, expected %s, got %2", SCARD_SVC_CHANNEL_NAME, + rdpdr_irp_string(IRP_MJ_DEVICE_CONTROL), rdpdr_irp_string(MajorFunction)); + return FALSE; + } + e.op.completionID = CompletionId; + e.op.deviceID = DeviceId; + + if (!rdpdr_write_iocompletion_header(out, DeviceId, CompletionId, 0)) + return FALSE; + } + + status = smartcard_irp_device_control_decode(s, CompletionId, FileId, &e.op); + if (status != 0) + goto fail; + + switch (e.op.ioControlCode) + { + case SCARD_IOCTL_LISTREADERGROUPSA: + case SCARD_IOCTL_LISTREADERGROUPSW: + case SCARD_IOCTL_LISTREADERSA: + case SCARD_IOCTL_LISTREADERSW: + case SCARD_IOCTL_LOCATECARDSA: + case SCARD_IOCTL_LOCATECARDSW: + case SCARD_IOCTL_LOCATECARDSBYATRA: + case SCARD_IOCTL_LOCATECARDSBYATRW: + case SCARD_IOCTL_GETSTATUSCHANGEA: + case SCARD_IOCTL_GETSTATUSCHANGEW: + case SCARD_IOCTL_CONNECTA: + case SCARD_IOCTL_CONNECTW: + case SCARD_IOCTL_RECONNECT: + case SCARD_IOCTL_DISCONNECT: + case SCARD_IOCTL_BEGINTRANSACTION: + case SCARD_IOCTL_ENDTRANSACTION: + case SCARD_IOCTL_STATE: + case SCARD_IOCTL_STATUSA: + case SCARD_IOCTL_STATUSW: + case SCARD_IOCTL_TRANSMIT: + case SCARD_IOCTL_CONTROL: + case SCARD_IOCTL_GETATTRIB: + case SCARD_IOCTL_SETATTRIB: + if (!start_irp_thread(scard, &e)) + goto fail; + return TRUE; + + default: + status = smartcard_irp_device_control_call(scard->callctx, out, &ioStatus, &e.op); + if (status != 0) + goto fail; + if (!pf_channel_client_write_iostatus(out, &e.op, ioStatus)) + goto fail; + break; + } + + rc = send_fkt(log, pc, out) == CHANNEL_RC_OK; + +fail: + smartcard_operation_free(&e.op, FALSE); + return rc; +} + +BOOL pf_channel_smartcard_server_handle(pServerContext* ps, wStream* s) +{ + WLog_ERR(TAG, "TODO: unimplemented"); + return TRUE; +} + +static void channel_stop_and_wait(pf_channel_client_context* scard, BOOL reset) +{ + WINPR_ASSERT(scard); + smartcard_call_context_signal_stop(scard->callctx, FALSE); + + while (ArrayList_Count(scard->workObjects) > 0) + { + PTP_WORK work = ArrayList_GetItem(scard->workObjects, 0); + if (!work) + continue; + WaitForThreadpoolWorkCallbacks(work, TRUE); + } + + smartcard_call_context_signal_stop(scard->callctx, reset); +} + +static void pf_channel_scard_client_context_free(InterceptContextMapEntry* base) +{ + pf_channel_client_context* entry = (pf_channel_client_context*)base; + if (!entry) + return; + + /* Set the stop event. + * All threads waiting in blocking operations will abort at the next + * available polling slot */ + channel_stop_and_wait(entry, FALSE); + ArrayList_Free(entry->workObjects); + CloseThreadpool(entry->ThreadPool); + DestroyThreadpoolEnvironment(&entry->ThreadPoolEnv); + + smartcard_call_context_free(entry->callctx); + free(entry); +} + +static void queue_free(void* obj) +{ + pf_channel_client_queue_element* element = obj; + if (!element) + return; + smartcard_operation_free(&element->op, FALSE); + Stream_Free(element->out, TRUE); + free(element); +} + +static void* queue_copy(const void* obj) +{ + const pf_channel_client_queue_element* other = obj; + pf_channel_client_queue_element* copy = NULL; + if (!other) + return NULL; + copy = calloc(1, sizeof(pf_channel_client_queue_element)); + if (!copy) + return NULL; + + *copy = *other; + copy->out = Stream_New(NULL, Stream_Capacity(other->out)); + if (!copy->out) + goto fail; + Stream_Write(copy->out, Stream_Buffer(other->out), Stream_GetPosition(other->out)); + return copy; +fail: + queue_free(copy); + return NULL; +} + +static void work_object_free(void* arg) +{ + PTP_WORK work = arg; + CloseThreadpoolWork(work); +} + +BOOL pf_channel_smartcard_client_new(pClientContext* pc) +{ + pf_channel_client_context* scard = NULL; + wObject* obj = NULL; + + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + + scard = calloc(1, sizeof(pf_channel_client_context)); + if (!scard) + return FALSE; + scard->base.free = pf_channel_scard_client_context_free; + scard->callctx = smartcard_call_context_new(pc->context.settings); + if (!scard->callctx) + goto fail; + + scard->workObjects = ArrayList_New(TRUE); + if (!scard->workObjects) + goto fail; + obj = ArrayList_Object(scard->workObjects); + WINPR_ASSERT(obj); + obj->fnObjectFree = work_object_free; + + scard->ThreadPool = CreateThreadpool(NULL); + if (!scard->ThreadPool) + goto fail; + InitializeThreadpoolEnvironment(&scard->ThreadPoolEnv); + SetThreadpoolCallbackPool(&scard->ThreadPoolEnv, scard->ThreadPool); + + return HashTable_Insert(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME, scard); +fail: + pf_channel_scard_client_context_free(&scard->base); + return FALSE; +} + +void pf_channel_smartcard_client_free(pClientContext* pc) +{ + WINPR_ASSERT(pc); + WINPR_ASSERT(pc->interceptContextMap); + HashTable_Remove(pc->interceptContextMap, SCARD_SVC_CHANNEL_NAME); +} + +BOOL pf_channel_smartcard_client_emulate(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return FALSE; + return smartcard_call_is_configured(scard->callctx); +} + +BOOL pf_channel_smartcard_client_reset(pClientContext* pc) +{ + pf_channel_client_context* scard = scard_get_client_context(pc); + if (!scard) + return TRUE; + + channel_stop_and_wait(scard, TRUE); + return TRUE; +} |