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.
2379 lines
74 KiB
2379 lines
74 KiB
/****************************************************************************/
|
|
// channel.c
|
|
//
|
|
// Terminal Server channel handling.
|
|
//
|
|
// Copyright (C) 1997-2000 Microsoft Corporation
|
|
/****************************************************************************/
|
|
|
|
#include <precomp.h>
|
|
#pragma hdrstop
|
|
#include <ntddkbd.h>
|
|
#include <ntddmou.h>
|
|
|
|
#include "ptdrvcom.h"
|
|
|
|
|
|
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
|
|
|
|
|
NTSTATUS
|
|
IcaExceptionFilter(
|
|
IN PWSTR OutputString,
|
|
IN PEXCEPTION_POINTERS pexi
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaReadChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaWriteChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaDeviceControlChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaFlushChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaCleanupChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaCloseChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
VOID
|
|
IcaFreeAllVcBind(
|
|
IN PICA_CONNECTION pConnect
|
|
);
|
|
|
|
NTSTATUS
|
|
IcaCancelReadChannel (
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
|
|
/*
|
|
* Local procedure prototypes
|
|
*/
|
|
NTSTATUS
|
|
_IcaReadChannelComplete(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS _IcaQueueReadChannelRequest(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp
|
|
);
|
|
|
|
NTSTATUS
|
|
_IcaCopyDataToUserBuffer(
|
|
IN PIRP Irp,
|
|
IN PUCHAR pBuffer,
|
|
IN ULONG ByteCount
|
|
);
|
|
|
|
VOID
|
|
_IcaReadChannelCancelIrp(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
);
|
|
|
|
VOID _IcaProcessIrpList(
|
|
IN PICA_CHANNEL pChannel
|
|
);
|
|
|
|
PICA_CHANNEL
|
|
_IcaAllocateChannel(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN PVIRTUALCHANNELNAME pVirtualName
|
|
);
|
|
|
|
void _IcaFreeChannel(IN PICA_CHANNEL);
|
|
|
|
NTSTATUS
|
|
_IcaCallStack(
|
|
IN PICA_STACK pStack,
|
|
IN ULONG ProcIndex,
|
|
IN OUT PVOID pParms
|
|
);
|
|
|
|
NTSTATUS
|
|
_IcaCallStackNoLock(
|
|
IN PICA_STACK pStack,
|
|
IN ULONG ProcIndex,
|
|
IN OUT PVOID pParms
|
|
);
|
|
|
|
NTSTATUS
|
|
_IcaRegisterVcBind(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PVIRTUALCHANNELNAME pVirtualName,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN ULONG Flags
|
|
);
|
|
|
|
VIRTUALCHANNELCLASS
|
|
_IcaFindVcBind(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PVIRTUALCHANNELNAME pVirtualName,
|
|
OUT PULONG pFlags
|
|
);
|
|
|
|
VOID
|
|
_IcaBindChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN ULONG Flags
|
|
);
|
|
|
|
|
|
|
|
/*
|
|
* Dispatch table for ICA channel objects
|
|
*/
|
|
PICA_DISPATCH IcaChannelDispatchTable[IRP_MJ_MAXIMUM_FUNCTION+1] = {
|
|
NULL, // IRP_MJ_CREATE
|
|
NULL, // IRP_MJ_CREATE_NAMED_PIPE
|
|
IcaCloseChannel, // IRP_MJ_CLOSE
|
|
IcaReadChannel, // IRP_MJ_READ
|
|
IcaWriteChannel, // IRP_MJ_WRITE
|
|
NULL, // IRP_MJ_QUERY_INFORMATION
|
|
NULL, // IRP_MJ_SET_INFORMATION
|
|
NULL, // IRP_MJ_QUERY_EA
|
|
NULL, // IRP_MJ_SET_EA
|
|
IcaFlushChannel, // IRP_MJ_FLUSH_BUFFERS
|
|
NULL, // IRP_MJ_QUERY_VOLUME_INFORMATION
|
|
NULL, // IRP_MJ_SET_VOLUME_INFORMATION
|
|
NULL, // IRP_MJ_DIRECTORY_CONTROL
|
|
NULL, // IRP_MJ_FILE_SYSTEM_CONTROL
|
|
IcaDeviceControlChannel, // IRP_MJ_DEVICE_CONTROL
|
|
NULL, // IRP_MJ_INTERNAL_DEVICE_CONTROL
|
|
NULL, // IRP_MJ_SHUTDOWN
|
|
NULL, // IRP_MJ_LOCK_CONTROL
|
|
IcaCleanupChannel, // IRP_MJ_CLEANUP
|
|
NULL, // IRP_MJ_CREATE_MAILSLOT
|
|
NULL, // IRP_MJ_QUERY_SECURITY
|
|
NULL, // IRP_MJ_SET_SECURITY
|
|
NULL, // IRP_MJ_SET_POWER
|
|
NULL, // IRP_MJ_QUERY_POWER
|
|
};
|
|
|
|
#if DBG
|
|
extern PICA_DISPATCH IcaStackDispatchTable[];
|
|
#endif
|
|
|
|
|
|
NTSTATUS IcaCreateChannel(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PICA_OPEN_PACKET openPacket,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to create a new ICA_CHANNEL object.
|
|
- the reference count is incremented by one
|
|
|
|
Arguments:
|
|
|
|
pConnect -- pointer to ICA_CONNECTION object
|
|
|
|
Irp - Pointer to I/O request packet
|
|
|
|
IrpSp - pointer to the stack location to use for this request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS -- Indicates whether the request was successfully queued.
|
|
|
|
--*/
|
|
|
|
{
|
|
PICA_CHANNEL pChannel;
|
|
CHANNELCLASS ChannelClass;
|
|
NTSTATUS Status;
|
|
|
|
/*
|
|
* Validate ChannelClass
|
|
*/
|
|
ChannelClass = openPacket->TypeInfo.ChannelClass;
|
|
if ( !(ChannelClass >= Channel_Keyboard && ChannelClass <= Channel_Virtual) )
|
|
return( STATUS_INVALID_PARAMETER );
|
|
|
|
/*
|
|
* Ensure VirtualName has a trailing NULL
|
|
*/
|
|
if ( !memchr( openPacket->TypeInfo.VirtualName,
|
|
'\0',
|
|
sizeof( openPacket->TypeInfo.VirtualName ) ) )
|
|
return( STATUS_INVALID_PARAMETER );
|
|
|
|
|
|
/*
|
|
* Must lock connection object to create new channel.
|
|
*/
|
|
IcaLockConnection( pConnect );
|
|
|
|
TRACE(( pConnect, TC_ICADD, TT_API2, "TermDD: IcaCreateChannel: cc %u, vn %s\n",
|
|
ChannelClass, openPacket->TypeInfo.VirtualName ));
|
|
|
|
/*
|
|
* Locate channel object
|
|
*/
|
|
pChannel = IcaFindChannelByName(pConnect,
|
|
ChannelClass,
|
|
openPacket->TypeInfo.VirtualName);
|
|
|
|
/*
|
|
* See if this channel has already been created.
|
|
* If not, then create/initialize it now.
|
|
*/
|
|
if ( pChannel == NULL ) {
|
|
/*
|
|
* Allocate a new ICA channel object
|
|
*/
|
|
pChannel = _IcaAllocateChannel(pConnect,
|
|
ChannelClass,
|
|
openPacket->TypeInfo.VirtualName);
|
|
if (pChannel == NULL) {
|
|
IcaUnlockConnection(pConnect);
|
|
return( STATUS_INSUFFICIENT_RESOURCES );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Increment open count for this channel
|
|
*/
|
|
if (InterlockedIncrement(&pChannel->OpenCount) <= 0) {
|
|
ASSERT( FALSE );
|
|
}
|
|
|
|
/*
|
|
* If the CHANNEL_CLOSING flag is set, then we are re-referenceing
|
|
* a channel object that was just closed by a previous caller,
|
|
* but has not yet been completely dereferenced.
|
|
* This can happen if this create call comes in between the
|
|
* calls to IcaCleanupChannel and IcaCloseChannel which happen
|
|
* when a channel handle is closed.
|
|
*/
|
|
if ( pChannel->Flags & CHANNEL_CLOSING ) {
|
|
/*
|
|
* Lock channel while we clear out the CHANNEL_CLOSING flag.
|
|
*/
|
|
IcaLockChannel(pChannel);
|
|
pChannel->Flags &= ~CHANNEL_CLOSING;
|
|
IcaUnlockChannel(pChannel);
|
|
}
|
|
|
|
IcaUnlockConnection(pConnect);
|
|
|
|
/*
|
|
* Save a pointer to the channel in the file object
|
|
* so that we can find it in future calls.
|
|
* - leave the reference on the channel object
|
|
*/
|
|
IrpSp->FileObject->FsContext = pChannel;
|
|
|
|
/*
|
|
* Exit with the channel reference count incremented by one
|
|
*/
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaReadChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the read routine for ICA channels.
|
|
|
|
Arguments:
|
|
|
|
pChannel -- pointer to ICA_CHANNEL object
|
|
|
|
Irp - Pointer to I/O request packet
|
|
|
|
IrpSp - pointer to the stack location to use for this request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS -- Indicates whether the request was successfully queued.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL cancelIrql;
|
|
NTSTATUS Status = STATUS_PENDING;
|
|
ULONG bChannelAlreadyLocked;
|
|
|
|
|
|
/*
|
|
* Determine the channel type to see if read is supported.
|
|
* Also do read size verification for keyboard/mouse.
|
|
*/
|
|
switch ( pChannel->ChannelClass ) {
|
|
/*
|
|
* Make sure input size is a multiple of KEYBOARD_INPUT_DATA
|
|
*/
|
|
case Channel_Keyboard :
|
|
if ( IrpSp->Parameters.Read.Length % sizeof(KEYBOARD_INPUT_DATA) )
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
break;
|
|
|
|
/*
|
|
* Make sure input size is a multiple of MOUSE_INPUT_DATA
|
|
*/
|
|
case Channel_Mouse :
|
|
if ( IrpSp->Parameters.Read.Length % sizeof(MOUSE_INPUT_DATA) )
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
break;
|
|
|
|
/*
|
|
* Nothing required for Command/Virtual channels
|
|
*/
|
|
case Channel_Command :
|
|
case Channel_Virtual :
|
|
break;
|
|
|
|
/*
|
|
* Read not supported for the following channels
|
|
*/
|
|
case Channel_Video :
|
|
case Channel_Beep :
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
|
|
default:
|
|
ASSERTMSG( "TermDD: Invalid Channel Class", FALSE );
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If read length is 0, or an error is being returned, return now.
|
|
*/
|
|
if (Status == STATUS_PENDING && IrpSp->Parameters.Read.Length == 0)
|
|
Status = STATUS_SUCCESS;
|
|
if (Status != STATUS_PENDING) {
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest(Irp, IcaPriorityBoost);
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status ));
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* Verify user's buffer is valid
|
|
*/
|
|
if (Irp->RequestorMode != KernelMode) {
|
|
try {
|
|
ProbeForWrite(Irp->UserBuffer, IrpSp->Parameters.Read.Length, sizeof(BYTE));
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = GetExceptionCode();
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest(Irp, IcaPriorityBoost);
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status));
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lock the channel while we determine how to handle this read request.
|
|
* One of the following will be true:
|
|
* 1) Input data is available; copy it to user buffer and complete IRP,
|
|
* 2) No data available, IRP cancel is requested; cancel/complete IRP,
|
|
* 3) No data; add IRP to pending read list, return STATUS_PENDING.
|
|
*/
|
|
if (ExIsResourceAcquiredExclusiveLite(&(pChannel->Resource))) {
|
|
bChannelAlreadyLocked = TRUE;
|
|
IcaReferenceChannel(pChannel);
|
|
}
|
|
else {
|
|
bChannelAlreadyLocked = FALSE;
|
|
IcaLockChannel(pChannel);
|
|
}
|
|
|
|
/*
|
|
* If the channel is being closed,
|
|
* then don't allow any further read requests.
|
|
*/
|
|
if (pChannel->Flags & CHANNEL_CLOSING) {
|
|
Status = STATUS_FILE_CLOSED;
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest(Irp, IcaPriorityBoost);
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status));
|
|
IcaUnlockChannel(pChannel);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* If the Winstation is terminating and Reads are cancelled
|
|
* then don't allow any further read requests.
|
|
*/
|
|
if (pChannel->Flags & CHANNEL_CANCEL_READS) {
|
|
Status = STATUS_FILE_CLOSED;
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest(Irp, IcaPriorityBoost);
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status));
|
|
IcaUnlockChannel(pChannel);
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
if (InterlockedCompareExchange(&(pChannel->CompletionRoutineCount), 1, 0) == 0) {
|
|
|
|
/*
|
|
* If there is already input data available,
|
|
* then use it to satisfy the caller's read request.
|
|
*/
|
|
if ( !IsListEmpty( &pChannel->InputBufHead ) ) {
|
|
_IcaProcessIrpList(pChannel);
|
|
|
|
if (!IsListEmpty( &pChannel->InputBufHead )) {
|
|
Status = _IcaReadChannelComplete( pChannel, Irp, IrpSp );
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_IN3, "TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status ));
|
|
|
|
_IcaProcessIrpList(pChannel);
|
|
}
|
|
else {
|
|
Status = _IcaQueueReadChannelRequest(pChannel, Irp, IrpSp);
|
|
}
|
|
}
|
|
else {
|
|
Status = _IcaQueueReadChannelRequest(pChannel, Irp, IrpSp);
|
|
}
|
|
|
|
InterlockedDecrement(&(pChannel->CompletionRoutineCount));
|
|
ASSERT(pChannel->CompletionRoutineCount == 0);
|
|
}
|
|
else {
|
|
Status = _IcaQueueReadChannelRequest(pChannel, Irp, IrpSp);
|
|
}
|
|
|
|
/*
|
|
* Unlock channel now
|
|
*/
|
|
if (bChannelAlreadyLocked) {
|
|
IcaDereferenceChannel( pChannel );
|
|
}
|
|
else {
|
|
IcaUnlockChannel(pChannel);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
void _IcaProcessIrpList(
|
|
IN PICA_CHANNEL pChannel)
|
|
{
|
|
KIRQL cancelIrql;
|
|
PIRP irpFromQueue;
|
|
PIO_STACK_LOCATION irpSpFromQueue;
|
|
PLIST_ENTRY irpQueueHead;
|
|
NTSTATUS irpStatus;
|
|
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pChannel->Resource ) );
|
|
|
|
/*
|
|
* Acquire IoCancel spinlock while checking InputIrp list
|
|
*/
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
|
|
/*
|
|
* If there is a pending read IRP, then remove it from the
|
|
* list and try to complete it now.
|
|
*/
|
|
|
|
while (!IsListEmpty( &pChannel->InputIrpHead ) &&
|
|
!IsListEmpty( &pChannel->InputBufHead )) {
|
|
|
|
irpQueueHead = RemoveHeadList( &pChannel->InputIrpHead );
|
|
irpFromQueue = CONTAINING_RECORD( irpQueueHead, IRP, Tail.Overlay.ListEntry );
|
|
irpSpFromQueue = IoGetCurrentIrpStackLocation( irpFromQueue );
|
|
|
|
/*
|
|
* Clear the cancel routine for this IRP
|
|
*/
|
|
IoSetCancelRoutine( irpFromQueue, NULL );
|
|
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
irpStatus = _IcaReadChannelComplete( pChannel, irpFromQueue, irpSpFromQueue );
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_IN3, "TermDD: IcaReadChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, irpStatus ));
|
|
|
|
/*
|
|
* Acquire IoCancel spinlock while checking InputIrp list
|
|
*/
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
}
|
|
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
}
|
|
|
|
NTSTATUS _IcaQueueReadChannelRequest(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
KIRQL cancelIrql;
|
|
NTSTATUS Status = STATUS_PENDING;
|
|
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pChannel->Resource ) );
|
|
|
|
/*
|
|
* Acquire the IoCancel spinlock.
|
|
* We use this spinlock to protect access to the InputIrp list.
|
|
*/
|
|
IoAcquireCancelSpinLock(&cancelIrql);
|
|
|
|
/*
|
|
* No input data is available.
|
|
* Add the Irp to the pending Irp list for this channel.
|
|
*/
|
|
InsertTailList(&pChannel->InputIrpHead, &Irp->Tail.Overlay.ListEntry);
|
|
IoMarkIrpPending(Irp);
|
|
/*
|
|
* If this IRP is being cancelled, then cancel it now.
|
|
* Otherwise, set the cancel routine for this request.
|
|
*/
|
|
if (Irp->Cancel) {
|
|
Irp->CancelIrql = cancelIrql;
|
|
_IcaReadChannelCancelIrp(IrpSp->DeviceObject, Irp);
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_IN3,
|
|
"TermDD: _IcaQueueReadChannelRequest, cc %u, vc %d (canceled)\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass));
|
|
return STATUS_CANCELLED;
|
|
}
|
|
|
|
IoSetCancelRoutine(Irp, _IcaReadChannelCancelIrp);
|
|
IoReleaseCancelSpinLock(cancelIrql);
|
|
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_IN3,
|
|
"TermDD: _IcaQueueReadChannelRequest, cc %u, vc %d (pending)\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass));
|
|
|
|
return STATUS_PENDING;
|
|
|
|
}
|
|
|
|
NTSTATUS _IcaReadChannelComplete(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
KIRQL cancelIrql;
|
|
PLIST_ENTRY Head;
|
|
PINBUF pInBuf;
|
|
PVOID pBuffer;
|
|
ULONG CopyCount;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pChannel->Resource ) );
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_IN4, "TermDD: _IcaReadChannelComplete, cc %u, vc %d\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass ));
|
|
|
|
/*
|
|
* Get pointer to first input buffer
|
|
*/
|
|
ASSERT( !IsListEmpty( &pChannel->InputBufHead ) );
|
|
Head = pChannel->InputBufHead.Flink;
|
|
pInBuf = CONTAINING_RECORD( Head, INBUF, Links );
|
|
|
|
/*
|
|
* Clear the cancel routine for this IRP,
|
|
* since one way or the other it will be completed.
|
|
*/
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
IoSetCancelRoutine( Irp, NULL );
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
/*
|
|
* If this is a message mode channel, all data from a single input
|
|
* buffer must fit in the user buffer, otherwise we return an error.
|
|
*/
|
|
if (IrpSp->Parameters.Read.Length < pInBuf->ByteCount &&
|
|
(pChannel->Flags & CHANNEL_MESSAGE_MODE)) {
|
|
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_ERROR,
|
|
"TermDD: _IcaReadChannelComplete: cc %u, vc %d (buffer too small)\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass ));
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
/*
|
|
* Determine amount of data to copy to user's buffer.
|
|
*/
|
|
CopyCount = min(IrpSp->Parameters.Read.Length, pInBuf->ByteCount);
|
|
|
|
/*
|
|
* Copy input data to user's buffer
|
|
*/
|
|
Status = _IcaCopyDataToUserBuffer(Irp, pInBuf->pBuffer, CopyCount);
|
|
|
|
|
|
/*
|
|
* Update ICA buffer pointer and bytes remaining.
|
|
* If no bytes remain, then unlink the input buffer and free it.
|
|
*/
|
|
if ( Status == STATUS_SUCCESS ) {
|
|
pChannel->InputBufCurSize -= CopyCount;
|
|
pInBuf->pBuffer += CopyCount;
|
|
pInBuf->ByteCount -= CopyCount;
|
|
if ( pInBuf->ByteCount == 0 ) {
|
|
RemoveEntryList( &pInBuf->Links );
|
|
ICA_FREE_POOL( pInBuf );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Mark the Irp complete
|
|
*/
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_IN3,
|
|
"TermDD: _IcaReadChannelComplete: cc %u, vc %d, bc %u, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, CopyCount, Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS _IcaCopyDataToUserBuffer(
|
|
IN PIRP Irp,
|
|
IN PUCHAR pBuffer,
|
|
IN ULONG ByteCount)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
/*
|
|
* If we are in the context of the original caller's process,
|
|
* then just copy the data into the user's buffer directly.
|
|
*/
|
|
if ( IoGetRequestorProcess( Irp ) == IoGetCurrentProcess() ) {
|
|
try {
|
|
Status = STATUS_SUCCESS;
|
|
RtlCopyMemory( Irp->UserBuffer, pBuffer, ByteCount );
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
/*
|
|
* If there is a MDL allocated for this IRP, then copy the data
|
|
* directly to the users buffer via the MDL.
|
|
*/
|
|
} else if ( Irp->MdlAddress ) {
|
|
PVOID UserBuffer;
|
|
|
|
UserBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress );
|
|
try {
|
|
if (UserBuffer != NULL) {
|
|
Status = STATUS_SUCCESS;
|
|
RtlCopyMemory( UserBuffer, pBuffer, ByteCount );
|
|
}else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
/*
|
|
* There is no MDL for this request. We must allocate a secondary
|
|
* buffer, copy the data to it, and indicate this is a buffered I/O
|
|
* request in the IRP. The I/O completion routine will copy the
|
|
* data to the user's buffer.
|
|
*/
|
|
} else {
|
|
ASSERT( Irp->AssociatedIrp.SystemBuffer == NULL );
|
|
Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag( PagedPool,
|
|
ByteCount,
|
|
ICA_POOL_TAG );
|
|
if ( Irp->AssociatedIrp.SystemBuffer == NULL )
|
|
return( STATUS_INSUFFICIENT_RESOURCES );
|
|
RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, pBuffer, ByteCount );
|
|
Irp->Flags |= (IRP_BUFFERED_IO |
|
|
IRP_DEALLOCATE_BUFFER |
|
|
IRP_INPUT_OPERATION);
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if ( Status == STATUS_SUCCESS )
|
|
Irp->IoStatus.Information = ByteCount;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
VOID _IcaReadChannelCancelIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PICA_CHANNEL pChannel;
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
pChannel = IrpSp->FileObject->FsContext;
|
|
|
|
/*
|
|
* Remove IRP from channel pending IRP list and release cancel spinlock
|
|
*/
|
|
RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
|
|
IoReleaseCancelSpinLock(Irp->CancelIrql);
|
|
|
|
/*
|
|
* Complete the IRP with a cancellation status code.
|
|
*/
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->IoStatus.Status = STATUS_CANCELLED;
|
|
IoCompleteRequest(Irp, IcaPriorityBoost);
|
|
}
|
|
|
|
|
|
NTSTATUS IcaWriteChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the write routine for ICA channels.
|
|
|
|
Arguments:
|
|
|
|
pChannel -- pointer to ICA_CHANNEL object
|
|
|
|
Irp - Pointer to I/O request packet. Flags, specific to this
|
|
driver, can be specified as a pointer to a ULONG flags value.
|
|
The pointer to this value is the first element in the
|
|
IRP.Tail.Overlay.DriverContext field.
|
|
|
|
Currently, only CHANNEL_WRITE_LOWPRIO is supported. Write IRP's with
|
|
this flag set will take lower priority than Write IRP's without this
|
|
flag set.
|
|
|
|
IrpSp - pointer to the stack location to use for this request.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS -- Indicates whether the request was successfully queued.
|
|
|
|
--*/
|
|
|
|
{
|
|
SD_CHANNELWRITE SdWrite;
|
|
NTSTATUS Status = STATUS_PENDING;
|
|
|
|
/*
|
|
* Determine the channel type to see if write is supported.
|
|
*/
|
|
switch ( pChannel->ChannelClass ) {
|
|
|
|
case Channel_Virtual :
|
|
if ( pChannel->VirtualClass == UNBOUND_CHANNEL ) {
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Write not supported for the following channels
|
|
*/
|
|
case Channel_Command :
|
|
case Channel_Keyboard :
|
|
case Channel_Mouse :
|
|
case Channel_Video :
|
|
case Channel_Beep :
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
|
|
default:
|
|
ASSERTMSG( "ICA.SYS: Invalid Channel Class", FALSE );
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the channel is being closed,
|
|
* then don't allow any further write requests.
|
|
*/
|
|
if ( pChannel->Flags & CHANNEL_CLOSING )
|
|
Status = STATUS_FILE_CLOSED;
|
|
|
|
/*
|
|
* If write length is 0, or an error is being returned, return now.
|
|
*/
|
|
if ( Status == STATUS_PENDING && IrpSp->Parameters.Write.Length == 0 )
|
|
Status = STATUS_SUCCESS;
|
|
if ( Status != STATUS_PENDING ) {
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_ERROR, "TermDD: IcaWriteChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status ));
|
|
return( Status );
|
|
}
|
|
|
|
/*
|
|
* Verify user's buffer is valid
|
|
*/
|
|
if ( Irp->RequestorMode != KernelMode ) {
|
|
try {
|
|
ProbeForRead( Irp->UserBuffer, IrpSp->Parameters.Write.Length, sizeof(BYTE) );
|
|
} except( EXCEPTION_EXECUTE_HANDLER ) {
|
|
Status = GetExceptionCode();
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_ERROR, "TermDD: IcaWriteChannel, cc %u, vc %d, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, Status ));
|
|
return( Status );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Call the top level stack driver to handle the write
|
|
*/
|
|
SdWrite.ChannelClass = pChannel->ChannelClass;
|
|
SdWrite.VirtualClass = pChannel->VirtualClass;
|
|
SdWrite.pBuffer = Irp->UserBuffer;
|
|
SdWrite.ByteCount = IrpSp->Parameters.Write.Length;
|
|
SdWrite.fScreenData = (BOOLEAN)(pChannel->Flags & CHANNEL_SCREENDATA);
|
|
SdWrite.fFlags = 0;
|
|
|
|
/*
|
|
* See if the low prio write flag is set in the IRP.
|
|
*
|
|
* The flags field is passed to termdd.sys via an IRP_MJ_WRITE
|
|
* Irp, as a ULONG pointer in the Irp->Tail.Overlay.DriverContext[0] field.
|
|
*/
|
|
if (Irp->Tail.Overlay.DriverContext[0] != NULL) {
|
|
ULONG flags = *((ULONG *)Irp->Tail.Overlay.DriverContext[0]);
|
|
if (flags & CHANNEL_WRITE_LOWPRIO) {
|
|
SdWrite.fFlags |= SD_CHANNELWRITE_LOWPRIO;
|
|
}
|
|
}
|
|
|
|
Status = IcaCallDriver( pChannel, SD$CHANNELWRITE, &SdWrite );
|
|
|
|
/*
|
|
* Complete the IRP now since all channel writes are synchronous
|
|
* (the user data is captured by the stack driver before returning).
|
|
*/
|
|
Irp->IoStatus.Status = Status;
|
|
if ( Status == STATUS_SUCCESS )
|
|
Irp->IoStatus.Information = IrpSp->Parameters.Write.Length;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_OUT3, "TermDD: IcaWriteChannel, cc %u, vc %d, bc %u, 0x%x\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, SdWrite.ByteCount, Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaDeviceControlChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
ULONG code;
|
|
PICA_TRACE_BUFFER pTraceBuffer;
|
|
NTSTATUS Status;
|
|
|
|
|
|
/*
|
|
* If the channel is being closed,
|
|
* then don't allow any further requests.
|
|
*/
|
|
if ( pChannel->Flags & CHANNEL_CLOSING )
|
|
return( STATUS_FILE_CLOSED );
|
|
|
|
/*
|
|
* Extract the IOCTL control code and process the request.
|
|
*/
|
|
code = IrpSp->Parameters.DeviceIoControl.IoControlCode;
|
|
|
|
|
|
#if DBG
|
|
if ( code != IOCTL_ICA_CHANNEL_TRACE ) {
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API1, "TermDD: IcaDeviceControlChannel, fc %d, ref %u (enter)\n",
|
|
(code & 0x3fff) >> 2, pChannel->RefCount ));
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Process generic channel ioctl requests
|
|
*/
|
|
try {
|
|
switch ( code ) {
|
|
|
|
case IOCTL_ICA_CHANNEL_TRACE :
|
|
|
|
if ( IrpSp->Parameters.DeviceIoControl.InputBufferLength < (ULONG)(FIELD_OFFSET(ICA_TRACE_BUFFER,Data[0])) )
|
|
return( STATUS_BUFFER_TOO_SMALL );
|
|
if ( IrpSp->Parameters.DeviceIoControl.InputBufferLength > sizeof(ICA_TRACE_BUFFER) )
|
|
return( STATUS_INVALID_BUFFER_SIZE );
|
|
if ( Irp->RequestorMode != KernelMode ) {
|
|
ProbeForRead( IrpSp->Parameters.DeviceIoControl.Type3InputBuffer,
|
|
IrpSp->Parameters.DeviceIoControl.InputBufferLength,
|
|
sizeof(BYTE) );
|
|
}
|
|
|
|
pTraceBuffer = (PICA_TRACE_BUFFER)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
IcaTraceFormat( &pChannel->pConnect->TraceInfo,
|
|
pTraceBuffer->TraceClass,
|
|
pTraceBuffer->TraceEnable,
|
|
pTraceBuffer->Data );
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_DISABLE_SESSION_IO:
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
pChannel->Flags |= CHANNEL_SESSION_DISABLEIO;
|
|
Status = IcaFlushChannel( pChannel, Irp, IrpSp );
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_ENABLE_SESSION_IO:
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
pChannel->Flags &= ~CHANNEL_SESSION_DISABLEIO;
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_CLOSE_COMMAND_CHANNEL :
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
Status = IcaCancelReadChannel(pChannel, Irp, IrpSp);
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_ENABLE_SHADOW :
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
pChannel->Flags |= CHANNEL_SHADOW_IO;
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_DISABLE_SHADOW :
|
|
|
|
IcaLockConnection( pChannel->pConnect );
|
|
pChannel->Flags &= ~CHANNEL_SHADOW_IO;
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IOCTL_ICA_CHANNEL_END_SHADOW :
|
|
{
|
|
PLIST_ENTRY Head, Next;
|
|
PICA_STACK pStack;
|
|
BOOLEAN bShadowEnded = FALSE;
|
|
PICA_CHANNEL_END_SHADOW_DATA pData;
|
|
|
|
if ( IrpSp->Parameters.DeviceIoControl.InputBufferLength != sizeof(ICA_CHANNEL_END_SHADOW_DATA) ) {
|
|
Status = STATUS_INVALID_BUFFER_SIZE;
|
|
break;
|
|
}
|
|
if ( Irp->RequestorMode != KernelMode ) {
|
|
ProbeForRead( IrpSp->Parameters.DeviceIoControl.Type3InputBuffer,
|
|
IrpSp->Parameters.DeviceIoControl.InputBufferLength,
|
|
sizeof(BYTE) );
|
|
}
|
|
|
|
pData = (PICA_CHANNEL_END_SHADOW_DATA)IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
|
|
|
|
/*
|
|
* Lock the connection object.
|
|
* This will serialize all channel calls for this connection.
|
|
*/
|
|
IcaLockConnection( pChannel->pConnect );
|
|
if ( IsListEmpty( &pChannel->pConnect->StackHead ) ) {
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Look for shadow stack(s).
|
|
*/
|
|
Head = &pChannel->pConnect->StackHead;
|
|
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
|
|
pStack = CONTAINING_RECORD( Next, ICA_STACK, StackEntry );
|
|
|
|
/*
|
|
* If this is a shadow stack, end it.
|
|
*/
|
|
if ( pStack->StackClass == Stack_Shadow ) {
|
|
if ( pStack->pBrokenEventObject ) {
|
|
KeSetEvent( pStack->pBrokenEventObject, 0, FALSE );
|
|
bShadowEnded = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unlock the connection object now.
|
|
*/
|
|
IcaUnlockConnection( pChannel->pConnect );
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (bShadowEnded && pData->bLogError) {
|
|
IcaLogError(NULL, pData->StatusCode, NULL, 0, NULL, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// This IOCTL is not supported by RDP or ICA driver
|
|
case IOCTL_VIDEO_ENUM_MONITOR_PDO:
|
|
|
|
Status = STATUS_DEVICE_NOT_READY;
|
|
break;
|
|
|
|
|
|
default :
|
|
|
|
/*
|
|
* Call the appropriate worker routine based on channel type
|
|
*/
|
|
switch ( pChannel->ChannelClass ) {
|
|
|
|
case Channel_Keyboard :
|
|
Status = IcaDeviceControlKeyboard( pChannel, Irp, IrpSp );
|
|
break;
|
|
|
|
case Channel_Mouse :
|
|
Status = IcaDeviceControlMouse( pChannel, Irp, IrpSp );
|
|
break;
|
|
|
|
case Channel_Video :
|
|
Status = IcaDeviceControlVideo( pChannel, Irp, IrpSp );
|
|
break;
|
|
|
|
case Channel_Beep :
|
|
Status = IcaDeviceControlBeep( pChannel, Irp, IrpSp );
|
|
break;
|
|
|
|
case Channel_Virtual :
|
|
Status = IcaDeviceControlVirtual( pChannel, Irp, IrpSp );
|
|
break;
|
|
|
|
case Channel_Command :
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
|
|
default:
|
|
ASSERTMSG( "ICA.SYS: Invalid Channel Class", FALSE );
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
}
|
|
}
|
|
} except( IcaExceptionFilter( L"IcaDeviceControlChannel TRAPPED!!",
|
|
GetExceptionInformation() ) ) {
|
|
Status = GetExceptionCode();
|
|
}
|
|
|
|
#if DBG
|
|
if ( code != IOCTL_ICA_CHANNEL_TRACE ) {
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API1, "TermDD: IcaDeviceControlChannel, fc %d, ref %u, 0x%x\n",
|
|
(code & 0x3fff) >> 2, pChannel->RefCount, Status ));
|
|
}
|
|
#endif
|
|
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaFlushChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
KIRQL cancelIrql;
|
|
PLIST_ENTRY Head;
|
|
PINBUF pInBuf;
|
|
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaFlushChannel, cc %u, vc %d\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass));
|
|
|
|
/*
|
|
* Lock channel while we flush any input buffers.
|
|
*/
|
|
IcaLockChannel(pChannel);
|
|
|
|
while (!IsListEmpty( &pChannel->InputBufHead)) {
|
|
Head = RemoveHeadList(&pChannel->InputBufHead);
|
|
pInBuf = CONTAINING_RECORD(Head, INBUF, Links);
|
|
ICA_FREE_POOL(pInBuf);
|
|
}
|
|
|
|
IcaUnlockChannel(pChannel);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaCleanupChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
KIRQL cancelIrql;
|
|
PLIST_ENTRY Head;
|
|
PIRP ReadIrp;
|
|
PINBUF pInBuf;
|
|
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaCleanupChannel, cc %u, vc %d\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass));
|
|
|
|
/*
|
|
* Decrement the open count; if it is 0, perform channel cleanup now.
|
|
*/
|
|
ASSERT(pChannel->OpenCount > 0);
|
|
if (InterlockedDecrement( &pChannel->OpenCount) == 0) {
|
|
|
|
/*
|
|
* Lock channel while we clear out any
|
|
* pending read IRPs and/or input buffers.
|
|
*/
|
|
IcaLockChannel(pChannel);
|
|
|
|
/*
|
|
* Indicate this channel is being closed
|
|
*/
|
|
pChannel->Flags |= CHANNEL_CLOSING;
|
|
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
while ( !IsListEmpty( &pChannel->InputIrpHead ) ) {
|
|
Head = pChannel->InputIrpHead.Flink;
|
|
ReadIrp = CONTAINING_RECORD( Head, IRP, Tail.Overlay.ListEntry );
|
|
ReadIrp->CancelIrql = cancelIrql;
|
|
IoSetCancelRoutine( ReadIrp, NULL );
|
|
_IcaReadChannelCancelIrp( IrpSp->DeviceObject, ReadIrp );
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
}
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
while ( !IsListEmpty( &pChannel->InputBufHead ) ) {
|
|
Head = RemoveHeadList( &pChannel->InputBufHead );
|
|
pInBuf = CONTAINING_RECORD( Head, INBUF, Links );
|
|
ICA_FREE_POOL( pInBuf );
|
|
}
|
|
|
|
IcaUnlockChannel(pChannel);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaCloseChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
PICA_CONNECTION pConnect;
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API2, "TermDD: IcaCloseChannel, cc %u, vc %d, vn %s\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, pChannel->VirtualName ));
|
|
|
|
pConnect = pChannel->pConnect;
|
|
|
|
/*
|
|
* Remove the file object reference for this channel.
|
|
*/
|
|
IcaDereferenceChannel(pChannel);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
NTSTATUS IcaChannelInput(
|
|
IN PSDCONTEXT pContext,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN PINBUF pInBuf OPTIONAL,
|
|
IN PUCHAR pBuffer OPTIONAL,
|
|
IN ULONG ByteCount)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the input (stack callup) routine for ICA channel input.
|
|
|
|
Arguments:
|
|
|
|
pContext - Pointer to SDCONTEXT for this Stack Driver
|
|
|
|
ChannelClass - Channel number for input
|
|
|
|
VirtualClass - Virtual channel number for input
|
|
|
|
pInBuf - Pointer to INBUF containing data
|
|
|
|
pBuffer - Pointer to input data
|
|
|
|
NOTE: Either pInBuf OR pBuffer must be specified, but not both.
|
|
|
|
ByteCount - length of data in pBuffer
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS -- Indicates whether the request was handled successfully.
|
|
|
|
--*/
|
|
|
|
{
|
|
PSDLINK pSdLink;
|
|
PICA_STACK pStack;
|
|
PICA_CONNECTION pConnect;
|
|
NTSTATUS Status;
|
|
|
|
/*
|
|
* Use SD passed context to get the SDLINK pointer.
|
|
*/
|
|
pSdLink = CONTAINING_RECORD( pContext, SDLINK, SdContext );
|
|
pStack = pSdLink->pStack; // save stack pointer for use below
|
|
pConnect = IcaGetConnectionForStack( pStack );
|
|
ASSERT( pSdLink->pStack->Header.Type == IcaType_Stack );
|
|
ASSERT( pSdLink->pStack->Header.pDispatchTable == IcaStackDispatchTable );
|
|
|
|
TRACESTACK(( pStack, TC_ICADD, TT_API1, "TermDD: IcaChannelInput, bc=%u (enter)\n", ByteCount ));
|
|
|
|
/*
|
|
* Only the stack object should be locked during input.
|
|
*/
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pStack->Resource ) );
|
|
|
|
/*
|
|
* Walk up the SDLINK list looking for a driver which has specified
|
|
* a ChannelInput callup routine. If we find one, then call the
|
|
* driver ChannelInput routine to let it handle the call.
|
|
*/
|
|
while ( (pSdLink = IcaGetPreviousSdLink( pSdLink )) != NULL ) {
|
|
ASSERT( pSdLink->pStack == pStack );
|
|
if ( pSdLink->SdContext.pCallup->pSdChannelInput ) {
|
|
IcaReferenceSdLink( pSdLink );
|
|
Status = (pSdLink->SdContext.pCallup->pSdChannelInput)(
|
|
pSdLink->SdContext.pContext,
|
|
ChannelClass,
|
|
VirtualClass,
|
|
pInBuf,
|
|
pBuffer,
|
|
ByteCount );
|
|
IcaDereferenceSdLink( pSdLink );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return IcaChannelInputInternal(pStack, ChannelClass, VirtualClass,
|
|
pInBuf, pBuffer, ByteCount);
|
|
}
|
|
|
|
|
|
NTSTATUS IcaChannelInputInternal(
|
|
IN PICA_STACK pStack,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN PINBUF pInBuf OPTIONAL,
|
|
IN PCHAR pBuffer OPTIONAL,
|
|
IN ULONG ByteCount)
|
|
{
|
|
PICA_COMMAND_HEADER pHeader;
|
|
PICA_CONNECTION pConnect;
|
|
PICA_CHANNEL pChannel;
|
|
PLIST_ENTRY Head;
|
|
PIRP Irp;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
KIRQL cancelIrql;
|
|
ULONG CopyCount;
|
|
NTSTATUS Status;
|
|
SD_IOCTL SdIoctl;
|
|
|
|
TRACESTACK(( pStack, TC_ICADD, TT_API2,
|
|
"TermDD: IcaChannelInputInternal: cc %u, vc %d, bc %u\n",
|
|
ChannelClass, VirtualClass, ByteCount ));
|
|
|
|
/*
|
|
* Check for channel command
|
|
*/
|
|
switch ( ChannelClass ) {
|
|
|
|
case Channel_Keyboard :
|
|
case Channel_Mouse :
|
|
KeQuerySystemTime( &pStack->LastInputTime );
|
|
break;
|
|
|
|
case Channel_Command :
|
|
|
|
if ( ByteCount < sizeof(ICA_COMMAND_HEADER) ) {
|
|
TRACESTACK(( pStack, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaChannelInputInternal: Channel_command bad bytecount\n" ));
|
|
break;
|
|
}
|
|
|
|
pHeader = (PICA_COMMAND_HEADER) pBuffer;
|
|
|
|
switch ( pHeader->Command ) {
|
|
case ICA_COMMAND_BROKEN_CONNECTION :
|
|
TRACESTACK(( pStack, TC_ICADD, TT_API1,
|
|
"TermDD: IcaChannelInputInternal, Broken Connection\n" ));
|
|
|
|
/* set closing flag */
|
|
pStack->fClosing = TRUE;
|
|
|
|
/*
|
|
* Send cancel i/o to stack drivers
|
|
* - fClosing flag must be set before issuing cancel i/o
|
|
*/
|
|
SdIoctl.IoControlCode = IOCTL_ICA_STACK_CANCEL_IO;
|
|
(void) _IcaCallStackNoLock( pStack, SD$IOCTL, &SdIoctl );
|
|
|
|
/*
|
|
* If a broken event has been registered for this stack,
|
|
* then signal the event now.
|
|
* NOTE: In this case we exit without forwarding the
|
|
* broken notification to the channel.
|
|
*/
|
|
if ( pStack->pBrokenEventObject ) {
|
|
KeSetEvent( pStack->pBrokenEventObject, 0, FALSE );
|
|
ObDereferenceObject( pStack->pBrokenEventObject );
|
|
pStack->pBrokenEventObject = NULL;
|
|
if ( pInBuf )
|
|
ICA_FREE_POOL( pInBuf );
|
|
return( STATUS_SUCCESS );
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Get the specified channel for this input packet.
|
|
* If not found, we have no choice but to bit-bucket the data.
|
|
*/
|
|
pConnect = IcaGetConnectionForStack(pStack);
|
|
pChannel = IcaFindChannel(pConnect, ChannelClass, VirtualClass);
|
|
if (pChannel == NULL) {
|
|
if (pInBuf)
|
|
ICA_FREE_POOL(pInBuf);
|
|
TRACESTACK((pStack, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaChannelInputInternal: channel not found\n" ));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Lock channel while processing I/O
|
|
*/
|
|
IcaLockChannel(pChannel);
|
|
|
|
/*
|
|
* If input is from a shadow stack and this channel should not
|
|
* process shadow I/O then bit bucket the data.
|
|
* Do the same if the channel is closing or IO are disabled.
|
|
*/
|
|
if ( (pChannel->Flags & (CHANNEL_SESSION_DISABLEIO | CHANNEL_CLOSING)) ||
|
|
(pStack->StackClass == Stack_Shadow &&
|
|
!(pChannel->Flags & CHANNEL_SHADOW_IO)) ) {
|
|
|
|
IcaUnlockChannel(pChannel);
|
|
IcaDereferenceChannel(pChannel);
|
|
if (pInBuf)
|
|
ICA_FREE_POOL(pInBuf);
|
|
TRACESTACK((pStack, TC_ICADD, TT_API2,
|
|
"TermDD: IcaChannelInputInternal: shadow or closing channel input\n"));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* If input is from an INBUF, initialize pBuffer and ByteCount
|
|
* with values from the buffer header.
|
|
*/
|
|
if (pInBuf) {
|
|
pBuffer = pInBuf->pBuffer;
|
|
ByteCount = pInBuf->ByteCount;
|
|
}
|
|
|
|
/*
|
|
* If there is a channel filter loaded for this channel,
|
|
* then pass the input data through it before going on.
|
|
*/
|
|
if (pChannel->pFilter) {
|
|
PINBUF pFilterBuf;
|
|
|
|
pChannel->pFilter->InputFilter(pChannel->pFilter, pBuffer, ByteCount,
|
|
&pFilterBuf);
|
|
if (pInBuf)
|
|
ICA_FREE_POOL(pInBuf);
|
|
|
|
/*
|
|
* Refresh INBUF pointer, buffer pointer, and byte count.
|
|
*/
|
|
pInBuf = pFilterBuf;
|
|
pBuffer = pInBuf->pBuffer;
|
|
ByteCount = pInBuf->ByteCount;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process the input data
|
|
*/
|
|
while ( ByteCount != 0 ) {
|
|
|
|
/*
|
|
* If this is a shadow stack, see if the stack we're shadowing is
|
|
* for a console session
|
|
*/
|
|
if (pStack->StackClass == Stack_Shadow)
|
|
{
|
|
PICA_STACK pTopStack;
|
|
PLIST_ENTRY Head, Next;
|
|
|
|
Head = &pConnect->StackHead;
|
|
Next = Head->Flink;
|
|
|
|
pTopStack = CONTAINING_RECORD( Next, ICA_STACK, StackEntry );
|
|
|
|
if (pTopStack->StackClass == Stack_Console)
|
|
{
|
|
/*
|
|
* It is the console, so put on our keyboard/mouse port
|
|
* driver hat and inject the input that way
|
|
*/
|
|
if (ChannelClass == Channel_Mouse)
|
|
{
|
|
MOUSE_INPUT_DATA *pmInputData;
|
|
ULONG count;
|
|
|
|
pmInputData = (MOUSE_INPUT_DATA *)pBuffer;
|
|
count = ByteCount / sizeof(MOUSE_INPUT_DATA);
|
|
|
|
/*
|
|
* This function will always consume all the data
|
|
*/
|
|
PtSendCurrentMouseInput(MouDeviceObject, pmInputData, count);
|
|
ByteCount = 0;
|
|
continue;
|
|
}
|
|
else if (ChannelClass == Channel_Keyboard)
|
|
{
|
|
KEYBOARD_INPUT_DATA *pkInputData;
|
|
ULONG count;
|
|
|
|
pkInputData = (KEYBOARD_INPUT_DATA *)pBuffer;
|
|
count = ByteCount / sizeof(KEYBOARD_INPUT_DATA);
|
|
|
|
/*
|
|
* This function will always consume all the data
|
|
*/
|
|
PtSendCurrentKeyboardInput(KbdDeviceObject, pkInputData, count);
|
|
ByteCount = 0;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Acquire IoCancel spinlock while checking InputIrp list
|
|
*/
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
|
|
/*
|
|
* If there is a pending read IRP, then remove it from the
|
|
* list and try to complete it now.
|
|
*/
|
|
if ( !IsListEmpty( &pChannel->InputIrpHead ) ) {
|
|
|
|
Head = RemoveHeadList( &pChannel->InputIrpHead );
|
|
Irp = CONTAINING_RECORD( Head, IRP, Tail.Overlay.ListEntry );
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
/*
|
|
* Clear the cancel routine for this IRP
|
|
*/
|
|
IoSetCancelRoutine( Irp, NULL );
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
/*
|
|
* If this is a message mode channel, all data from a single input
|
|
* buffer must fit in the user buffer, otherwise we return an error.
|
|
*/
|
|
if ( IrpSp->Parameters.Read.Length < ByteCount &&
|
|
(pChannel->Flags & CHANNEL_MESSAGE_MODE) ) {
|
|
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaChannelInputInternal: cc %u, vc %d, (too small)\n",
|
|
ChannelClass, VirtualClass ));
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Determine amount of data to copy to user's buffer.
|
|
*/
|
|
CopyCount = min( IrpSp->Parameters.Read.Length, ByteCount );
|
|
|
|
/*
|
|
* Copy input data to user's buffer
|
|
*/
|
|
Status = _IcaCopyDataToUserBuffer( Irp, pBuffer, CopyCount );
|
|
|
|
/*
|
|
* Mark the Irp complete and return success
|
|
*/
|
|
Irp->IoStatus.Status = Status;
|
|
IoCompleteRequest( Irp, IcaPriorityBoost );
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaChannelInputInternal: cc %u, vc %d, bc %u, 0x%x\n",
|
|
ChannelClass, VirtualClass, CopyCount, Status ));
|
|
|
|
/*
|
|
* Update input data pointer and count remaining.
|
|
* Note no need to update pChannel->InputBufCurSize since we never
|
|
* stored this data.
|
|
*/
|
|
if ( Status == STATUS_SUCCESS ) {
|
|
pBuffer += CopyCount;
|
|
ByteCount -= CopyCount;
|
|
if ( pInBuf ) {
|
|
pInBuf->pBuffer += CopyCount;
|
|
pInBuf->ByteCount -= CopyCount;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There are no pending IRPs for this channel, so just queue the data.
|
|
*/
|
|
} else {
|
|
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
/*
|
|
* Check to see if we need to discard the data (too much data
|
|
* backed up). This policy only takes effect when the max size
|
|
* is nonzero, which is currently only the case for mouse and
|
|
* keyboard inputs which can withstand being dropped.
|
|
* Note that the read IRPs sent for channels that can have
|
|
* data dropped must request in integral numbers of input
|
|
* blocks -- e.g. a mouse read IRP must have a read buffer size
|
|
* that is a multiple of sizeof(MOUSE_INPUT_DATA). If this is
|
|
* not the case the immediate-copy block above may copy
|
|
* partial input blocks before arriving here.
|
|
*/
|
|
if (pChannel->InputBufMaxSize == 0 ||
|
|
(pChannel->InputBufCurSize + ByteCount) <=
|
|
pChannel->InputBufMaxSize) {
|
|
/*
|
|
* If necessary, allocate an input buffer and copy the data
|
|
*/
|
|
if (pInBuf == NULL) {
|
|
/*
|
|
* Get input buffer and copy the data
|
|
* If this fails, we have no choice but to bail out.
|
|
*/
|
|
pInBuf = ICA_ALLOCATE_POOL(NonPagedPool, sizeof(INBUF) +
|
|
ByteCount);
|
|
if (pInBuf != NULL) {
|
|
pInBuf->ByteCount = ByteCount;
|
|
pInBuf->MaxByteCount = ByteCount;
|
|
pInBuf->pBuffer = (PUCHAR)(pInBuf + 1);
|
|
RtlCopyMemory(pInBuf->pBuffer, pBuffer, ByteCount);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Add buffer to tail of input list and clear pInBuf
|
|
* to indicate we have no buffer to free when done.
|
|
*/
|
|
InsertTailList( &pChannel->InputBufHead, &pInBuf->Links );
|
|
pChannel->InputBufCurSize += ByteCount;
|
|
pInBuf = NULL;
|
|
|
|
/*
|
|
* If any read(s) were posted while we allocated the input
|
|
* buffer, then try to complete as many as possible.
|
|
*/
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
while ( !IsListEmpty( &pChannel->InputIrpHead ) &&
|
|
!IsListEmpty( &pChannel->InputBufHead ) ) {
|
|
|
|
Head = RemoveHeadList( &pChannel->InputIrpHead );
|
|
Irp = CONTAINING_RECORD( Head, IRP, Tail.Overlay.ListEntry );
|
|
IoSetCancelRoutine( Irp, NULL );
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
Status = _IcaReadChannelComplete( pChannel, Irp, IrpSp );
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
}
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
}
|
|
else {
|
|
TRACESTACK(( pStack, TC_ICADD, TT_ERROR,
|
|
"TermDD: IcaChannelInputInternal: Dropped %u bytes "
|
|
"on channelclass %u\n", ByteCount, ChannelClass));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unlock channel now
|
|
*/
|
|
IcaUnlockChannel(pChannel);
|
|
|
|
/*
|
|
* If we still have an INBUF, free it now.
|
|
*/
|
|
if (pInBuf)
|
|
ICA_FREE_POOL(pInBuf);
|
|
|
|
/*
|
|
* Decrement channel refcount and return
|
|
*/
|
|
IcaDereferenceChannel(pChannel);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/****************************************************************************/
|
|
// IcaFindChannel
|
|
// IcaFindChannelByName
|
|
//
|
|
// Searches for a given channel in the connection channel list, and returns
|
|
// a pointer to it (with an added reference). Returns NULL if not found.
|
|
/****************************************************************************/
|
|
PICA_CHANNEL IcaFindChannel(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN VIRTUALCHANNELCLASS VirtualClass)
|
|
{
|
|
PICA_CHANNEL pChannel;
|
|
KIRQL oldIrql;
|
|
NTSTATUS Status;
|
|
|
|
/*
|
|
* Ensure we're not looking for an invalid virtual channel number
|
|
*/
|
|
ASSERT( ChannelClass != Channel_Virtual ||
|
|
(VirtualClass >= 0 && VirtualClass < VIRTUAL_MAXIMUM) );
|
|
|
|
/*
|
|
* If channel does not exist, return NULL.
|
|
*/
|
|
|
|
IcaLockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
pChannel = pConnect->pChannel[ ChannelClass + VirtualClass ];
|
|
|
|
if (pChannel == NULL) {
|
|
TRACE(( pConnect, TC_ICADD, TT_API3,
|
|
"TermDD: IcaFindChannel, cc %u, vc %d (not found)\n",
|
|
ChannelClass, VirtualClass ));
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
return NULL;
|
|
}
|
|
|
|
IcaReferenceChannel(pChannel);
|
|
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
TRACE((pConnect, TC_ICADD, TT_API3,
|
|
"TermDD: IcaFindChannel, cc %u, vc %d -> %s\n",
|
|
ChannelClass, VirtualClass, pChannel->VirtualName));
|
|
|
|
return pChannel;
|
|
}
|
|
|
|
|
|
PICA_CHANNEL IcaFindChannelByName(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN PVIRTUALCHANNELNAME pVirtualName)
|
|
{
|
|
PICA_CHANNEL pChannel;
|
|
PLIST_ENTRY Head, Next;
|
|
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pConnect->Resource ) );
|
|
|
|
/*
|
|
* If this is not a virtual channel use channel class only
|
|
*/
|
|
if (ChannelClass != Channel_Virtual) {
|
|
return IcaFindChannel( pConnect, ChannelClass, 0);
|
|
}
|
|
|
|
/*
|
|
* Search the existing channel structures to locate virtual channel name
|
|
*/
|
|
|
|
IcaLockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
Head = &pConnect->ChannelHead;
|
|
for ( Next = Head->Flink; Next != Head; Next = Next->Flink ) {
|
|
pChannel = CONTAINING_RECORD( Next, ICA_CHANNEL, Links );
|
|
if ( (pChannel->ChannelClass == Channel_Virtual) &&
|
|
!_stricmp( pChannel->VirtualName, pVirtualName ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If name does not exist, return unbound
|
|
*/
|
|
if (Next == Head) {
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: IcaFindChannelByName: vn %s (not found)\n", pVirtualName));
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
return(NULL);
|
|
}
|
|
|
|
IcaReferenceChannel(pChannel);
|
|
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: IcaFindChannelByName: vn %s, vc %d, ref %u\n",
|
|
pVirtualName, pChannel->VirtualClass,
|
|
(pChannel != NULL ? pChannel->RefCount : 0)));
|
|
|
|
return pChannel;
|
|
}
|
|
|
|
|
|
VOID IcaReferenceChannel(IN PICA_CHANNEL pChannel)
|
|
{
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaReferenceChannel: cc %u, vc %d, ref %u\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass, pChannel->RefCount));
|
|
|
|
ASSERT(pChannel->RefCount >= 0);
|
|
|
|
/*
|
|
* Increment the reference count
|
|
*/
|
|
if (InterlockedIncrement( &pChannel->RefCount) <= 0) {
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
VOID IcaDereferenceChannel(
|
|
IN PICA_CHANNEL pChannel)
|
|
{
|
|
BOOLEAN bNeedLock = FALSE;
|
|
BOOLEAN bChannelFreed = FALSE;
|
|
PERESOURCE pResource = pChannel->pChannelTableLock;
|
|
PICA_CONNECTION pConnect = pChannel->pConnect;
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaDefeferenceChannel: cc %u, vc %d, ref %u\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass,
|
|
pChannel->RefCount));
|
|
|
|
ASSERT(pChannel->RefCount > 0);
|
|
|
|
/*
|
|
* Lock the channel table since a reference going to Zero would cause
|
|
* to change table entry.
|
|
*/
|
|
if (pChannel->RefCount == 1) {
|
|
bNeedLock = TRUE;
|
|
IcaLockChannelTable(pResource);
|
|
}
|
|
|
|
/*
|
|
* Decrement the reference count; if it is 0, free the channel.
|
|
*/
|
|
if (InterlockedDecrement(&pChannel->RefCount) == 0){
|
|
ASSERT(bNeedLock);
|
|
_IcaFreeChannel(pChannel);
|
|
bChannelFreed = TRUE;
|
|
}
|
|
|
|
if (bNeedLock) {
|
|
IcaUnlockChannelTable(pResource);
|
|
}
|
|
|
|
/*
|
|
* Remove the reference to the Connection object for this channel.
|
|
* moved this here from _IcaFreeChannel because we need to be sure
|
|
* the connection object can't go away before the call to IcaUnlockChannelTable
|
|
* because the connection object is where the channel table locck live.
|
|
*/
|
|
if (bChannelFreed) {
|
|
IcaDereferenceConnection(pConnect);
|
|
}
|
|
}
|
|
|
|
|
|
NTSTATUS IcaBindVirtualChannels(IN PICA_STACK pStack)
|
|
{
|
|
PICA_CONNECTION pConnect;
|
|
PSD_VCBIND pSdVcBind = NULL;
|
|
SD_VCBIND aSdVcBind[ VIRTUAL_MAXIMUM ];
|
|
ULONG SdVcBindCount;
|
|
VIRTUALCHANNELCLASS VirtualClass;
|
|
PICA_CHANNEL pChannel;
|
|
NTSTATUS Status;
|
|
ULONG i, Flags;
|
|
SD_IOCTL SdIoctl;
|
|
|
|
pConnect = IcaLockConnectionForStack(pStack);
|
|
|
|
SdIoctl.IoControlCode = IOCTL_ICA_VIRTUAL_QUERY_BINDINGS;
|
|
SdIoctl.InputBuffer = NULL;
|
|
SdIoctl.InputBufferLength = 0;
|
|
SdIoctl.OutputBuffer = aSdVcBind;
|
|
SdIoctl.OutputBufferLength = sizeof(aSdVcBind);
|
|
Status = _IcaCallStack(pStack, SD$IOCTL, &SdIoctl);
|
|
if (NT_SUCCESS(Status)) {
|
|
pSdVcBind = &aSdVcBind[0];
|
|
SdVcBindCount = SdIoctl.BytesReturned / sizeof(SD_VCBIND);
|
|
|
|
for (i = 0; i < SdVcBindCount; i++, pSdVcBind++) {
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: IcaBindVirtualChannels: %s -> %d Flags=%x\n",
|
|
pSdVcBind->VirtualName, pSdVcBind->VirtualClass, pSdVcBind->Flags));
|
|
|
|
/*
|
|
* Locate virtual class binding
|
|
*/
|
|
VirtualClass = _IcaFindVcBind(pConnect, pSdVcBind->VirtualName, &Flags);
|
|
|
|
/*
|
|
* If virtual class binding does not exist, create one
|
|
*/
|
|
if (VirtualClass == UNBOUND_CHANNEL) {
|
|
/*
|
|
* Allocate a new virtual bind object
|
|
*/
|
|
Status = _IcaRegisterVcBind(pConnect, pSdVcBind->VirtualName,
|
|
pSdVcBind->VirtualClass, pSdVcBind->Flags );
|
|
if (!NT_SUCCESS(Status))
|
|
goto PostLockConnection;
|
|
}
|
|
|
|
/*
|
|
* Locate channel object
|
|
*/
|
|
pChannel = IcaFindChannelByName(pConnect, Channel_Virtual,
|
|
pSdVcBind->VirtualName);
|
|
|
|
/*
|
|
* If we found an existing channel object - update it
|
|
*/
|
|
if (pChannel != NULL) {
|
|
IcaLockChannel(pChannel);
|
|
_IcaBindChannel(pChannel, Channel_Virtual, pSdVcBind->VirtualClass, pSdVcBind->Flags);
|
|
IcaUnlockChannel(pChannel);
|
|
IcaDereferenceChannel(pChannel);
|
|
}
|
|
}
|
|
}
|
|
|
|
PostLockConnection:
|
|
IcaUnlockConnection(pConnect);
|
|
return Status;
|
|
}
|
|
|
|
|
|
VOID IcaRebindVirtualChannels(IN PICA_CONNECTION pConnect)
|
|
{
|
|
PLIST_ENTRY Head, Next;
|
|
PICA_VCBIND pVcBind;
|
|
PICA_CHANNEL pChannel;
|
|
|
|
Head = &pConnect->VcBindHead;
|
|
for (Next = Head->Flink; Next != Head; Next = Next->Flink) {
|
|
pVcBind = CONTAINING_RECORD(Next, ICA_VCBIND, Links);
|
|
|
|
/*
|
|
* Locate channel object
|
|
*/
|
|
pChannel = IcaFindChannelByName(pConnect, Channel_Virtual,
|
|
pVcBind->VirtualName);
|
|
|
|
/*
|
|
* If we found an existing channel object - update it
|
|
*/
|
|
if (pChannel != NULL) {
|
|
IcaLockChannel(pChannel);
|
|
_IcaBindChannel(pChannel, Channel_Virtual, pVcBind->VirtualClass, pVcBind->Flags);
|
|
IcaUnlockChannel(pChannel);
|
|
IcaDereferenceChannel(pChannel);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
VOID IcaUnbindVirtualChannels(IN PICA_CONNECTION pConnect)
|
|
{
|
|
PLIST_ENTRY Head, Next;
|
|
PICA_CHANNEL pChannel;
|
|
KIRQL oldIrql;
|
|
|
|
/*
|
|
* Loop through the channel list and clear the virtual class
|
|
* for all virtual channels. Also remove the channel pointer
|
|
* from the channel pointers array in the connection object.
|
|
*/
|
|
|
|
IcaLockChannelTable(&pConnect->ChannelTableLock);
|
|
Head = &pConnect->ChannelHead;
|
|
for (Next = Head->Flink; Next != Head; Next = Next->Flink) {
|
|
pChannel = CONTAINING_RECORD(Next, ICA_CHANNEL, Links);
|
|
if (pChannel->ChannelClass == Channel_Virtual &&
|
|
pChannel->VirtualClass != UNBOUND_CHANNEL) {
|
|
pConnect->pChannel[pChannel->ChannelClass +
|
|
pChannel->VirtualClass] = NULL;
|
|
pChannel->VirtualClass = UNBOUND_CHANNEL;
|
|
}
|
|
}
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
}
|
|
|
|
|
|
NTSTATUS IcaUnbindVirtualChannel(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PVIRTUALCHANNELNAME pVirtualName)
|
|
{
|
|
PLIST_ENTRY Head, Next;
|
|
PICA_CHANNEL pChannel;
|
|
PICA_VCBIND pVcBind;
|
|
KIRQL oldIrql;
|
|
|
|
/*
|
|
* Loop through the channel list and clear the virtual class
|
|
* for the matching virtual channel. Also remove the channel pointer
|
|
* from the channel pointers array in the connection object.
|
|
*/
|
|
|
|
IcaLockChannelTable(&pConnect->ChannelTableLock);
|
|
Head = &pConnect->ChannelHead;
|
|
for (Next = Head->Flink; Next != Head; Next = Next->Flink) {
|
|
pChannel = CONTAINING_RECORD(Next, ICA_CHANNEL, Links);
|
|
if (pChannel->ChannelClass == Channel_Virtual &&
|
|
pChannel->VirtualClass != UNBOUND_CHANNEL &&
|
|
!_stricmp( pChannel->VirtualName, pVirtualName)) {
|
|
pConnect->pChannel[pChannel->ChannelClass +
|
|
pChannel->VirtualClass] = NULL;
|
|
pChannel->VirtualClass = UNBOUND_CHANNEL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Head = &pConnect->VcBindHead;
|
|
for (Next = Head->Flink; Next != Head; Next = Next->Flink) {
|
|
pVcBind = CONTAINING_RECORD( Next, ICA_VCBIND, Links );
|
|
if (!_stricmp(pVcBind->VirtualName, pVirtualName)) {
|
|
RemoveEntryList( &pVcBind->Links );
|
|
ICA_FREE_POOL(pVcBind);
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
|
|
PICA_CHANNEL _IcaAllocateChannel(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN PVIRTUALCHANNELNAME pVirtualName)
|
|
{
|
|
PICA_CHANNEL pChannel;
|
|
VIRTUALCHANNELCLASS VirtualClass;
|
|
KIRQL oldIrql;
|
|
NTSTATUS Status;
|
|
ULONG Flags;
|
|
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&pConnect->Resource));
|
|
|
|
pChannel = ICA_ALLOCATE_POOL(NonPagedPool, sizeof(*pChannel));
|
|
if (pChannel == NULL)
|
|
return( NULL );
|
|
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaAllocateChannel: cc %u, vn %s, %x\n",
|
|
ChannelClass, pVirtualName, pChannel));
|
|
|
|
RtlZeroMemory(pChannel, sizeof(*pChannel));
|
|
|
|
|
|
/*
|
|
* Reference the connection object this channel belongs to
|
|
*/
|
|
IcaReferenceConnection(pConnect);
|
|
pChannel->pConnect = pConnect;
|
|
pChannel->pChannelTableLock = &pConnect->ChannelTableLock;
|
|
|
|
|
|
/*
|
|
* Initialize channel reference count to 1;
|
|
* for the file object reference that will be made by the caller.
|
|
*/
|
|
pChannel->RefCount = 1;
|
|
pChannel->CompletionRoutineCount = 0;
|
|
|
|
/*
|
|
* Initialize the rest of the channel object for non-zero values.
|
|
*/
|
|
pChannel->Header.Type = IcaType_Channel;
|
|
pChannel->Header.pDispatchTable = IcaChannelDispatchTable;
|
|
|
|
ExInitializeResourceLite(&pChannel->Resource);
|
|
InitializeListHead(&pChannel->InputIrpHead);
|
|
InitializeListHead(&pChannel->InputBufHead);
|
|
|
|
IcaLockChannel(pChannel);
|
|
|
|
if (ChannelClass == Channel_Virtual) {
|
|
strncpy(pChannel->VirtualName, pVirtualName, VIRTUALCHANNELNAME_LENGTH);
|
|
VirtualClass = _IcaFindVcBind(pConnect, pVirtualName, &Flags);
|
|
} else {
|
|
VirtualClass = 0;
|
|
Flags = 0;
|
|
}
|
|
|
|
_IcaBindChannel(pChannel, ChannelClass, VirtualClass, Flags);
|
|
|
|
/*
|
|
* Link channel object to connect object
|
|
*/
|
|
|
|
IcaLockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
InsertHeadList(&pConnect->ChannelHead, &pChannel->Links);
|
|
|
|
IcaUnlockChannelTable(&pConnect->ChannelTableLock);
|
|
|
|
/*
|
|
* Set channel type specific flags/fields
|
|
* (i.e. shadow I/O is implicitly enabled for the video, beep,
|
|
* and command channels; the command and all virtual channels
|
|
* are message mode channels)
|
|
* Also sets throttling values taken from registry (if appropriate,
|
|
* plus remember a zeromem done to channel struct above).
|
|
*/
|
|
switch (ChannelClass) {
|
|
case Channel_Keyboard:
|
|
pChannel->InputBufMaxSize = SysParams.KeyboardThrottleSize;
|
|
break;
|
|
|
|
case Channel_Mouse :
|
|
pChannel->InputBufMaxSize = SysParams.MouseThrottleSize;
|
|
break;
|
|
|
|
case Channel_Video :
|
|
case Channel_Beep :
|
|
pChannel->Flags |= CHANNEL_SHADOW_IO;
|
|
break;
|
|
|
|
case Channel_Command :
|
|
pChannel->Flags |= CHANNEL_SHADOW_IO;
|
|
/* fall through */
|
|
|
|
case Channel_Virtual :
|
|
pChannel->Flags |= CHANNEL_MESSAGE_MODE;
|
|
if (!_stricmp( pVirtualName, VIRTUAL_THINWIRE)) {
|
|
pChannel->Flags |= CHANNEL_SCREENDATA;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Per above assert, this function is assumed to be called while the
|
|
// connection lock is held.
|
|
IcaUnlockChannel(pChannel);
|
|
|
|
return pChannel;
|
|
}
|
|
|
|
|
|
void _IcaFreeChannel(IN PICA_CHANNEL pChannel)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
ASSERT(pChannel->RefCount == 0);
|
|
ASSERT(IsListEmpty(&pChannel->InputIrpHead));
|
|
ASSERT(IsListEmpty(&pChannel->InputBufHead));
|
|
ASSERT(!ExIsResourceAcquiredExclusiveLite(&pChannel->Resource));
|
|
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaFreeChannel: cc %u, vn %s, \n",
|
|
pChannel->ChannelClass, pChannel->VirtualName));
|
|
|
|
|
|
|
|
/*
|
|
* Unlink this channel from the channel list for this connection.
|
|
* this routine must be called with channel table lock held.
|
|
*/
|
|
|
|
RemoveEntryList(&pChannel->Links);
|
|
|
|
if (pChannel->VirtualClass != UNBOUND_CHANNEL) {
|
|
pChannel->pConnect->pChannel[pChannel->ChannelClass + pChannel->VirtualClass] = NULL;
|
|
}
|
|
|
|
|
|
ExDeleteResourceLite(&pChannel->Resource);
|
|
|
|
ICA_FREE_POOL(pChannel);
|
|
}
|
|
|
|
|
|
NTSTATUS _IcaRegisterVcBind(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PVIRTUALCHANNELNAME pVirtualName,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN ULONG Flags)
|
|
{
|
|
PICA_VCBIND pVcBind;
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&pConnect->Resource));
|
|
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaRegisterVcBind: %s -> %d\n",
|
|
pVirtualName, VirtualClass));
|
|
|
|
/*
|
|
* Allocate bind structure
|
|
*/
|
|
pVcBind = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(*pVcBind) );
|
|
if (pVcBind != NULL) {
|
|
/*
|
|
* Initialize structure
|
|
*/
|
|
RtlZeroMemory(pVcBind, sizeof(*pVcBind));
|
|
strncpy(pVcBind->VirtualName, pVirtualName, VIRTUALCHANNELNAME_LENGTH);
|
|
pVcBind->VirtualClass = VirtualClass;
|
|
pVcBind->Flags = Flags;
|
|
|
|
/*
|
|
* Link bind structure to connect object
|
|
*/
|
|
InsertHeadList(&pConnect->VcBindHead, &pVcBind->Links);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
|
|
VOID IcaFreeAllVcBind(IN PICA_CONNECTION pConnect)
|
|
{
|
|
PICA_VCBIND pVcBind;
|
|
PLIST_ENTRY Head;
|
|
|
|
TRACE(( pConnect, TC_ICADD, TT_API2, "TermDD: IcaFreeAllVcBind\n" ));
|
|
|
|
/*
|
|
* Free all bind structures
|
|
*/
|
|
while ( !IsListEmpty( &pConnect->VcBindHead ) ) {
|
|
Head = RemoveHeadList( &pConnect->VcBindHead );
|
|
pVcBind = CONTAINING_RECORD( Head, ICA_VCBIND, Links );
|
|
ICA_FREE_POOL( pVcBind );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
VIRTUALCHANNELCLASS _IcaFindVcBind(
|
|
IN PICA_CONNECTION pConnect,
|
|
IN PVIRTUALCHANNELNAME pVirtualName,
|
|
OUT PULONG pFlags)
|
|
{
|
|
PICA_VCBIND pVcBind;
|
|
PLIST_ENTRY Head, Next;
|
|
|
|
ASSERT( ExIsResourceAcquiredExclusiveLite( &pConnect->Resource ) );
|
|
|
|
/*
|
|
* Search the existing VC bind structures to locate virtual channel name
|
|
*/
|
|
Head = &pConnect->VcBindHead;
|
|
for (Next = Head->Flink; Next != Head; Next = Next->Flink) {
|
|
pVcBind = CONTAINING_RECORD(Next, ICA_VCBIND, Links);
|
|
if (!_stricmp(pVcBind->VirtualName, pVirtualName)) {
|
|
TRACE((pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaFindVcBind: vn %s -> vc %d\n",
|
|
pVirtualName, pVcBind->VirtualClass));
|
|
*pFlags = pVcBind->Flags;
|
|
return pVcBind->VirtualClass;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If name does not exist, return UNBOUND_CHANNEL
|
|
*/
|
|
TRACE(( pConnect, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaFindVcBind: vn %s (not found)\n", pVirtualName ));
|
|
return UNBOUND_CHANNEL;
|
|
}
|
|
|
|
|
|
VOID _IcaBindChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN CHANNELCLASS ChannelClass,
|
|
IN VIRTUALCHANNELCLASS VirtualClass,
|
|
IN ULONG Flags)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
ASSERT(ExIsResourceAcquiredExclusiveLite(&pChannel->Resource));
|
|
|
|
TRACECHANNEL(( pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: _IcaBindChannel: cc %u, vn %s vc %d\n",
|
|
ChannelClass, pChannel->VirtualName, VirtualClass ));
|
|
|
|
pChannel->ChannelClass = ChannelClass;
|
|
pChannel->VirtualClass = VirtualClass;
|
|
IcaLockChannelTable(pChannel->pChannelTableLock);
|
|
|
|
if (Flags & SD_CHANNEL_FLAG_SHADOW_PERSISTENT)
|
|
pChannel->Flags |= CHANNEL_SHADOW_PERSISTENT;
|
|
|
|
if (VirtualClass != UNBOUND_CHANNEL) {
|
|
ASSERT(pChannel->pConnect->pChannel[ChannelClass + VirtualClass] == NULL);
|
|
pChannel->pConnect->pChannel[ChannelClass + VirtualClass] = pChannel;
|
|
}
|
|
IcaUnlockChannelTable(pChannel->pChannelTableLock);
|
|
}
|
|
|
|
|
|
|
|
BOOLEAN IcaLockChannelTable(PERESOURCE pResource)
|
|
{
|
|
KIRQL oldIrql;
|
|
BOOLEAN Result;
|
|
|
|
|
|
/*
|
|
* lock the channel object
|
|
*/
|
|
KeEnterCriticalRegion(); // Disable APC calls when holding a resource.
|
|
Result = ExAcquireResourceExclusiveLite( pResource, TRUE );
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
void IcaUnlockChannelTable(PERESOURCE pResource)
|
|
{
|
|
|
|
ExReleaseResourceLite(pResource);
|
|
KeLeaveCriticalRegion(); // Resume APC calls after releasing resource.
|
|
|
|
}
|
|
|
|
NTSTATUS IcaCancelReadChannel(
|
|
IN PICA_CHANNEL pChannel,
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
KIRQL cancelIrql;
|
|
PLIST_ENTRY Head;
|
|
PIRP ReadIrp;
|
|
PINBUF pInBuf;
|
|
|
|
|
|
TRACECHANNEL((pChannel, TC_ICADD, TT_API2,
|
|
"TermDD: IcaCancelReadChannel, cc %u, vc %d\n",
|
|
pChannel->ChannelClass, pChannel->VirtualClass));
|
|
|
|
/*
|
|
* Lock channel while we clear out any
|
|
* pending read IRPs and/or input buffers.
|
|
*/
|
|
IcaLockChannel(pChannel);
|
|
|
|
/*
|
|
* Indicate that Reads are cancelled to this channel
|
|
*/
|
|
pChannel->Flags |= CHANNEL_CANCEL_READS;
|
|
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
while ( !IsListEmpty( &pChannel->InputIrpHead ) ) {
|
|
Head = pChannel->InputIrpHead.Flink;
|
|
ReadIrp = CONTAINING_RECORD( Head, IRP, Tail.Overlay.ListEntry );
|
|
ReadIrp->CancelIrql = cancelIrql;
|
|
IoSetCancelRoutine( ReadIrp, NULL );
|
|
_IcaReadChannelCancelIrp( IrpSp->DeviceObject, ReadIrp );
|
|
IoAcquireCancelSpinLock( &cancelIrql );
|
|
}
|
|
IoReleaseCancelSpinLock( cancelIrql );
|
|
|
|
|
|
IcaUnlockChannel(pChannel);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|