FreeRDP/channels/rdpdr/smartcard/scard_main.c
Brent Collins 9b9398ec12 Issue #684.
Fix the problem with a smart card reader being lost when a smart card is removed during a reconnected Windows 2008r2 rdp session. This was due to a improper handling of a message with a duplicate completionID. There is a lengthy comment in the code with explicit details. Also, many minor updates for the code to more closely reflect the protocol documentation - such as correcting packet lengths, undocumented padding, etc.
2012-08-01 17:05:45 -05:00

343 lines
9.1 KiB
C

/*
FreeRDP: A Remote Desktop Protocol client.
Redirected Smart Card Device Service
Copyright 2011 O.S. Systems Software Ltda.
Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
Copyright 2011 Anthony Tong <atong@trustedcs.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.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <freerdp/utils/list.h>
#include <freerdp/utils/thread.h>
#include <freerdp/utils/svc_plugin.h>
#include <freerdp/utils/mutex.h>
#include <freerdp/utils/debug.h>
#include "rdpdr_types.h"
#include "rdpdr_constants.h"
#include "scard_main.h"
static void
scard_free(DEVICE* dev)
{
SCARD_DEVICE* scard = (SCARD_DEVICE*)dev;
IRP* irp;
COMPLETIONIDINFO* CompletionIdInfo;
freerdp_thread_stop(scard->thread);
freerdp_thread_free(scard->thread);
while ((irp = (IRP*)list_dequeue(scard->irp_list)) != NULL)
irp->Discard(irp);
list_free(scard->irp_list);
/* Begin TS Client defect workaround. */
while ((CompletionIdInfo = (COMPLETIONIDINFO*)list_dequeue(scard->CompletionIds)) != NULL)
xfree(CompletionIdInfo);
list_free(scard->CompletionIds);
/* End TS Client defect workaround. */
xfree(dev);
return;
}
static void
scard_process_irp(SCARD_DEVICE* scard, IRP* irp)
{
switch (irp->MajorFunction)
{
case IRP_MJ_DEVICE_CONTROL:
scard_device_control(scard, irp);
break;
default:
printf("MajorFunction 0x%X unexpected for smartcards.", irp->MajorFunction);
DEBUG_WARN("Smartcard MajorFunction 0x%X not supported.", irp->MajorFunction);
irp->IoStatus = STATUS_NOT_SUPPORTED;
irp->Complete(irp);
break;
}
}
static void
scard_process_irp_list(SCARD_DEVICE* scard)
{
IRP *irp;
while (!freerdp_thread_is_stopped(scard->thread))
{
freerdp_thread_lock(scard->thread);
irp = (IRP *) list_dequeue(scard->irp_list);
freerdp_thread_unlock(scard->thread);
if (irp == NULL)
break;
scard_process_irp(scard, irp);
}
}
struct scard_irp_thread_args {
SCARD_DEVICE* scard;
IRP* irp;
freerdp_thread* thread;
};
static void
scard_process_irp_thread_func(struct scard_irp_thread_args* args)
{
scard_process_irp(args->scard, args->irp);
freerdp_thread_free(args->thread);
xfree(args);
}
static void *
scard_thread_func(void* arg)
{
SCARD_DEVICE* scard = (SCARD_DEVICE*) arg;
while (1)
{
freerdp_thread_wait(scard->thread);
if (freerdp_thread_is_stopped(scard->thread))
break;
freerdp_thread_reset(scard->thread);
scard_process_irp_list(scard);
}
freerdp_thread_quit(scard->thread);
return NULL;
}
/* Begin TS Client defect workaround. */
static COMPLETIONIDINFO*
scard_mark_duplicate_id(SCARD_DEVICE* scard, uint32 CompletionId)
{
/*
* Search from the beginning of the LIST for one outstanding "CompletionID"
* that matches the one passed in. If there is one, mark it as a duplicate
* if it is not already marked.
*/
LIST_ITEM* item;
COMPLETIONIDINFO* CompletionIdInfo;
for (item = scard->CompletionIds->head; item; item = item->next)
{
CompletionIdInfo = (COMPLETIONIDINFO*)item->data;
if (CompletionIdInfo->ID == CompletionId)
{
if (false == CompletionIdInfo->duplicate)
{
CompletionIdInfo->duplicate = true;
DEBUG_WARN("CompletionID number %u is now marked as a duplicate.", CompletionId);
}
return CompletionIdInfo;
}
}
return NULL; /* Either no items in the list or no match. */
}
static boolean
scard_check_for_duplicate_id(SCARD_DEVICE* scard, uint32 CompletionId)
{
/*
* Search from the end of the LIST for one outstanding "CompletionID"
* that matches the one passed in. Remove it from the list and free the
* memory associated with it. Return whether or not it was marked
* as a duplicate.
*/
LIST_ITEM* item;
COMPLETIONIDINFO* CompletionIdInfo;
boolean duplicate;
for (item = scard->CompletionIds->tail; item; item = item->prev)
{
CompletionIdInfo = (COMPLETIONIDINFO*)item->data;
if (CompletionIdInfo->ID == CompletionId)
{
duplicate = CompletionIdInfo->duplicate;
if (true == duplicate)
{
DEBUG_WARN("CompletionID number %u was previously marked as a duplicate. The response to the command is removed.", CompletionId);
}
list_remove(scard->CompletionIds, CompletionIdInfo);
xfree(CompletionIdInfo);
return duplicate;
}
}
/* This function should only be called when there is
* at least one outstanding CompletionID item in the list.
*/
DEBUG_WARN("Error!!! No CompletionIDs (or no matching IDs) in the list!");
return false;
}
static void
scard_irp_complete(IRP* irp)
{
/* This function is (mostly) a copy of the statically-declared "irp_complete()"
* function except that this function adds extra operations for the
* smart card's handling of duplicate "CompletionID"s. This function needs
* to be in this file so that "scard_irp_request()" can reference it.
*/
int pos;
boolean duplicate;
SCARD_DEVICE* scard = (SCARD_DEVICE*)irp->device;
DEBUG_SVC("DeviceId %d FileId %d CompletionId %d", irp->device->id, irp->FileId, irp->CompletionId);
pos = stream_get_pos(irp->output);
stream_set_pos(irp->output, 12);
stream_write_uint32(irp->output, irp->IoStatus);
stream_set_pos(irp->output, pos);
/* Begin TS Client defect workaround. */
freerdp_mutex_lock(scard->CompletionIdsMutex);
/* Remove from the list the item identified by the CompletionID.
* The function returns whether or not it was a duplicate CompletionID.
*/
duplicate = scard_check_for_duplicate_id(scard, irp->CompletionId);
freerdp_mutex_unlock(scard->CompletionIdsMutex);
if (false == duplicate)
{
svc_plugin_send(irp->devman->plugin, irp->output);
irp->output = NULL;
}
/* End TS Client defect workaround. */
/* irp_free(irp); The "irp_free()" function is statically-declared
* and so is not available to be called
* here. Instead, call it indirectly by calling
* the IRP's "Discard()" function,
* which has already been assigned
* to point to "irp_free()" in "irp_new()".
*/
irp->Discard(irp);
}
/* End TS Client defect workaround. */
static void
scard_irp_request(DEVICE* device, IRP* irp)
{
COMPLETIONIDINFO* CompletionIdInfo;
SCARD_DEVICE* scard = (SCARD_DEVICE*)device;
/* Begin TS Client defect workaround. */
CompletionIdInfo= xnew(COMPLETIONIDINFO);
CompletionIdInfo->ID = irp->CompletionId;/* "duplicate" member is set
* to false by "xnew()"
*/
freerdp_mutex_lock(scard->CompletionIdsMutex);
scard_mark_duplicate_id(scard, irp->CompletionId);
list_enqueue(scard->CompletionIds, CompletionIdInfo);
freerdp_mutex_unlock(scard->CompletionIdsMutex);
irp->Complete = scard_irp_complete; /* Overwrite the previous
* assignment made in
* "irp_new()".
*/
/* End TS Client defect workaround. */
if (irp->MajorFunction == IRP_MJ_DEVICE_CONTROL &&
scard_async_op(irp))
{
/*
* certain potentially long running operations
* get their own thread
* TODO: revise this mechanism.. maybe worker pool
*/
struct scard_irp_thread_args *args = xmalloc(sizeof(struct scard_irp_thread_args));
args->thread = freerdp_thread_new();
args->scard = scard;
args->irp = irp;
freerdp_thread_start(args->thread, scard_process_irp_thread_func, args);
return;
}
freerdp_thread_lock(scard->thread);
list_enqueue(scard->irp_list, irp);
freerdp_thread_unlock(scard->thread);
freerdp_thread_signal(scard->thread);
}
int
DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints)
{
SCARD_DEVICE* scard;
char* name;
char* path;
int i, length;
name = (char *)pEntryPoints->plugin_data->data[1];
path = (char *)pEntryPoints->plugin_data->data[2];
if (name)
{
/* TODO: check if server supports sc redirect (version 5.1) */
scard = xnew(SCARD_DEVICE);
scard->device.type = RDPDR_DTYP_SMARTCARD;
scard->device.name = "SCARD";
scard->device.IRPRequest = scard_irp_request;
scard->device.Free = scard_free;
length = strlen(scard->device.name);
scard->device.data = stream_new(length + 1);
for (i = 0; i <= length; i++)
stream_write_uint8(scard->device.data, name[i] < 0 ? '_' : name[i]);
scard->path = path;
scard->irp_list = list_new();
scard->thread = freerdp_thread_new();
scard->CompletionIds = list_new();
scard->CompletionIdsMutex = freerdp_mutex_new();
pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE *)scard);
freerdp_thread_start(scard->thread, scard_thread_func, scard);
}
return 0;
}