freerdp/channels/smartcard/client/smartcard_main.c

811 lines
20 KiB
C

/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Smartcard Device Service Virtual Channel
*
* Copyright 2011 O.S. Systems Software Ltda.
* Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
* Copyright 2011 Anthony Tong <atong@trustedcs.com>
* Copyright 2015 Thincast Technologies GmbH
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
* Copyright 2016 David PHAM-VAN <d.phamvan@inuvika.com>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <winpr/crt.h>
#include <winpr/smartcard.h>
#include <winpr/environment.h>
#include <freerdp/channels/rdpdr.h>
#include "smartcard_main.h"
#define CAST_FROM_DEVICE(device) cast_device_from(device, __FUNCTION__, __FILE__, __LINE__)
static SMARTCARD_DEVICE* sSmartcard = NULL;
static SMARTCARD_DEVICE* cast_device_from(DEVICE* device, const char* fkt, const char* file,
int line)
{
if (!device)
{
WLog_ERR(TAG, "%s [%s:%d] Called smartcard channel with NULL device", fkt, file, line);
return NULL;
}
if (device->type != RDPDR_DTYP_SMARTCARD)
{
WLog_ERR(TAG, "%s [%s:%d] Called smartcard channel with invalid device of type %" PRIx32,
fkt, file, line, device->type);
return NULL;
}
return (SMARTCARD_DEVICE*)device;
}
static DWORD WINAPI smartcard_context_thread(LPVOID arg)
{
SMARTCARD_CONTEXT* pContext = (SMARTCARD_CONTEXT*)arg;
DWORD nCount;
LONG status = 0;
DWORD waitStatus;
HANDLE hEvents[2];
wMessage message;
SMARTCARD_DEVICE* smartcard;
SMARTCARD_OPERATION* operation;
UINT error = CHANNEL_RC_OK;
smartcard = pContext->smartcard;
nCount = 0;
hEvents[nCount++] = MessageQueue_Event(pContext->IrpQueue);
while (1)
{
waitStatus = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE);
if (waitStatus == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
break;
}
waitStatus = WaitForSingleObject(MessageQueue_Event(pContext->IrpQueue), 0);
if (waitStatus == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
break;
}
if (waitStatus == WAIT_OBJECT_0)
{
if (!MessageQueue_Peek(pContext->IrpQueue, &message, TRUE))
{
WLog_ERR(TAG, "MessageQueue_Peek failed!");
status = ERROR_INTERNAL_ERROR;
break;
}
if (message.id == WMQ_QUIT)
break;
operation = (SMARTCARD_OPERATION*)message.wParam;
if (operation)
{
if ((status = smartcard_irp_device_control_call(smartcard, operation)))
{
WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %" PRIu32 "",
status);
break;
}
if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*)operation->irp))
{
WLog_ERR(TAG, "Queue_Enqueue failed!");
status = ERROR_INTERNAL_ERROR;
break;
}
free(operation);
}
}
}
if (status && smartcard->rdpcontext)
setChannelError(smartcard->rdpcontext, error, "smartcard_context_thread reported an error");
ExitThread(status);
return error;
}
SMARTCARD_CONTEXT* smartcard_context_new(SMARTCARD_DEVICE* smartcard, SCARDCONTEXT hContext)
{
SMARTCARD_CONTEXT* pContext;
pContext = (SMARTCARD_CONTEXT*)calloc(1, sizeof(SMARTCARD_CONTEXT));
if (!pContext)
{
WLog_ERR(TAG, "calloc failed!");
return pContext;
}
pContext->smartcard = smartcard;
pContext->hContext = hContext;
pContext->IrpQueue = MessageQueue_New(NULL);
if (!pContext->IrpQueue)
{
WLog_ERR(TAG, "MessageQueue_New failed!");
goto error_irpqueue;
}
pContext->thread = CreateThread(NULL, 0, smartcard_context_thread, pContext, 0, NULL);
if (!pContext->thread)
{
WLog_ERR(TAG, "CreateThread failed!");
goto error_thread;
}
return pContext;
error_thread:
MessageQueue_Free(pContext->IrpQueue);
error_irpqueue:
free(pContext);
return NULL;
}
void smartcard_context_free(void* pCtx)
{
SMARTCARD_CONTEXT* pContext = pCtx;
if (!pContext)
return;
/* cancel blocking calls like SCardGetStatusChange */
SCardCancel(pContext->hContext);
SCardReleaseContext(pContext->hContext);
if (MessageQueue_PostQuit(pContext->IrpQueue, 0) &&
(WaitForSingleObject(pContext->thread, INFINITE) == WAIT_FAILED))
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", GetLastError());
CloseHandle(pContext->thread);
MessageQueue_Free(pContext->IrpQueue);
free(pContext);
}
static void smartcard_release_all_contexts(SMARTCARD_DEVICE* smartcard)
{
int index;
int keyCount;
ULONG_PTR* pKeys;
SCARDCONTEXT hContext;
SMARTCARD_CONTEXT* pContext;
/**
* On protocol termination, the following actions are performed:
* For each context in rgSCardContextList, SCardCancel is called causing all
* SCardGetStatusChange calls to be processed. After that, SCardReleaseContext is called on each
* context and the context MUST be removed from rgSCardContextList.
*/
/**
* Call SCardCancel on existing contexts, unblocking all outstanding SCardGetStatusChange calls.
*/
ListDictionary_Lock(smartcard->rgSCardContextList);
if (ListDictionary_Count(smartcard->rgSCardContextList) > 0)
{
pKeys = NULL;
keyCount = ListDictionary_GetKeys(smartcard->rgSCardContextList, &pKeys);
for (index = 0; index < keyCount; index++)
{
pContext = (SMARTCARD_CONTEXT*)ListDictionary_GetItemValue(
smartcard->rgSCardContextList, (void*)pKeys[index]);
if (!pContext)
continue;
hContext = pContext->hContext;
if (SCardIsValidContext(hContext) == SCARD_S_SUCCESS)
{
SCardCancel(hContext);
}
}
free(pKeys);
}
ListDictionary_Unlock(smartcard->rgSCardContextList);
/* Put thread to sleep so that PC/SC can process the cancel requests. This fixes a race
* condition that sometimes caused the pc/sc daemon to crash on MacOS (_xpc_api_misuse) */
SleepEx(100, FALSE);
/**
* Call SCardReleaseContext on remaining contexts and remove them from rgSCardContextList.
*/
ListDictionary_Lock(smartcard->rgSCardContextList);
if (ListDictionary_Count(smartcard->rgSCardContextList) > 0)
{
pKeys = NULL;
keyCount = ListDictionary_GetKeys(smartcard->rgSCardContextList, &pKeys);
for (index = 0; index < keyCount; index++)
{
ListDictionary_SetItemValue(smartcard->rgSCardContextList, (void*)pKeys[index], NULL);
}
free(pKeys);
}
ListDictionary_Unlock(smartcard->rgSCardContextList);
}
static UINT smartcard_free_(SMARTCARD_DEVICE* smartcard)
{
if (!smartcard)
return CHANNEL_RC_OK;
if (smartcard->IrpQueue)
{
MessageQueue_Free(smartcard->IrpQueue);
CloseHandle(smartcard->thread);
}
Stream_Free(smartcard->device.data, TRUE);
LinkedList_Free(smartcard->names);
ListDictionary_Free(smartcard->rgSCardContextList);
ListDictionary_Free(smartcard->rgOutstandingMessages);
Queue_Free(smartcard->CompletedIrpQueue);
if (smartcard->StartedEvent)
SCardReleaseStartedEvent();
free(smartcard);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT smartcard_free(DEVICE* device)
{
UINT error;
SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
if (!smartcard)
return ERROR_INVALID_PARAMETER;
/**
* Calling smartcard_release_all_contexts to unblock all operations waiting for transactions
* to unlock.
*/
smartcard_release_all_contexts(smartcard);
/* Stopping all threads and cancelling all IRPs */
if (smartcard->IrpQueue)
{
if (MessageQueue_PostQuit(smartcard->IrpQueue, 0) &&
(WaitForSingleObject(smartcard->thread, INFINITE) == WAIT_FAILED))
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
return error;
}
}
if (sSmartcard == smartcard)
sSmartcard = NULL;
return smartcard_free_(smartcard);
}
/**
* Initialization occurs when the protocol server sends a device announce message.
* At that time, we need to cancel all outstanding IRPs.
*/
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT smartcard_init(DEVICE* device)
{
SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
if (!smartcard)
return ERROR_INVALID_PARAMETER;
smartcard_release_all_contexts(smartcard);
return CHANNEL_RC_OK;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT smartcard_complete_irp(SMARTCARD_DEVICE* smartcard, IRP* irp)
{
void* key;
key = (void*)(size_t)irp->CompletionId;
ListDictionary_Remove(smartcard->rgOutstandingMessages, key);
return irp->Complete(irp);
}
/**
* Multiple threads and SCardGetStatusChange:
* http://musclecard.996296.n3.nabble.com/Multiple-threads-and-SCardGetStatusChange-td4430.html
*/
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT smartcard_process_irp(SMARTCARD_DEVICE* smartcard, IRP* irp)
{
void* key;
LONG status;
BOOL asyncIrp = FALSE;
SMARTCARD_CONTEXT* pContext = NULL;
SMARTCARD_OPERATION* operation = NULL;
key = (void*)(size_t)irp->CompletionId;
if (!ListDictionary_Add(smartcard->rgOutstandingMessages, key, irp))
{
WLog_ERR(TAG, "ListDictionary_Add failed!");
return ERROR_INTERNAL_ERROR;
}
if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
operation = (SMARTCARD_OPERATION*)calloc(1, sizeof(SMARTCARD_OPERATION));
if (!operation)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
operation->irp = irp;
status = smartcard_irp_device_control_decode(smartcard, operation);
if (status != SCARD_S_SUCCESS)
{
irp->IoStatus = (UINT32)STATUS_UNSUCCESSFUL;
if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*)irp))
{
free(operation);
WLog_ERR(TAG, "Queue_Enqueue failed!");
return ERROR_INTERNAL_ERROR;
}
free(operation);
return CHANNEL_RC_OK;
}
asyncIrp = TRUE;
switch (operation->ioControlCode)
{
case SCARD_IOCTL_ESTABLISHCONTEXT:
case SCARD_IOCTL_RELEASECONTEXT:
case SCARD_IOCTL_ISVALIDCONTEXT:
case SCARD_IOCTL_CANCEL:
case SCARD_IOCTL_ACCESSSTARTEDEVENT:
case SCARD_IOCTL_RELEASETARTEDEVENT:
asyncIrp = FALSE;
break;
case SCARD_IOCTL_LISTREADERGROUPSA:
case SCARD_IOCTL_LISTREADERGROUPSW:
case SCARD_IOCTL_LISTREADERSA:
case SCARD_IOCTL_LISTREADERSW:
case SCARD_IOCTL_INTRODUCEREADERGROUPA:
case SCARD_IOCTL_INTRODUCEREADERGROUPW:
case SCARD_IOCTL_FORGETREADERGROUPA:
case SCARD_IOCTL_FORGETREADERGROUPW:
case SCARD_IOCTL_INTRODUCEREADERA:
case SCARD_IOCTL_INTRODUCEREADERW:
case SCARD_IOCTL_FORGETREADERA:
case SCARD_IOCTL_FORGETREADERW:
case SCARD_IOCTL_ADDREADERTOGROUPA:
case SCARD_IOCTL_ADDREADERTOGROUPW:
case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
case SCARD_IOCTL_LOCATECARDSA:
case SCARD_IOCTL_LOCATECARDSW:
case SCARD_IOCTL_LOCATECARDSBYATRA:
case SCARD_IOCTL_LOCATECARDSBYATRW:
case SCARD_IOCTL_READCACHEA:
case SCARD_IOCTL_READCACHEW:
case SCARD_IOCTL_WRITECACHEA:
case SCARD_IOCTL_WRITECACHEW:
case SCARD_IOCTL_GETREADERICON:
case SCARD_IOCTL_GETDEVICETYPEID:
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:
case SCARD_IOCTL_GETTRANSMITCOUNT:
asyncIrp = TRUE;
break;
}
pContext =
ListDictionary_GetItemValue(smartcard->rgSCardContextList, (void*)operation->hContext);
if (!pContext)
asyncIrp = FALSE;
if (!asyncIrp)
{
if ((status = smartcard_irp_device_control_call(smartcard, operation)))
{
WLog_ERR(TAG, "smartcard_irp_device_control_call failed with error %" PRId32 "!",
status);
return (UINT32)status;
}
if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*)irp))
{
free(operation);
WLog_ERR(TAG, "Queue_Enqueue failed!");
return ERROR_INTERNAL_ERROR;
}
free(operation);
}
else
{
if (pContext)
{
if (!MessageQueue_Post(pContext->IrpQueue, NULL, 0, (void*)operation, NULL))
{
WLog_ERR(TAG, "MessageQueue_Post failed!");
return ERROR_INTERNAL_ERROR;
}
}
}
}
else
{
WLog_ERR(TAG,
"Unexpected SmartCard IRP: MajorFunction 0x%08" PRIX32
" MinorFunction: 0x%08" PRIX32 "",
irp->MajorFunction, irp->MinorFunction);
irp->IoStatus = (UINT32)STATUS_NOT_SUPPORTED;
if (!Queue_Enqueue(smartcard->CompletedIrpQueue, (void*)irp))
{
WLog_ERR(TAG, "Queue_Enqueue failed!");
return ERROR_INTERNAL_ERROR;
}
}
return CHANNEL_RC_OK;
}
static DWORD WINAPI smartcard_thread_func(LPVOID arg)
{
IRP* irp;
DWORD nCount;
DWORD status;
HANDLE hEvents[2];
wMessage message;
UINT error = CHANNEL_RC_OK;
SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(arg);
if (!smartcard)
return ERROR_INVALID_PARAMETER;
nCount = 0;
hEvents[nCount++] = MessageQueue_Event(smartcard->IrpQueue);
hEvents[nCount++] = Queue_Event(smartcard->CompletedIrpQueue);
while (1)
{
status = WaitForMultipleObjects(nCount, hEvents, FALSE, INFINITE);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
break;
}
status = WaitForSingleObject(MessageQueue_Event(smartcard->IrpQueue), 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
break;
}
if (status == WAIT_OBJECT_0)
{
if (!MessageQueue_Peek(smartcard->IrpQueue, &message, TRUE))
{
WLog_ERR(TAG, "MessageQueue_Peek failed!");
error = ERROR_INTERNAL_ERROR;
break;
}
if (message.id == WMQ_QUIT)
{
while (1)
{
status = WaitForSingleObject(Queue_Event(smartcard->CompletedIrpQueue), 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
goto out;
}
if (status == WAIT_TIMEOUT)
break;
irp = (IRP*)Queue_Dequeue(smartcard->CompletedIrpQueue);
if (irp)
{
if (irp->thread)
{
status = WaitForSingleObject(irp->thread, INFINITE);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!",
error);
goto out;
}
CloseHandle(irp->thread);
irp->thread = NULL;
}
if ((error = smartcard_complete_irp(smartcard, irp)))
{
WLog_ERR(TAG, "smartcard_complete_irp failed with error %" PRIu32 "!",
error);
goto out;
}
}
}
break;
}
irp = (IRP*)message.wParam;
if (irp)
{
if ((error = smartcard_process_irp(smartcard, irp)))
{
WLog_ERR(TAG, "smartcard_process_irp failed with error %" PRIu32 "!", error);
goto out;
}
}
}
status = WaitForSingleObject(Queue_Event(smartcard->CompletedIrpQueue), 0);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
break;
}
if (status == WAIT_OBJECT_0)
{
irp = (IRP*)Queue_Dequeue(smartcard->CompletedIrpQueue);
if (irp)
{
if (irp->thread)
{
status = WaitForSingleObject(irp->thread, INFINITE);
if (status == WAIT_FAILED)
{
error = GetLastError();
WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", error);
break;
}
CloseHandle(irp->thread);
irp->thread = NULL;
}
if ((error = smartcard_complete_irp(smartcard, irp)))
{
if (error == CHANNEL_RC_NOT_CONNECTED)
{
error = CHANNEL_RC_OK;
goto out;
}
WLog_ERR(TAG, "smartcard_complete_irp failed with error %" PRIu32 "!", error);
goto out;
}
}
}
}
out:
if (error && smartcard->rdpcontext)
setChannelError(smartcard->rdpcontext, error, "smartcard_thread_func reported an error");
ExitThread(error);
return error;
}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
static UINT smartcard_irp_request(DEVICE* device, IRP* irp)
{
SMARTCARD_DEVICE* smartcard = CAST_FROM_DEVICE(device);
if (!smartcard)
return ERROR_INVALID_PARAMETER;
if (!MessageQueue_Post(smartcard->IrpQueue, NULL, 0, (void*)irp, NULL))
{
WLog_ERR(TAG, "MessageQueue_Post failed!");
return ERROR_INTERNAL_ERROR;
}
return CHANNEL_RC_OK;
}
/* smartcard is always built-in */
#define DeviceServiceEntry smartcard_DeviceServiceEntry
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
UINT DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)
{
SMARTCARD_DEVICE* smartcard = NULL;
size_t length;
UINT error = CHANNEL_RC_NO_MEMORY;
if (!sSmartcard)
{
wObject* obj;
smartcard = (SMARTCARD_DEVICE*)calloc(1, sizeof(SMARTCARD_DEVICE));
if (!smartcard)
{
WLog_ERR(TAG, "calloc failed!");
return CHANNEL_RC_NO_MEMORY;
}
smartcard->device.type = RDPDR_DTYP_SMARTCARD;
smartcard->device.name = "SCARD";
smartcard->device.IRPRequest = smartcard_irp_request;
smartcard->device.Init = smartcard_init;
smartcard->device.Free = smartcard_free;
smartcard->names = LinkedList_New();
smartcard->rdpcontext = pEntryPoints->rdpcontext;
length = strlen(smartcard->device.name);
smartcard->device.data = Stream_New(NULL, length + 1);
if (!smartcard->device.data || !smartcard->names)
{
WLog_ERR(TAG, "Stream_New failed!");
goto fail;
}
Stream_Write(smartcard->device.data, "SCARD", 6);
smartcard->IrpQueue = MessageQueue_New(NULL);
if (!smartcard->IrpQueue)
{
WLog_ERR(TAG, "MessageQueue_New failed!");
goto fail;
}
smartcard->CompletedIrpQueue = Queue_New(TRUE, -1, -1);
if (!smartcard->CompletedIrpQueue)
{
WLog_ERR(TAG, "Queue_New failed!");
goto fail;
}
smartcard->rgSCardContextList = ListDictionary_New(TRUE);
if (!smartcard->rgSCardContextList)
{
WLog_ERR(TAG, "ListDictionary_New failed!");
goto fail;
}
obj = ListDictionary_ValueObject(smartcard->rgSCardContextList);
obj->fnObjectFree = smartcard_context_free;
smartcard->rgOutstandingMessages = ListDictionary_New(TRUE);
if (!smartcard->rgOutstandingMessages)
{
WLog_ERR(TAG, "ListDictionary_New failed!");
goto fail;
}
if ((error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &smartcard->device)))
{
WLog_ERR(TAG, "RegisterDevice failed!");
goto fail;
}
smartcard->thread =
CreateThread(NULL, 0, smartcard_thread_func, smartcard, CREATE_SUSPENDED, NULL);
if (!smartcard->thread)
{
WLog_ERR(TAG, "ListDictionary_New failed!");
error = ERROR_INTERNAL_ERROR;
goto fail;
}
ResumeThread(smartcard->thread);
}
else
smartcard = sSmartcard;
if (pEntryPoints->device->Name)
LinkedList_AddLast(smartcard->names, pEntryPoints->device->Name);
sSmartcard = smartcard;
return CHANNEL_RC_OK;
fail:
smartcard_free_(smartcard);
return error;
}