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.
558 lines
19 KiB
558 lines
19 KiB
/* (C) 1997-1999 Microsoft Corp.
|
|
*
|
|
* file : IcaIFace.c
|
|
* author : Erik Mavrinac
|
|
*
|
|
* description: MCS setup/shutdown and direct entry points for use with the
|
|
* ICA programming model. See also Decode.c for IcaRawInput() handling.
|
|
*
|
|
* History:
|
|
* 10-Aug-1997 jparsons Revised for new calling model
|
|
* 05-Aug-1998 jparsons Added shadowing support
|
|
*
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#include <MCSImpl.h>
|
|
|
|
|
|
// Prototype for WD function used below, so we don't need to include
|
|
// lots of extra headers.
|
|
void WDW_OnClientDisconnected(void *);
|
|
|
|
|
|
/*
|
|
* Main initialization entry point for kernel-mode MCS.
|
|
* Called by the WD during its processing of WdOpen().
|
|
*/
|
|
MCSError APIENTRY MCSInitialize(
|
|
PSDCONTEXT pContext,
|
|
PSD_OPEN pSdOpen,
|
|
DomainHandle *phDomain,
|
|
void *pSMData)
|
|
{
|
|
Domain *pDomain;
|
|
unsigned i;
|
|
ULONG ulBufferLen;
|
|
|
|
TraceOut(pContext, "MCSInitialize(): entry");
|
|
|
|
//
|
|
// Alloc the Domain struct. We allocate the basic size plus the typical
|
|
// input buffer size. The default in the registry today is 2048 which
|
|
// nicely fits the max virtual channel PDU of about 1640. If we get a
|
|
// message that exceeds this length we will dynamically allocate a buffer
|
|
// just for use in a one time reassembly then delete it.
|
|
//
|
|
if (pSdOpen->StackClass != Stack_Passthru) {
|
|
ulBufferLen = pSdOpen->WdConfig.WdInputBufferLength;
|
|
}
|
|
else {
|
|
ulBufferLen = 1024 * 10;
|
|
}
|
|
|
|
pDomain = ExAllocatePoolWithTag(PagedPool, sizeof(Domain) +
|
|
ulBufferLen + INPUT_BUFFER_BIAS, MCS_POOL_TAG);
|
|
|
|
if (pDomain != NULL) {
|
|
memset(pDomain, 0, sizeof(Domain));
|
|
}
|
|
else {
|
|
ErrOut(pContext, "MCSInitialize(kernel): Alloc failure "
|
|
"allocating Domain");
|
|
return MCS_ALLOCATION_FAILURE;
|
|
}
|
|
|
|
// Save pContext -- it is needed for future tracing and ICA interaction.
|
|
pDomain->pContext = pContext;
|
|
|
|
// Save pSMData - needed for calling fast-path input decoding function.
|
|
pDomain->pSMData = pSMData;
|
|
|
|
// Store what we need from SD_OPEN.
|
|
pDomain->pStat = pSdOpen->pStatus;
|
|
pDomain->ReceiveBufSize = ulBufferLen;
|
|
|
|
// We have one reference to this pDomain
|
|
pDomain->PseudoRefCount = 1;
|
|
|
|
// Indicate that we do not want TermDD managing outbuf headers/trailers
|
|
pSdOpen->SdOutBufHeader = 0;
|
|
pSdOpen->SdOutBufTrailer = 0;
|
|
|
|
// Initialize MCS-specific Domain members. We already zeroed mem
|
|
// so set only nonzero variables.
|
|
SListInit(&pDomain->ChannelList, DefaultNumChannels);
|
|
SListInit(&pDomain->UserAttachmentList, DefaultNumUserAttachments);
|
|
pDomain->bTopProvider = TRUE;
|
|
pDomain->NextAvailDynChannel = MinDynamicChannel;
|
|
pDomain->State = State_Unconnected;
|
|
pDomain->StackClass = pSdOpen->StackClass;
|
|
for (i = 0; i < NumPreallocUA; i++) {
|
|
pDomain->PreallocUA[i].bInUse = FALSE;
|
|
pDomain->PreallocUA[i].bPreallocated = TRUE;
|
|
}
|
|
for (i = 0; i < NumPreallocChannel; i++) {
|
|
pDomain->PreallocChannel[i].bInUse = FALSE;
|
|
pDomain->PreallocChannel[i].bPreallocated = TRUE;
|
|
}
|
|
|
|
// Give the Domain to the caller.
|
|
*phDomain = pDomain;
|
|
|
|
// If this is a shadow or passthru stack, the default all the info
|
|
// otherwise, all this info gets built when the client connects
|
|
if ((pDomain->StackClass == Stack_Passthru) ||
|
|
(pDomain->StackClass == Stack_Shadow))
|
|
MCSCreateDefaultDomain(pContext, *phDomain);
|
|
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by WD during shadow connect processing to retrieve the client MCS
|
|
* domain parameters for use by the shadow target stack.
|
|
*/
|
|
MCSError APIENTRY MCSGetDomainInfo(
|
|
DomainHandle hDomain,
|
|
PDomainParameters pDomParams,
|
|
unsigned *MaxSendSize,
|
|
unsigned *MaxX224DataSize,
|
|
unsigned *X224SourcePort)
|
|
{
|
|
Domain *pDomain = hDomain;
|
|
|
|
TraceOut(pDomain->pContext, "MCSGetDomainInfo(): entry");
|
|
|
|
*pDomParams = pDomain->DomParams;
|
|
*MaxSendSize = pDomain->MaxSendSize;
|
|
*MaxX224DataSize = pDomain->MaxX224DataSize;
|
|
*X224SourcePort = pDomain->X224SourcePort;
|
|
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by WD during shadow connect processing to initialize the MCS
|
|
* domain for the shadow & passthru stacks.
|
|
*/
|
|
MCSError APIENTRY MCSCreateDefaultDomain(PSDCONTEXT pContext,
|
|
DomainHandle hDomain)
|
|
{
|
|
Domain *pDomain = hDomain;
|
|
|
|
TraceOut(pContext, "MCSCreateDefaultDomain(): entry");
|
|
|
|
pDomain->DomParams.MaxChannels = 34;
|
|
pDomain->DomParams.MaxUsers = RequiredMinUsers;
|
|
pDomain->DomParams.MaxTokens = 0;
|
|
pDomain->DomParams.NumPriorities = RequiredPriorities;
|
|
pDomain->DomParams.MinThroughput = 0;
|
|
pDomain->DomParams.MaxDomainHeight = RequiredDomainHeight;
|
|
pDomain->DomParams.MaxPDUSize = X224_DefaultDataSize;
|
|
pDomain->DomParams.ProtocolVersion = RequiredProtocolVer;
|
|
|
|
pDomain->MaxSendSize = X224_DefaultDataSize - 6 -
|
|
GetTotalLengthDeterminantEncodingSize(X224_DefaultDataSize);
|
|
pDomain->MaxX224DataSize = X224_DefaultDataSize;
|
|
pDomain->X224SourcePort = 0x1234;
|
|
pDomain->State = State_MCS_Connected;
|
|
pDomain->bCanSendData = 1;
|
|
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
* Called by WD during shadow connect processing to get the default domain
|
|
* params for the shadow target stack.
|
|
*/
|
|
MCSError APIENTRY MCSGetDefaultDomain(PSDCONTEXT pContext,
|
|
PDomainParameters pDomParams,
|
|
unsigned *MaxSendSize,
|
|
unsigned *MaxX224DataSize,
|
|
unsigned *X224SourcePort)
|
|
{
|
|
TraceOut(pContext, "MCSGetDefaultDomain(): entry");
|
|
|
|
pDomParams->MaxChannels = 34;
|
|
pDomParams->MaxUsers = RequiredMinUsers;
|
|
pDomParams->MaxTokens = 0;
|
|
pDomParams->NumPriorities = RequiredPriorities;
|
|
pDomParams->MinThroughput = 0;
|
|
pDomParams->MaxDomainHeight = RequiredDomainHeight;
|
|
pDomParams->MaxPDUSize = X224_DefaultDataSize;
|
|
pDomParams->ProtocolVersion = RequiredProtocolVer;
|
|
|
|
*MaxSendSize = pDomParams->MaxPDUSize - 6 -
|
|
GetTotalLengthDeterminantEncodingSize(pDomParams->MaxPDUSize);
|
|
*MaxX224DataSize = X224_DefaultDataSize;
|
|
*X224SourcePort = 0x1234;
|
|
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by WD during shadow connect processing to register which channel
|
|
* should receive all shadow data.
|
|
*/
|
|
MCSError APIENTRY MCSSetShadowChannel(
|
|
DomainHandle hDomain,
|
|
ChannelID shadowChannel)
|
|
{
|
|
Domain *pDomain = hDomain;
|
|
|
|
TraceOut(pDomain->pContext, "MCSSetShadowChannel: entry");
|
|
|
|
pDomain->shadowChannel = shadowChannel;
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Main destruction entry point for kernel-mode MCS.
|
|
* Called by the WD during its processing of WdClose().
|
|
*/
|
|
MCSError APIENTRY MCSCleanup(DomainHandle *phDomain)
|
|
{
|
|
Domain *pDomain;
|
|
UINT_PTR ChannelID;
|
|
MCSChannel *pMCSChannel;
|
|
UserHandle hUser;
|
|
UserAttachment *pUA;
|
|
|
|
pDomain = (Domain *)(*phDomain);
|
|
|
|
TraceOut1(pDomain->pContext, "MCSCleanup(): pDomain=%X", pDomain);
|
|
|
|
/*
|
|
* Free any remaining data in the Domain.
|
|
*/
|
|
|
|
// Deallocate all remaining channels, if present. Note we should take care
|
|
// of channels first since they're usually attached to other objects and
|
|
// need to have their bPreallocated status determined first.
|
|
for (;;) {
|
|
SListRemoveFirst(&pDomain->ChannelList, &ChannelID, &pMCSChannel);
|
|
if (pMCSChannel == NULL)
|
|
break;
|
|
SListDestroy(&pMCSChannel->UserList);
|
|
if (!pMCSChannel->bPreallocated)
|
|
ExFreePool(pMCSChannel);
|
|
}
|
|
|
|
// Deallocate all remaining user attachments, if present.
|
|
for (;;) {
|
|
SListRemoveFirst(&pDomain->UserAttachmentList, (UINT_PTR *)&hUser,
|
|
&pUA);
|
|
if (pUA == NULL)
|
|
break;
|
|
SListDestroy(&pUA->JoinedChannelList);
|
|
if (!pUA->bPreallocated)
|
|
ExFreePool(pUA);
|
|
}
|
|
|
|
// Kill lists.
|
|
SListDestroy(&pDomain->ChannelList);
|
|
SListDestroy(&pDomain->UserAttachmentList);
|
|
|
|
// Free outstanding dynamic input reassembly buffer if present.
|
|
if (pDomain->pReassembleData != NULL &&
|
|
pDomain->pReassembleData != pDomain->PacketBuf)
|
|
ExFreePool(pDomain->pReassembleData);
|
|
|
|
// Free the Domain.
|
|
PDomainRelease(pDomain);
|
|
*phDomain = NULL;
|
|
|
|
return MCS_NO_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Callout from WD when an IOCTL_ICA_VIRTUAL_QUERY_BINDINGS is received.
|
|
* pVBind is a pointer to an empty SD_VCBIND struct.
|
|
*/
|
|
NTSTATUS MCSIcaVirtualQueryBindings(
|
|
DomainHandle hDomain,
|
|
PSD_VCBIND *ppVBind,
|
|
unsigned *pBytesReturned)
|
|
{
|
|
Domain *pDomain;
|
|
NTSTATUS Status;
|
|
PSD_VCBIND pVBind;
|
|
|
|
pDomain = (Domain *)hDomain;
|
|
pVBind = *ppVBind;
|
|
|
|
// Define the user mode T120 channel.
|
|
if (!pDomain->bChannelBound) {
|
|
RtlCopyMemory(pVBind->VirtualName, Virtual_T120,
|
|
sizeof(Virtual_T120));
|
|
pVBind->VirtualClass = Virtual_T120ChannelNum;
|
|
*pBytesReturned = sizeof(SD_VCBIND);
|
|
pDomain->bChannelBound = TRUE;
|
|
|
|
// Skip our entry and advance the caller's pointer.
|
|
pVBind++;
|
|
*ppVBind = pVBind;
|
|
}
|
|
else {
|
|
*pBytesReturned = 0;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
// This is one of the events which must occur before the data flow can be
|
|
// sent across the net. If we have gotten an MCS_T120_START indication
|
|
// already, and an X.224 connect-request, then it is now time to send
|
|
// the X.224 response and kick off the data flow.
|
|
if (pDomain->bCanSendData && pDomain->State == State_X224_Requesting) {
|
|
TraceOut(pDomain->pContext,
|
|
"IcaQueryVirtBind(): Sending X.224 response");
|
|
Status = SendX224Confirm(pDomain);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Callout from WD upon reception of a IOCTL_T120_REQUEST, i.e. a user-mode
|
|
* ioctl.
|
|
*/
|
|
NTSTATUS MCSIcaT120Request(DomainHandle hDomain, PSD_IOCTL pSdIoctl)
|
|
{
|
|
Domain *pDomain;
|
|
IoctlHeader *pHeader;
|
|
|
|
pDomain = (Domain *)hDomain;
|
|
|
|
// Get the request type.
|
|
ASSERT(pSdIoctl->InputBufferLength >= sizeof(IoctlHeader));
|
|
pHeader = (IoctlHeader *)pSdIoctl->InputBuffer;
|
|
|
|
// Make sure request within bounds.
|
|
if (pHeader->Type < MCS_ATTACH_USER_REQUEST ||
|
|
pHeader->Type > MCS_T120_START) {
|
|
ErrOut(pDomain->pContext, "Invalid IOCTL_T120_REQUEST type");
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
// Check that request is supported.
|
|
if (g_T120RequestDispatch[pHeader->Type] == NULL) {
|
|
ErrOut(pDomain->pContext, "IOCTL_T120_REQUEST type unsupported");
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
// Make the call. The entry points are defined in MCSIoctl.c.
|
|
return (g_T120RequestDispatch[pHeader->Type])(pDomain, pSdIoctl);
|
|
}
|
|
|
|
|
|
/*
|
|
* Processes channel inputs from TD. For MCS we only need to check for
|
|
* upward-bound command channel inputs for broken-connection indications;
|
|
* everything else can be passed up the stack.
|
|
*/
|
|
|
|
// Utility function. Used here and in Decode.c for X.224 disconnection.
|
|
void SignalBrokenConnection(Domain *pDomain)
|
|
{
|
|
NTSTATUS Status;
|
|
DisconnectProviderIndicationIoctl DPin;
|
|
|
|
// Check if disconnection already happened.
|
|
if (pDomain->State != State_MCS_Connected)
|
|
return;
|
|
|
|
if (!pDomain->bChannelBound) {
|
|
TraceOut(pDomain->pContext, "SignalBrokenConnection(): Cannot "
|
|
"send disconnect-provider indication: user mode link broken");
|
|
return;
|
|
}
|
|
|
|
TraceOut(pDomain->pContext, "SignalBrokenConnection(): Sending "
|
|
"disconnect-provider indication to user mode");
|
|
|
|
// Begin filling out disconnect-provider indication for the node controller.
|
|
DPin.Header.Type = MCS_DISCONNECT_PROVIDER_INDICATION;
|
|
DPin.Header.hUser = NULL; // Node controller.
|
|
DPin.hConn = NULL;
|
|
DPin.Reason = REASON_DOMAIN_DISCONNECTED;
|
|
|
|
TraceOut1(pDomain->pContext, "%s: SignalBrokenConnection!!!",
|
|
pDomain->StackClass == Stack_Primary ? "Primary" :
|
|
(pDomain->StackClass == Stack_Shadow ? "Shadow" :
|
|
"PassThru"));
|
|
|
|
// Send the DPin to the node controller channel.
|
|
Status = IcaChannelInput(pDomain->pContext, Channel_Virtual,
|
|
Virtual_T120ChannelNum, NULL, (BYTE *)&DPin, sizeof(DPin));
|
|
if (!NT_SUCCESS(Status)) {
|
|
ErrOut(pDomain->pContext, "SignalBrokenConn(): Could not send "
|
|
"disconnect-provider indication: error on ChannelInput()");
|
|
// Ignore errors sending disconnect-provider upward. If the stack is
|
|
// going down we will no longer have connectivity.
|
|
}
|
|
|
|
// Transition to state unconnected, detach nonlocal users.
|
|
DisconnectProvider(pDomain, FALSE, REASON_DOMAIN_DISCONNECTED);
|
|
}
|
|
|
|
|
|
/*
|
|
* This function is called directly by TermDD with a pointer to the WD data
|
|
* structure. By convention, we assume that the DomainHandle is first in
|
|
* that struct so we can simply do a double-indirection to get to our data.
|
|
*/
|
|
NTSTATUS MCSIcaChannelInput(
|
|
void *pTSWd,
|
|
CHANNELCLASS ChannelClass,
|
|
VIRTUALCHANNELCLASS VirtualClass,
|
|
PINBUF pInBuf,
|
|
BYTE *pBuffer,
|
|
ULONG ByteCount)
|
|
{
|
|
Domain *pDomain;
|
|
NTSTATUS Status;
|
|
PICA_CHANNEL_COMMAND pCommand;
|
|
|
|
pDomain = (Domain *)(*((HANDLE *)pTSWd));
|
|
|
|
if (ChannelClass != Channel_Command)
|
|
goto SendUpStack;
|
|
|
|
if (ByteCount < sizeof(ICA_CHANNEL_COMMAND)) {
|
|
ErrOut(pDomain->pContext, "ChannelInput(): Channel_Command bad "
|
|
"byte count");
|
|
goto SendUpStack;
|
|
}
|
|
|
|
pCommand = (PICA_CHANNEL_COMMAND)pBuffer;
|
|
|
|
if (pCommand->Header.Command != ICA_COMMAND_BROKEN_CONNECTION)
|
|
goto SendUpStack;
|
|
|
|
TraceOut1(pDomain->pContext, "%s: ChannelInput(): broken connection received",
|
|
pDomain->StackClass == Stack_Primary ? "Primary" :
|
|
(pDomain->StackClass == Stack_Shadow ? "Shadow" :
|
|
"PassThru"));
|
|
|
|
// Block further send attempts from MCS. We will eventually receive an
|
|
// IOCTL_ICA_STACK_CANCEL_IO which means the same thing, but that is
|
|
// only done after we issue the ICA_COMMAND_BROKEN_CONNECTION
|
|
// upward.
|
|
pDomain->bCanSendData = FALSE;
|
|
|
|
// Signal that the client closed the connection, both for MCS and
|
|
// directly to the WD to release any session locks waiting on
|
|
// the client to complete a connection protocol sequence.
|
|
if (pDomain->pBrokenEvent)
|
|
KeSetEvent (pDomain->pBrokenEvent, EVENT_INCREMENT, FALSE);
|
|
WDW_OnClientDisconnected(pTSWd);
|
|
|
|
// If we have not already received a disconnect-provider request from
|
|
// user mode, send an indication.
|
|
if (pDomain->State == State_MCS_Connected && pDomain->bChannelBound)
|
|
SignalBrokenConnection(pDomain);
|
|
|
|
SendUpStack:
|
|
Status = IcaChannelInput(pDomain->pContext, ChannelClass, VirtualClass,
|
|
pInBuf, pBuffer, ByteCount);
|
|
if (!NT_SUCCESS(Status))
|
|
ErrOut(pDomain->pContext, "MCSIcaChannelInput(): Failed to forward "
|
|
"input upward");
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Receives signal from WD that an IOCTL_ICA_STACK_CANCEL_IO was received
|
|
* which signals that I/O on the stack is no longer allowed. After this
|
|
* point no further ICA buffer allocation, freeing, or data sends should be
|
|
* performed.
|
|
*/
|
|
void MCSIcaStackCancelIo(DomainHandle hDomain)
|
|
{
|
|
TraceOut(((Domain *)hDomain)->pContext, "Received STACK_CANCEL_IO");
|
|
|
|
((Domain *)hDomain)->bCanSendData = FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns number of bytes (octets) consumed finding the size in NBytesConsumed.
|
|
* Returns length in Result. Sets *pbLarge to nonzero if there are more
|
|
* encoded blocks following this one.
|
|
* Note that the maximum size encoded is 64K -- 0xC4 indicates
|
|
* that 4 16K blocks are encoded here. If the block is larger, for instance
|
|
* in a large MCS Send Data PDU, multiple blocks will be encoded one
|
|
* after another. If the block is an exact multiple of 16K, a trailing byte
|
|
* code 0x00 is appended as a placemarker to indicate that the encoding is
|
|
* complete.
|
|
* Examples of large encodings:
|
|
*
|
|
* 16K: 0xC1, then 16K of data, then 0x00 as the final placeholder.
|
|
* 16K + 1: 0xC1, then 16K of data, then 0x01, and finally the extra byte of data.
|
|
* 64K: 0xC4, then 64K of data, then 0x00 as the final placeholder.
|
|
* 128K + 1: 0xC4 then 64K of data, 0xC4 + 64K of data, 0x01 + 1 byte of data.
|
|
*
|
|
* pStart is assumed to be an octet(BYTE)-aligned address -- this function is
|
|
* designed for ALIGNED-PER encoding type, which is the type used in MCS.
|
|
* Note that bit references here are in the range 7..0 where 7 is the high bit.
|
|
* The ASN.1 spec uses 8..1.
|
|
* Returns FALSE if length could not be retrieved.
|
|
*/
|
|
BOOLEAN __fastcall DecodeLengthDeterminantPER(
|
|
BYTE *pStart, // [IN], points to start of encoded bytes.
|
|
unsigned BytesLeft, // [IN], number of bytes remaining in frame.
|
|
BOOLEAN *pbLarge, // [OUT] TRUE if there are more encoded blocks following.
|
|
unsigned *Length, // [OUT] Number of bytes encoded here.
|
|
unsigned *pNBytesConsumed) // [OUT] Count of bytes consumed in decoding.
|
|
{
|
|
if (BytesLeft >= 1) {
|
|
if (*pStart <= 0x7F) {
|
|
*pNBytesConsumed = 1;
|
|
*Length = *pStart;
|
|
*pbLarge = FALSE;
|
|
}
|
|
else {
|
|
// High bit 7 set, check to see if bit 6 is set.
|
|
if (*pStart & 0x40) {
|
|
// Bit 6 is set, the lowest 3 bits encode the number (1..4) of
|
|
// full 16K chunks following.
|
|
*pNBytesConsumed = 1;
|
|
*Length = 16384 * (*pStart & 0x07);
|
|
*pbLarge = TRUE;
|
|
}
|
|
else {
|
|
// Bit 6 is clear, length is encoded in 14 bits of last 6 bits
|
|
// of *pStart as most significant bits and all of the next
|
|
// byte as least significant.
|
|
if (BytesLeft >= 2) {
|
|
*pNBytesConsumed = 2;
|
|
*Length = ((unsigned)((*pStart & 0x3F) << 8) +
|
|
(unsigned)(*(pStart + 1)));
|
|
*pbLarge = FALSE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|