/*++ 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 &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 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 Session = _Session; SmartPtr FileObj; SmartPtr 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 Session = _Session; SmartPtr Device = static_cast(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 Session = _Session; DrFile *pFile = (DrFile *)RxContext->pFobx->Context2; SmartPtr 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 Exchange; SmartPtr 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; }