You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
572 lines
19 KiB
572 lines
19 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
scardss.cpp
|
|
|
|
Abstract:
|
|
|
|
Smart card subsystem Device object handles one redirected smart card subsystem
|
|
|
|
Revision History:
|
|
|
|
JoyC 9/11/2000 Created
|
|
|
|
--*/
|
|
#include "precomp.hxx"
|
|
#define TRC_FILE "scardss"
|
|
#include "trc.h"
|
|
#include "scioctl.h"
|
|
|
|
DrSmartCard::DrSmartCard(SmartPtr<DrSession> &Session, ULONG DeviceType, ULONG DeviceId,
|
|
PUCHAR PreferredDosName) : DrDevice(Session, DeviceType, DeviceId, PreferredDosName)
|
|
{
|
|
BEGIN_FN("DrSmartCard::DrSmartCard");
|
|
SetClassName("DrSmartCard");
|
|
_SmartCardState = dsCreated;
|
|
TRC_NRM((TB, "Create SmartCard object = %p", this));
|
|
}
|
|
|
|
BOOL DrSmartCard::IsDeviceNameValid()
|
|
{
|
|
BEGIN_FN("DrSmartCard::IsDeviceNameValid");
|
|
BOOL fRet = FALSE;
|
|
//
|
|
// device name is valid only if it contains the string
|
|
// "SCARD"
|
|
//
|
|
if (!strcmp((char*)_PreferredDosName, DR_SMARTCARD_SUBSYSTEM)) {
|
|
fRet = TRUE;
|
|
}
|
|
|
|
ASSERT(fRet);
|
|
return fRet;
|
|
}
|
|
|
|
NTSTATUS DrSmartCard::Initialize(PRDPDR_DEVICE_ANNOUNCE DeviceAnnounce, ULONG Length)
|
|
{
|
|
NTSTATUS Status;
|
|
DrSmartCardState smartcardState;
|
|
|
|
BEGIN_FN("DrSmartCard::Initialize");
|
|
|
|
if (!IsDeviceNameValid()) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
Status = DrDevice::Initialize(DeviceAnnounce, Length);
|
|
|
|
// Initialize the device ref count if not already initialized
|
|
smartcardState = (DrSmartCardState)InterlockedExchange((long *)&_SmartCardState, dsInitialized);
|
|
if (smartcardState == dsCreated) {
|
|
_CreateRefCount = 0;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
void DrSmartCard::ClientConnect(PRDPDR_DEVICE_ANNOUNCE devAnnouceMsg, ULONG Length)
|
|
{
|
|
|
|
SmartPtr<DrExchange> Exchange;
|
|
ListEntry *ListEnum;
|
|
USHORT Mid;
|
|
|
|
BEGIN_FN("DrSmartCard::ClientConnect");
|
|
|
|
// Set the smartcard device to be connected by the client
|
|
// And set the real device id
|
|
_DeviceStatus = dsConnected;
|
|
_DeviceId = devAnnouceMsg->DeviceId;
|
|
|
|
LONG l;
|
|
l = InterlockedIncrement(&_CreateRefCount);
|
|
|
|
// walk through the mid list that's waiting on the client
|
|
// smartcard subsystem comes online and signal them
|
|
_MidList.LockShared();
|
|
ListEnum = _MidList.First();
|
|
while (ListEnum != NULL) {
|
|
|
|
Mid = (USHORT)ListEnum->Node();
|
|
if (_Session->GetExchangeManager().Find(Mid, Exchange)) {
|
|
if (MarkBusy(Exchange)) {
|
|
DrIoContext *Context = NULL;
|
|
PRX_CONTEXT RxContext;
|
|
|
|
Context = (DrIoContext *)Exchange->_Context;
|
|
ASSERT(Context != NULL);
|
|
|
|
//
|
|
// If the IRP was timed out, then we just discard this exchange
|
|
//
|
|
if (Context->_TimedOut) {
|
|
TRC_NRM((TB, "Irp was timed out"));
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
else {
|
|
RxContext = Context->_RxContext;
|
|
if (RxContext != NULL) {
|
|
CompleteBusyExchange(Exchange, STATUS_SUCCESS, 0);
|
|
} else {
|
|
TRC_NRM((TB, "Irp was cancelled"));
|
|
DiscardBusyExchange(Exchange);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListEnum = _MidList.Next(ListEnum);
|
|
}
|
|
|
|
_MidList.Unlock();
|
|
}
|
|
|
|
NTSTATUS DrSmartCard::Create(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
RxCaptureFcb;
|
|
PMRX_SRV_OPEN SrvOpen = RxContext->pRelevantSrvOpen;
|
|
PMRX_SRV_CALL SrvCall = RxContext->Create.pSrvCall;
|
|
PMRX_NET_ROOT NetRoot = RxContext->Create.pNetRoot;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
SmartPtr<DrFile> FileObj;
|
|
SmartPtr<DrDevice> Device(this);
|
|
|
|
BEGIN_FN("DrSmartCard::Create");
|
|
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CREATE);
|
|
|
|
//
|
|
// Security check the irp.
|
|
//
|
|
Status = VerifyCreateSecurity(RxContext, Session->GetSessionId());
|
|
|
|
if (NT_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// We already have an exclusive lock on the fcb. Finish the create.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
//
|
|
// JC: Worry about this when do buffering
|
|
//
|
|
SrvOpen->Flags |= SRVOPEN_FLAG_DONTUSE_WRITE_CACHING;
|
|
SrvOpen->Flags |= SRVOPEN_FLAG_DONTUSE_READ_CACHING;
|
|
|
|
RxContext->pFobx = RxCreateNetFobx(RxContext, RxContext->pRelevantSrvOpen);
|
|
|
|
if (RxContext->pFobx != NULL) {
|
|
// Fobx keeps a reference to the device so it won't go away
|
|
|
|
AddRef();
|
|
RxContext->pFobx->Context = (DrDevice *)this;
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We are using a file object to keep track of file open instance
|
|
// and any information stored in the mini-redir for this instance
|
|
//
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
// NOTE: the special FileId agreed upon by both the client
|
|
// and server code is used here as the FileId
|
|
FileObj = new(NonPagedPool) DrFile(Device, DR_SMARTCARD_FILEID);
|
|
|
|
if (FileObj) {
|
|
//
|
|
// Explicit reference the file object here
|
|
//
|
|
FileObj->AddRef();
|
|
RxContext->pFobx->Context2 = (VOID *)(FileObj);
|
|
}
|
|
else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We don't send the create request to the client, always return TRUE
|
|
//
|
|
if (NT_SUCCESS(Status)) {
|
|
LONG l;
|
|
l = InterlockedIncrement(&_CreateRefCount);
|
|
FinishCreate(RxContext);
|
|
}
|
|
else {
|
|
// Release the Device Reference
|
|
if (RxContext->pFobx != NULL) {
|
|
((DrDevice *)RxContext->pFobx->Context)->Release();
|
|
RxContext->pFobx->Context = NULL;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS DrSmartCard::Close(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
SmartPtr<DrDevice> Device = static_cast<DrDevice*>(this);
|
|
|
|
BEGIN_FN("DrSmartCard::Close");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_CLOSE);
|
|
|
|
// Remove the smartcard subsystem if we close the last handle
|
|
LONG l;
|
|
|
|
if ((l = InterlockedDecrement(&_CreateRefCount)) == 0) {
|
|
_DeviceStatus = dsDisabled;
|
|
Session->GetDevMgr().RemoveDevice(Device);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
BOOL DrSmartCard::SupportDiscon()
|
|
{
|
|
|
|
BOOL rc = TRUE;
|
|
DrSmartCardState smartcardState;
|
|
|
|
smartcardState = (DrSmartCardState)InterlockedExchange((long *)&_SmartCardState, dsDisconnected);
|
|
|
|
if (smartcardState == dsInitialized) {
|
|
|
|
// Remove the smartcard subsystem if we close the last handle
|
|
LONG l;
|
|
|
|
if ((l = InterlockedDecrement(&_CreateRefCount)) == 0) {
|
|
_DeviceStatus = dsDisabled;
|
|
rc = FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void DrSmartCard::Disconnect ()
|
|
{
|
|
BEGIN_FN("DrSmartCard::Disconnect");
|
|
|
|
_DeviceStatus = dsAvailable;
|
|
}
|
|
|
|
NTSTATUS DrSmartCard::IoControl(IN OUT PRX_CONTEXT RxContext)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
RxCaptureFcb;
|
|
RxCaptureFobx;
|
|
PMRX_NET_ROOT NetRoot = capFcb->pNetRoot;
|
|
PMRX_SRV_CALL SrvCall = NetRoot->pSrvCall;
|
|
SmartPtr<DrSession> Session = _Session;
|
|
DrFile *pFile = (DrFile *)RxContext->pFobx->Context2;
|
|
SmartPtr<DrFile> FileObj = pFile;
|
|
PRDPDR_IOREQUEST_PACKET pIoPacket;
|
|
PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
|
|
ULONG cbPacketSize = sizeof(RDPDR_IOREQUEST_PACKET) +
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
ULONG IoControlCode = LowIoContext->ParamsFor.IoCtl.IoControlCode;
|
|
ULONG InputBufferLength = LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
ULONG OutputBufferLength = LowIoContext->ParamsFor.IoCtl.OutputBufferLength;
|
|
PVOID InputBuffer = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
|
|
PVOID OutputBuffer = LowIoContext->ParamsFor.IoCtl.pOutputBuffer;
|
|
|
|
BEGIN_FN("DrDevice::IoControl");
|
|
|
|
//
|
|
// Make sure it's okay to access the Client at this time
|
|
// This is an optimization, we don't need to acquire the spin lock,
|
|
// because it is okay if we're not, we'll just catch it later
|
|
//
|
|
|
|
ASSERT(Session != NULL);
|
|
ASSERT(RxContext != NULL);
|
|
ASSERT(RxContext->MajorFunction == IRP_MJ_DEVICE_CONTROL ||
|
|
RxContext->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL ||
|
|
RxContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL);
|
|
|
|
//if (COMPARE_VERSION(Session->GetClientVersion().Minor,
|
|
// Session->GetClientVersion().Major, RDPDR_MINOR_VERSION_PORTS,
|
|
// RDPDR_MAJOR_VERSION_PORTS) < 0) {
|
|
// TRC_ALT((TB, "Failing IoCtl for client that doesn't support it"));
|
|
// return STATUS_NOT_IMPLEMENTED;
|
|
//}
|
|
|
|
//
|
|
// Make sure the device is still enabled
|
|
//
|
|
if (_DeviceStatus != dsConnected && IoControlCode != SCARD_IOCTL_SMARTCARD_ONLINE) {
|
|
TRC_ALT((TB, "Tried to send IoControl to client device which is not "
|
|
"available. State: %ld", _DeviceStatus));
|
|
return STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
//
|
|
// Validate the buffer
|
|
//
|
|
if (RxContext->CurrentIrp->RequestorMode != KernelMode) {
|
|
__try {
|
|
// If the buffering method is METHOD_NEITHER or METHOD_IN_DIRECT
|
|
// then we need to probe the input buffer
|
|
if ((IoControlCode & 0x1) &&
|
|
InputBuffer != NULL && InputBufferLength != 0) {
|
|
ProbeForRead(InputBuffer, InputBufferLength, sizeof(UCHAR));
|
|
}
|
|
|
|
// If the buffering method is METHOD_NEITHER or METHOD_OUT_DIRECT
|
|
// then we need to probe the output buffer
|
|
if ((IoControlCode & 0x2) &&
|
|
OutputBuffer != NULL && OutputBufferLength != 0) {
|
|
ProbeForWrite(OutputBuffer, OutputBufferLength, sizeof(UCHAR));
|
|
}
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
|
TRC_ERR((TB, "Invalid buffer parameter(s)"));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send the request to the client
|
|
//
|
|
if (IoControlCode != SCARD_IOCTL_SMARTCARD_ONLINE) {
|
|
|
|
pIoPacket = (PRDPDR_IOREQUEST_PACKET)new(PagedPool) BYTE[cbPacketSize];
|
|
|
|
if (pIoPacket != NULL) {
|
|
memset(pIoPacket, 0, cbPacketSize);
|
|
|
|
//
|
|
// FS Control uses the same path as IO Control.
|
|
//
|
|
pIoPacket->Header.Component = RDPDR_CTYP_CORE;
|
|
pIoPacket->Header.PacketId = DR_CORE_DEVICE_IOREQUEST;
|
|
pIoPacket->IoRequest.DeviceId = _DeviceId;
|
|
pIoPacket->IoRequest.FileId = FileObj->GetFileId();
|
|
pIoPacket->IoRequest.MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
pIoPacket->IoRequest.MinorFunction =
|
|
LowIoContext->ParamsFor.IoCtl.MinorFunction;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.OutputBufferLength =
|
|
LowIoContext->ParamsFor.IoCtl.OutputBufferLength;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.InputBufferLength =
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength;
|
|
pIoPacket->IoRequest.Parameters.DeviceIoControl.IoControlCode =
|
|
LowIoContext->ParamsFor.IoCtl.IoControlCode;
|
|
|
|
if (LowIoContext->ParamsFor.IoCtl.InputBufferLength != 0) {
|
|
|
|
TRC_NRM((TB, "DrIoControl inputbufferlength: %lx",
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength));
|
|
|
|
RtlCopyMemory(pIoPacket + 1, LowIoContext->ParamsFor.IoCtl.pInputBuffer,
|
|
LowIoContext->ParamsFor.IoCtl.InputBufferLength);
|
|
} else {
|
|
TRC_NRM((TB, "DrIoControl with no inputbuffer"));
|
|
}
|
|
|
|
Status = SendIoRequest(RxContext, pIoPacket, cbPacketSize,
|
|
(BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION));
|
|
TRC_NRM((TB, "IoRequestWrite returned to DrIoControl: %lx", Status));
|
|
delete pIoPacket;
|
|
} else {
|
|
TRC_ERR((TB, "DrIoControl unable to allocate packet: %lx", Status));
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
//
|
|
// This is the special IOCTL waiting for client smartcard subsystem come online
|
|
// We are already online, so just return
|
|
//
|
|
else if (_DeviceStatus == dsConnected){
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
//
|
|
// We'll have to wait for client to come online
|
|
//
|
|
else {
|
|
USHORT Mid = INVALID_MID;
|
|
BOOL ExchangeCreated = FALSE;
|
|
DrIoContext *Context = NULL;
|
|
SmartPtr<DrExchange> Exchange;
|
|
SmartPtr<DrDevice> Device(this);
|
|
|
|
Status = STATUS_PENDING;
|
|
|
|
// Need to keep a list of this.
|
|
// on create comes back, signal them
|
|
|
|
Context = new DrIoContext(RxContext, Device);
|
|
|
|
if (Context != NULL) {
|
|
Status = STATUS_SUCCESS;
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Set up a mapping so the completion response handler can
|
|
// find this context
|
|
//
|
|
|
|
TRC_DBG((TB, "Create the context for this I/O"));
|
|
|
|
KeClearEvent(&RxContext->SyncEvent);
|
|
|
|
ExchangeCreated =
|
|
_Session->GetExchangeManager().CreateExchange(this, Context, Exchange);
|
|
|
|
if (ExchangeCreated) {
|
|
|
|
//
|
|
// No need to explicit Refcount for the RxContext
|
|
// The place it's been used is the cancel routine.
|
|
// Since CreateExchange holds the ref count. we are okay
|
|
//
|
|
|
|
//Exchange->AddRef();
|
|
RxContext->MRxContext[MRX_DR_CONTEXT] = (DrExchange *)Exchange;
|
|
|
|
if (_MidList.CreateEntry((PVOID)Exchange->_Mid)) {
|
|
|
|
//
|
|
// successfully added this entry
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// Unable to add it to the list, clean up
|
|
//
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
} else {
|
|
delete Context;
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
TRC_DBG((TB, "Setting cancel routine for Io"));
|
|
|
|
//
|
|
// Set this after sending the IO to the client
|
|
// if cancel was requested already, we can just call the
|
|
// cancel routine ourselves
|
|
//
|
|
|
|
Status = RxSetMinirdrCancelRoutine(RxContext,
|
|
MinirdrCancelRoutine);
|
|
|
|
if (Status == STATUS_CANCELLED) {
|
|
TRC_NRM((TB, "Io was already cancelled"));
|
|
|
|
MinirdrCancelRoutine(RxContext);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if ((BOOLEAN)!BooleanFlagOn(RxContext->Flags,RX_CONTEXT_FLAG_ASYNC_OPERATION)) {
|
|
//
|
|
// Some failure is going to prevent our completions routine from
|
|
// being called. Do that work now.
|
|
//
|
|
if (!ExchangeCreated) {
|
|
//
|
|
// If we couldn't even create the exchange, we need to just
|
|
// complete the IO as failed
|
|
//
|
|
|
|
CompleteRxContext(RxContext, Status, 0);
|
|
}
|
|
else {
|
|
LARGE_INTEGER TimeOut;
|
|
|
|
//
|
|
// If we created the exchange and then got a transport failure
|
|
// we'll be disconnected, and the the I/O will be completed
|
|
// the same way all outstanding I/O is completed when we are
|
|
// disconnected.
|
|
//
|
|
|
|
TRC_DBG((TB, "Waiting for IoResult for synchronous request"));
|
|
|
|
TimeOut = RtlEnlargedIntegerMultiply( 6000000, -1000 );
|
|
Status = KeWaitForSingleObject(&RxContext->SyncEvent, UserRequest,
|
|
KernelMode, FALSE, &TimeOut);
|
|
|
|
if (Status == STATUS_TIMEOUT) {
|
|
RxContext->IoStatusBlock.Status = Status;
|
|
|
|
TRC_DBG((TB, "Wait timed out"));
|
|
MarkTimedOut(Exchange);
|
|
}
|
|
else {
|
|
Status = RxContext->IoStatusBlock.Status;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
TRC_DBG((TB, "Not waiting for IoResult for asynchronous request"));
|
|
|
|
//
|
|
// Some failure is going to prevent our completions routine from
|
|
// being called. Do that work now.
|
|
//
|
|
if (!ExchangeCreated) {
|
|
//
|
|
// If we couldn't even create the exchange, we need to just
|
|
// complete the IO as failed
|
|
//
|
|
|
|
CompleteRxContext(RxContext, Status, 0);
|
|
}
|
|
else {
|
|
//
|
|
// If we created the exchange and then got a transport failure
|
|
// we'll be disconnected, and the the I/O will be completed
|
|
// the same way all outstanding I/O is completed when we are
|
|
// disconnected.
|
|
//
|
|
}
|
|
|
|
Status = STATUS_PENDING;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|