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.
5946 lines
151 KiB
5946 lines
151 KiB
/*++
|
|
|
|
Copyright (c) 1998-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
cgroup.c
|
|
|
|
Abstract:
|
|
|
|
Note that most of the routines in this module assume they are called
|
|
at PASSIVE_LEVEL.
|
|
|
|
See end of file for somewhat dated design notes
|
|
|
|
Author:
|
|
|
|
Paul McDaniel (paulmcd) 12-Jan-1999
|
|
|
|
Revision History:
|
|
|
|
Anish Desai (anishd) 1-May-2002 Add Namespace reservation
|
|
and registration support
|
|
|
|
--*/
|
|
|
|
#include "precomp.h" // Project wide headers
|
|
#include "cgroupp.h" // Private data structures
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text( INIT, UlInitializeCG )
|
|
#pragma alloc_text( PAGE, UlTerminateCG )
|
|
|
|
#pragma alloc_text( PAGE, UlAddUrlToConfigGroup )
|
|
#pragma alloc_text( PAGE, UlConfigGroupFromListEntry )
|
|
#pragma alloc_text( PAGE, UlCreateConfigGroup )
|
|
#pragma alloc_text( PAGE, UlDeleteConfigGroup )
|
|
#pragma alloc_text( PAGE, UlGetConfigGroupInfoForUrl )
|
|
#pragma alloc_text( PAGE, UlQueryConfigGroupInformation )
|
|
#pragma alloc_text( PAGE, UlRemoveUrlFromConfigGroup )
|
|
#pragma alloc_text( PAGE, UlRemoveAllUrlsFromConfigGroup )
|
|
#pragma alloc_text( PAGE, UlSetConfigGroupInformation )
|
|
#pragma alloc_text( PAGE, UlNotifyOrphanedConfigGroup )
|
|
#pragma alloc_text( PAGE, UlSanitizeUrl )
|
|
#pragma alloc_text( PAGE, UlRemoveSite )
|
|
|
|
#pragma alloc_text( PAGE, UlpSetUrlInfoSpecial )
|
|
#pragma alloc_text( PAGE, UlpCreateConfigGroupObject )
|
|
#pragma alloc_text( PAGE, UlpCleanAllUrls )
|
|
#pragma alloc_text( PAGE, UlpDeferredRemoveSite )
|
|
#pragma alloc_text( PAGE, UlpDeferredRemoveSiteWorker )
|
|
#pragma alloc_text( PAGE, UlpSetUrlInfo )
|
|
#pragma alloc_text( PAGE, UlConfigGroupInfoRelease )
|
|
#pragma alloc_text( PAGE, UlConfigGroupInfoDeepCopy )
|
|
#pragma alloc_text( PAGE, UlpTreeFreeNode )
|
|
#pragma alloc_text( PAGE, UlpTreeDeleteRegistration )
|
|
#pragma alloc_text( PAGE, UlpTreeDeleteReservation )
|
|
#pragma alloc_text( PAGE, UlpTreeFindNode )
|
|
#pragma alloc_text( PAGE, UlpTreeFindNodeWalker )
|
|
#pragma alloc_text( PAGE, UlpTreeFindNodeHelper )
|
|
#pragma alloc_text( PAGE, UlpTreeFindReservationNode )
|
|
#pragma alloc_text( PAGE, UlpTreeFindRegistrationNode )
|
|
#pragma alloc_text( PAGE, UlpTreeBinaryFindEntry )
|
|
#pragma alloc_text( PAGE, UlpTreeCreateSite )
|
|
#pragma alloc_text( PAGE, UlpTreeFindSite )
|
|
#pragma alloc_text( PAGE, UlpTreeFindWildcardSite )
|
|
#pragma alloc_text( PAGE, UlpTreeFindSiteIpMatch )
|
|
#pragma alloc_text( PAGE, UlpTreeInsert )
|
|
#pragma alloc_text( PAGE, UlpTreeInsertEntry )
|
|
#pragma alloc_text( PAGE, UlLookupHostPlusIPSite )
|
|
#pragma alloc_text( PAGE, UlCGLockWriteSyncRemoveSite )
|
|
#pragma alloc_text( PAGE, UlpExtractSchemeHostPortIp )
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
|
|
//
|
|
// Globals
|
|
//
|
|
|
|
PUL_CG_URL_TREE_HEADER g_pSites = NULL;
|
|
BOOLEAN g_InitCGCalled = FALSE;
|
|
KEVENT g_RemoveSiteEvent;
|
|
LONG g_RemoveSiteCount = 0;
|
|
LONG g_NameIPSiteCount = 0;
|
|
LIST_ENTRY g_ReservationListHead;
|
|
|
|
//
|
|
// Macro for uniformity with CG_LOCK_* macros.
|
|
//
|
|
|
|
#define CG_LOCK_WRITE_SYNC_REMOVE_SITE() UlCGLockWriteSyncRemoveSite()
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This inline function waits for g_RemoveSiteCount to drop to zero and
|
|
acquires the CG lock exclusively.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--**************************************************************************/
|
|
__forceinline
|
|
VOID
|
|
UlCGLockWriteSyncRemoveSite(
|
|
VOID
|
|
)
|
|
{
|
|
for(;;)
|
|
{
|
|
CG_LOCK_WRITE();
|
|
|
|
if (InterlockedExchangeAdd(&g_RemoveSiteCount, 0))
|
|
{
|
|
CG_UNLOCK_WRITE();
|
|
|
|
//
|
|
// The wait has to be outside the CG lock or we can run into
|
|
// a deadlock where DeferredRemoveSiteWorker waits for the
|
|
// connections to go away but UlpHandleRequest is blocked on
|
|
// the CG lock so the request never has a chance to release
|
|
// its ref on the connection.
|
|
//
|
|
|
|
KeWaitForSingleObject(
|
|
&g_RemoveSiteEvent,
|
|
UserRequest,
|
|
UserMode,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Free's the node pEntry. This funciton walks up and tree of parent entries
|
|
and deletes them if they are supposed to free'd (dummy nodes) .
|
|
|
|
Arguments:
|
|
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry - the entry to free
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFreeNode(
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CG_URL_TREE_HEADER pHeader;
|
|
ULONG Index;
|
|
PUL_CG_URL_TREE_ENTRY pParent;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Loop! we are going to walk up the tree deleting as much
|
|
// as we can of this branch.
|
|
//
|
|
|
|
while (pEntry != NULL)
|
|
{
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
//
|
|
// Init
|
|
//
|
|
|
|
pParent = NULL;
|
|
|
|
UlTrace(
|
|
CONFIG_GROUP_TREE, (
|
|
"http!UlpTreeFreeNode - pEntry(%p, '%S', %d, %d, %S, %S%S)\n",
|
|
pEntry,
|
|
pEntry->pToken,
|
|
(int) pEntry->Registration,
|
|
(int) pEntry->Reservation,
|
|
(pEntry->pChildren == NULL || pEntry->pChildren->UsedCount == 0)
|
|
? L"no children" : L"children",
|
|
pEntry->pParent == NULL ? L"no parent" : L"parent=",
|
|
pEntry->pParent == NULL ? L"" : pEntry->pParent->pToken
|
|
)
|
|
);
|
|
|
|
//
|
|
// 1) might not be a "real" leaf - we are walking up the tree in this
|
|
// loop
|
|
//
|
|
// 2) also we clean this first because we might not be deleting
|
|
// this node at all, if it has dependent children.
|
|
//
|
|
|
|
ASSERT(pEntry->Registration == FALSE);
|
|
ASSERT(pEntry->pConfigGroup == NULL);
|
|
ASSERT(pEntry->Reservation == FALSE);
|
|
ASSERT(pEntry->pSecurityDescriptor == NULL);
|
|
ASSERT(pEntry->SiteAddedToEndpoint == FALSE);
|
|
ASSERT(pEntry->pRemoveSiteWorkItem == NULL);
|
|
|
|
//
|
|
// do we have children?
|
|
//
|
|
|
|
if (pEntry->pChildren != NULL && pEntry->pChildren->UsedCount > 0)
|
|
{
|
|
//
|
|
// can't delete it. dependant children exist.
|
|
// it's already be converted to a dummy node above.
|
|
//
|
|
// leave it. it will get cleaned by a subsequent child.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// we are really deleting this one, remove it from the sibling list.
|
|
//
|
|
|
|
//
|
|
// find our location in the sibling list
|
|
//
|
|
|
|
if (pEntry->pParent == NULL)
|
|
{
|
|
pHeader = g_pSites;
|
|
}
|
|
else
|
|
{
|
|
pHeader = pEntry->pParent->pChildren;
|
|
}
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
pHeader,
|
|
pEntry->pToken,
|
|
pEntry->TokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
ASSERT(FALSE);
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// time to remove it
|
|
//
|
|
// if not the last one, shift left the array at Index
|
|
//
|
|
|
|
if (Index < (pHeader->UsedCount-1))
|
|
{
|
|
RtlMoveMemory(
|
|
&(pHeader->pEntries[Index]),
|
|
&(pHeader->pEntries[Index+1]),
|
|
(pHeader->UsedCount - 1 - Index) * sizeof(UL_CG_HEADER_ENTRY)
|
|
);
|
|
}
|
|
|
|
//
|
|
// now we have 1 less
|
|
//
|
|
|
|
pHeader->UsedCount -= 1;
|
|
|
|
//
|
|
// update count for different site types
|
|
//
|
|
|
|
switch (pEntry->UrlType)
|
|
{
|
|
case HttpUrlSite_Name:
|
|
{
|
|
pHeader->NameSiteCount--;
|
|
ASSERT(pHeader->NameSiteCount >= 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_IP:
|
|
{
|
|
pHeader->IPSiteCount--;
|
|
ASSERT(pHeader->IPSiteCount >= 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_StrongWildcard:
|
|
{
|
|
pHeader->StrongWildcardCount--;
|
|
ASSERT(pHeader->StrongWildcardCount >= 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_WeakWildcard:
|
|
{
|
|
pHeader->WeakWildcardCount--;
|
|
ASSERT(pHeader->WeakWildcardCount >= 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_NamePlusIP:
|
|
{
|
|
pHeader->NameIPSiteCount--;
|
|
InterlockedDecrement(&g_NameIPSiteCount);
|
|
ASSERT(pHeader->NameIPSiteCount >= 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_None:
|
|
default:
|
|
{
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(
|
|
pHeader->UsedCount == (ULONG)
|
|
(pHeader->NameSiteCount +
|
|
pHeader->IPSiteCount +
|
|
pHeader->WeakWildcardCount +
|
|
pHeader->StrongWildcardCount +
|
|
pHeader->NameIPSiteCount
|
|
));
|
|
|
|
//
|
|
// need to clean parent entries that were here just for this leaf
|
|
//
|
|
|
|
if (pEntry->pParent != NULL)
|
|
{
|
|
//
|
|
// Does this parent have any other children?
|
|
//
|
|
|
|
ASSERT(IS_VALID_TREE_HEADER(pEntry->pParent->pChildren));
|
|
|
|
if (pEntry->pParent->pChildren->UsedCount == 0)
|
|
{
|
|
//
|
|
// no more, time to clean the child list
|
|
//
|
|
|
|
UL_FREE_POOL_WITH_SIG(
|
|
pEntry->pParent->pChildren,
|
|
UL_CG_TREE_HEADER_POOL_TAG
|
|
);
|
|
|
|
//
|
|
// is the parent a real url entry?
|
|
//
|
|
|
|
if (pEntry->pParent->Registration == FALSE &&
|
|
pEntry->pParent->Reservation == FALSE)
|
|
{
|
|
//
|
|
// nope . let's scrub it.
|
|
//
|
|
|
|
pParent = pEntry->pParent;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// ouch. siblings. can't nuke parent.
|
|
//
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free the entry
|
|
//
|
|
|
|
UL_FREE_POOL_WITH_SIG(pEntry, UL_CG_TREE_ENTRY_POOL_TAG);
|
|
|
|
//
|
|
// Move on to the next one
|
|
//
|
|
|
|
pEntry = pParent;
|
|
}
|
|
|
|
end:
|
|
return Status;
|
|
|
|
} // UlpTreeFreeNode
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine deletes a registration. If the node is not a reservation
|
|
node, it is physically deleted from the tree.
|
|
|
|
Arguments:
|
|
|
|
pEntry - Supplies the entry of the registration to be deleted.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeDeleteRegistration(
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
//
|
|
// delete registration.
|
|
//
|
|
|
|
ASSERT(pEntry->Registration == TRUE);
|
|
|
|
//
|
|
// remove it from the config group list.
|
|
//
|
|
|
|
RemoveEntryList(&(pEntry->ConfigGroupListEntry));
|
|
pEntry->ConfigGroupListEntry.Flink = NULL;
|
|
pEntry->ConfigGroupListEntry.Blink = NULL;
|
|
pEntry->pConfigGroup = NULL;
|
|
|
|
if (pEntry->SiteAddedToEndpoint)
|
|
{
|
|
//
|
|
// the registration was added to the endpoint list, remove it.
|
|
//
|
|
|
|
ASSERT(pEntry->pRemoveSiteWorkItem != NULL);
|
|
UlpDeferredRemoveSite(pEntry);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pEntry->pRemoveSiteWorkItem == NULL);
|
|
}
|
|
|
|
//
|
|
// mark it as a non-registration node.
|
|
//
|
|
|
|
pEntry->Registration = FALSE;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// clean up the node if necessary.
|
|
//
|
|
|
|
if (pEntry->Reservation == FALSE)
|
|
{
|
|
//
|
|
// if it is not a reservation node, try to free it.
|
|
// this will also remove it from endpoint if necessary.
|
|
//
|
|
|
|
Status = UlpTreeFreeNode(pEntry);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeDeleteRegistration
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine delete a reservation. If the node is not a registration
|
|
node, it is physically deleted from the tree.
|
|
|
|
Arguments:
|
|
|
|
pEntry - Supplies a pointer to the reservation to be deleted.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeDeleteReservation(
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
ASSERT(pEntry->Reservation == TRUE);
|
|
|
|
//
|
|
// delete reservation.
|
|
//
|
|
|
|
ASSERT(pEntry->Reservation == TRUE);
|
|
|
|
//
|
|
// remove it from the list global reservation list.
|
|
//
|
|
|
|
RemoveEntryList(&pEntry->ReservationListEntry);
|
|
pEntry->ReservationListEntry.Flink = NULL;
|
|
pEntry->ReservationListEntry.Blink = NULL;
|
|
|
|
//
|
|
// mark it as non-reservation node.
|
|
//
|
|
|
|
pEntry->Reservation = FALSE;
|
|
|
|
//
|
|
// delete security descriptor.
|
|
//
|
|
|
|
ASSERT(pEntry->pSecurityDescriptor != NULL);
|
|
|
|
SeReleaseSecurityDescriptor(
|
|
pEntry->pSecurityDescriptor,
|
|
KernelMode, // always captured in kernel mode
|
|
TRUE // force capture
|
|
);
|
|
|
|
pEntry->pSecurityDescriptor = NULL;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// if it is not a registration node, try to free it.
|
|
//
|
|
|
|
if (pEntry->Registration == FALSE)
|
|
{
|
|
Status = UlpTreeFreeNode(pEntry);
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeDeleteReservation
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes a config group object.
|
|
|
|
Arguments:
|
|
|
|
ppObject - gets a pointer to the object on success
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpCreateConfigGroupObject(
|
|
PUL_CONFIG_GROUP_OBJECT * ppObject
|
|
)
|
|
{
|
|
HTTP_CONFIG_GROUP_ID NewId = HTTP_NULL_ID;
|
|
PUL_CONFIG_GROUP_OBJECT pNewObject = NULL;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(ppObject != NULL);
|
|
|
|
//
|
|
// Create an empty config group object structure - PAGED
|
|
//
|
|
|
|
pNewObject = UL_ALLOCATE_STRUCT(
|
|
NonPagedPool,
|
|
UL_CONFIG_GROUP_OBJECT,
|
|
UL_CG_OBJECT_POOL_TAG
|
|
);
|
|
|
|
if (pNewObject == NULL)
|
|
{
|
|
//
|
|
// Oops. Couldn't allocate the memory for it.
|
|
//
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory(pNewObject, sizeof(UL_CONFIG_GROUP_OBJECT));
|
|
|
|
//
|
|
// Create an opaque id for it
|
|
//
|
|
|
|
Status = UlAllocateOpaqueId(
|
|
&NewId, // pOpaqueId
|
|
UlOpaqueIdTypeConfigGroup, // OpaqueIdType
|
|
pNewObject // pContext
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlpCreateConfigGroupObject, obj=%p, ID=%I64x\n",
|
|
pNewObject, NewId
|
|
));
|
|
|
|
//
|
|
// Fill in the structure
|
|
//
|
|
|
|
pNewObject->Signature = UL_CG_OBJECT_POOL_TAG;
|
|
pNewObject->RefCount = 1;
|
|
pNewObject->ConfigGroupId = NewId;
|
|
|
|
pNewObject->AppPoolFlags.Present = 0;
|
|
pNewObject->pAppPool = NULL;
|
|
|
|
pNewObject->MaxBandwidth.Flags.Present = 0;
|
|
pNewObject->MaxConnections.Flags.Present = 0;
|
|
pNewObject->State.Flags.Present = 0;
|
|
pNewObject->LoggingConfig.Flags.Present = 0;
|
|
pNewObject->pLogFileEntry = NULL;
|
|
|
|
//
|
|
// init the bandwidth throttling flow list
|
|
//
|
|
InitializeListHead(&pNewObject->FlowListHead);
|
|
|
|
//
|
|
// init notification entries & head
|
|
//
|
|
UlInitializeNotifyEntry(&pNewObject->HandleEntry, pNewObject);
|
|
UlInitializeNotifyEntry(&pNewObject->ParentEntry, pNewObject);
|
|
|
|
UlInitializeNotifyHead(
|
|
&pNewObject->ChildHead,
|
|
&g_pUlNonpagedData->ConfigGroupResource
|
|
);
|
|
|
|
//
|
|
// init the url list
|
|
//
|
|
|
|
InitializeListHead(&pNewObject->UrlListHead);
|
|
|
|
//
|
|
// return the pointer
|
|
//
|
|
*ppObject = pNewObject;
|
|
|
|
end:
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Something failed. Let's clean up.
|
|
//
|
|
|
|
if (pNewObject != NULL)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG(pNewObject, UL_CG_OBJECT_POOL_TAG);
|
|
}
|
|
|
|
if (!HTTP_IS_NULL_ID(&NewId))
|
|
{
|
|
UlFreeOpaqueId(NewId, UlOpaqueIdTypeConfigGroup);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpCreateConfigGroupObject
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This will clean all of the urls in the LIST_ENTRY for the config group
|
|
|
|
Arguments:
|
|
|
|
IN PUL_CONFIG_GROUP_OBJECT pObject the group to clean the urls for
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpCleanAllUrls(
|
|
IN PUL_CONFIG_GROUP_OBJECT pObject
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
|
|
ASSERT(pObject != NULL);
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlpCleanAllUrls, obj=%p\n",
|
|
pObject
|
|
));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Remove all of the url's associated with this cfg group
|
|
//
|
|
|
|
//
|
|
// walk the list
|
|
//
|
|
|
|
while (IsListEmpty(&pObject->UrlListHead) == FALSE)
|
|
{
|
|
PUL_CG_URL_TREE_ENTRY pTreeEntry;
|
|
|
|
//
|
|
// get the containing struct
|
|
//
|
|
pTreeEntry = CONTAINING_RECORD(
|
|
pObject->UrlListHead.Flink,
|
|
UL_CG_URL_TREE_ENTRY,
|
|
ConfigGroupListEntry
|
|
);
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pTreeEntry) && pTreeEntry->Registration == TRUE);
|
|
|
|
//
|
|
// delete it - this unlinks it from the list
|
|
//
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = UlpTreeDeleteRegistration(pTreeEntry);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// just record the first error, but still attempt to free all
|
|
//
|
|
|
|
UlpTreeDeleteRegistration(pTreeEntry);
|
|
}
|
|
|
|
}
|
|
|
|
// the list is empty now
|
|
|
|
return Status;
|
|
|
|
} // UlpCleanAllUrls
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes a site entry's url from the listening endpoint.
|
|
|
|
We have to stop listening on another thread because otherwise there
|
|
will be a deadlock between the config group lock and http connection
|
|
locks.
|
|
|
|
Arguments:
|
|
|
|
pEntry - the site entry
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpDeferredRemoveSite(
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry
|
|
)
|
|
{
|
|
PUL_DEFERRED_REMOVE_ITEM pRemoveItem;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT( IS_CG_LOCK_OWNED_WRITE() );
|
|
ASSERT( IS_VALID_TREE_ENTRY(pEntry) );
|
|
ASSERT( pEntry->SiteAddedToEndpoint == TRUE );
|
|
ASSERT( IS_VALID_DEFERRED_REMOVE_ITEM(pEntry->pRemoveSiteWorkItem) );
|
|
|
|
pRemoveItem = pEntry->pRemoveSiteWorkItem;
|
|
|
|
//
|
|
// Update pEntry.
|
|
//
|
|
|
|
pEntry->pRemoveSiteWorkItem = NULL;
|
|
pEntry->SiteAddedToEndpoint = FALSE;
|
|
|
|
UlRemoveSite(pRemoveItem);
|
|
|
|
} // UlpDeferredRemoveSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes a site entry's url from the listening endpoint.
|
|
|
|
We have to stop listening on another thread because otherwise there
|
|
will be a deadlock between the config group lock and http connection
|
|
locks.
|
|
|
|
Arguments:
|
|
|
|
pRemoveItem - the worker item for the removal
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlRemoveSite(
|
|
IN PUL_DEFERRED_REMOVE_ITEM pRemoveItem
|
|
)
|
|
{
|
|
ASSERT( IS_VALID_DEFERRED_REMOVE_ITEM(pRemoveItem) );
|
|
|
|
//
|
|
// Initialize the work item.
|
|
//
|
|
|
|
UlInitializeWorkItem(&pRemoveItem->WorkItem);
|
|
|
|
//
|
|
// REVIEW: Because UlRemoveSiteFromEndpointList can block
|
|
// REVIEW: indefinitely while waiting for other work items
|
|
// REVIEW: to complete, we must not queue it with UlQueueWorkItem.
|
|
// REVIEW: (could lead to deadlock, esp. in a single-threded queue)
|
|
//
|
|
|
|
if (1 == InterlockedIncrement(&g_RemoveSiteCount))
|
|
{
|
|
UlTrace(CONFIG_GROUP_TREE,
|
|
("http!UlRemoveSite: Clearing g_RemoveSiteEvent >>>\n" ));
|
|
|
|
KeClearEvent(&g_RemoveSiteEvent);
|
|
}
|
|
|
|
UL_QUEUE_WAIT_ITEM(
|
|
&pRemoveItem->WorkItem,
|
|
&UlpDeferredRemoveSiteWorker
|
|
);
|
|
|
|
} // UlRemoveSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes a site entry's url from the listening endpoint.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - in a UL_DEFERRED_REMOVE_ITEM struct with the endpoint name
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpDeferredRemoveSiteWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_DEFERRED_REMOVE_ITEM pRemoveItem;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
PAGED_CODE();
|
|
ASSERT( pWorkItem );
|
|
|
|
//
|
|
// get the remove item
|
|
//
|
|
|
|
pRemoveItem = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_DEFERRED_REMOVE_ITEM,
|
|
WorkItem
|
|
);
|
|
|
|
//
|
|
// Remove the endpoint
|
|
//
|
|
|
|
Status = UlRemoveSiteFromEndpointList(
|
|
pRemoveItem->UrlSecure,
|
|
pRemoveItem->UrlPort
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
// c'est la vie
|
|
UlTraceError(CONFIG_GROUP_TREE, (
|
|
"http!UlpDeferredRemoveSiteWorker(%s, %d) failed %s\n",
|
|
pRemoveItem->UrlSecure ? "https" : "http",
|
|
pRemoveItem->UrlPort,
|
|
HttpStatusToString(Status)
|
|
));
|
|
|
|
//
|
|
// This is not suppose to happend. If an error occured, then we
|
|
// could not drop the endpoint usage count. The endpoint will
|
|
// probably hang around because of it.
|
|
//
|
|
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
//
|
|
// Signal we are clear for new endpoint creations.
|
|
//
|
|
|
|
if (0 == InterlockedDecrement(&g_RemoveSiteCount))
|
|
{
|
|
UlTrace(CONFIG_GROUP_TREE,
|
|
("http!UlpDeferredRemoveSiteWorker: Setting g_RemoveSiteEvent <<<\n" ));
|
|
|
|
KeSetEvent(
|
|
&g_RemoveSiteEvent,
|
|
0,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
UL_FREE_POOL_WITH_SIG(pRemoveItem, UL_DEFERRED_REMOVE_ITEM_POOL_TAG);
|
|
|
|
} // UlpDeferredRemoveSiteWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This will return a fresh buffer containing the url sanitized. caller
|
|
must free this from paged pool.
|
|
|
|
CODEWORK: log errors to event log or error log
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING pUrl, the url to clean
|
|
|
|
OUT PWSTR * ppUrl the cleaned url
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY_TYPE pUrlType Name, Ip, or WildCard site
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_NO_MEMORY the memory alloc failed
|
|
STATUS_INVALID_PARAMETER malformed URL
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlSanitizeUrl(
|
|
IN PWCHAR pUrl,
|
|
IN ULONG UrlCharCount,
|
|
IN BOOLEAN TrailingSlashRequired,
|
|
OUT PWSTR* ppUrl,
|
|
OUT PHTTP_PARSED_URL pParsedUrl
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(UrlCharCount > 0);
|
|
ASSERT(ppUrl != NULL);
|
|
ASSERT(pParsedUrl != NULL);
|
|
|
|
*ppUrl = NULL;
|
|
|
|
Status = HttpParseUrl(
|
|
&g_UrlC14nConfig,
|
|
pUrl,
|
|
UrlCharCount,
|
|
TrailingSlashRequired,
|
|
TRUE, // Force routing IP to be same as IP literal
|
|
pParsedUrl
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = HttpNormalizeParsedUrl(
|
|
pParsedUrl,
|
|
&g_UrlC14nConfig,
|
|
TRUE, // Force an allocation
|
|
FALSE, // Don't free the original (pUrl->Buffer)
|
|
TRUE, // Force routing IP to be same as IP literal
|
|
PagedPool,
|
|
URL_POOL_TAG
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
*ppUrl = pParsedUrl->pFullUrl;
|
|
|
|
ASSERT(NULL != ppUrl);
|
|
}
|
|
}
|
|
|
|
RETURN(Status);
|
|
|
|
} // UlSanitizeUrl
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds a node under a site in the config group tree. Based
|
|
on the criteria, it can find the longest matching reservation node,
|
|
longest matching registration node, or longest matching node that is
|
|
either a registration or a reservation. This routine always finds the
|
|
exact matching node.
|
|
|
|
Arguments:
|
|
|
|
pSiteEntry - Supplies the site level tree entry.
|
|
|
|
pNextToken - Supplies the remaining of the path to be searched.
|
|
|
|
Criteria - Supplies the criteria (longest reservation, longest
|
|
registration, or longest reservation or registration.)
|
|
|
|
ppMatchEntry - Returns the entry matching the criteria.
|
|
|
|
ppExactEntry - Returns the exact matching entry.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - if an exact matching entry is found.
|
|
STATUS_OBJECT_NAME_NOT_FOUND - if no exact matching entry found.
|
|
|
|
Notes:
|
|
|
|
If the return code is STATUS_SUCCESS or STATUS_OBJECT_NAME_NOT_FOUND,
|
|
ppMatchEntry returns a node matching the criteria.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindNodeHelper(
|
|
IN PUL_CG_URL_TREE_ENTRY pSiteEntry,
|
|
IN PWSTR pNextToken,
|
|
IN ULONG Criteria,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppMatchEntry,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppExactEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pToken;
|
|
ULONG TokenLength;
|
|
ULONG Index;
|
|
PUL_CG_URL_TREE_ENTRY pEntry;
|
|
PUL_CG_URL_TREE_ENTRY pMatch;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_VALID_TREE_ENTRY(pSiteEntry));
|
|
ASSERT(pSiteEntry->pParent == NULL);
|
|
ASSERT(pNextToken != NULL);
|
|
ASSERT(ppMatchEntry != NULL || ppExactEntry != NULL);
|
|
|
|
//
|
|
// Initialize output arguments.
|
|
//
|
|
|
|
if (ppMatchEntry)
|
|
{
|
|
*ppMatchEntry = NULL;
|
|
}
|
|
|
|
if (ppExactEntry)
|
|
{
|
|
*ppExactEntry = NULL;
|
|
}
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
pEntry = pSiteEntry;
|
|
pMatch = NULL;
|
|
Status = STATUS_SUCCESS;
|
|
|
|
for(;;)
|
|
{
|
|
//
|
|
// A bonafide match?
|
|
//
|
|
|
|
if (pEntry->Registration)
|
|
{
|
|
if (Criteria & FNC_LONGEST_REGISTRATION)
|
|
{
|
|
//
|
|
// found a longer registration entry
|
|
//
|
|
|
|
pMatch = pEntry;
|
|
}
|
|
}
|
|
|
|
if (pEntry->Reservation)
|
|
{
|
|
if (Criteria & FNC_LONGEST_RESERVATION)
|
|
{
|
|
//
|
|
// found a longer reservation entry
|
|
//
|
|
|
|
pMatch = pEntry;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Are we already at the end of the url?
|
|
//
|
|
|
|
if (pNextToken == NULL || *pNextToken == UNICODE_NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// find the next token
|
|
//
|
|
|
|
pToken = pNextToken;
|
|
pNextToken = wcschr(pNextToken, L'/');
|
|
|
|
//
|
|
// can be null if this is a leaf
|
|
//
|
|
|
|
if (pNextToken != NULL)
|
|
{
|
|
//
|
|
// replace the '/' with a null, we'll fix it later
|
|
//
|
|
|
|
pNextToken[0] = UNICODE_NULL;
|
|
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
|
|
pNextToken += 1;
|
|
}
|
|
else
|
|
{
|
|
TokenLength = (ULONG)(wcslen(pToken) * sizeof(WCHAR));
|
|
}
|
|
|
|
//
|
|
// match?
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
pEntry->pChildren,
|
|
pToken,
|
|
TokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (pNextToken != NULL)
|
|
{
|
|
//
|
|
// Fix the string, i replaced the '/' with a UNICODE_NULL
|
|
//
|
|
|
|
(pNextToken-1)[0] = L'/';
|
|
}
|
|
|
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
//
|
|
// it's a sorted tree, the first non-match means we're done
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// other error?
|
|
//
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// found a match, look for deeper matches.
|
|
//
|
|
|
|
pEntry = pEntry->pChildren->pEntries[Index].pEntry;
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
}
|
|
|
|
//
|
|
// Return results.
|
|
//
|
|
|
|
if (ppMatchEntry != NULL)
|
|
{
|
|
*ppMatchEntry = pMatch;
|
|
}
|
|
|
|
//
|
|
// pEntry contains exact matching node when NT_SUCCESS(Status) is TRUE.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status) && ppExactEntry != NULL)
|
|
{
|
|
*ppExactEntry = pEntry;
|
|
}
|
|
|
|
end:
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindNodeHelper
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
walks the tree and find's a matching entry for pUrl. 2 output options,
|
|
you can get the entry back, or a computed URL_INFO with inheritence applied.
|
|
you must free the URL_INFO from NonPagedPool.
|
|
|
|
Arguments:
|
|
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry, the top of the tree
|
|
|
|
IN PWSTR pNextToken, where to start looking under
|
|
the tree
|
|
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO * ppInfo, [optional] the info to set,
|
|
might have to grow it.
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry [optional] returns the found
|
|
entry
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_OBJECT_NAME_NOT_FOUND no entry found
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindNodeWalker(
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry,
|
|
IN PWSTR pNextToken,
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO pInfo OPTIONAL,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry OPTIONAL
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CG_URL_TREE_ENTRY pMatchEntry;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
ASSERT(pNextToken != NULL);
|
|
ASSERT(pInfo != NULL || ppEntry != NULL);
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
pMatchEntry = NULL;
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
//
|
|
// Find a longest matching reservation or registration.
|
|
//
|
|
|
|
UlpTreeFindNodeHelper(
|
|
pEntry,
|
|
pNextToken,
|
|
FNC_LONGEST_EITHER,
|
|
&pMatchEntry,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Return error if a longer reservation is found.
|
|
//
|
|
|
|
if (pMatchEntry != NULL &&
|
|
pMatchEntry->Reservation == TRUE &&
|
|
pMatchEntry->Registration == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// did we find a match?
|
|
//
|
|
|
|
if (pMatchEntry != NULL)
|
|
{
|
|
ASSERT(pMatchEntry->Registration == TRUE);
|
|
|
|
if (pInfo != NULL)
|
|
{
|
|
//
|
|
// Go backwards from the last matched entry and call UlpSetUrlInfo
|
|
// for each Registration entries along the way. If the last matched
|
|
// entry is also the root, we can just reference the
|
|
// UL_CONFIG_GROUP_OBJECT once.
|
|
//
|
|
|
|
pEntry = pMatchEntry;
|
|
|
|
if (NULL == pEntry->pParent)
|
|
{
|
|
//
|
|
// Special case, add one reference to pEntry->pConfigGroup.
|
|
//
|
|
|
|
Status = UlpSetUrlInfoSpecial(pInfo, pEntry);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
}
|
|
else
|
|
{
|
|
while (NULL != pEntry)
|
|
{
|
|
if (pEntry->Registration == TRUE)
|
|
{
|
|
Status = UlpSetUrlInfo(pInfo, pEntry);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
}
|
|
|
|
pEntry = pEntry->pParent;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Adjust ConnectionTimeout to save an InterlockedCompareExchange64
|
|
// per-request.
|
|
//
|
|
|
|
if (pInfo->ConnectionTimeout == g_TM_ConnectionTimeout)
|
|
{
|
|
pInfo->ConnectionTimeout = 0;
|
|
}
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
if (ppEntry != NULL)
|
|
{
|
|
*ppEntry = pMatchEntry;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(Status == STATUS_OBJECT_NAME_NOT_FOUND || NT_SUCCESS(Status));
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
end:
|
|
|
|
UlTraceVerbose(CONFIG_GROUP_TREE,
|
|
("http!UlpTreeFindNodeWalker(Entry=%p, NextToken='%S', "
|
|
"Info=%p): *ppEntry=%p, %s\n",
|
|
pEntry, pNextToken, pInfo, (ppEntry ? *ppEntry : NULL),
|
|
HttpStatusToString(Status)
|
|
));
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindNodeWalker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
walks the tree and find's a matching entry for pUrl. 2 output options,
|
|
you can get the entry back, or a computed URL_INFO with inheritence applied.
|
|
you must free the URL_INFO from NonPagedPool.
|
|
|
|
Arguments:
|
|
|
|
IN PWSTR pUrl, the entry to find
|
|
|
|
pHttpConn [optional] If non-NULL, use IP of
|
|
server to find Node (if not found
|
|
on first pass). This search is done
|
|
prior to the Wildcard search.
|
|
|
|
OUT PUL_URL_CONFIG_GROUP_INFO * ppInfo, [optional] will be alloced
|
|
and generated.
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry [optional] returns the found
|
|
entry
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindNode(
|
|
IN PWSTR pUrl,
|
|
IN PUL_INTERNAL_REQUEST pRequest OPTIONAL,
|
|
OUT PUL_URL_CONFIG_GROUP_INFO pInfo OPTIONAL,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry OPTIONAL
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
PWSTR pNextToken = NULL;
|
|
PUL_CG_URL_TREE_ENTRY pEntry = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(pRequest == NULL || UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(pInfo != NULL || ppEntry != NULL);
|
|
|
|
//
|
|
// get the strong wildcard match
|
|
//
|
|
|
|
if (g_pSites->StrongWildcardCount)
|
|
{
|
|
Status = UlpTreeFindWildcardSite(pUrl, TRUE, &pNextToken, &pEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// and now check in the strong wildcard tree
|
|
//
|
|
|
|
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlpTreeFindNode (StrongWildcardCount) "
|
|
"pUrl:(%S) pNextToken: (%S) Matched (%s)\n",
|
|
pUrl,
|
|
pNextToken,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
|
|
//
|
|
// If we found a match or an error (other than "not found") occured,
|
|
// end the search. Otherwise continue searching.
|
|
//
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the Host + Port + IP match
|
|
//
|
|
|
|
if (pRequest != NULL && g_pSites->NameIPSiteCount)
|
|
{
|
|
//
|
|
// There is an Name Plus IP Bound Site e.g.
|
|
// "http://site.com:80:1.1.1.1/"
|
|
// Need to generate the routing token and do the special match.
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
Status = UlGenerateRoutingToken(pRequest, FALSE);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = UlpTreeFindSiteIpMatch(
|
|
pRequest,
|
|
&pEntry
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Does it exist in this tree?
|
|
//
|
|
|
|
pNextToken = pRequest->CookedUrl.pAbsPath;
|
|
pNextToken++; // Skip the L'/' at the beginnig of Abs Path
|
|
|
|
Status = UlpTreeFindNodeWalker(
|
|
pEntry,
|
|
pNextToken,
|
|
pInfo,
|
|
ppEntry
|
|
);
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlpTreeFindNode (Host + Port + IP) "
|
|
"pRoutingToken:(%S) pAbsPath: (%S) Matched: (%s)\n",
|
|
pRequest->CookedUrl.pRoutingToken,
|
|
pRequest->CookedUrl.pAbsPath,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
}
|
|
|
|
//
|
|
// If we found a match or an error (other than "not found") occured,
|
|
// end the search. Otherwise continue searching.
|
|
//
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the Host + Port match ( exact url match )
|
|
//
|
|
|
|
if ( g_pSites->NameSiteCount
|
|
|| g_pSites->NameIPSiteCount
|
|
|| g_pSites->IPSiteCount)
|
|
{
|
|
Status = UlpTreeFindSite(pUrl, &pNextToken, &pEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// does it exist in this tree?
|
|
//
|
|
|
|
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlpTreeFindNode (Host + Port ) "
|
|
"pUrl:(%S) pNextToken: (%S) Matched: (%s)\n",
|
|
pUrl,
|
|
pNextToken,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
|
|
//
|
|
// If we found a match or an error (other than "not found") occured,
|
|
// end the search. Otherwise continue searching.
|
|
//
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// get the IP + Port + IP match
|
|
//
|
|
|
|
if (0 != g_pSites->IPSiteCount && NULL != pRequest)
|
|
{
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// Didn't find it yet. See if there is a binding for
|
|
// the IP address & TCP Port on which the request was received.
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
Status = UlGenerateRoutingToken(pRequest, TRUE);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = UlpTreeFindSiteIpMatch(
|
|
pRequest,
|
|
&pEntry
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
pNextToken = pRequest->CookedUrl.pAbsPath;
|
|
pNextToken++; // Skip the L'/' at the beginnig of Abs Path
|
|
|
|
//
|
|
// does it exist in *this* tree?
|
|
//
|
|
|
|
Status = UlpTreeFindNodeWalker(
|
|
pEntry,
|
|
pNextToken,
|
|
pInfo,
|
|
ppEntry
|
|
);
|
|
}
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlpTreeFindNode (IP + Port + IP) "
|
|
"pRoutingToken:(%S) pNextToken: (%S) Matched: (%s)\n",
|
|
pRequest->CookedUrl.pRoutingToken,
|
|
pNextToken,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
|
|
//
|
|
// If we found a match or an error (other than "not found") occured,
|
|
// end the search. Otherwise continue searching.
|
|
//
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// shoot, didn't find a match. let's check wildcards
|
|
//
|
|
|
|
if (g_pSites->WeakWildcardCount)
|
|
{
|
|
Status = UlpTreeFindWildcardSite(pUrl, FALSE, &pNextToken, &pEntry);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// and now check in the wildcard tree
|
|
//
|
|
|
|
Status = UlpTreeFindNodeWalker(pEntry, pNextToken, pInfo, ppEntry);
|
|
|
|
}
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlpTreeFindNode (WildcardCount) "
|
|
"pUrl:(%S) pNextToken: (%S) Matched (%s)\n",
|
|
pUrl,
|
|
pNextToken,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
}
|
|
|
|
end:
|
|
//
|
|
// all done.
|
|
//
|
|
|
|
if (pRequest != NULL && NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
pRequest->ConfigInfo.SiteUrlType = pEntry->UrlType;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindNode
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds a reservation node matching the given url.
|
|
|
|
Arguments:
|
|
|
|
pUrl - Supplies the url.
|
|
|
|
ppEntry - Returns the reservation node, if found.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - a matching reservation node is returned.
|
|
STATUS_OBJECT_NAME_NOT_FOUND - no match found.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindReservationNode(
|
|
IN PWSTR pUrl,
|
|
IN PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pNextToken;
|
|
PUL_CG_URL_TREE_ENTRY pSiteEntry;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// First find the matching site.
|
|
//
|
|
|
|
Status = UlpTreeFindSite(pUrl, &pNextToken, &pSiteEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Now, find a matching reservation entry
|
|
//
|
|
|
|
Status = UlpTreeFindNodeHelper(
|
|
pSiteEntry,
|
|
pNextToken,
|
|
FNC_DONT_CARE,
|
|
NULL,
|
|
ppEntry
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(IS_VALID_TREE_ENTRY(*ppEntry));
|
|
|
|
//
|
|
// The node must be a reservation node.
|
|
//
|
|
|
|
if ((*ppEntry)->Reservation == FALSE)
|
|
{
|
|
*ppEntry = NULL;
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindReservationNode
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds a matching registation entry for the given url.
|
|
|
|
Arguments:
|
|
|
|
pUrl - Supplies the url to be searched.
|
|
|
|
ppEntry - Returns the matching node, if any.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - success.
|
|
STATUS_OBJECT_NAME_NOT_FOUND - no match found.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindRegistrationNode(
|
|
IN PWSTR pUrl,
|
|
IN PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pNextToken;
|
|
PUL_CG_URL_TREE_ENTRY pSiteEntry;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// First find the site.
|
|
//
|
|
|
|
Status = UlpTreeFindSite(pUrl, &pNextToken, &pSiteEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Now, find the matching registration node.
|
|
//
|
|
|
|
Status = UlpTreeFindNodeHelper(
|
|
pSiteEntry,
|
|
pNextToken,
|
|
FNC_DONT_CARE,
|
|
NULL,
|
|
ppEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(IS_VALID_TREE_ENTRY(*ppEntry));
|
|
|
|
//
|
|
// The node must be a registration node.
|
|
//
|
|
|
|
if ((*ppEntry)->Registration == FALSE)
|
|
{
|
|
*ppEntry = NULL;
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindRegistrationNode
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
finds any matching wildcard site in g_pSites for pUrl.
|
|
|
|
Arguments:
|
|
|
|
IN PWSTR pUrl, the url to match
|
|
|
|
OUT PWSTR * ppNextToken, output's the next token after
|
|
matching the url
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry, returns the entry
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindWildcardSite(
|
|
IN PWSTR pUrl,
|
|
IN BOOLEAN StrongWildcard,
|
|
OUT PWSTR * ppNextToken,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pNextToken;
|
|
ULONG TokenLength;
|
|
ULONG Index;
|
|
PWSTR pPortNum;
|
|
ULONG PortLength;
|
|
WCHAR WildSiteUrl[HTTPS_WILD_PREFIX_LENGTH + MAX_PORT_LENGTH + 1];
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(ppNextToken != NULL);
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// find the port #, colon index + 1 (to skip ':') + 2 (to skip "//")
|
|
//
|
|
|
|
pPortNum = &pUrl[HTTP_PREFIX_COLON_INDEX + 3];
|
|
|
|
if (pPortNum[0] == L'[' || pPortNum[1] == L'[')
|
|
{
|
|
pPortNum = wcschr(pPortNum, L']');
|
|
|
|
if (pPortNum == NULL)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
pPortNum = wcschr(pPortNum, L':');
|
|
|
|
if (pPortNum == NULL)
|
|
{
|
|
//
|
|
// ouch
|
|
//
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Skip the ':'
|
|
//
|
|
|
|
pPortNum += 1;
|
|
|
|
//
|
|
// find the trailing '/' after the port number
|
|
//
|
|
|
|
pNextToken = wcschr(pPortNum, L'/');
|
|
if (pNextToken == NULL)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (DIFF(pNextToken - pPortNum) > MAX_PORT_LENGTH)
|
|
{
|
|
ASSERT(!"port length > MAX_PORT_LENGTH");
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
PortLength = DIFF(pNextToken - pPortNum) * sizeof(WCHAR);
|
|
|
|
//
|
|
// HTTPS or HTTP?
|
|
//
|
|
|
|
if (pUrl[HTTP_PREFIX_COLON_INDEX] == L':')
|
|
{
|
|
RtlCopyMemory(
|
|
WildSiteUrl,
|
|
(StrongWildcard ? HTTP_STRONG_WILD_PREFIX : HTTP_WILD_PREFIX),
|
|
HTTP_WILD_PREFIX_LENGTH
|
|
);
|
|
|
|
TokenLength = HTTP_WILD_PREFIX_LENGTH + PortLength;
|
|
ASSERT(TokenLength < (sizeof(WildSiteUrl)-sizeof(WCHAR)));
|
|
|
|
RtlCopyMemory(
|
|
&(WildSiteUrl[HTTP_WILD_PREFIX_LENGTH/sizeof(WCHAR)]),
|
|
pPortNum,
|
|
PortLength
|
|
);
|
|
|
|
WildSiteUrl[TokenLength/sizeof(WCHAR)] = UNICODE_NULL;
|
|
}
|
|
else
|
|
{
|
|
RtlCopyMemory(
|
|
WildSiteUrl,
|
|
(StrongWildcard ? HTTPS_STRONG_WILD_PREFIX : HTTPS_WILD_PREFIX),
|
|
HTTPS_WILD_PREFIX_LENGTH
|
|
);
|
|
|
|
TokenLength = HTTPS_WILD_PREFIX_LENGTH + PortLength;
|
|
ASSERT(TokenLength < (sizeof(WildSiteUrl)-sizeof(WCHAR)));
|
|
|
|
RtlCopyMemory(
|
|
&(WildSiteUrl[HTTPS_WILD_PREFIX_LENGTH/sizeof(WCHAR)]),
|
|
pPortNum,
|
|
PortLength
|
|
);
|
|
|
|
WildSiteUrl[TokenLength/sizeof(WCHAR)] = UNICODE_NULL;
|
|
}
|
|
|
|
//
|
|
// is there a wildcard entry?
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
g_pSites,
|
|
WildSiteUrl,
|
|
TokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// return the spot right after the token we just ate
|
|
//
|
|
|
|
*ppNextToken = pNextToken + 1;
|
|
|
|
//
|
|
// and return the entry
|
|
//
|
|
|
|
*ppEntry = g_pSites->pEntries[Index].pEntry;
|
|
|
|
end:
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
*ppEntry = NULL;
|
|
*ppNextToken = NULL;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindWildcardSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Finds the matching Ip bound site in g_pSites for pUrl.
|
|
It uses the entire pUrl as a site token. pUrl should be null terminated.
|
|
|
|
Before you call this function, pRoutingToken in the request should be
|
|
cooked already.
|
|
|
|
Arguments:
|
|
|
|
IN PWSTR pUrl, the site to match
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry, returns the entry
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindSiteIpMatch(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
ULONG Index = 0;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// Find the matching site
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
g_pSites,
|
|
pRequest->CookedUrl.pRoutingToken,
|
|
pRequest->CookedUrl.RoutingTokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
*ppEntry = NULL;
|
|
}
|
|
else
|
|
{
|
|
*ppEntry = g_pSites->pEntries[Index].pEntry;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindSiteIpMatch
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine finds a site that matches the url.
|
|
|
|
Arguments:
|
|
|
|
pUrl - Supplies the url.
|
|
|
|
ppNextToken - Returns a pointer to in the url to the first char after
|
|
"scheme://host:port:ip/" (including the last /).
|
|
|
|
ppEntry - Returns the pointer to the matched site entry.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeFindSite(
|
|
IN PWSTR pUrl,
|
|
OUT PWSTR * ppNextToken,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG CharCount;
|
|
ULONG Index;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(ppNextToken != NULL);
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// Find the length of "scheme://host:port:ip" section of the url.
|
|
//
|
|
|
|
Status = UlpExtractSchemeHostPortIp(pUrl, &CharCount);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Null terminate. We'll revert the change later.
|
|
//
|
|
|
|
ASSERT(pUrl[CharCount] == L'/');
|
|
|
|
pUrl[CharCount] = L'\0';
|
|
|
|
//
|
|
// Try to find the site.
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
g_pSites,
|
|
pUrl,
|
|
CharCount * sizeof(WCHAR), // length in bytes
|
|
&Index
|
|
);
|
|
|
|
//
|
|
// Put back the slash.
|
|
//
|
|
|
|
ASSERT(pUrl[CharCount] == '\0');
|
|
|
|
pUrl[CharCount] = L'/';
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
*ppNextToken = &pUrl[CharCount] + 1; // Skip first '/' in abspath.
|
|
*ppEntry = g_pSites->pEntries[Index].pEntry;
|
|
|
|
end:
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
*ppNextToken = NULL;
|
|
*ppEntry = NULL;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeFindSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
walks the sorted children array pHeader looking for a matching entry
|
|
for pToken.
|
|
|
|
Arguments:
|
|
|
|
IN PUL_CG_URL_TREE_HEADER pHeader, The children array to look in
|
|
|
|
IN PWSTR pToken, the token to look for
|
|
|
|
IN ULONG TokenLength, the length in bytes of pToken
|
|
|
|
OUT ULONG * pIndex the found index. or if not found
|
|
the index of the place an entry
|
|
with pToken should be inserted.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_OBJECT_NAME_NOT_FOUND didn't find it
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeBinaryFindEntry(
|
|
IN PUL_CG_URL_TREE_HEADER pHeader OPTIONAL,
|
|
IN PWSTR pToken,
|
|
IN ULONG TokenLength,
|
|
OUT PULONG pIndex
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
LONG Index = 0;
|
|
LONG StartIndex = 0;
|
|
LONG EndIndex;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pHeader == NULL || IS_VALID_TREE_HEADER(pHeader));
|
|
ASSERT(pIndex != NULL);
|
|
|
|
if (TokenLength == 0)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Assume we didn't find it
|
|
//
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
ASSERT(TokenLength > 0 && pToken != NULL && pToken[0] != UNICODE_NULL);
|
|
|
|
//
|
|
// any siblings to search through?
|
|
//
|
|
|
|
if (pHeader != NULL)
|
|
{
|
|
//
|
|
// Walk the sorted array looking for a match (binary search)
|
|
//
|
|
|
|
StartIndex = 0;
|
|
EndIndex = pHeader->UsedCount - 1;
|
|
|
|
while (StartIndex <= EndIndex)
|
|
{
|
|
Index = (StartIndex + EndIndex) / 2;
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pHeader->pEntries[Index].pEntry));
|
|
|
|
//
|
|
// How does the length compare?
|
|
//
|
|
|
|
if (TokenLength == pHeader->pEntries[Index].pEntry->TokenLength)
|
|
{
|
|
//
|
|
// double check with a strcmp just for fun
|
|
//
|
|
|
|
int Temp = _wcsnicmp(
|
|
pToken,
|
|
pHeader->pEntries[Index].pEntry->pToken,
|
|
TokenLength/sizeof(WCHAR)
|
|
);
|
|
|
|
if (Temp == 0)
|
|
{
|
|
//
|
|
// Found it
|
|
//
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (Temp < 0)
|
|
{
|
|
//
|
|
// Adjust StartIndex forward.
|
|
//
|
|
StartIndex = Index + 1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Adjust EndIndex backward.
|
|
//
|
|
EndIndex = Index - 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (TokenLength < pHeader->pEntries[Index].pEntry->TokenLength)
|
|
{
|
|
//
|
|
// Adjust StartIndex forward.
|
|
//
|
|
StartIndex = Index + 1;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Adjust EndIndex backward.
|
|
//
|
|
EndIndex = Index - 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If an entry was found, then Index is the place where it was present.
|
|
// If no entry was found, then StartIndex is the place where it must
|
|
// be added.
|
|
//
|
|
|
|
*pIndex = ((Status == STATUS_SUCCESS) ? Index : StartIndex);
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeBinaryFindEntry
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates and initializes a site for the give url. There
|
|
must not be any matching sites already present.
|
|
|
|
Arguments:
|
|
|
|
pUrl - Supplies a url specifying the site to be created.
|
|
|
|
UrlType - Supplies the type of the url.
|
|
|
|
ppNextToken - Returns a pointer to the unparsed url.
|
|
|
|
ppSiteEntry - Returns a pointer to the newly created site.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeCreateSite(
|
|
IN PWSTR pUrl,
|
|
IN HTTP_URL_SITE_TYPE UrlType,
|
|
OUT PWSTR * ppNextToken,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppSiteEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pToken;
|
|
PWSTR pNextToken = NULL;
|
|
ULONG TokenLength;
|
|
ULONG Index;
|
|
ULONG CharCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(ppNextToken != NULL);
|
|
ASSERT(ppSiteEntry != NULL);
|
|
|
|
//
|
|
// Find the length of "scheme://host:port[:ip]" prefix in the url.
|
|
//
|
|
|
|
Status = UlpExtractSchemeHostPortIp(pUrl, &CharCount);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pToken = pUrl;
|
|
pNextToken = pUrl + CharCount;
|
|
|
|
//
|
|
// Null terminate pToken, we'll fix it later.
|
|
//
|
|
|
|
ASSERT(pNextToken[0] == L'/');
|
|
|
|
pNextToken[0] = L'\0';
|
|
|
|
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
|
|
|
|
pNextToken += 1;
|
|
|
|
//
|
|
// Find the matching site.
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
g_pSites,
|
|
pToken,
|
|
TokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// We just found a matching site. Can't create.
|
|
//
|
|
|
|
ASSERT(FALSE); // Catch this misuse.
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
}
|
|
else if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
//
|
|
// Create the new site.
|
|
//
|
|
|
|
Status = UlpTreeInsertEntry(
|
|
&g_pSites,
|
|
NULL,
|
|
UrlType,
|
|
pToken,
|
|
TokenLength,
|
|
Index
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// UlpTreeBinaryFindEntry returned an error other than
|
|
// "not found". Bail out.
|
|
//
|
|
|
|
goto end;
|
|
}
|
|
|
|
|
|
//
|
|
// set returns
|
|
//
|
|
|
|
*ppSiteEntry = g_pSites->pEntries[Index].pEntry;
|
|
*ppNextToken = pNextToken;
|
|
|
|
|
|
end:
|
|
|
|
if (pNextToken != NULL)
|
|
{
|
|
//
|
|
// Fix the string, i replaced the '/' with a UNICODE_NULL
|
|
//
|
|
|
|
(pNextToken-1)[0] = L'/';
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
*ppSiteEntry = NULL;
|
|
*ppNextToken = NULL;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeCreateSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
inserts a new entry storing pToken as a child in the array ppHeader.
|
|
it will grow/allocate ppHeader as necessary.
|
|
|
|
Arguments:
|
|
|
|
IN OUT PUL_CG_URL_TREE_HEADER * ppHeader, the children array (might change)
|
|
|
|
IN PUL_CG_URL_TREE_ENTRY pParent, the parent to set this child to
|
|
|
|
IN HTTP_URL_SITE_TYPE UrlType, the type of the Url
|
|
|
|
IN PWSTR pToken, the token of the new entry
|
|
|
|
IN ULONG TokenLength, token length
|
|
|
|
IN ULONG Index the index to insert it at.
|
|
it will shuffle the array
|
|
if necessary.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_NO_MEMORY allocation failed
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeInsertEntry(
|
|
IN OUT PUL_CG_URL_TREE_HEADER * ppHeader,
|
|
IN PUL_CG_URL_TREE_ENTRY pParent OPTIONAL,
|
|
IN HTTP_URL_SITE_TYPE UrlType,
|
|
IN PWSTR pToken,
|
|
IN ULONG TokenLength,
|
|
IN ULONG Index
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CG_URL_TREE_HEADER pHeader = NULL;
|
|
PUL_CG_URL_TREE_ENTRY pEntry = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
|
|
ASSERT(ppHeader != NULL);
|
|
ASSERT(pParent == NULL || IS_VALID_TREE_ENTRY(pParent));
|
|
ASSERT(pToken != NULL);
|
|
ASSERT(TokenLength > 0);
|
|
ASSERT(
|
|
(*ppHeader == NULL) ?
|
|
Index == 0 :
|
|
IS_VALID_TREE_HEADER(*ppHeader) && (Index <= (*ppHeader)->UsedCount)
|
|
);
|
|
|
|
pHeader = *ppHeader;
|
|
|
|
//
|
|
// any existing siblings?
|
|
//
|
|
|
|
if (pHeader == NULL)
|
|
{
|
|
//
|
|
// allocate a sibling array
|
|
//
|
|
|
|
pHeader = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
PagedPool,
|
|
UL_CG_URL_TREE_HEADER,
|
|
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH,
|
|
UL_CG_TREE_HEADER_POOL_TAG
|
|
);
|
|
|
|
if (pHeader == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory(
|
|
pHeader,
|
|
sizeof(UL_CG_URL_TREE_HEADER) +
|
|
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH
|
|
);
|
|
|
|
pHeader->Signature = UL_CG_TREE_HEADER_POOL_TAG;
|
|
pHeader->AllocCount = UL_CG_DEFAULT_TREE_WIDTH;
|
|
|
|
}
|
|
else if ((pHeader->UsedCount + 1) > pHeader->AllocCount)
|
|
{
|
|
PUL_CG_URL_TREE_HEADER pNewHeader;
|
|
|
|
//
|
|
// Grow a bigger array
|
|
//
|
|
|
|
pNewHeader = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
PagedPool,
|
|
UL_CG_URL_TREE_HEADER,
|
|
sizeof(UL_CG_HEADER_ENTRY) * (pHeader->AllocCount * 2),
|
|
UL_CG_TREE_HEADER_POOL_TAG
|
|
);
|
|
|
|
if (pNewHeader == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
pNewHeader,
|
|
pHeader,
|
|
sizeof(UL_CG_URL_TREE_HEADER) +
|
|
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount
|
|
);
|
|
|
|
RtlZeroMemory(
|
|
((PUCHAR)pNewHeader) + sizeof(UL_CG_URL_TREE_HEADER) +
|
|
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount,
|
|
sizeof(UL_CG_HEADER_ENTRY) * pHeader->AllocCount
|
|
);
|
|
|
|
pNewHeader->AllocCount *= 2;
|
|
|
|
pHeader = pNewHeader;
|
|
|
|
}
|
|
|
|
//
|
|
// Allocate an entry
|
|
//
|
|
|
|
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
PagedPool,
|
|
UL_CG_URL_TREE_ENTRY,
|
|
TokenLength + sizeof(WCHAR),
|
|
UL_CG_TREE_ENTRY_POOL_TAG
|
|
);
|
|
|
|
if (pEntry == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory(
|
|
pEntry,
|
|
sizeof(UL_CG_URL_TREE_ENTRY) +
|
|
TokenLength + sizeof(WCHAR)
|
|
);
|
|
|
|
pEntry->Signature = UL_CG_TREE_ENTRY_POOL_TAG;
|
|
pEntry->pParent = pParent;
|
|
pEntry->UrlType = UrlType;
|
|
pEntry->SiteAddedToEndpoint = FALSE;
|
|
pEntry->TokenLength = TokenLength;
|
|
|
|
RtlCopyMemory(pEntry->pToken, pToken, TokenLength + sizeof(WCHAR));
|
|
|
|
//
|
|
// need to shuffle things around?
|
|
//
|
|
|
|
if (Index < pHeader->UsedCount)
|
|
{
|
|
//
|
|
// shift right the chunk at Index
|
|
//
|
|
|
|
RtlMoveMemory(
|
|
&(pHeader->pEntries[Index+1]),
|
|
&(pHeader->pEntries[Index]),
|
|
(pHeader->UsedCount - Index) * sizeof(UL_CG_HEADER_ENTRY)
|
|
);
|
|
}
|
|
|
|
pHeader->pEntries[Index].pEntry = pEntry;
|
|
pHeader->UsedCount += 1;
|
|
|
|
//
|
|
// update count for different site types
|
|
//
|
|
|
|
switch (UrlType)
|
|
{
|
|
case HttpUrlSite_Name:
|
|
{
|
|
pHeader->NameSiteCount++;
|
|
ASSERT(pHeader->NameSiteCount > 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_IP:
|
|
{
|
|
pHeader->IPSiteCount++;
|
|
ASSERT(pHeader->IPSiteCount > 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_StrongWildcard:
|
|
{
|
|
pHeader->StrongWildcardCount++;
|
|
ASSERT(pHeader->StrongWildcardCount > 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_WeakWildcard:
|
|
{
|
|
pHeader->WeakWildcardCount++;
|
|
ASSERT(pHeader->WeakWildcardCount > 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_NamePlusIP:
|
|
{
|
|
pHeader->NameIPSiteCount++;
|
|
InterlockedIncrement(&g_NameIPSiteCount);
|
|
ASSERT(pHeader->NameIPSiteCount > 0);
|
|
break;
|
|
}
|
|
|
|
case HttpUrlSite_None:
|
|
default:
|
|
{
|
|
ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ASSERT(
|
|
pHeader->UsedCount == (ULONG)
|
|
(pHeader->NameSiteCount +
|
|
pHeader->IPSiteCount +
|
|
pHeader->StrongWildcardCount +
|
|
pHeader->WeakWildcardCount +
|
|
pHeader->NameIPSiteCount
|
|
));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
UlTraceVerbose(
|
|
CONFIG_GROUP_TREE, (
|
|
"http!UlpTreeInsertEntry('%S', %lu) %S%S\n",
|
|
pToken, Index,
|
|
(Index < (pHeader->UsedCount - 1)) ? L"[shifted]" : L"",
|
|
(*ppHeader == NULL) ? L"[alloc'd siblings]" : L""
|
|
)
|
|
);
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
if (*ppHeader != pHeader && pHeader != NULL)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG(pHeader, UL_CG_TREE_HEADER_POOL_TAG);
|
|
}
|
|
if (pEntry != NULL)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG(pEntry, UL_CG_TREE_ENTRY_POOL_TAG);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// return a new buffer to the caller ?
|
|
//
|
|
|
|
if (*ppHeader != pHeader)
|
|
{
|
|
if (*ppHeader != NULL)
|
|
{
|
|
//
|
|
// free the old one
|
|
//
|
|
|
|
UL_FREE_POOL_WITH_SIG(*ppHeader, UL_CG_TREE_HEADER_POOL_TAG);
|
|
|
|
}
|
|
|
|
*ppHeader = pHeader;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeInsertEntry
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Inserts pUrl into the url tree. returns the inserted entry.
|
|
|
|
Arguments:
|
|
|
|
IN PWSTR pUrl, the url to insert
|
|
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry the new entry
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_ADDRESS_ALREADY_EXISTS this url is already in the tree
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpTreeInsert(
|
|
IN PWSTR pUrl,
|
|
IN HTTP_URL_SITE_TYPE UrlType,
|
|
IN PWSTR pNextToken,
|
|
IN PUL_CG_URL_TREE_ENTRY pEntry,
|
|
OUT PUL_CG_URL_TREE_ENTRY * ppEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PWSTR pToken;
|
|
ULONG TokenLength;
|
|
ULONG Index;
|
|
|
|
UNREFERENCED_PARAMETER(pUrl);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(g_pSites != NULL);
|
|
ASSERT(IS_CG_LOCK_OWNED_WRITE());
|
|
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(pNextToken != NULL);
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
//
|
|
// any abs_path to add also?
|
|
//
|
|
|
|
while (pNextToken != NULL && pNextToken[0] != UNICODE_NULL)
|
|
{
|
|
pToken = pNextToken;
|
|
|
|
pNextToken = wcschr(pNextToken, L'/');
|
|
|
|
//
|
|
// can be null if this is a leaf
|
|
//
|
|
|
|
if (pNextToken != NULL)
|
|
{
|
|
pNextToken[0] = UNICODE_NULL;
|
|
TokenLength = DIFF(pNextToken - pToken) * sizeof(WCHAR);
|
|
pNextToken += 1;
|
|
}
|
|
else
|
|
{
|
|
TokenLength = (ULONG)(wcslen(pToken) * sizeof(WCHAR));
|
|
}
|
|
|
|
//
|
|
// insert this token as a child
|
|
//
|
|
|
|
Status = UlpTreeBinaryFindEntry(
|
|
pEntry->pChildren,
|
|
pToken,
|
|
TokenLength,
|
|
&Index
|
|
);
|
|
|
|
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
//
|
|
// no match, let's add this new one
|
|
//
|
|
|
|
Status = UlpTreeInsertEntry(
|
|
&pEntry->pChildren,
|
|
pEntry,
|
|
UrlType,
|
|
pToken,
|
|
TokenLength,
|
|
Index
|
|
);
|
|
}
|
|
|
|
if (pNextToken != NULL)
|
|
{
|
|
//
|
|
// fixup the UNICODE_NULL from above
|
|
//
|
|
|
|
(pNextToken-1)[0] = L'/';
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// dive down deeper !
|
|
//
|
|
|
|
pEntry = pEntry->pChildren->pEntries[Index].pEntry;
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
//
|
|
// loop!
|
|
//
|
|
}
|
|
|
|
//
|
|
// all done
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Something failed. Need to clean up the partial branch
|
|
//
|
|
|
|
if (pEntry != NULL && pEntry->Registration == FALSE &&
|
|
pEntry->Reservation == FALSE)
|
|
{
|
|
NTSTATUS TempStatus;
|
|
|
|
TempStatus = UlpTreeFreeNode(pEntry);
|
|
ASSERT(NT_SUCCESS(TempStatus));
|
|
}
|
|
|
|
*ppEntry = NULL;
|
|
}
|
|
else
|
|
{
|
|
*ppEntry = pEntry;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlpTreeInsert
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
init code. not re-entrant.
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_NO_MEMORY allocation failed
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlInitializeCG(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( g_InitCGCalled == FALSE );
|
|
|
|
if (g_InitCGCalled == FALSE)
|
|
{
|
|
//
|
|
// init our globals
|
|
//
|
|
|
|
//
|
|
// Alloc our site array
|
|
//
|
|
|
|
g_pSites = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
PagedPool,
|
|
UL_CG_URL_TREE_HEADER,
|
|
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH,
|
|
UL_CG_TREE_HEADER_POOL_TAG
|
|
);
|
|
|
|
if (g_pSites == NULL)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
RtlZeroMemory(
|
|
g_pSites,
|
|
sizeof(UL_CG_URL_TREE_HEADER) +
|
|
sizeof(UL_CG_HEADER_ENTRY) * UL_CG_DEFAULT_TREE_WIDTH
|
|
);
|
|
|
|
g_pSites->Signature = UL_CG_TREE_HEADER_POOL_TAG;
|
|
g_pSites->AllocCount = UL_CG_DEFAULT_TREE_WIDTH;
|
|
|
|
g_pSites->NameSiteCount = 0;
|
|
g_pSites->IPSiteCount = 0;
|
|
g_pSites->StrongWildcardCount = 0;
|
|
g_pSites->WeakWildcardCount = 0;
|
|
g_pSites->NameIPSiteCount = 0;
|
|
g_NameIPSiteCount = 0;
|
|
|
|
//
|
|
// init our non-paged entries
|
|
//
|
|
|
|
Status = UlInitializeResource(
|
|
&(g_pUlNonpagedData->ConfigGroupResource),
|
|
"ConfigGroupResource",
|
|
0,
|
|
UL_CG_RESOURCE_TAG
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG(g_pSites, UL_CG_TREE_HEADER_POOL_TAG);
|
|
return Status;
|
|
}
|
|
|
|
KeInitializeEvent(
|
|
&g_RemoveSiteEvent,
|
|
NotificationEvent,
|
|
FALSE
|
|
);
|
|
|
|
//
|
|
// Initialize reservation list.
|
|
//
|
|
|
|
InitializeListHead(&g_ReservationListHead);
|
|
|
|
g_InitCGCalled = TRUE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlInitializeCG
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
termination code
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlTerminateCG(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
if (g_InitCGCalled)
|
|
{
|
|
//
|
|
// Delete all reservation entries.
|
|
//
|
|
|
|
CG_LOCK_WRITE();
|
|
|
|
while(!IsListEmpty(&g_ReservationListHead))
|
|
{
|
|
PLIST_ENTRY pListEntry;
|
|
PUL_CG_URL_TREE_ENTRY pTreeEntry;
|
|
NTSTATUS TempStatus;
|
|
|
|
pListEntry = g_ReservationListHead.Flink;
|
|
|
|
pTreeEntry = CONTAINING_RECORD(
|
|
pListEntry,
|
|
UL_CG_URL_TREE_ENTRY,
|
|
ReservationListEntry
|
|
);
|
|
|
|
TempStatus = UlpTreeDeleteReservation(pTreeEntry);
|
|
ASSERT(NT_SUCCESS(TempStatus));
|
|
}
|
|
|
|
CG_UNLOCK_WRITE();
|
|
|
|
Status = UlDeleteResource(&(g_pUlNonpagedData->ConfigGroupResource));
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
if (g_pSites != NULL)
|
|
{
|
|
ASSERT( g_pSites->UsedCount == 0 );
|
|
|
|
//
|
|
// Nuke the header.
|
|
//
|
|
|
|
UL_FREE_POOL_WITH_SIG(
|
|
g_pSites,
|
|
UL_CG_TREE_HEADER_POOL_TAG
|
|
);
|
|
}
|
|
|
|
//
|
|
// The tree should be gone, all handles have been closed
|
|
//
|
|
|
|
ASSERT(g_pSites == NULL || g_pSites->UsedCount == 0);
|
|
|
|
g_InitCGCalled = FALSE;
|
|
}
|
|
} // UlTerminateCG
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
creates a new config group and returns the id
|
|
|
|
Arguments:
|
|
|
|
OUT PUL_CONFIG_GROUP_ID pConfigGroupId returns the new id
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_NO_MEMORY allocation failed
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlCreateConfigGroup(
|
|
IN PUL_CONTROL_CHANNEL pControlChannel,
|
|
OUT PHTTP_CONFIG_GROUP_ID pConfigGroupId
|
|
)
|
|
{
|
|
PUL_CONFIG_GROUP_OBJECT pNewObject = NULL;
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pControlChannel != NULL);
|
|
ASSERT(pConfigGroupId != NULL);
|
|
|
|
UlTrace(CONFIG_GROUP_FNC, ("http!UlCreateConfigGroup\n"));
|
|
|
|
__try
|
|
{
|
|
//
|
|
// Create an empty config group object structure - PAGED
|
|
//
|
|
Status = UlpCreateConfigGroupObject(&pNewObject);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Link it into the control channel
|
|
//
|
|
|
|
UlAddNotifyEntry(
|
|
&pControlChannel->ConfigGroupHead,
|
|
&pNewObject->HandleEntry
|
|
);
|
|
|
|
//
|
|
// remember the control channel
|
|
//
|
|
|
|
REFERENCE_CONTROL_CHANNEL(pControlChannel);
|
|
pNewObject->pControlChannel = pControlChannel;
|
|
|
|
//
|
|
// Return the new id.
|
|
//
|
|
|
|
*pConfigGroupId = pNewObject->ConfigGroupId;
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
|
|
}
|
|
|
|
|
|
end:
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Something failed. Let's clean up.
|
|
//
|
|
|
|
HTTP_SET_NULL_ID(pConfigGroupId);
|
|
|
|
if (pNewObject != NULL)
|
|
{
|
|
UlDeleteConfigGroup(pNewObject->ConfigGroupId);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlCreateConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
returns the config group id that matches the config group object linked
|
|
in list_entry
|
|
|
|
Arguments:
|
|
|
|
IN PLIST_ENTRY pControlChannelEntry - the listentry for this config group
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
HTTP_CONFIG_GROUP_ID
|
|
UlConfigGroupFromListEntry(
|
|
IN PLIST_ENTRY pControlChannelEntry
|
|
)
|
|
{
|
|
PUL_CONFIG_GROUP_OBJECT pObject;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pControlChannelEntry != NULL);
|
|
|
|
pObject = CONTAINING_RECORD(
|
|
pControlChannelEntry,
|
|
UL_CONFIG_GROUP_OBJECT,
|
|
ControlChannelEntry
|
|
);
|
|
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
|
|
|
|
return pObject->ConfigGroupId;
|
|
|
|
} // UlConfigGroupFromListEntry
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
deletes the config group ConfigGroupId cleaning all of it's urls.
|
|
|
|
Arguments:
|
|
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId the group to delete
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_INVALID_PARAMETER bad config group id
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlDeleteConfigGroup(
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CONFIG_GROUP_OBJECT pObject;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlDeleteConfigGroup(%I64x)\n",
|
|
ConfigGroupId
|
|
));
|
|
|
|
CG_LOCK_WRITE();
|
|
|
|
//
|
|
// Get ConfigGroup from opaque id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
);
|
|
|
|
if (pObject == NULL)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
|
|
|
|
HTTP_SET_NULL_ID(&(pObject->ConfigGroupId));
|
|
|
|
//
|
|
// Drop the extra reference as a result of the successful get
|
|
//
|
|
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
|
|
//
|
|
// Unlink it from the control channel and parent
|
|
//
|
|
|
|
UlRemoveNotifyEntry(&pObject->HandleEntry);
|
|
UlRemoveNotifyEntry(&pObject->ParentEntry);
|
|
|
|
//
|
|
// flush the URI cache.
|
|
// CODEWORK: if we were smarter we could make this more granular
|
|
//
|
|
UlFlushCache(pObject->pControlChannel);
|
|
|
|
//
|
|
// unlink any urls below us
|
|
//
|
|
UlNotifyAllEntries(
|
|
UlNotifyOrphanedConfigGroup,
|
|
&pObject->ChildHead,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Unlink all of the url's in the config group
|
|
//
|
|
|
|
Status = UlpCleanAllUrls(pObject);
|
|
|
|
//
|
|
// let the error fall through ....
|
|
//
|
|
|
|
//
|
|
// In this case, the config group is going away, which means this site
|
|
// counter block should no longer be returned to the perfmon counters, nor
|
|
// should it prevent addition of another site counter block with the same
|
|
// ID. Decouple it explicitly here.
|
|
//
|
|
|
|
UlDecoupleSiteCounterEntry( pObject );
|
|
|
|
//
|
|
// remove the opaque id and its reference
|
|
//
|
|
|
|
UlFreeOpaqueId(ConfigGroupId, UlOpaqueIdTypeConfigGroup);
|
|
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
|
|
//
|
|
// all done
|
|
//
|
|
|
|
end:
|
|
|
|
CG_UNLOCK_WRITE();
|
|
return Status;
|
|
|
|
} // UlDeleteConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Addref's the config group object
|
|
|
|
Arguments:
|
|
|
|
pConfigGroup - the object to add ref
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlReferenceConfigGroup(
|
|
IN PVOID pObject
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
PUL_CONFIG_GROUP_OBJECT pConfigGroup = (PUL_CONFIG_GROUP_OBJECT) pObject;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
|
|
|
|
refCount = InterlockedIncrement(&pConfigGroup->RefCount);
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pConfigGroupTraceLog,
|
|
REF_ACTION_REFERENCE_CONFIG_GROUP,
|
|
refCount,
|
|
pConfigGroup,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlReferenceConfigGroup cgroup=%p refcount=%ld\n",
|
|
pConfigGroup,
|
|
refCount)
|
|
);
|
|
|
|
} // UlReferenceConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Releases the config group object
|
|
|
|
Arguments:
|
|
|
|
pConfigGroup - the object to deref
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlDereferenceConfigGroup(
|
|
PUL_CONFIG_GROUP_OBJECT pConfigGroup
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
|
|
|
|
refCount = InterlockedDecrement( &pConfigGroup->RefCount );
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pConfigGroupTraceLog,
|
|
REF_ACTION_DEREFERENCE_CONFIG_GROUP,
|
|
refCount,
|
|
pConfigGroup,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlDereferenceConfigGroup cgroup=%p refcount=%ld\n",
|
|
pConfigGroup,
|
|
refCount)
|
|
);
|
|
|
|
if (refCount == 0)
|
|
{
|
|
//
|
|
// now it's time to free the object
|
|
//
|
|
|
|
// If OpaqueId is non-zero, then refCount should not be zero
|
|
ASSERT(HTTP_IS_NULL_ID(&pConfigGroup->ConfigGroupId));
|
|
|
|
#if INVESTIGATE_LATER
|
|
|
|
//
|
|
// Release the opaque id
|
|
//
|
|
|
|
UlFreeOpaqueId(pConfigGroup->ConfigGroupId, UlOpaqueIdTypeConfigGroup);
|
|
#endif
|
|
|
|
//
|
|
// let the control channel go
|
|
//
|
|
|
|
DEREFERENCE_CONTROL_CHANNEL(pConfigGroup->pControlChannel);
|
|
pConfigGroup->pControlChannel = NULL;
|
|
|
|
//
|
|
// Release the app pool
|
|
//
|
|
|
|
if (pConfigGroup->AppPoolFlags.Present == 1)
|
|
{
|
|
if (pConfigGroup->pAppPool != NULL)
|
|
{
|
|
DEREFERENCE_APP_POOL(pConfigGroup->pAppPool);
|
|
pConfigGroup->pAppPool = NULL;
|
|
}
|
|
|
|
pConfigGroup->AppPoolFlags.Present = 0;
|
|
}
|
|
|
|
//
|
|
// Release the entire object
|
|
//
|
|
|
|
if (pConfigGroup->LoggingConfig.Flags.Present &&
|
|
pConfigGroup->LoggingConfig.LogFileDir.Buffer != NULL)
|
|
{
|
|
UlRemoveLogEntry(pConfigGroup);
|
|
}
|
|
else
|
|
{
|
|
ASSERT( NULL == pConfigGroup->pLogFileEntry );
|
|
}
|
|
|
|
|
|
//
|
|
// Remove any qos flows for this site. This settings should
|
|
// only exists for the root app's cgroup.
|
|
//
|
|
|
|
if (!IsListEmpty(&pConfigGroup->FlowListHead))
|
|
{
|
|
ASSERT(pConfigGroup->MaxBandwidth.Flags.Present);
|
|
UlTcRemoveFlows( pConfigGroup, FALSE );
|
|
}
|
|
|
|
// Deref the connection limit stuff
|
|
if (pConfigGroup->pConnectionCountEntry)
|
|
{
|
|
DEREFERENCE_CONNECTION_COUNT_ENTRY(pConfigGroup->pConnectionCountEntry);
|
|
}
|
|
|
|
// Check Site Counters object (should have been cleaned up by now)
|
|
ASSERT(!pConfigGroup->pSiteCounters);
|
|
|
|
UL_FREE_POOL_WITH_SIG(pConfigGroup, UL_CG_OBJECT_POOL_TAG);
|
|
}
|
|
} // UlDereferenceConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
adds pUrl to the config group ConfigGroupId.
|
|
|
|
Arguments:
|
|
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, the cgroup id
|
|
|
|
IN PUNICODE_STRING pUrl, the url. must be null terminated.
|
|
|
|
IN HTTP_URL_CONTEXT UrlContext the context to associate
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_INVALID_PARAMETER bad config group id
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlAddUrlToConfigGroup(
|
|
IN PHTTP_CONFIG_GROUP_URL_INFO pInfo,
|
|
IN PUNICODE_STRING pUrl,
|
|
IN PACCESS_STATE AccessState,
|
|
IN ACCESS_MASK AccessMask,
|
|
IN KPROCESSOR_MODE RequestorMode
|
|
)
|
|
{
|
|
HTTP_URL_CONTEXT UrlContext;
|
|
HTTP_CONFIG_GROUP_ID ConfigGroupId;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
|
|
PWSTR pNewUrl = NULL;
|
|
BOOLEAN LockTaken = FALSE;
|
|
HTTP_PARSED_URL ParsedUrl;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pInfo != NULL);
|
|
|
|
ConfigGroupId = pInfo->ConfigGroupId;
|
|
UrlContext = pInfo->UrlContext;
|
|
|
|
__try
|
|
{
|
|
ASSERT(pUrl != NULL && pUrl->Length > 0 && pUrl->Buffer != NULL);
|
|
ASSERT(pUrl->Buffer[pUrl->Length / sizeof(WCHAR)] == UNICODE_NULL);
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlAddUrlToConfigGroup('%S' -> %I64x)\n",
|
|
pUrl->Buffer, ConfigGroupId));
|
|
|
|
//
|
|
// Clean up the url
|
|
//
|
|
|
|
Status = UlSanitizeUrl(
|
|
pUrl->Buffer,
|
|
pUrl->Length / sizeof(WCHAR),
|
|
TRUE,
|
|
&pNewUrl,
|
|
&ParsedUrl
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
UlTraceError(CONFIG_GROUP_FNC,
|
|
("http!UlAddUrlToConfigGroup Sanitized Url:'%S' FAILED !\n",
|
|
pUrl->Buffer));
|
|
|
|
goto end;
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlAddUrlToConfigGroup Sanitized Url:'%S' \n", pNewUrl));
|
|
|
|
//
|
|
// Wait for all calls of UlpDeferredRemoveSiteWorker to complete
|
|
// before we add a new Endpoint so we won't run into conflicts
|
|
//
|
|
|
|
CG_LOCK_WRITE_SYNC_REMOVE_SITE();
|
|
LockTaken = TRUE;
|
|
|
|
if(pInfo->UrlType == HttpUrlOperatorTypeRegistration)
|
|
{
|
|
//
|
|
// Get the object ptr from id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)(
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
)
|
|
);
|
|
|
|
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = UlpRegisterUrlNamespace(
|
|
&ParsedUrl,
|
|
UrlContext,
|
|
pObject,
|
|
AccessState,
|
|
AccessMask,
|
|
RequestorMode
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else if(pInfo->UrlType == HttpUrlOperatorTypeReservation)
|
|
{
|
|
Status = UlpAddReservationEntry(
|
|
&ParsedUrl,
|
|
pInfo->pSecurityDescriptor,
|
|
pInfo->SecurityDescriptorLength,
|
|
AccessState,
|
|
AccessMask,
|
|
RequestorMode,
|
|
TRUE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unknown operator type. This should have been caught before.
|
|
//
|
|
|
|
ASSERT(FALSE);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// flush the URI cache.
|
|
// CODEWORK: if we were smarter we could make this more granular
|
|
//
|
|
|
|
UlFlushCache(pObject ? pObject->pControlChannel : NULL);
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
|
|
}
|
|
|
|
end:
|
|
|
|
if (pObject != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
pObject = NULL;
|
|
}
|
|
|
|
if (LockTaken)
|
|
{
|
|
CG_UNLOCK_WRITE();
|
|
}
|
|
|
|
if (pNewUrl != NULL)
|
|
{
|
|
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
|
|
pNewUrl = NULL;
|
|
}
|
|
|
|
RETURN(Status);
|
|
|
|
} // UlAddUrlToConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
removes pUrl from the url tree (and thus the config group) .
|
|
|
|
Arguments:
|
|
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, the cgroup id. ignored.
|
|
|
|
IN PUNICODE_STRING pUrl, the url. must be null terminated.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlRemoveUrlFromConfigGroup(
|
|
IN PHTTP_CONFIG_GROUP_URL_INFO pInfo,
|
|
IN PUNICODE_STRING pUrl,
|
|
IN PACCESS_STATE AccessState,
|
|
IN ACCESS_MASK AccessMask,
|
|
IN KPROCESSOR_MODE RequestorMode
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CG_URL_TREE_ENTRY pEntry;
|
|
PWSTR pNewUrl = NULL;
|
|
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
|
|
BOOLEAN LockTaken = FALSE;
|
|
HTTP_CONFIG_GROUP_ID ConfigGroupId;
|
|
HTTP_PARSED_URL ParsedUrl;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pInfo != NULL);
|
|
|
|
ConfigGroupId = pInfo->ConfigGroupId;
|
|
|
|
__try
|
|
{
|
|
ASSERT(pUrl != NULL && pUrl->Buffer != NULL && pUrl->Length > 0);
|
|
ASSERT(pUrl->Buffer[pUrl->Length / sizeof(WCHAR)] == UNICODE_NULL);
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlRemoveUrlFromConfigGroup(%I64x)\n",
|
|
ConfigGroupId));
|
|
|
|
//
|
|
// Cleanup the passed in url
|
|
//
|
|
|
|
Status = UlSanitizeUrl(
|
|
pUrl->Buffer,
|
|
pUrl->Length / sizeof(WCHAR),
|
|
TRUE,
|
|
&pNewUrl,
|
|
&ParsedUrl
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// no goto end, resource not grabbed
|
|
//
|
|
|
|
UlTraceError(CONFIG_GROUP_FNC,
|
|
("http!UlRemoveUrlFromConfigGroup: "
|
|
"Sanitized Url:'%S' FAILED !\n",
|
|
pUrl->Buffer));
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// grab the lock
|
|
//
|
|
|
|
CG_LOCK_WRITE_SYNC_REMOVE_SITE();
|
|
LockTaken = TRUE;
|
|
|
|
if (pInfo->UrlType == HttpUrlOperatorTypeRegistration)
|
|
{
|
|
//
|
|
// Lookup the entry in the tree
|
|
//
|
|
|
|
Status = UlpTreeFindRegistrationNode(pNewUrl, &pEntry);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
ASSERT(IS_VALID_TREE_ENTRY(pEntry));
|
|
|
|
//
|
|
// Get the object ptr from id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)(
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
)
|
|
);
|
|
|
|
if (!IS_VALID_CONFIG_GROUP(pObject))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Does this tree entry match this config group?
|
|
//
|
|
|
|
if (pEntry->pConfigGroup != pObject)
|
|
{
|
|
Status = STATUS_INVALID_OWNER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Everything looks good, free the node!
|
|
//
|
|
|
|
Status = UlpTreeDeleteRegistration(pEntry);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(FALSE);
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// flush the URI cache.
|
|
// CODEWORK: if we were smarter we could make this more granular
|
|
//
|
|
UlFlushCache(pObject->pControlChannel);
|
|
|
|
//
|
|
// When there are no URLs attached to the cgroup, disable the
|
|
// logging, if there was logging config for this cgroup.
|
|
//
|
|
|
|
if (IsListEmpty(&pObject->UrlListHead) &&
|
|
IS_LOGGING_ENABLED(pObject))
|
|
{
|
|
UlDisableLogEntry(pObject->pLogFileEntry);
|
|
}
|
|
}
|
|
else if (pInfo->UrlType == HttpUrlOperatorTypeReservation)
|
|
{
|
|
//
|
|
// Delete reservation. No need to flush cache in this case.
|
|
//
|
|
|
|
Status = UlpDeleteReservationEntry(
|
|
&ParsedUrl,
|
|
AccessState,
|
|
AccessMask,
|
|
RequestorMode
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Unknown operator type. This should have been caught before.
|
|
//
|
|
|
|
ASSERT(FALSE);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
|
|
}
|
|
//
|
|
// NOTE: don't do any more cleanup here... put it in freenode.
|
|
// otherwise it won't get cleaned on handle closes.
|
|
//
|
|
|
|
end:
|
|
|
|
if (pObject != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
pObject = NULL;
|
|
}
|
|
|
|
if (LockTaken)
|
|
{
|
|
CG_UNLOCK_WRITE();
|
|
}
|
|
|
|
if (pNewUrl != NULL)
|
|
{
|
|
UL_FREE_POOL(pNewUrl, URL_POOL_TAG);
|
|
pNewUrl = NULL;
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlRemoveUrlFromConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes all URLS from the config group.
|
|
|
|
Arguments:
|
|
|
|
ConfigGroupId - Supplies the config group ID.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlRemoveAllUrlsFromConfigGroup(
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlRemoveAllUrlsFromConfigGroup(%I64x)\n",
|
|
ConfigGroupId
|
|
));
|
|
|
|
//
|
|
// grab the lock
|
|
//
|
|
|
|
CG_LOCK_WRITE();
|
|
|
|
//
|
|
// Get the object ptr from id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)(
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
)
|
|
);
|
|
|
|
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// flush the URI cache.
|
|
// CODEWORK: if we were smarter we could make this more granular
|
|
//
|
|
UlFlushCache(pObject->pControlChannel);
|
|
|
|
//
|
|
// Clean it.
|
|
//
|
|
|
|
Status = UlpCleanAllUrls( pObject );
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// When there are no URLs attached to the cgroup, disable the
|
|
// logging, If there was logging config for this cgroup.
|
|
//
|
|
|
|
if (IS_LOGGING_ENABLED(pObject))
|
|
{
|
|
UlDisableLogEntry(pObject->pLogFileEntry);
|
|
}
|
|
}
|
|
|
|
end:
|
|
|
|
if (pObject != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
pObject = NULL;
|
|
}
|
|
|
|
CG_UNLOCK_WRITE();
|
|
|
|
return Status;
|
|
|
|
} // UlRemoveAllUrlsFromConfigGroup
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
allows query information for cgroups. see uldef.h
|
|
|
|
Arguments:
|
|
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, cgroup id
|
|
|
|
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass, what to fetch
|
|
|
|
IN PVOID pConfigGroupInformation, output buffer
|
|
|
|
IN ULONG Length, length of pConfigGroupInformation
|
|
|
|
OUT PULONG pReturnLength OPTIONAL how much was copied into the
|
|
output buffer
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_INVALID_PARAMETER bad cgroup id
|
|
|
|
STATUS_BUFFER_OVERFLOW output buffer too small
|
|
|
|
STATUS_INVALID_PARAMETER invalid infoclass
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlQueryConfigGroupInformation(
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
|
|
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass,
|
|
IN PVOID pConfigGroupInformation,
|
|
IN ULONG Length,
|
|
OUT PULONG pReturnLength
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(Length);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pReturnLength != NULL);
|
|
ASSERT(pConfigGroupInformation != NULL);
|
|
|
|
//
|
|
// If no buffer is supplied, we are being asked to return the length needed
|
|
//
|
|
|
|
if (pConfigGroupInformation == NULL && pReturnLength == NULL)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
CG_LOCK_READ();
|
|
|
|
//
|
|
// Get the object ptr from id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)(
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
)
|
|
);
|
|
|
|
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// What are we being asked to do?
|
|
//
|
|
|
|
switch (InformationClass)
|
|
{
|
|
case HttpConfigGroupBandwidthInformation:
|
|
*((PHTTP_CONFIG_GROUP_MAX_BANDWIDTH)pConfigGroupInformation) =
|
|
pObject->MaxBandwidth;
|
|
|
|
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_MAX_BANDWIDTH);
|
|
break;
|
|
|
|
case HttpConfigGroupConnectionInformation:
|
|
*((PHTTP_CONFIG_GROUP_MAX_CONNECTIONS)pConfigGroupInformation) =
|
|
pObject->MaxConnections;
|
|
|
|
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_MAX_CONNECTIONS);
|
|
break;
|
|
|
|
case HttpConfigGroupStateInformation:
|
|
*((PHTTP_CONFIG_GROUP_STATE)pConfigGroupInformation) =
|
|
pObject->State;
|
|
|
|
*pReturnLength = sizeof(HTTP_CONFIG_GROUP_STATE);
|
|
break;
|
|
|
|
case HttpConfigGroupConnectionTimeoutInformation:
|
|
*((ULONG *)pConfigGroupInformation) =
|
|
(ULONG)(pObject->ConnectionTimeout / C_NS_TICKS_PER_SEC);
|
|
|
|
*pReturnLength = sizeof(ULONG);
|
|
break;
|
|
|
|
case HttpConfigGroupAppPoolInformation:
|
|
//
|
|
// this is illegal
|
|
//
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Should have been caught in UlQueryConfigGroupIoctl.
|
|
//
|
|
ASSERT(FALSE);
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
if (pObject != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
pObject = NULL;
|
|
}
|
|
|
|
CG_UNLOCK_READ();
|
|
return Status;
|
|
|
|
} // UlQueryConfigGroupInformation
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
allows you to set info for the cgroup. see uldef.h
|
|
|
|
Arguments:
|
|
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId, cgroup id
|
|
|
|
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass, what to fetch
|
|
|
|
IN PVOID pConfigGroupInformation, input buffer
|
|
|
|
IN ULONG Length, length of pConfigGroupInformation
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
STATUS_INVALID_PARAMETER bad cgroup id
|
|
|
|
STATUS_BUFFER_TOO_SMALL input buffer too small
|
|
|
|
STATUS_INVALID_PARAMETER invalid infoclass
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlSetConfigGroupInformation(
|
|
IN HTTP_CONFIG_GROUP_ID ConfigGroupId,
|
|
IN HTTP_CONFIG_GROUP_INFORMATION_CLASS InformationClass,
|
|
IN PVOID pConfigGroupInformation,
|
|
IN ULONG Length,
|
|
IN KPROCESSOR_MODE RequestorMode
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_CONFIG_GROUP_OBJECT pObject = NULL;
|
|
HTTP_CONFIG_GROUP_LOGGING LoggingInfo;
|
|
PHTTP_CONFIG_GROUP_MAX_BANDWIDTH pMaxBandwidth;
|
|
BOOLEAN FlushCache = FALSE;
|
|
PUL_CONTROL_CHANNEL pControlChannel = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(Length);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pConfigGroupInformation);
|
|
|
|
CG_LOCK_WRITE();
|
|
|
|
//
|
|
// Get the object ptr from id
|
|
//
|
|
|
|
pObject = (PUL_CONFIG_GROUP_OBJECT)(
|
|
UlGetObjectFromOpaqueId(
|
|
ConfigGroupId,
|
|
UlOpaqueIdTypeConfigGroup,
|
|
UlReferenceConfigGroup
|
|
)
|
|
);
|
|
|
|
if (IS_VALID_CONFIG_GROUP(pObject) == FALSE)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// What are we being asked to do?
|
|
//
|
|
|
|
switch (InformationClass)
|
|
{
|
|
case HttpConfigGroupAppPoolInformation:
|
|
{
|
|
PHTTP_CONFIG_GROUP_APP_POOL pAppPoolInfo;
|
|
PUL_APP_POOL_OBJECT pOldAppPool;
|
|
|
|
pAppPoolInfo = (PHTTP_CONFIG_GROUP_APP_POOL)pConfigGroupInformation;
|
|
|
|
//
|
|
// remember the old app pool if there is one, so we can deref it
|
|
// if we need to
|
|
//
|
|
if (pObject->AppPoolFlags.Present == 1 && pObject->pAppPool != NULL)
|
|
{
|
|
pOldAppPool = pObject->pAppPool;
|
|
}
|
|
else
|
|
{
|
|
pOldAppPool = NULL;
|
|
}
|
|
|
|
if (pAppPoolInfo->Flags.Present == 1)
|
|
{
|
|
//
|
|
// ok, were expecting a handle to the file object for the app pool
|
|
//
|
|
// let's open it
|
|
//
|
|
|
|
Status = UlGetPoolFromHandle(
|
|
pAppPoolInfo->AppPoolHandle,
|
|
UserMode,
|
|
&pObject->pAppPool
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pObject->AppPoolFlags.Present = 1;
|
|
|
|
}
|
|
else
|
|
{
|
|
pObject->AppPoolFlags.Present = 0;
|
|
pObject->pAppPool = NULL;
|
|
}
|
|
|
|
//
|
|
// deref the old app pool
|
|
//
|
|
if (pOldAppPool) {
|
|
DEREFERENCE_APP_POOL(pOldAppPool);
|
|
}
|
|
|
|
FlushCache = TRUE;
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupLogInformation:
|
|
{
|
|
UNICODE_STRING LogFileDir;
|
|
|
|
//
|
|
// This CG property is for admins only.
|
|
//
|
|
Status = UlThreadAdminCheck(
|
|
FILE_WRITE_DATA,
|
|
RequestorMode,
|
|
HTTP_CONTROL_DEVICE_NAME
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pControlChannel = pObject->pControlChannel;
|
|
ASSERT(IS_VALID_CONTROL_CHANNEL(pControlChannel));
|
|
|
|
//
|
|
// Discard normal logging settings if binary logging is configured.
|
|
// No support for both types working at the same time.
|
|
//
|
|
|
|
if (pControlChannel->BinaryLoggingConfig.Flags.Present)
|
|
{
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
goto end;
|
|
}
|
|
|
|
RtlInitEmptyUnicodeString(&LogFileDir, NULL, 0);
|
|
RtlZeroMemory(&LoggingInfo, sizeof(LoggingInfo));
|
|
|
|
__try
|
|
{
|
|
// Copy the input buffer into a local variable. We may
|
|
// overwrite some of the fields.
|
|
|
|
LoggingInfo =
|
|
(*((PHTTP_CONFIG_GROUP_LOGGING)
|
|
pConfigGroupInformation));
|
|
|
|
//
|
|
// Do the range check for the configuration params.
|
|
//
|
|
|
|
Status = UlCheckLoggingConfig(NULL, &LoggingInfo);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// If the logging is -being- turned off. Fields other than the
|
|
// LoggingEnabled are discarded. And the directory string might
|
|
// be null, therefore we should only probe it if the logging is
|
|
// enabled.
|
|
//
|
|
|
|
if (LoggingInfo.LoggingEnabled)
|
|
{
|
|
Status =
|
|
UlProbeAndCaptureUnicodeString(
|
|
&LoggingInfo.LogFileDir,
|
|
RequestorMode,
|
|
&LogFileDir,
|
|
MAX_PATH
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Validity check for the logging directory.
|
|
//
|
|
|
|
if (!UlIsValidLogDirectory(
|
|
&LogFileDir,
|
|
TRUE, // UncSupport
|
|
FALSE // SystemRootSupport
|
|
))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
UlFreeCapturedUnicodeString(&LogFileDir);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
__except( UL_EXCEPTION_FILTER() )
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
// Now reinit the unicode_strings in LoggingInfo struct
|
|
// to point to the captured one.
|
|
|
|
LoggingInfo.LogFileDir = LogFileDir;
|
|
|
|
if (pObject->LoggingConfig.Flags.Present)
|
|
{
|
|
// Log settings are being reconfigured
|
|
|
|
Status = UlReConfigureLogEntry(
|
|
pObject,
|
|
&pObject->LoggingConfig, // The old config
|
|
&LoggingInfo // The new config
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Delay the creation until it becomes enabled.
|
|
|
|
if (LoggingInfo.LoggingEnabled)
|
|
{
|
|
Status = UlCreateLogEntry(
|
|
pObject,
|
|
&LoggingInfo
|
|
);
|
|
}
|
|
}
|
|
|
|
// Cleanup the captured LogFileDir.
|
|
|
|
UlFreeCapturedUnicodeString(&LogFileDir);
|
|
|
|
if ( NT_SUCCESS(Status) )
|
|
{
|
|
FlushCache = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupBandwidthInformation:
|
|
{
|
|
//
|
|
// This CG property is for admins only.
|
|
//
|
|
Status = UlThreadAdminCheck(
|
|
FILE_WRITE_DATA,
|
|
RequestorMode,
|
|
HTTP_CONTROL_DEVICE_NAME
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pMaxBandwidth = (PHTTP_CONFIG_GROUP_MAX_BANDWIDTH) pConfigGroupInformation;
|
|
|
|
//
|
|
// Rate can not be lower than the min allowed.
|
|
//
|
|
if (pMaxBandwidth->MaxBandwidth < HTTP_MIN_ALLOWED_BANDWIDTH_THROTTLING_RATE)
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Interpret the ZERO as HTTP_LIMIT_INFINITE
|
|
//
|
|
if (pMaxBandwidth->MaxBandwidth == 0)
|
|
{
|
|
pMaxBandwidth->MaxBandwidth = HTTP_LIMIT_INFINITE;
|
|
}
|
|
|
|
//
|
|
// But check to see if PSched is installed or not before proceeding.
|
|
// By returning an error here, WAS will raise an event warning but
|
|
// proceed w/o terminating the web server
|
|
//
|
|
if (!UlTcPSchedInstalled())
|
|
{
|
|
NTSTATUS TempStatus;
|
|
|
|
if (pMaxBandwidth->MaxBandwidth == HTTP_LIMIT_INFINITE)
|
|
{
|
|
// By default Config Store has HTTP_LIMIT_INFINITE. Therefore
|
|
// return success for non-actions to prevent unnecessary event
|
|
// warnings.
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Try to wake up psched state.
|
|
//
|
|
|
|
TempStatus = UlTcInitPSched();
|
|
|
|
if (!NT_SUCCESS(TempStatus))
|
|
{
|
|
// There's a BWT limit coming down but PSched is not installed
|
|
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create the flow if this is the first time we see Bandwidth set
|
|
// otherwise call reconfiguration for the existing flow. The case
|
|
// that the limit is infinite can be interpreted as BTW disabled
|
|
//
|
|
if (pObject->MaxBandwidth.Flags.Present &&
|
|
pObject->MaxBandwidth.MaxBandwidth != HTTP_LIMIT_INFINITE)
|
|
{
|
|
//
|
|
// See if there is really a change.
|
|
//
|
|
if (pMaxBandwidth->MaxBandwidth != pObject->MaxBandwidth.MaxBandwidth)
|
|
{
|
|
if (pMaxBandwidth->MaxBandwidth != HTTP_LIMIT_INFINITE)
|
|
{
|
|
Status = UlTcModifyFlows(
|
|
(PVOID) pObject, // for this site
|
|
pMaxBandwidth->MaxBandwidth, // the new bandwidth
|
|
FALSE // not global flows
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Handle BTW disabling by removing the existing flows.
|
|
//
|
|
|
|
UlTcRemoveFlows((PVOID) pObject, FALSE);
|
|
}
|
|
|
|
//
|
|
// Update the config in case of success.
|
|
//
|
|
pObject->MaxBandwidth.MaxBandwidth = pMaxBandwidth->MaxBandwidth;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Its about time to add the flows for the site entry.
|
|
//
|
|
if (pMaxBandwidth->MaxBandwidth != HTTP_LIMIT_INFINITE)
|
|
{
|
|
Status = UlTcAddFlows(
|
|
(PVOID) pObject,
|
|
pMaxBandwidth->MaxBandwidth,
|
|
FALSE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Success! Remember the bandwidth limit inside the cgroup
|
|
//
|
|
pObject->MaxBandwidth = *pMaxBandwidth;
|
|
pObject->MaxBandwidth.Flags.Present = 1;
|
|
|
|
//
|
|
// When the last reference to this cgroup released, corresponding
|
|
// flows are going to be removed.Alternatively flows might be removed
|
|
// by explicitly setting the bandwidth throttling limit to infinite
|
|
// or reseting the flags.present.The latter case is handled above
|
|
// Look at the deref config group for the former.
|
|
//
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupConnectionInformation:
|
|
|
|
//
|
|
// This CG property is for admins only.
|
|
//
|
|
Status = UlThreadAdminCheck(
|
|
FILE_WRITE_DATA,
|
|
RequestorMode,
|
|
HTTP_CONTROL_DEVICE_NAME
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pObject->MaxConnections =
|
|
*((PHTTP_CONFIG_GROUP_MAX_CONNECTIONS)pConfigGroupInformation);
|
|
|
|
if (pObject->pConnectionCountEntry)
|
|
{
|
|
// Update
|
|
UlSetMaxConnections(
|
|
&pObject->pConnectionCountEntry->MaxConnections,
|
|
pObject->MaxConnections.MaxConnections
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Create
|
|
Status = UlCreateConnectionCountEntry(
|
|
pObject,
|
|
pObject->MaxConnections.MaxConnections
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupStateInformation:
|
|
{
|
|
PHTTP_CONFIG_GROUP_STATE pCGState =
|
|
((PHTTP_CONFIG_GROUP_STATE) pConfigGroupInformation);
|
|
HTTP_ENABLED_STATE NewState = pCGState->State;
|
|
|
|
if ((NewState != HttpEnabledStateActive)
|
|
&& (NewState != HttpEnabledStateInactive))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
else
|
|
{
|
|
pObject->State = *pCGState;
|
|
|
|
UlTrace(ROUTING,
|
|
("UlSetConfigGroupInfo(StateInfo): obj=%p, "
|
|
"Flags.Present=%lu, State=%sactive.\n",
|
|
pObject,
|
|
(ULONG) pObject->State.Flags.Present,
|
|
(NewState == HttpEnabledStateActive) ? "" : "in"
|
|
));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupSiteInformation:
|
|
{
|
|
PHTTP_CONFIG_GROUP_SITE pSite;
|
|
|
|
if ( pObject->pSiteCounters )
|
|
{
|
|
// ERROR: Site Counters already exist. Bail out.
|
|
Status = STATUS_OBJECTID_EXISTS;
|
|
goto end;
|
|
}
|
|
|
|
pSite = (PHTTP_CONFIG_GROUP_SITE)pConfigGroupInformation;
|
|
|
|
Status = UlCreateSiteCounterEntry(
|
|
pObject,
|
|
pSite->SiteId
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpConfigGroupConnectionTimeoutInformation:
|
|
{
|
|
LONGLONG Timeout;
|
|
|
|
Timeout = *((ULONG *)pConfigGroupInformation);
|
|
|
|
//
|
|
// NOTE: setting to Zero is OK, since this means
|
|
// "revert to using control channel default"
|
|
//
|
|
if ( Timeout < 0L || Timeout > 0xFFFF )
|
|
{
|
|
// ERROR: Invalid Connection Timeout value
|
|
// NOTE: 64K seconds ~= 18.2 hours
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Set the per site Connection Timeout limit override
|
|
//
|
|
pObject->ConnectionTimeout = Timeout * C_NS_TICKS_PER_SEC;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Should have been caught in UlSetConfigGroupIoctl.
|
|
//
|
|
ASSERT(FALSE);
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// flush the URI cache.
|
|
// CODEWORK: if we were smarter we could make this more granular
|
|
//
|
|
|
|
if (FlushCache)
|
|
{
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
|
|
UlFlushCache(pObject->pControlChannel);
|
|
}
|
|
|
|
end:
|
|
|
|
if (pObject != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pObject);
|
|
pObject = NULL;
|
|
}
|
|
|
|
CG_UNLOCK_WRITE();
|
|
return Status;
|
|
|
|
} // UlSetConfigGroupInformation
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
applies the inheritence, gradually setting the information from pMatchEntry
|
|
into pInfo. it only copies present info from pMatchEntry.
|
|
|
|
also updates the timestamp info in pInfo. there MUST be enough space for
|
|
1 more index prior to calling this function.
|
|
|
|
Notes:
|
|
IMPORTANT: The calling function is walking the tree from bottom to top;
|
|
In order to do inheritance correctly, we should only pickup configuration
|
|
info ONLY if it has not been set in the pInfo object already.
|
|
|
|
Arguments:
|
|
|
|
IN PUL_URL_CONFIG_GROUP_INFO pInfo, the place to set the info
|
|
|
|
IN PUL_CG_URL_TREE_ENTRY pMatchEntry the entry to use to set it
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpSetUrlInfo(
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO pInfo,
|
|
IN PUL_CG_URL_TREE_ENTRY pMatchEntry
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pInfo != NULL && IS_VALID_URL_CONFIG_GROUP_INFO(pInfo));
|
|
ASSERT(IS_VALID_TREE_ENTRY(pMatchEntry));
|
|
ASSERT(pMatchEntry->Registration == TRUE);
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pMatchEntry->pConfigGroup));
|
|
|
|
//
|
|
// set the control channel. The current level might
|
|
// not have one (if it's transient), but in that
|
|
// case a parent should have had one.
|
|
//
|
|
|
|
if (pMatchEntry->pConfigGroup->pControlChannel)
|
|
{
|
|
if (!pInfo->pControlChannel)
|
|
{
|
|
pInfo->pControlChannel =
|
|
pMatchEntry->pConfigGroup->pControlChannel;
|
|
}
|
|
}
|
|
ASSERT(pInfo->pControlChannel);
|
|
|
|
if (pMatchEntry->pConfigGroup->AppPoolFlags.Present == 1)
|
|
{
|
|
if (pInfo->pAppPool == NULL)
|
|
{
|
|
pInfo->pAppPool = pMatchEntry->pConfigGroup->pAppPool;
|
|
REFERENCE_APP_POOL(pInfo->pAppPool);
|
|
}
|
|
}
|
|
|
|
//
|
|
// url context
|
|
//
|
|
|
|
if (!pInfo->UrlInfoSet)
|
|
{
|
|
pInfo->UrlContext = pMatchEntry->UrlContext;
|
|
}
|
|
|
|
if (pMatchEntry->pConfigGroup->MaxBandwidth.Flags.Present == 1)
|
|
{
|
|
if (!pInfo->pMaxBandwidth)
|
|
{
|
|
pInfo->pMaxBandwidth = pMatchEntry->pConfigGroup;
|
|
REFERENCE_CONFIG_GROUP(pInfo->pMaxBandwidth);
|
|
}
|
|
}
|
|
|
|
if (pMatchEntry->pConfigGroup->MaxConnections.Flags.Present == 1)
|
|
{
|
|
if (!pInfo->pMaxConnections)
|
|
{
|
|
ASSERT(!pInfo->pConnectionCountEntry);
|
|
|
|
pInfo->pMaxConnections = pMatchEntry->pConfigGroup;
|
|
REFERENCE_CONFIG_GROUP(pInfo->pMaxConnections);
|
|
|
|
pInfo->pConnectionCountEntry = pMatchEntry->pConfigGroup->pConnectionCountEntry;
|
|
REFERENCE_CONNECTION_COUNT_ENTRY(pInfo->pConnectionCountEntry);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Logging Info config can only be set from the Root App of
|
|
// the site. We do not need to keep updating it down the tree
|
|
// Therefore its update is slightly different.
|
|
//
|
|
|
|
if (pMatchEntry->pConfigGroup->LoggingConfig.Flags.Present == 1 &&
|
|
pMatchEntry->pConfigGroup->LoggingConfig.LoggingEnabled == TRUE)
|
|
{
|
|
if (!pInfo->pLoggingConfig)
|
|
{
|
|
pInfo->pLoggingConfig = pMatchEntry->pConfigGroup;
|
|
REFERENCE_CONFIG_GROUP(pInfo->pLoggingConfig);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Site Counter Entry
|
|
//
|
|
if (pMatchEntry->pConfigGroup->pSiteCounters)
|
|
{
|
|
// the pSiteCounters entry will only be set on
|
|
// the "Site" ConfigGroup object.
|
|
if (!pInfo->pSiteCounters)
|
|
{
|
|
UlTrace(PERF_COUNTERS,
|
|
("http!UlpSetUrlInfo: pSiteCounters %p set on pInfo %p for SiteId %lu\n",
|
|
pMatchEntry->pConfigGroup->pSiteCounters,
|
|
pInfo,
|
|
pMatchEntry->pConfigGroup->pSiteCounters->Counters.SiteId
|
|
));
|
|
|
|
pInfo->pSiteCounters = pMatchEntry->pConfigGroup->pSiteCounters;
|
|
pInfo->SiteId = pInfo->pSiteCounters->Counters.SiteId;
|
|
|
|
REFERENCE_SITE_COUNTER_ENTRY(pInfo->pSiteCounters);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Connection Timeout (100ns Ticks)
|
|
//
|
|
if (0 == pInfo->ConnectionTimeout &&
|
|
pMatchEntry->pConfigGroup->ConnectionTimeout)
|
|
{
|
|
pInfo->ConnectionTimeout = pMatchEntry->pConfigGroup->ConnectionTimeout;
|
|
}
|
|
|
|
//
|
|
// Enabled State
|
|
//
|
|
if (pMatchEntry->pConfigGroup->State.Flags.Present == 1)
|
|
{
|
|
if (!pInfo->pCurrentState)
|
|
{
|
|
pInfo->pCurrentState = pMatchEntry->pConfigGroup;
|
|
REFERENCE_CONFIG_GROUP(pInfo->pCurrentState);
|
|
|
|
//
|
|
// and a copy
|
|
//
|
|
|
|
pInfo->CurrentState = pInfo->pCurrentState->State.State;
|
|
}
|
|
}
|
|
|
|
UlTraceVerbose(CONFIG_GROUP_TREE, (
|
|
"http!UlpSetUrlInfo: Matching entry(%S) points to cfg group(%p)\n",
|
|
pMatchEntry->pToken,
|
|
pMatchEntry->pConfigGroup
|
|
)
|
|
);
|
|
|
|
pInfo->UrlInfoSet = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlpSetUrlInfo
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Setting the information from pMatchEntry into pInfo. It only add 1
|
|
reference of pConfigGroup from pMatchEntry without referencing each
|
|
individual fields inside it.
|
|
|
|
Arguments:
|
|
|
|
IN PUL_URL_CONFIG_GROUP_INFO pInfo, the place to set the info
|
|
|
|
IN PUL_CG_URL_TREE_ENTRY pMatchEntry the entry to use to set it
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpSetUrlInfoSpecial(
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO pInfo,
|
|
IN PUL_CG_URL_TREE_ENTRY pMatchEntry
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pInfo != NULL && IS_VALID_URL_CONFIG_GROUP_INFO(pInfo));
|
|
ASSERT(IS_VALID_TREE_ENTRY(pMatchEntry));
|
|
ASSERT(pMatchEntry->Registration == TRUE);
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pMatchEntry->pConfigGroup));
|
|
|
|
//
|
|
// set the control channel. The current level might
|
|
// not have one (if it's transient), but in that
|
|
// case a parent should have had one.
|
|
//
|
|
|
|
if (pMatchEntry->pConfigGroup->pControlChannel) {
|
|
pInfo->pControlChannel = pMatchEntry->pConfigGroup->pControlChannel;
|
|
}
|
|
ASSERT(pInfo->pControlChannel);
|
|
|
|
if (pMatchEntry->pConfigGroup->AppPoolFlags.Present == 1)
|
|
{
|
|
pInfo->pAppPool = pMatchEntry->pConfigGroup->pAppPool;
|
|
REFERENCE_APP_POOL(pInfo->pAppPool);
|
|
}
|
|
|
|
//
|
|
// url context
|
|
//
|
|
|
|
pInfo->UrlContext = pMatchEntry->UrlContext;
|
|
|
|
//
|
|
|
|
if (pMatchEntry->pConfigGroup->MaxBandwidth.Flags.Present == 1)
|
|
{
|
|
pInfo->pMaxBandwidth = pMatchEntry->pConfigGroup;
|
|
}
|
|
|
|
if (pMatchEntry->pConfigGroup->MaxConnections.Flags.Present == 1)
|
|
{
|
|
pInfo->pMaxConnections = pMatchEntry->pConfigGroup;
|
|
pInfo->pConnectionCountEntry = pMatchEntry->pConfigGroup->pConnectionCountEntry;
|
|
REFERENCE_CONNECTION_COUNT_ENTRY(pInfo->pConnectionCountEntry);
|
|
}
|
|
|
|
//
|
|
// Logging Info config can only be set from the Root App of
|
|
// the site. We do not need to keep updating it down the tree
|
|
// Therefore its update is slightly different.
|
|
//
|
|
|
|
if (pMatchEntry->pConfigGroup->LoggingConfig.Flags.Present == 1 &&
|
|
pMatchEntry->pConfigGroup->LoggingConfig.LoggingEnabled == TRUE)
|
|
{
|
|
pInfo->pLoggingConfig = pMatchEntry->pConfigGroup;
|
|
}
|
|
|
|
//
|
|
// Site Counter Entry
|
|
//
|
|
if (pMatchEntry->pConfigGroup->pSiteCounters)
|
|
{
|
|
// the pSiteCounters entry will only be set on
|
|
// the "Site" ConfigGroup object.
|
|
UlTrace(PERF_COUNTERS,
|
|
("http!UlpSetUrlInfoSpecial: pSiteCounters %p set on pInfo %p for SiteId %lu\n",
|
|
pMatchEntry->pConfigGroup->pSiteCounters,
|
|
pInfo,
|
|
pMatchEntry->pConfigGroup->pSiteCounters->Counters.SiteId
|
|
));
|
|
|
|
pInfo->pSiteCounters = pMatchEntry->pConfigGroup->pSiteCounters;
|
|
pInfo->SiteId = pInfo->pSiteCounters->Counters.SiteId;
|
|
REFERENCE_SITE_COUNTER_ENTRY(pInfo->pSiteCounters);
|
|
}
|
|
|
|
//
|
|
// Connection Timeout (100ns Ticks)
|
|
//
|
|
if (pMatchEntry->pConfigGroup->ConnectionTimeout)
|
|
{
|
|
pInfo->ConnectionTimeout = pMatchEntry->pConfigGroup->ConnectionTimeout;
|
|
}
|
|
|
|
if (pMatchEntry->pConfigGroup->State.Flags.Present == 1)
|
|
{
|
|
pInfo->pCurrentState = pMatchEntry->pConfigGroup;
|
|
|
|
//
|
|
// and a copy
|
|
//
|
|
|
|
pInfo->CurrentState = pInfo->pCurrentState->State.State;
|
|
}
|
|
|
|
UlTraceVerbose(CONFIG_GROUP_TREE, (
|
|
"http!UlpSetUrlInfoSpecial: Matching entry(%S) points to cfg group(%p)\n",
|
|
pMatchEntry->pToken,
|
|
pMatchEntry->pConfigGroup
|
|
)
|
|
);
|
|
|
|
//
|
|
// Add a ref to the ConfigGroup if it has been used
|
|
//
|
|
|
|
if (pInfo->pMaxBandwidth ||
|
|
pInfo->pMaxConnections ||
|
|
pInfo->pCurrentState ||
|
|
pInfo->pLoggingConfig)
|
|
{
|
|
pInfo->pConfigGroup = pMatchEntry->pConfigGroup;
|
|
REFERENCE_CONFIG_GROUP(pInfo->pConfigGroup);
|
|
}
|
|
|
|
pInfo->UrlInfoSet = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlpSetUrlInfoSpecial
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Caller may asks for config group info for a Url (PWSTR) , this could be in
|
|
an existing request. Then UlpTreeFindNode walks the url tree and builds
|
|
the URL_INFO for the caller.
|
|
|
|
When there are IP bound sites in the config group, the routing token
|
|
in the cooked url of the request will be used for cgroup lookup as well
|
|
as the original cooked url in the request.
|
|
|
|
Arguments:
|
|
|
|
IN PWSTR The Url to fetch the cgroup info for.
|
|
|
|
IN PUL_INTERNAL_REQUEST The request to fetch the cgroup info for.OPTIONAL
|
|
|
|
OUT PUL_URL_CONFIG_GROUP_INFO The result cgroup info.
|
|
|
|
When a pRequest is passed in, it must have a proper pHttpConn.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlGetConfigGroupInfoForUrl(
|
|
IN PWSTR pUrl,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
OUT PUL_URL_CONFIG_GROUP_INFO pInfo
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pInfo != NULL);
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(pRequest == NULL || UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlGetConfigGroupInfoForUrl pUrl:(%S), pRequest=%p\n",
|
|
pUrl, pRequest
|
|
));
|
|
|
|
//
|
|
// Hold the CG Lock while walking the cgroup tree.
|
|
//
|
|
|
|
CG_LOCK_READ();
|
|
|
|
Status = UlpTreeFindNode(pUrl, pRequest, pInfo, NULL);
|
|
|
|
CG_UNLOCK_READ();
|
|
|
|
return Status;
|
|
|
|
} // UlGetConfigGroupInfoForUrl
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Attemtps to see if there's a host plus ip site configured and passed in
|
|
request's url matches with the site.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Request for the lookup
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlLookupHostPlusIPSite(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
PUL_CG_URL_TREE_ENTRY pSiteEntry = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// Return quickly if there is not an Host Plus IP Site configured.
|
|
// Don't try to access g_pSites outside of the CG Lock, it may get
|
|
// freed-up. Use another global counter instead. We need to avoid
|
|
// to acquire the CG lock if there is not any host plus ip site.
|
|
//
|
|
|
|
if (g_NameIPSiteCount > 0)
|
|
{
|
|
if (pRequest->CookedUrl.pQueryString != NULL)
|
|
{
|
|
ASSERT(pRequest->CookedUrl.pQueryString[0] == L'?');
|
|
pRequest->CookedUrl.pQueryString[0] = UNICODE_NULL;
|
|
}
|
|
|
|
ASSERT(pRequest->Verb == HttpVerbGET);
|
|
|
|
CG_LOCK_READ();
|
|
|
|
if (g_pSites->NameIPSiteCount)
|
|
{
|
|
//
|
|
// There is an Name Plus IP Bound Site e.g.
|
|
// "http://site.com:80:1.1.1.1/"
|
|
// Need to generate the routing token and do the special match.
|
|
//
|
|
|
|
Status = UlGenerateRoutingToken(pRequest, FALSE);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = UlpTreeFindSiteIpMatch(pRequest, &pSiteEntry);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(IS_VALID_TREE_ENTRY(pSiteEntry));
|
|
|
|
if (pSiteEntry->UrlType == HttpUrlSite_NamePlusIP)
|
|
{
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("Http!UlLookupHostPlusIPSite (Host + Port + IP) "
|
|
"pRoutingToken:(%S) Found: (%s)\n",
|
|
pRequest->CookedUrl.pRoutingToken,
|
|
NT_SUCCESS(Status) ? "Yes" : "No"
|
|
));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It's possible that the request may have a host header
|
|
// identical to an IP address on which a site is
|
|
// listening to. In that case we should not match this
|
|
// request with IP based site.
|
|
//
|
|
|
|
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CG_UNLOCK_READ();
|
|
|
|
if (pRequest->CookedUrl.pQueryString != NULL)
|
|
{
|
|
pRequest->CookedUrl.pQueryString[0] = L'?';
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlLookupHostPlusIPSite
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
must be called to free the info buffer.
|
|
|
|
Arguments:
|
|
|
|
IN PUL_URL_CONFIG_GROUP_INFO pInfo the info to free
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlConfigGroupInfoRelease(
|
|
IN PUL_URL_CONFIG_GROUP_INFO pInfo
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
if (!IS_VALID_URL_CONFIG_GROUP_INFO(pInfo))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
UlTrace(CONFIG_GROUP_FNC, ("http!UlConfigGroupInfoRelease(%p)\n", pInfo));
|
|
|
|
if (pInfo->pAppPool != NULL)
|
|
{
|
|
DEREFERENCE_APP_POOL(pInfo->pAppPool);
|
|
}
|
|
|
|
if (pInfo->pConfigGroup)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pInfo->pConfigGroup);
|
|
}
|
|
else
|
|
{
|
|
if (pInfo->pMaxBandwidth != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pInfo->pMaxBandwidth);
|
|
}
|
|
|
|
if (pInfo->pMaxConnections != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pInfo->pMaxConnections);
|
|
}
|
|
|
|
if (pInfo->pCurrentState != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pInfo->pCurrentState);
|
|
}
|
|
|
|
if (pInfo->pLoggingConfig != NULL)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pInfo->pLoggingConfig);
|
|
}
|
|
}
|
|
|
|
if (pInfo->pSiteCounters != NULL)
|
|
{
|
|
DEREFERENCE_SITE_COUNTER_ENTRY(pInfo->pSiteCounters);
|
|
}
|
|
|
|
if (pInfo->pConnectionCountEntry != NULL)
|
|
{
|
|
DEREFERENCE_CONNECTION_COUNT_ENTRY(pInfo->pConnectionCountEntry);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlConfigGroupInfoRelease
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Rough equivalent of the asignment operator for safely copying the
|
|
UL_URL_CONFIG_GROUP_INFO object and all of its contained pointers.
|
|
|
|
Arguments:
|
|
|
|
IN pOrigInfo the info to copy from
|
|
IN OUT pNewInfo the destination object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlConfigGroupInfoDeepCopy(
|
|
IN const PUL_URL_CONFIG_GROUP_INFO pOrigInfo,
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO pNewInfo
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(CONFIG_GROUP_FNC,
|
|
("http!UlConfigGroupInfoDeepCopy(Orig: %p, New: %p)\n",
|
|
pOrigInfo,
|
|
pNewInfo
|
|
));
|
|
|
|
ASSERT( pOrigInfo != NULL && pNewInfo != NULL );
|
|
|
|
|
|
if (pOrigInfo->pAppPool != NULL)
|
|
{
|
|
REFERENCE_APP_POOL(pOrigInfo->pAppPool);
|
|
}
|
|
|
|
if (pOrigInfo->pMaxBandwidth != NULL)
|
|
{
|
|
REFERENCE_CONFIG_GROUP(pOrigInfo->pMaxBandwidth);
|
|
}
|
|
|
|
if (pOrigInfo->pMaxConnections != NULL)
|
|
{
|
|
REFERENCE_CONFIG_GROUP(pOrigInfo->pMaxConnections);
|
|
}
|
|
|
|
if (pOrigInfo->pCurrentState != NULL)
|
|
{
|
|
REFERENCE_CONFIG_GROUP(pOrigInfo->pCurrentState);
|
|
}
|
|
|
|
if (pOrigInfo->pLoggingConfig != NULL)
|
|
{
|
|
REFERENCE_CONFIG_GROUP(pOrigInfo->pLoggingConfig);
|
|
}
|
|
|
|
// UL_SITE_COUNTER_ENTRY
|
|
if (pOrigInfo->pSiteCounters != NULL)
|
|
{
|
|
REFERENCE_SITE_COUNTER_ENTRY(pOrigInfo->pSiteCounters);
|
|
}
|
|
|
|
if (pOrigInfo->pConnectionCountEntry != NULL)
|
|
{
|
|
REFERENCE_CONNECTION_COUNT_ENTRY(pOrigInfo->pConnectionCountEntry);
|
|
}
|
|
|
|
//
|
|
// Copy the old stuff over
|
|
//
|
|
|
|
RtlCopyMemory(
|
|
pNewInfo,
|
|
pOrigInfo,
|
|
sizeof(UL_URL_CONFIG_GROUP_INFO)
|
|
);
|
|
|
|
//
|
|
// Make sure we unset pConfigGroup since we have referenced all individual
|
|
// fields inside UL_CONFIG_GROUP_OBJECT.
|
|
//
|
|
|
|
pNewInfo->pConfigGroup = NULL;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlConfigGroupInfoDeepCopy
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This function gets called when a static config group's control channel
|
|
goes away, or when a transient config group's app pool or static parent
|
|
goes away.
|
|
|
|
Deletes the config group.
|
|
|
|
Arguments:
|
|
|
|
pEntry - A pointer to HandleEntry or ParentEntry.
|
|
pHost - Pointer to the config group
|
|
pv - unused
|
|
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UlNotifyOrphanedConfigGroup(
|
|
IN PUL_NOTIFY_ENTRY pEntry,
|
|
IN PVOID pHost,
|
|
IN PVOID pv
|
|
)
|
|
{
|
|
PUL_CONFIG_GROUP_OBJECT pObject = (PUL_CONFIG_GROUP_OBJECT) pHost;
|
|
|
|
UNREFERENCED_PARAMETER(pEntry);
|
|
UNREFERENCED_PARAMETER(pv);
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pEntry);
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pObject));
|
|
|
|
UlDeleteConfigGroup(pObject->ConfigGroupId);
|
|
|
|
return TRUE;
|
|
|
|
} // UlNotifyOrphanedConfigGroup
|
|
|
|
|
|
/**************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
It returns the number of characters that form the
|
|
"scheme://host:port:ip" portion of the input url. The ip component
|
|
is optional. Even though the routine does minimal checking of the url,
|
|
the caller must sanitize the url before calling this function.
|
|
|
|
Arguments:
|
|
|
|
pUrl - Supplies the url to parse.
|
|
pCharCount - Returns the number of chars that form the prefix.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--**************************************************************************/
|
|
NTSTATUS
|
|
UlpExtractSchemeHostPortIp(
|
|
IN PWSTR pUrl,
|
|
OUT PULONG pCharCount
|
|
)
|
|
{
|
|
PWSTR pToken;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pUrl != NULL);
|
|
ASSERT(pCharCount != NULL);
|
|
|
|
//
|
|
// Initialize output argument.
|
|
//
|
|
|
|
*pCharCount = 0;
|
|
|
|
//
|
|
// Find the "://" after scheme name.
|
|
//
|
|
|
|
pToken = wcschr(pUrl, L':');
|
|
|
|
if (pToken == NULL || pToken[1] != L'/' || pToken[2] != L'/')
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Skip "://".
|
|
//
|
|
|
|
pToken += 3;
|
|
|
|
//
|
|
// Find the closing '/'.
|
|
//
|
|
|
|
pToken = wcschr(pToken, L'/');
|
|
|
|
if (pToken == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*pCharCount = (ULONG)(pToken - pUrl);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlpExtractSchemeHostPort
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
[design notes]
|
|
|
|
|
|
[url format]
|
|
|
|
url format = http[s]://[ ip-address|hostname|* :port-number/ [abs_path] ]
|
|
|
|
no escaping is allowed.
|
|
|
|
[caching cfg group info]
|
|
the tree was designed for quick lookup, but it is still costly to do the
|
|
traversal. so the cfg info for an url was designed to be cached and stored
|
|
in the actual response cache, which will be able to be directly hashed into.
|
|
|
|
this is the fast fast path.
|
|
|
|
in order to do this, the buffer for is allocated in non-paged pool. the
|
|
timestamp for each cfg group used to build the info (see building the url
|
|
info) is remembered in the returned struct. actually just indexes into the
|
|
global timestamp array are kept. the latest timestamp is then stored in the
|
|
struct itself.
|
|
|
|
later, if a cfg group is updated, the timestamp for that cfg group (in the
|
|
global array) is updated to current time.
|
|
|
|
if a request comes in, and we have a resposne cache hit, the driver will
|
|
check to see if it's cfg group info is stale. this simple requires scanning
|
|
the global timestamp array to see if any stamps are greater than the stamp
|
|
in the struct. this is not expensive . 1 memory lookup per level of url
|
|
depth.
|
|
|
|
this means that the passive mode code + dispatch mode code contend for the
|
|
timestamp spinlock. care is made to not hold this lock long. memory
|
|
allocs + frees are carefully moved outside the spinlocks.
|
|
|
|
care is also made as to the nature of tree updates + invalidating cached
|
|
data. to do this, the parent cfg group (non-dummy) is marked dirty. this
|
|
is to prevent the case that a new child cfg group is added that suddenly
|
|
affects an url it didn't effect before. image http://paul.com:80/ being
|
|
the only registered cfg group. a request comes in for /images/1.jpg.
|
|
the matching cfg group is that root one. later, a /images cfg group
|
|
is created. it now is a matching cfg group for that url sitting in the
|
|
response cache. thus needs to be invalidated.
|
|
|
|
|
|
[paging + irq]
|
|
the entire module assumes that it is called at IRQ==PASSIVE except for the
|
|
stale detection code.
|
|
|
|
this code accesses the timestamp array (see caching cfg group info) which
|
|
is stored in non-paged memory and synch'd with passive level access
|
|
using a spinlock.
|
|
|
|
|
|
[the tree]
|
|
|
|
the tree is made up of headers + entries. headers represent a group of entries
|
|
that share a parent. it's basically a length prefixed array of entries.
|
|
|
|
the parent pointer is in the entry not the header, as the entry does not really
|
|
now it's an element in an array.
|
|
|
|
entries have a pointer to a header that represents it's children. a header is
|
|
all of an entries children. headers don't link horizontally. they dyna-grow.
|
|
if you need to add a child to an entry, and his children header is full, boom.
|
|
gotta realloc to grow .
|
|
|
|
each node in the tree represents a token in a url, the things between the '/'
|
|
characters.
|
|
|
|
the entries in a header array are sorted. today they are sorted by their hash
|
|
value. this might change if the tokens are small enough, it's probably more
|
|
expensive to compute the hash value then just strcmp. the hash is 2bytes. the
|
|
token length is also 2bytes so no tokens greater than 32k.
|
|
|
|
i chose an array at first to attempt to get rid of right-left pointers. it
|
|
turned out to be futile as my array is an array of pointers. it has to be
|
|
an array of pointers as i grow + shrink it to keep it sorted. so i'm not
|
|
saving memory. however, as an array of pointers, it enables a binary search
|
|
as the array is sorted. this yields log(n) perf on a width search.
|
|
|
|
there are 2 types of entries in the tree. dummy entries and full url entries.
|
|
a full url is the leaf of an actual entry to UlAddUrl... dummy nodes are ones
|
|
that are there only to be parents to full url nodes.
|
|
|
|
dummy nodes have 2 ptrs + 2 ulongs.
|
|
full urls nodes have an extra 4 ptrs.
|
|
|
|
both store the actual token along with the entry. (unicode baby. 2 bytes per char).
|
|
|
|
at the top of the tree are sites. these are stored in a global header as siblings
|
|
in g_pSites. this can grow quite wide.
|
|
|
|
adding a full url entry creates the branch down to the entry.
|
|
|
|
deleting a full entry removes as far up the branch as possible without removing other
|
|
entries parents.
|
|
|
|
delete is also careful to not actually delete if other children exist. in this case
|
|
the full url entry is converted to a dummy node entry.
|
|
|
|
it was attempted to have big string url stored in the leaf node and the dummy nodes
|
|
simply point into this string for it's pToken. this doesn't work as the dummy nodes
|
|
pointers become invalid if the leaf node is later deleted. individual ownership of
|
|
tokens is needed to allow shared parents in the tree, with arbitrary node deletion.
|
|
|
|
an assumption throughout this code is that the tree is relatively static. changes
|
|
don't happen that often. basically inserts only on boot. and deletes only on
|
|
shutdown. this is why single clusters of siblings were chosen, as opposed to linked
|
|
clusters. children group width will remain fairly static meaning array growth is
|
|
rare.
|
|
|
|
[is it a graph or a tree?]
|
|
notice that the cfg groups are stored as a simple list with no relationships. its
|
|
the urls that are indexed with their relations.
|
|
|
|
so the urls build a tree, however do to the links from url to cfg group, it kind
|
|
of builds a graph also. 2 urls can be in the same cfg group. 1 url's child
|
|
can be in the same cfg group as the original url's parent.
|
|
|
|
when you focus on the actual url tree, it becomes less confusing. it really is a
|
|
tree. there can even be duplicates in the inheritence model, but the tree cleans
|
|
everything.
|
|
|
|
example of duplicates:
|
|
|
|
cgroup A = a/b + a/b/c/d
|
|
cgroup B = a/b/c
|
|
|
|
this walking the lineage branch for a/b/c/d you get A, then B, and A again. nodes
|
|
lower in the tree override parent values, so in this case A's values override B's .
|
|
|
|
[recursion]
|
|
|
|
|
|
|
|
[a sample tree]
|
|
|
|
server runs sites msw and has EVERY directory mapped to a cfg groups (really obscene)
|
|
|
|
<coming later>
|
|
|
|
|
|
|
|
[memory assumptions with the url tree]
|
|
|
|
[Paged]
|
|
a node per token in the url. 2 types. dummy nodes + leaf nodes.
|
|
|
|
(note: when 2 sizes are given, the second size is the sundown size)
|
|
|
|
dummy node = 4/6 longs
|
|
leaf node = 8/14 longs
|
|
+ each node holds 2 * TokenLength+1 for the token.
|
|
+ each cluster of siblings holds 2 longs + 1/2 longs per node in the cluster.
|
|
|
|
[NonPaged]
|
|
|
|
2 longs per config group
|
|
|
|
|
|
[assume]
|
|
sites
|
|
max 100k
|
|
average <10
|
|
assume 32 char hostname
|
|
|
|
max apps per site
|
|
in the max case : 2 (main + admin)
|
|
in the avg case : 10
|
|
max apps : 1000s (in 1 site)
|
|
assume 32 char app name
|
|
|
|
(assume just hostheader access for now)
|
|
|
|
[max]
|
|
|
|
hostname strings = 100000*((32+1)*2) = 6.6 MB
|
|
cluster entries = 100000*8*4 = 3.2 MB
|
|
cluster header = 1*(2+(1*100000))*4 = .4 MB
|
|
total = 10.2 MB
|
|
|
|
per site:
|
|
app strings = 2*((32+1)*2) = 132 B
|
|
cluster entries = 2*8*4 = 64 B
|
|
cluster header = 1*(2+(1*2))*4 = 16 B
|
|
= 132 + 64 + 16 = 212 B
|
|
total = 100000*(212) = 21.2 MB
|
|
|
|
Paged Total = 10.2mb + 21.2mb = 31.4 MB
|
|
NonPaged Total = 200k*2*4 = 1.6 MB
|
|
|
|
[avg]
|
|
|
|
hostname strings = 10*((32+1)*2) = 660 B
|
|
cluster entries = 10*8*4 = 320 B
|
|
cluster header = 1*(2+(1*10))*4 = 48 B
|
|
total = 1028 B
|
|
|
|
per site:
|
|
app strings = 10*((32+1)*2) = 660 B
|
|
cluster entries = 10*8*4 = 320 B
|
|
cluster header = 1*(2+(1*10))*4 = 48 B
|
|
total = 10*(1028) = 10.2 KB
|
|
|
|
Paged Total = 1028b + 10.2lKB = 11.3 KB
|
|
NonPaged Total = 110*2*4 = 880 B
|
|
|
|
note: can we save space by refcounting strings. if all of these
|
|
100k have apps with the same name, we save massive string space.
|
|
~13MB .
|
|
|
|
[efficiency of the tree]
|
|
|
|
[lookup]
|
|
|
|
[insert]
|
|
|
|
[delete]
|
|
|
|
[data locality]
|
|
|
|
[alterates investigated to the tree]
|
|
|
|
hashing - was pretty much scrapped due to the longest prefix match issue.
|
|
basically to hash, we would have to compute a hash for each level in
|
|
the url, to see if there is a match. then we have the complete list
|
|
of matches. assuming an additive hash, the hash expense could be
|
|
minimized, but the lookup still requires cycles.
|
|
|
|
|
|
alpha trie - was expense for memory. however the tree we have though
|
|
is very very similar to a trie. each level of the tree is a token,
|
|
however, not a letter.
|
|
|
|
|
|
[building the url info]
|
|
|
|
the url info is actually built walking down the tree. for each match
|
|
node, we set the info. this allows for the top-down inheritence.
|
|
we also snapshot the timestamp offsets for each matching node in the
|
|
tree as we dive down it (see caching) .
|
|
|
|
this dynamic building of an urls' match was chosen over precomputing
|
|
every possible config through the inheritence tree. each leaf node
|
|
could have stored a cooked version of this, but it would have made
|
|
updates a bear. it's not that expensive to build it while we walk down
|
|
as we already visit each node in our lineage branch.
|
|
|
|
[locking]
|
|
|
|
2 locks are used in the module. a spinlock to protect the global
|
|
timestamp array, and a resource to protect both the cgroup objects
|
|
and the url tree (which links to the cgroup objects) .
|
|
|
|
the spinlock is sometimes acquired while the resource is locked,
|
|
but never vice-versa. also, while the spinlock is held, very
|
|
little is done, and definetly no other locks are contended.
|
|
|
|
1 issue here is granularity of contention. currently the entire
|
|
tree + object list is protected with 1 resource, which is reader
|
|
writer. if this is a perf issue, we can look into locking single
|
|
lineage branches (sites) .
|
|
|
|
|
|
[legal]
|
|
> 1 cfg groups in an app pool
|
|
children cfg groups with no app pool (they inherit)
|
|
children cfg groups with no max bandwitdth (they inherit)
|
|
children cfg groups with no max connections (they inherit)
|
|
* for a host name
|
|
fully qualified url of for http[s]://[host|*]:post/[abs_path]/
|
|
must have trailing slash if not pting to a file
|
|
only 1 cfg group for the site AND the root url. e.g. http://foo.com:80/
|
|
allow you to set config on http:// and https:// for "root" config info
|
|
|
|
[not legal]
|
|
an url in > 1 cfg group
|
|
> 1 app pools for 1 cfg group
|
|
> 1 root cfg group
|
|
* embedded anywhere in the url other than replacing a hostname
|
|
query strings in url's.
|
|
url's not ending in slash.
|
|
|
|
|
|
|
|
|
|
--***************************************************************************/
|