Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

2663 lines
75 KiB

/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
announce.c
Abstract:
This module implements the routines needed to manage the bowser's
announcement table.
Author:
Larry Osterman (larryo) 18-Oct-1991
Revision History:
18-Oct-1991 larryo
Created
--*/
#include "precomp.h"
#pragma hdrstop
//
// List containing the chain of server announcement buffers. These structures
// are allocated out of paged pool and are used to while transfering the
// contents of the server announcement from the datagram reception indication
// routine to the Bowser's FSP where they will be added to the announcement
// database.
//
LIST_ENTRY
BowserViewBufferHead = {0};
KSPIN_LOCK
BowserViewBufferListSpinLock = {0};
LONG
BowserNumberOfServerAnnounceBuffers = {0};
BOOLEAN
PackServerAnnouncement (
IN ULONG Level,
IN ULONG ServerTypeMask,
IN OUT LPTSTR *BufferStart,
IN OUT LPTSTR *BufferEnd,
IN ULONG_PTR BufferDisplacment,
IN PANNOUNCE_ENTRY Announcement,
OUT PULONG TotalBytesNeeded
);
NTSTATUS
AgeServerAnnouncements(
PTRANSPORT Transport,
PVOID Context
);
VOID
BowserPromoteToBackup(
IN PTRANSPORT Transport,
IN PWSTR ServerName
);
VOID
BowserShutdownRemoteBrowser(
IN PTRANSPORT Transport,
IN PWSTR ServerName
);
typedef struct _ENUM_SERVERS_CONTEXT {
ULONG Level;
PLUID LogonId;
ULONG ServerTypeMask;
PUNICODE_STRING DomainName OPTIONAL;
PVOID OutputBuffer;
PVOID OutputBufferEnd;
ULONG OutputBufferSize;
ULONG EntriesRead;
ULONG TotalEntries;
ULONG TotalBytesNeeded;
ULONG_PTR OutputBufferDisplacement;
ULONG ResumeKey;
ULONG OriginalResumeKey;
} ENUM_SERVERS_CONTEXT, *PENUM_SERVERS_CONTEXT;
NTSTATUS
EnumerateServersWorker(
IN PTRANSPORT Transport,
IN OUT PVOID Ctx
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, BowserCompareAnnouncement)
#pragma alloc_text(PAGE, BowserAllocateAnnouncement)
#pragma alloc_text(PAGE, BowserFreeAnnouncement)
#pragma alloc_text(PAGE, BowserProcessHostAnnouncement)
#pragma alloc_text(PAGE, BowserProcessDomainAnnouncement)
#pragma alloc_text(PAGE, BowserAgeServerAnnouncements)
#pragma alloc_text(PAGE, AgeServerAnnouncements)
#pragma alloc_text(PAGE, BowserPromoteToBackup)
#pragma alloc_text(PAGE, BowserShutdownRemoteBrowser)
#pragma alloc_text(PAGE, BowserEnumerateServers)
#pragma alloc_text(PAGE, EnumerateServersWorker)
#pragma alloc_text(PAGE, PackServerAnnouncement)
#pragma alloc_text(PAGE, BowserDeleteGenericTable)
#pragma alloc_text(PAGE, BowserpInitializeAnnounceTable)
#pragma alloc_text(PAGE, BowserpUninitializeAnnounceTable)
#pragma alloc_text(PAGE4BROW, BowserFreeViewBuffer)
#pragma alloc_text(PAGE4BROW, BowserAllocateViewBuffer)
#pragma alloc_text(PAGE4BROW, BowserHandleServerAnnouncement)
#pragma alloc_text(PAGE4BROW, BowserHandleDomainAnnouncement)
#endif
INLINE
ULONG
BowserSafeStrlen(
IN PSZ String,
IN ULONG MaximumStringLength
)
{
ULONG Length = 0;
while (MaximumStringLength-- && *String++) {
Length += 1;
}
return Length;
}
DATAGRAM_HANDLER(
BowserHandleServerAnnouncement
)
/*++
Routine Description:
This routine will process receive datagram indication messages, and
process them as appropriate.
Arguments:
IN PTRANSPORT Transport - The transport provider for this request.
IN ULONG BytesAvailable - number of bytes in complete Tsdu
IN PHOST_ANNOUNCE_PACKET_1 HostAnnouncement - the server announcement.
IN ULONG BytesAvailable - The number of bytes in the announcement.
OUT ULONG *BytesTaken - number of bytes used
IN UCHAR Opcode - the mailslot write opcode.
Return Value:
NTSTATUS - Status of operation.
--*/
{
PVIEW_BUFFER ViewBuffer;
ULONG HostNameLength = 0;
ULONG CommentLength = 0;
ULONG NameCommentMaxLength = 0;
PHOST_ANNOUNCE_PACKET_1 HostAnnouncement = Buffer;
DISCARDABLE_CODE( BowserDiscardableCodeSection );
#ifdef ENABLE_PSEUDO_BROWSER
if ( BowserData.PseudoServerLevel == BROWSER_PSEUDO ) {
// no-op for black hole server
return STATUS_SUCCESS;
}
#endif
ExInterlockedAddLargeStatistic(&BowserStatistics.NumberOfServerAnnouncements, 1);
ViewBuffer = BowserAllocateViewBuffer();
//
// If we are unable to allocate a view buffer, ditch this datagram on
// the floor.
//
if (ViewBuffer == NULL) {
return STATUS_REQUEST_NOT_ACCEPTED;
}
if ((TransportName->NameType == MasterBrowser) ||
(TransportName->NameType == BrowserElection)) {
ULONG ServerElectionVersion;
//
// If this server announcement is sent to the master name, then
// it is a BROWSE_ANNOUNCE packet, not a HOST_ANNOUNCE (ie, it's an
// NT/WinBALL server, not a Lan Manager server.
//
// We need to grovel the bits out of the packet in an appropriate
// manner.
//
PBROWSE_ANNOUNCE_PACKET_1 BrowseAnnouncement = (PBROWSE_ANNOUNCE_PACKET_1)HostAnnouncement;
//
// If this packet was smaller than a minimal server announcement,
// ignore the request, it cannot be a legal request.
//
if (BytesAvailable < FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, Comment)) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
//
// This is a Lan Manager style server announcement.
//
#if DBG
ViewBuffer->ServerType = 0xffffffff;
#endif
//
// Verify that this announcement is not going to blow away the view
// buffer.
//
HostNameLength = BowserSafeStrlen(BROWSE_ANNC_NAME(BrowseAnnouncement),
BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, ServerName));
if (HostNameLength > NETBIOS_NAME_LEN-1) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
if (BowserSafeStrlen(BROWSE_ANNC_COMMENT(BrowseAnnouncement),
BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, Comment)) > LM20_MAXCOMMENTSZ) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
strncpy(ViewBuffer->ServerName, BROWSE_ANNC_NAME(BrowseAnnouncement),
min(BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, ServerName),
NETBIOS_NAME_LEN));
ViewBuffer->ServerName[NETBIOS_NAME_LEN] = '\0';
strncpy(ViewBuffer->ServerComment, BROWSE_ANNC_COMMENT(BrowseAnnouncement),
min(BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, Comment), LM20_MAXCOMMENTSZ));
ViewBuffer->ServerComment[LM20_MAXCOMMENTSZ] = '\0';
ServerElectionVersion = SmbGetUlong(&BrowseAnnouncement->CommentPointer);
//
// Save away the election version of this server.
//
if ((ServerElectionVersion >> 16) == 0xaa55) {
ViewBuffer->ServerBrowserVersion = (USHORT)(ServerElectionVersion & 0xffff);
} else {
if (!(BrowseAnnouncement->Type & SV_TYPE_NT)) {
ViewBuffer->ServerBrowserVersion = (BROWSER_VERSION_MAJOR << 8) + BROWSER_VERSION_MINOR;
} else {
ViewBuffer->ServerBrowserVersion = 0;
}
}
ViewBuffer->ServerType = SmbGetUlong(&BrowseAnnouncement->Type);
dprintf(DPRT_ANNOUNCE, ("Received announcement from %s on transport %lx. Server type: %lx\n", ViewBuffer->ServerName, TransportName->Transport, ViewBuffer->ServerType));
ViewBuffer->ServerVersionMajor = BrowseAnnouncement->VersionMajor;
ViewBuffer->ServerVersionMinor = BrowseAnnouncement->VersionMinor;
ViewBuffer->ServerPeriodicity = (USHORT)((SmbGetUlong(&BrowseAnnouncement->Periodicity) + 999) / 1000);
} else {
//
// If this packet was smaller than a minimal server announcement,
// ignore the request, it cannot be a legal request.
//
if (BytesAvailable < FIELD_OFFSET(HOST_ANNOUNCE_PACKET_1, NameComment)) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
//
// This is a Lan Manager style server announcement.
//
#if DBG
ViewBuffer->ServerType = 0xffffffff;
#endif
//
// Verify that this announcement is not going to blow away the view
// buffer.
//
NameCommentMaxLength = BytesAvailable - FIELD_OFFSET(HOST_ANNOUNCE_PACKET_1, NameComment);
HostNameLength = BowserSafeStrlen(HOST_ANNC_NAME(HostAnnouncement),
NameCommentMaxLength);
if (HostNameLength > NETBIOS_NAME_LEN) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
//
// We need to make sure that the hostname string was properly terminated
// before using HOST_ANNC_COMMENT (which calls strlen on the hostname string).
// The BowserSafeStrlen call above may have been terminated by the end of the
// input buffer. If the length was terminated by the end of the buffer, the
// conditional below will fail.
//
if (HostNameLength < NameCommentMaxLength) {
CommentLength = BowserSafeStrlen(HOST_ANNC_COMMENT(HostAnnouncement),
NameCommentMaxLength - HostNameLength - 1);
if (CommentLength > LM20_MAXCOMMENTSZ) {
BowserFreeViewBuffer(ViewBuffer);
return STATUS_REQUEST_NOT_ACCEPTED;
}
}
if (HostNameLength) {
RtlCopyMemory(ViewBuffer->ServerName,HOST_ANNC_NAME(HostAnnouncement),HostNameLength);
}
ViewBuffer->ServerName[HostNameLength] = '\0';
if (CommentLength) {
RtlCopyMemory(ViewBuffer->ServerComment,HOST_ANNC_COMMENT(HostAnnouncement),CommentLength);
}
ViewBuffer->ServerComment[CommentLength] = '\0';
ViewBuffer->ServerBrowserVersion = (BROWSER_VERSION_MAJOR << 8) + BROWSER_VERSION_MINOR;
ViewBuffer->ServerType = SmbGetUlong(&HostAnnouncement->Type);
dprintf(DPRT_ANNOUNCE, ("Received announcement from %s on transport %lx. Server type: %lx\n", ViewBuffer->ServerName, TransportName->Transport, ViewBuffer->ServerType));
ViewBuffer->ServerVersionMajor = HostAnnouncement->VersionMajor;
ViewBuffer->ServerVersionMinor = HostAnnouncement->VersionMinor;
ViewBuffer->ServerPeriodicity = SmbGetUshort(&HostAnnouncement->Periodicity);
}
ViewBuffer->TransportName = TransportName;
BowserReferenceTransportName(TransportName);
dprintf(DPRT_REF, ("Call Reference transport %lx from BowserHandlerServerAnnouncement.\n", TransportName->Transport));
BowserReferenceTransport( TransportName->Transport );
ExInitializeWorkItem(&ViewBuffer->Overlay.WorkHeader, BowserProcessHostAnnouncement, ViewBuffer);
BowserQueueDelayedWorkItem( &ViewBuffer->Overlay.WorkHeader );
*BytesTaken = BytesAvailable;
return STATUS_SUCCESS;
}
DATAGRAM_HANDLER(
BowserHandleDomainAnnouncement
)
/*++
Routine Description:
This routine will process receive datagram indication messages, and
process them as appropriate.
Arguments:
IN PTRANSPORT Transport - The transport provider for this request.
IN ULONG BytesAvailable, - number of bytes in complete Tsdu
IN PBROWSE_ANNOUNCE_PACKET_1 HostAnnouncement - the server announcement.
IN ULONG BytesAvailable - The number of bytes in the announcement.
OUT ULONG *BytesTaken, - number of bytes used
Return Value:
NTSTATUS - Status of operation.
--*/
{
PVIEW_BUFFER ViewBuffer;
PBROWSE_ANNOUNCE_PACKET_1 DomainAnnouncement = Buffer;
ULONG HostNameLength;
DISCARDABLE_CODE( BowserDiscardableCodeSection );
#ifdef ENABLE_PSEUDO_BROWSER
if ( BowserData.PseudoServerLevel == BROWSER_PSEUDO ) {
// no-op for black hole server
return STATUS_SUCCESS;
}
#endif
//
// If we are not processing host announcements for this
// name, ignore this request.
//
if (!TransportName->ProcessHostAnnouncements) {
return STATUS_REQUEST_NOT_ACCEPTED;
}
ExInterlockedAddLargeStatistic(&BowserStatistics.NumberOfDomainAnnouncements, 1);
//
// If this packet was smaller than a minimal server announcement,
// ignore the request, it cannot be a legal request.
//
if (BytesAvailable < FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, Comment)) {
return STATUS_REQUEST_NOT_ACCEPTED;
}
//
// Verify that this announcement is not going to blow away the view
// buffer.
//
HostNameLength = BowserSafeStrlen(BROWSE_ANNC_NAME(DomainAnnouncement),
BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, ServerName));
if (HostNameLength > NETBIOS_NAME_LEN) {
return STATUS_REQUEST_NOT_ACCEPTED;
}
ViewBuffer = BowserAllocateViewBuffer();
//
// If we are unable to allocate a view buffer, ditch this datagram on
// the floor.
//
if (ViewBuffer == NULL) {
return STATUS_REQUEST_NOT_ACCEPTED;
}
#if DBG
ViewBuffer->ServerType = 0xffffffff;
#endif
strncpy(ViewBuffer->ServerName, BROWSE_ANNC_NAME(DomainAnnouncement),
min(BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, ServerName),
NETBIOS_NAME_LEN));
ViewBuffer->ServerName[CNLEN] = '\0';
//
// The comment on a server announcement is the computer name.
//
// ASSERT (strlen(BROWSE_ANNC_COMMENT(DomainAnnouncement)) <= CNLEN);
strncpy(ViewBuffer->ServerComment, BROWSE_ANNC_COMMENT(DomainAnnouncement),
min(BytesAvailable - FIELD_OFFSET(BROWSE_ANNOUNCE_PACKET_1, Comment),
CNLEN));
//
// Force a null termination at the appropriate time.
//
ViewBuffer->ServerComment[CNLEN] = '\0';
ViewBuffer->TransportName = TransportName;
if (SmbGetUlong(&DomainAnnouncement->Type) & SV_TYPE_DOMAIN_ENUM) {
ViewBuffer->ServerType = SmbGetUlong(&DomainAnnouncement->Type);
} else {
ViewBuffer->ServerType = SV_TYPE_DOMAIN_ENUM;
}
ViewBuffer->ServerVersionMajor = DomainAnnouncement->VersionMajor;
ViewBuffer->ServerVersionMinor = DomainAnnouncement->VersionMinor;
ViewBuffer->ServerPeriodicity = (USHORT)((SmbGetUlong(&DomainAnnouncement->Periodicity) + 999) / 1000);
BowserReferenceTransportName(TransportName);
dprintf(DPRT_REF, ("Call Reference transport %lx from BowserHandlerDomainAnnouncement.\n", TransportName->Transport));
BowserReferenceTransport( TransportName->Transport );
ExInitializeWorkItem(&ViewBuffer->Overlay.WorkHeader, BowserProcessDomainAnnouncement, ViewBuffer);
BowserQueueDelayedWorkItem( &ViewBuffer->Overlay.WorkHeader );
*BytesTaken = BytesAvailable;
return STATUS_SUCCESS;
}
RTL_GENERIC_COMPARE_RESULTS
BowserCompareAnnouncement(
IN PRTL_GENERIC_TABLE Table,
IN PVOID FirstStruct,
IN PVOID SecondStruct
)
/*++
Routine Description:
This routine will compare two server announcements to see how they compare
Arguments:
IN PRTL_GENERIC_TABLE - Supplies the table containing the announcements
IN PVOID FirstStuct - The first structure to compare.
IN PVOID SecondStruct - The second structure to compare.
Return Value:
Result of the comparison.
--*/
{
UNICODE_STRING ServerName1, ServerName2;
PANNOUNCE_ENTRY Server1 = FirstStruct;
PANNOUNCE_ENTRY Server2 = SecondStruct;
LONG CompareResult;
PAGED_CODE();
RtlInitUnicodeString(&ServerName1, Server1->ServerName);
RtlInitUnicodeString(&ServerName2, Server2->ServerName);
CompareResult = RtlCompareUnicodeString(&ServerName1, &ServerName2, FALSE);
if (CompareResult < 0) {
return GenericLessThan;
} else if (CompareResult > 0) {
return GenericGreaterThan;
} else {
return GenericEqual;
}
UNREFERENCED_PARAMETER(Table);
}
PVOID
BowserAllocateAnnouncement(
IN PRTL_GENERIC_TABLE Table,
IN CLONG ByteSize
)
/*++
Routine Description:
This routine will allocate space to hold an entry in a generic table.
Arguments:
IN PRTL_GENERIC_TABLE Table - Supplies the table to allocate entries for.
IN CLONG ByteSize - Supplies the number of bytes to allocate for the entry.
Return Value:
None.
--*/
{
PAGED_CODE();
return ALLOCATE_POOL(PagedPool, ByteSize, POOL_ANNOUNCEMENT);
UNREFERENCED_PARAMETER(Table);
}
VOID
BowserFreeAnnouncement (
IN PRTL_GENERIC_TABLE Table,
IN PVOID Buffer
)
/*++
Routine Description:
This routine will free an entry in a generic table that is too old.
Arguments:
IN PRTL_GENERIC_TABLE Table - Supplies the table to allocate entries for.
IN PVOID Buffer - Supplies the buffer to free.
Return Value:
None.
--*/
{
PAGED_CODE();
FREE_POOL(Buffer);
UNREFERENCED_PARAMETER(Table);
}
INLINE
BOOLEAN
BowserIsLegalBackupBrowser(
IN PANNOUNCE_ENTRY Announcement,
IN PUNICODE_STRING ComputerName
)
{
//
// If we received this announcement on an "otherdomain", we will ignore
// it.
//
if (Announcement->Name->NameType == OtherDomain) {
return FALSE;
}
//
// If the server doesn't indicate that it's a legal backup browser, we
// want to ignore it.
//
if (!FlagOn(Announcement->ServerType, SV_TYPE_BACKUP_BROWSER)) {
return FALSE;
}
//
// If the server is the master browser, then we want to ignore it.
//
if (FlagOn(Announcement->ServerType, SV_TYPE_MASTER_BROWSER)) {
return FALSE;
}
//
// If the server is too old, we want to ignore it.
//
if (Announcement->ServerBrowserVersion < (BROWSER_VERSION_MAJOR << 8) + BROWSER_VERSION_MINOR) {
return FALSE;
}
//
// If the machine we're looking at is the current machine, then it cannot
// be a legal backup - it must be a stale announcement sent before we
// actually became the master.
//
if (RtlCompareMemory(Announcement->ServerName,
ComputerName->Buffer,
ComputerName->Length) == ComputerName->Length) {
return FALSE;
}
return TRUE;
}
VOID
BowserProcessHostAnnouncement(
IN PVOID Context
)
/*++
Routine Description:
This routine will put a server announcement into the server announcement
table
Arguments:
IN PWORK_HEADER Header - Supplies a pointer to a work header in a view buffer
Return Value:
None.
--*/
{
PVIEW_BUFFER ViewBuffer = Context;
ANNOUNCE_ENTRY ProtoEntry;
UNICODE_STRING TempUString;
OEM_STRING TempAString;
PANNOUNCE_ENTRY Announcement;
BOOLEAN NewElement = FALSE;
ULONG Periodicity;
ULONG ExpirationTime;
NTSTATUS Status;
PPAGED_TRANSPORT PagedTransport;
PTRANSPORT_NAME TransportName = ViewBuffer->TransportName;
PTRANSPORT Transport = TransportName->Transport;
PAGED_CODE();
// DbgBreakPoint();
ASSERT (ViewBuffer->Signature == STRUCTURE_SIGNATURE_VIEW_BUFFER);
//
// If we're not a master browser on this transport, don't process the
// announcement.
// Or no-op for black hole server
//
#ifdef ENABLE_PSEUDO_BROWSER
ASSERT( BowserData.PseudoServerLevel != BROWSER_PSEUDO );
#endif
if (Transport->PagedTransport->Role != Master) {
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
//
// Convert the computername to unicode.
//
TempUString.Buffer = ProtoEntry.ServerName;
TempUString.MaximumLength = sizeof(ProtoEntry.ServerName);
RtlInitAnsiString(&TempAString, ViewBuffer->ServerName);
Status = RtlOemStringToUnicodeString(&TempUString, &TempAString, FALSE);
if (!NT_SUCCESS(Status)) {
BowserLogIllegalName( Status, TempAString.Buffer, TempAString.Length );
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
//
// Convert the comment to unicode.
//
TempUString.Buffer = ProtoEntry.ServerComment;
TempUString.MaximumLength = sizeof(ProtoEntry.ServerComment);
RtlInitAnsiString(&TempAString, ViewBuffer->ServerComment);
Status = RtlOemStringToUnicodeString(&TempUString, &TempAString, FALSE);
if (!NT_SUCCESS(Status)) {
BowserLogIllegalName( Status, TempAString.Buffer, TempAString.Length );
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
ProtoEntry.Signature = STRUCTURE_SIGNATURE_ANNOUNCE_ENTRY;
ProtoEntry.Size = sizeof(ProtoEntry) -
sizeof(ProtoEntry.ServerComment) +
TempUString.Length + sizeof(WCHAR);
ProtoEntry.ServerType = ViewBuffer->ServerType;
ProtoEntry.ServerVersionMajor = ViewBuffer->ServerVersionMajor;
ProtoEntry.ServerVersionMinor = ViewBuffer->ServerVersionMinor;
ProtoEntry.Name = ViewBuffer->TransportName->PagedTransportName->Name;
//
// Initialize the forward and backward link to NULL.
//
ProtoEntry.BackupLink.Flink = NULL;
ProtoEntry.BackupLink.Blink = NULL;
ProtoEntry.ServerPeriodicity = ViewBuffer->ServerPeriodicity;
ProtoEntry.Flags = 0;
ProtoEntry.ServerBrowserVersion = ViewBuffer->ServerBrowserVersion;
PagedTransport = Transport->PagedTransport;
//
// We're done with the view buffer, now free it.
//
BowserFreeViewBuffer(ViewBuffer);
LOCK_ANNOUNCE_DATABASE(Transport);
try {
//
// If this guy isn't a server, then we're supposed to remove this
// guy from our list of servers. We do this because the server (NT,
// WfW, and OS/2) will issue a dummy announcement with the
// appropriate bit turned off when they stop.
//
if (!FlagOn(ProtoEntry.ServerType, SV_TYPE_SERVER)) {
//
// Look up this entry in the table.
//
Announcement = RtlLookupElementGenericTable(&PagedTransport->AnnouncementTable, &ProtoEntry);
//
// The entry wasn't found, so just return, we got rid of it
// some other way (maybe from a timeout scan, etc).
//
if (Announcement == NULL) {
try_return(NOTHING);
}
//
// If this element is on the backup list, remove it from the
// backup list.
//
if (Announcement->BackupLink.Flink != NULL) {
ASSERT (Announcement->BackupLink.Blink != NULL);
RemoveEntryList(&Announcement->BackupLink);
PagedTransport->NumberOfBackupServerListEntries -= 1;
Announcement->BackupLink.Flink = NULL;
Announcement->BackupLink.Blink = NULL;
}
//
// Now delete the element from the announcement table.
//
BowserDereferenceName( Announcement->Name );
if (!RtlDeleteElementGenericTable(&PagedTransport->AnnouncementTable, Announcement)) {
KdPrint(("Unable to delete server element %ws\n", Announcement->ServerName));
}
try_return(NOTHING);
}
Announcement = RtlInsertElementGenericTable(&PagedTransport->AnnouncementTable,
&ProtoEntry, ProtoEntry.Size, &NewElement);
if (Announcement == NULL) {
//
// We couldn't allocate pool for this announcement. Skip it.
//
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
// Indicate the name is referenced by the announce entry we just inserted.
BowserReferenceName( ProtoEntry.Name );
if (!NewElement) {
ULONG NumberOfPromotionAttempts = Announcement->NumberOfPromotionAttempts;
//
// If this announcement was a backup browser, remove it from the
// list of backup browsers.
//
if (Announcement->BackupLink.Flink != NULL) {
ASSERT (Announcement->ServerType & SV_TYPE_BACKUP_BROWSER);
ASSERT (Announcement->BackupLink.Blink != NULL);
RemoveEntryList(&Announcement->BackupLink);
PagedTransport->NumberOfBackupServerListEntries -= 1;
Announcement->BackupLink.Flink = NULL;
Announcement->BackupLink.Blink = NULL;
}
//
// If this is not a new announcement, copy the announcement entry
// with the new information.
//
// The Previous entry no longer references the name
BowserDereferenceName( Announcement->Name );
if ( Announcement->Size >= ProtoEntry.Size ) {
CSHORT TempSize;
TempSize = Announcement->Size;
RtlCopyMemory( Announcement, &ProtoEntry, ProtoEntry.Size );
Announcement->Size = TempSize;
} else {
if (!RtlDeleteElementGenericTable(
&PagedTransport->AnnouncementTable,
Announcement)) {
KdPrint(("Unable to delete server element %ws\n", Announcement->ServerName));
} else {
Announcement = RtlInsertElementGenericTable(
&PagedTransport->AnnouncementTable,
&ProtoEntry,
ProtoEntry.Size,
&NewElement);
if (Announcement == NULL) {
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
ASSERT( NewElement );
}
}
if (ProtoEntry.ServerType & SV_TYPE_BACKUP_BROWSER) {
Announcement->NumberOfPromotionAttempts = 0;
} else {
Announcement->NumberOfPromotionAttempts = NumberOfPromotionAttempts;
}
} else {
//
// This is a new entry. Initialize the number of promotion
// attempts to 0.
//
Announcement->NumberOfPromotionAttempts = 0;
dlog( DPRT_MASTER,
("%s: %ws: New server: %ws. Periodicity: %ld\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName,
Announcement->ServerPeriodicity));
//
// If there are too many entries,
// ditch this one.
//
if ( RtlNumberGenericTableElements(&PagedTransport->AnnouncementTable) > BowserMaximumBrowseEntries ) {
dlog( DPRT_MASTER,
("%s: %ws: New server (Deleted because too many): %ws. Periodicity: %ld\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName,
Announcement->ServerPeriodicity));
BowserDereferenceName( Announcement->Name );
if (!RtlDeleteElementGenericTable(&PagedTransport->AnnouncementTable, Announcement)) {
KdPrint(("Unable to delete server element %ws\n", Announcement->ServerName));
}
//
// Chaulk it up as a missed announcement
//
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
}
//
// If this new server is a legal backup browser (but not a master
// browser, link it into the announcement database).
//
//
ASSERT (Announcement->BackupLink.Flink == NULL);
ASSERT (Announcement->BackupLink.Blink == NULL);
if (BowserIsLegalBackupBrowser(Announcement, &Transport->DomainInfo->DomUnicodeComputerName)) {
InsertHeadList(&PagedTransport->BackupBrowserList, &Announcement->BackupLink);
PagedTransport->NumberOfBackupServerListEntries += 1;
}
Periodicity = Announcement->ServerPeriodicity;
ExpirationTime = BowserCurrentTime+(Periodicity*HOST_ANNOUNCEMENT_AGE);
Announcement->ExpirationTime = ExpirationTime;
try_exit:NOTHING;
} finally {
UNLOCK_ANNOUNCE_DATABASE(Transport);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
}
return;
}
VOID
BowserProcessDomainAnnouncement(
IN PVOID Context
)
/*++
Routine Description:
This routine will put a server announcement into the server announcement
table
Arguments:
IN PWORK_HEADER Header - Supplies a pointer to a work header in a view buffer
Return Value:
None.
--*/
{
PVIEW_BUFFER ViewBuffer = Context;
ANNOUNCE_ENTRY ProtoEntry;
UNICODE_STRING TempUString;
OEM_STRING TempAString;
PANNOUNCE_ENTRY Announcement;
BOOLEAN NewElement = FALSE;
ULONG Periodicity;
ULONG ExpirationTime;
NTSTATUS Status;
PPAGED_TRANSPORT PagedTransport;
PTRANSPORT_NAME TransportName = ViewBuffer->TransportName;
PTRANSPORT Transport = TransportName->Transport;
PAGED_CODE();
// DbgBreakPoint();
ASSERT (ViewBuffer->Signature == STRUCTURE_SIGNATURE_VIEW_BUFFER);
//
// If we're not a master browser on this transport, don't process the
// announcement.
// Or no-op for black hole server
//
#ifdef ENABLE_PSEUDO_BROWSER
ASSERT( BowserData.PseudoServerLevel != BROWSER_PSEUDO );
#endif
if (ViewBuffer->TransportName->Transport->PagedTransport->Role != Master) {
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
//
// Convert the computername to unicode.
//
TempUString.Buffer = ProtoEntry.ServerName;
TempUString.MaximumLength = sizeof(ProtoEntry.ServerName);
RtlInitAnsiString(&TempAString, ViewBuffer->ServerName);
Status = RtlOemStringToUnicodeString(&TempUString, &TempAString, FALSE);
if (!NT_SUCCESS(Status)) {
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
//
// Convert the comment to unicode.
//
TempUString.Buffer = ProtoEntry.ServerComment;
TempUString.MaximumLength = sizeof(ProtoEntry.ServerComment);
RtlInitAnsiString(&TempAString, ViewBuffer->ServerComment);
Status = RtlOemStringToUnicodeString(&TempUString, &TempAString, FALSE);
if (!NT_SUCCESS(Status)) {
BowserFreeViewBuffer(ViewBuffer);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
return;
}
ProtoEntry.Signature = STRUCTURE_SIGNATURE_ANNOUNCE_ENTRY;
ProtoEntry.Size = sizeof(ProtoEntry) -
sizeof(ProtoEntry.ServerComment) +
TempUString.Length + sizeof(WCHAR);
ProtoEntry.ServerType = ViewBuffer->ServerType;
ProtoEntry.ServerVersionMajor = ViewBuffer->ServerVersionMajor;
ProtoEntry.ServerVersionMinor = ViewBuffer->ServerVersionMinor;
ProtoEntry.Name = ViewBuffer->TransportName->PagedTransportName->Name;
ProtoEntry.ServerPeriodicity = ViewBuffer->ServerPeriodicity;
ProtoEntry.BackupLink.Flink = NULL;
ProtoEntry.BackupLink.Blink = NULL;
ProtoEntry.Flags = 0;
PagedTransport = Transport->PagedTransport;
//
// We're done with the view buffer, now free it.
//
BowserFreeViewBuffer(ViewBuffer);
LOCK_ANNOUNCE_DATABASE(Transport);
try {
Announcement = RtlInsertElementGenericTable(&PagedTransport->DomainTable,
&ProtoEntry, ProtoEntry.Size, &NewElement);
if (Announcement == NULL) {
//
// We couldn't allocate pool for this announcement. Skip it.
//
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
// Indicate the name is referenced by the announce entry we just inserted.
BowserReferenceName( ProtoEntry.Name );
if (!NewElement) {
//
// If this is not a new announcement, copy the announcement entry
// with the new information.
//
// The Previous entry no longer references the name
BowserDereferenceName( Announcement->Name );
if ( Announcement->Size >= ProtoEntry.Size ) {
CSHORT TempSize;
TempSize = Announcement->Size;
RtlCopyMemory( Announcement, &ProtoEntry, ProtoEntry.Size );
Announcement->Size = TempSize;
} else {
if (!RtlDeleteElementGenericTable(
&PagedTransport->DomainTable,
Announcement)) {
KdPrint(("Unable to delete server element %ws\n", Announcement->ServerName));
} else {
Announcement = RtlInsertElementGenericTable(
&PagedTransport->DomainTable,
&ProtoEntry,
ProtoEntry.Size,
&NewElement);
if (Announcement == NULL) {
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
ASSERT( NewElement );
}
}
dlog( DPRT_MASTER,
("%s: %ws Domain:%ws P: %ld\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName,
Announcement->ServerPeriodicity));
} else {
dlog( DPRT_MASTER,
("%s: %ws New domain:%ws P: %ld\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName,
Announcement->ServerPeriodicity));
//
// If there are too many entries,
// ditch this one.
//
if ( RtlNumberGenericTableElements(&PagedTransport->DomainTable) > BowserMaximumBrowseEntries ) {
dlog( DPRT_MASTER,
("%s: %ws New domain (deleted because too many):%ws P: %ld\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName,
Announcement->ServerPeriodicity));
BowserDereferenceName( Announcement->Name );
if (!RtlDeleteElementGenericTable(&PagedTransport->DomainTable, Announcement)) {
// KdPrint(("Unable to delete element %ws\n", Announcement->ServerName));
}
//
// Chaulk it up as a missed announcement
//
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
try_return(NOTHING);
}
}
Periodicity = Announcement->ServerPeriodicity;
ExpirationTime = BowserCurrentTime+(Periodicity*HOST_ANNOUNCEMENT_AGE);
Announcement->ExpirationTime = ExpirationTime;
try_exit:NOTHING;
} finally {
UNLOCK_ANNOUNCE_DATABASE(Transport);
BowserDereferenceTransportName(TransportName);
BowserDereferenceTransport(Transport);
}
return;
}
VOID
BowserAgeServerAnnouncements(
VOID
)
/*++
Routine Description:
This routine will age server announcements in the server announce table.
Arguments:
None.
Return Value:
None.
--*/
{
PAGED_CODE();
BowserForEachTransport(AgeServerAnnouncements, NULL);
}
INLINE
BOOLEAN
BowserIsValidPotentialBrowser(
IN PTRANSPORT Transport,
IN PANNOUNCE_ENTRY Announcement
)
{
if (Announcement->Name->NameType != MasterBrowser) {
return FALSE;
}
//
// If this guy is a potential browser, and is not
// currently a backup or master browser, promote
// him to a browser.
//
if (!(Announcement->ServerType & SV_TYPE_POTENTIAL_BROWSER)) {
return FALSE;
}
//
// And this guy isn't either a master or backup browser already
//
if (Announcement->ServerType & (SV_TYPE_BACKUP_BROWSER | SV_TYPE_MASTER_BROWSER)) {
return FALSE;
}
//
// If this guy is running a current version of the browser.
//
if (Announcement->ServerBrowserVersion < (BROWSER_VERSION_MAJOR << 8) + BROWSER_VERSION_MINOR) {
return FALSE;
}
//
// If this machine is ourselves, and we've not yet announced ourselves as
// a master, don't promote ourselves.
//
if (!_wcsicmp(Announcement->ServerName, Transport->DomainInfo->DomUnicodeComputerNameBuffer)) {
return FALSE;
}
//
// If we've tried to promote this machine more than # of ignored promotions,
// we don't want to consider it either.
//
if (Announcement->NumberOfPromotionAttempts >= NUMBER_IGNORED_PROMOTIONS) {
return FALSE;
}
return TRUE;
}
INLINE
BOOLEAN
BowserIsValidBackupBrowser(
IN PTRANSPORT Transport,
IN PANNOUNCE_ENTRY Announcement
)
/*++
Routine Description:
This routine determines if a server is eligable for demotion.
Arguments:
PTRANSPORT Transport - Transport we are scanning.
PANNOUNCE_ENTRY Announcement - Announce entry for server to check.
Return Value:
BOOLEAN - True if browser is eligable for demotion
--*/
{
PUNICODE_STRING PagedComputerName = &Transport->DomainInfo->DomUnicodeComputerName;
//
// If the name came in on the master browser name
//
if (Announcement->Name->NameType != MasterBrowser) {
return FALSE;
}
//
// And this guy is currently a backup browser,
//
if (!(Announcement->ServerType & SV_TYPE_BACKUP_BROWSER)) {
return FALSE;
}
//
// And this guy was a promoted browser,
//
if (!(Announcement->ServerType & SV_TYPE_POTENTIAL_BROWSER)) {
return FALSE;
}
//
// And this guy isn't an NTAS machine,
//
if (Announcement->ServerType & (SV_TYPE_DOMAIN_BAKCTRL | SV_TYPE_DOMAIN_CTRL)) {
return FALSE;
}
//
// And this isn't ourselves.
//
if (RtlCompareMemory(Announcement->ServerName,
PagedComputerName->Buffer,
PagedComputerName->Length) == PagedComputerName->Length) {
return FALSE;
}
//
// Then it's a valid backup browser to demote.
//
return TRUE;
}
NTSTATUS
AgeServerAnnouncements(
PTRANSPORT Transport,
PVOID Context
)
/*++
Routine Description:
This routine is the worker routine for BowserAgeServerAnnouncements.
It is called for each of the serviced transports in the bowser and
ages the servers received on each transport.
Arguments:
None.
Return Value:
None.
--*/
{
PANNOUNCE_ENTRY Announcement;
ULONG BackupsNeeded;
ULONG BackupsFound;
ULONG NumberOfConfiguredBrowsers;
PVOID ResumeKey = NULL;
PVOID PreviousResumeKey = NULL;
ULONG NumberOfServersDeleted = 0;
ULONG NumberOfDomainsDeleted = 0;
PPAGED_TRANSPORT PagedTransport = Transport->PagedTransport;
PAGED_CODE();
LOCK_TRANSPORT(Transport);
//
// If we're not a master, don't bother.
//
if (PagedTransport->Role != Master) {
UNLOCK_TRANSPORT(Transport);
return STATUS_SUCCESS;
}
UNLOCK_TRANSPORT(Transport);
LOCK_ANNOUNCE_DATABASE(Transport);
try {
BackupsFound = 0;
NumberOfConfiguredBrowsers = 0;
dlog(DPRT_MASTER,
("%s: %ws: Scavenge Servers:",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer));
for (Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ;
Announcement != NULL ;
Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ) {
if (BowserCurrentTime > Announcement->ExpirationTime) {
if (Announcement->Name->NameType != OtherDomain) {
if (Announcement->ServerType & SV_TYPE_BACKUP_BROWSER) {
//
// This guy was a backup - indicate that we're not tracking
// him any more.
//
PagedTransport->NumberOfBrowserServers -= 1;
}
}
dlog(DPRT_MASTER, ("%ws ", Announcement->ServerName));
// Continue the search from where we found this entry.
ResumeKey = PreviousResumeKey;
BackupsFound = 0;
NumberOfConfiguredBrowsers = 0;
NumberOfServersDeleted += 1;
//
// If this announcement was a backup browser, remove it from the
// list of backup browsers.
//
if (Announcement->BackupLink.Flink != NULL) {
ASSERT (Announcement->BackupLink.Blink != NULL);
ASSERT (Announcement->ServerType & SV_TYPE_BACKUP_BROWSER);
RemoveEntryList(&Announcement->BackupLink);
PagedTransport->NumberOfBackupServerListEntries -= 1;
Announcement->BackupLink.Flink = NULL;
Announcement->BackupLink.Blink = NULL;
}
BowserDereferenceName( Announcement->Name );
if (!RtlDeleteElementGenericTable(&PagedTransport->AnnouncementTable, Announcement)) {
KdPrint(("Unable to delete server element %ws\n", Announcement->ServerName));
}
} else {
if (BowserIsLegalBackupBrowser(Announcement, &Transport->DomainInfo->DomUnicodeComputerName )) {
//
// This announcement should be on the backup list.
//
ASSERT (Announcement->BackupLink.Flink != NULL);
ASSERT (Announcement->BackupLink.Blink != NULL);
//
// Found a backup that has not timed out.
//
BackupsFound++;
}
//
// If this machine is a DC or BDC and is an NT machine, then
// assume it's a Lanman/NT machine.
//
if (Announcement->ServerType & (SV_TYPE_DOMAIN_CTRL|SV_TYPE_DOMAIN_BAKCTRL)) {
//
// If this DC is an NT DC, it is running the browser
// service, and it is NOT the master, we consider it a
// configured browser.
//
if ((Announcement->ServerType & SV_TYPE_NT)
&&
(Announcement->ServerType & SV_TYPE_BACKUP_BROWSER)
&&
!(Announcement->ServerType & SV_TYPE_MASTER_BROWSER)) {
NumberOfConfiguredBrowsers += 1;
}
} else {
//
// If this guy isn't a DC, then if it is a backup browser
// but not a potential browser, then it's a configured
// browser (non-configured browsers get promoted).
//
if ((Announcement->ServerType & SV_TYPE_BACKUP_BROWSER) &&
!(Announcement->ServerType & SV_TYPE_POTENTIAL_BROWSER)) {
NumberOfConfiguredBrowsers += 1;
}
}
//
// Remember where this valid entry was found.
//
PreviousResumeKey = ResumeKey;
}
}
dlog(DPRT_MASTER, ("\n"));
//
// If we've found enough configured backup servers, we don't need to
// promote any more backups.
//
// Also don't attempt a promotion scan for the first MASTER_TIME_UP
// milliseconds (15 minutes) we are the master.
//
if ((BowserTimeUp() - PagedTransport->TimeMaster) > MASTER_TIME_UP) {
//
// If there are fewer than the minimum configured browsers,
// rely only on the configured browsers.
//
if (NumberOfConfiguredBrowsers < BowserMinimumConfiguredBrowsers) {
//
// We will need 1 backup for every SERVERS_PER_BACKUP servers in the domain.
//
PagedTransport->NumberOfBrowserServers = BackupsFound;
BackupsNeeded = (RtlNumberGenericTableElements(&PagedTransport->AnnouncementTable) + (SERVERS_PER_BACKUP-1)) / SERVERS_PER_BACKUP;
dlog(DPRT_MASTER,
("%s: %ws: We need %lx backups, and have %lx.\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
BackupsNeeded,
PagedTransport->NumberOfBrowserServers));
if (PagedTransport->NumberOfBrowserServers < BackupsNeeded) {
//
// We only need this many more backup browsers.
//
BackupsNeeded = BackupsNeeded - PagedTransport->NumberOfBrowserServers;
//
// We need to promote a machine to a backup if possible.
//
ResumeKey = NULL;
for (Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ;
Announcement != NULL ;
Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ) {
//
// If this announcement came from the master browser name
//
if (BowserIsValidPotentialBrowser(Transport, Announcement)) {
dlog(DPRT_MASTER,
("%s: %ws: Found browser to promote: %ws.\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
Announcement->ServerName));
BowserPromoteToBackup(Transport, Announcement->ServerName);
//
// Flag that we've attempted to promote this
// browser.
//
Announcement->NumberOfPromotionAttempts += 1;
BackupsNeeded -= 1;
//
// If we've promoted all the people we need to promote,
// we're done, and can stop looping now.
//
if (BackupsNeeded == 0) {
break;
}
} else if ((Announcement->ServerType & SV_TYPE_BACKUP_BROWSER) &&
(Announcement->ServerBrowserVersion < (BROWSER_VERSION_MAJOR << 8) + BROWSER_VERSION_MINOR)) {
//
// If this guy is out of revision, shut him down.
//
BowserShutdownRemoteBrowser(Transport, Announcement->ServerName);
}
}
}
} else {
//
// If we have enough configured browsers that we won't have
// any more backups, then demote all the non-configured
// browsers.
//
ResumeKey = NULL;
for (Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ;
Announcement != NULL ;
Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->AnnouncementTable, &ResumeKey) ) {
//
// If this machine is a valid machine to demote, do it.
//
if (BowserIsValidBackupBrowser(Transport, Announcement)) {
//
// This machine shouldn't be a backup, since we
// already have enough machines to be backups.
// Demote this backup browser.
//
BowserShutdownRemoteBrowser(Transport, Announcement->ServerName);
}
}
}
}
ResumeKey = NULL;
PreviousResumeKey = NULL;
dlog(DPRT_MASTER,
("%s: %ws: Scavenge Domains:",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer));
for (Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->DomainTable, &ResumeKey) ;
Announcement != NULL ;
Announcement = RtlEnumerateGenericTableWithoutSplaying(&PagedTransport->DomainTable, &ResumeKey) ) {
if (BowserCurrentTime > Announcement->ExpirationTime) {
NumberOfDomainsDeleted += 1;
// Continue the search from where we found this entry.
ResumeKey = PreviousResumeKey;
dlog(DPRT_MASTER, ("%ws ", Announcement->ServerName));
BowserDereferenceName( Announcement->Name );
if (!RtlDeleteElementGenericTable(&PagedTransport->DomainTable, Announcement)) {
// KdPrint(("Unable to delete element %ws\n", Announcement->ServerName));
}
} else {
//
// Remember where this valid entry was found.
//
PreviousResumeKey = ResumeKey;
}
}
dlog(DPRT_MASTER, ("\n", Announcement->ServerName));
} finally {
#if DBG
//
// Log an indication that we might have deleted too many servers.
//
if (NumberOfServersDeleted > BowserServerDeletionThreshold) {
dlog(DPRT_MASTER,
("%s: %ws: Aged out %ld servers.\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
NumberOfServersDeleted ));
}
if (NumberOfDomainsDeleted > BowserDomainDeletionThreshold) {
dlog(DPRT_MASTER,
("%s: %ws: Aged out %ld domains.\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
NumberOfDomainsDeleted ));
}
#endif
UNLOCK_ANNOUNCE_DATABASE(Transport);
}
UNREFERENCED_PARAMETER(Context);
return STATUS_SUCCESS;
}
VOID
BowserShutdownRemoteBrowser(
IN PTRANSPORT Transport,
IN PWSTR ServerName
)
/*++
Routine Description:
This routine will send a request to the remote machine to make it become
a browser server.
Arguments:
None.
Return Value:
None.
--*/
{
RESET_STATE ResetStateRequest;
UNICODE_STRING Name;
PPAGED_TRANSPORT PagedTransport = Transport->PagedTransport;
PAGED_CODE();
dlog(DPRT_BROWSER,
("%s: %ws: Demoting server %ws\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
ServerName ));
RtlInitUnicodeString(&Name, ServerName);
ResetStateRequest.Type = ResetBrowserState;
ResetStateRequest.ResetStateRequest.Options = RESET_STATE_CLEAR_ALL;
//
// Send this reset state (tickle) packet to the computer specified.
//
BowserSendSecondClassMailslot(Transport,
&Name,
ComputerName,
&ResetStateRequest,
sizeof(ResetStateRequest),
TRUE,
MAILSLOT_BROWSER_NAME,
NULL);
}
VOID
BowserPromoteToBackup(
IN PTRANSPORT Transport,
IN PWSTR ServerName
)
/*++
Routine Description:
This routine will send a request to the remote machine to make it become
a browser server.
Arguments:
None.
Return Value:
None.
--*/
{
UCHAR Buffer[LM20_CNLEN+1+sizeof(BECOME_BACKUP)];
PBECOME_BACKUP BecomeBackup = (PBECOME_BACKUP)Buffer;
UNICODE_STRING UString;
OEM_STRING AString;
NTSTATUS Status;
ULONG BufferSize;
PPAGED_TRANSPORT PagedTransport = Transport->PagedTransport;
PAGED_CODE();
dlog(DPRT_BROWSER,
("%s: %ws: Promoting server %ws to backup on %wZ\n",
Transport->DomainInfo->DomOemDomainName,
PagedTransport->TransportName.Buffer,
ServerName ));
BecomeBackup->Type = BecomeBackupServer;
RtlInitUnicodeString(&UString, ServerName);
AString.Buffer = BecomeBackup->BecomeBackup.BrowserToPromote;
AString.MaximumLength = (USHORT)(sizeof(Buffer)-FIELD_OFFSET(BECOME_BACKUP, BecomeBackup.BrowserToPromote));
Status = RtlUnicodeStringToOemString(&AString, &UString, FALSE);
if (!NT_SUCCESS(Status)) {
BowserLogIllegalName( Status, UString.Buffer, UString.Length );
return;
}
BufferSize = FIELD_OFFSET(BECOME_BACKUP, BecomeBackup.BrowserToPromote) +
AString.Length + sizeof(CHAR);
BowserSendSecondClassMailslot(Transport,
NULL,
BrowserElection,
BecomeBackup,
BufferSize,
TRUE,
MAILSLOT_BROWSER_NAME,
NULL);
}
NTSTATUS
BowserEnumerateServers(
IN ULONG Level,
IN PLUID LogonId OPTIONAL,
IN OUT PULONG ResumeKey,
IN ULONG ServerTypeMask,
IN PUNICODE_STRING TransportName OPTIONAL,
IN PUNICODE_STRING EmulatedDomainName,
IN PUNICODE_STRING DomainName OPTIONAL,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferSize,
OUT PULONG EntriesRead,
OUT PULONG TotalEntries,
OUT PULONG TotalBytesNeeded,
IN ULONG_PTR OutputBufferDisplacement
)
/*++
Routine Description:
This routine will enumerate the servers in the bowsers current announcement
table.
Arguments:
Level - The level of information to return
LogonId - An optional logon id to indicate which user requested this info
ResumeKey - The resume key (we return all entries after this one)
ServerTypeMask - Mask of servers to return.
TransportName - Name of the transport to enumerated on
EmulatedDomainName - Name of the domain being emulated.
DomainName OPTIONAL - Domain to filter (all if not specified)
OutputBuffer - Buffer to fill with server info.
OutputBufferSize - Filled in with size of buffer.
EntriesRead - Filled in with the # of entries returned.
TotalEntries - Filled in with the total # of entries.
TotalBytesNeeded - Filled in with the # of bytes needed.
Return Value:
None.
--*/
{
LPTSTR OutputBufferEnd;
NTSTATUS Status;
ENUM_SERVERS_CONTEXT Context;
PVOID OriginalOutputBuffer = OutputBuffer;
PAGED_CODE();
OutputBuffer = ALLOCATE_POOL(PagedPool,OutputBufferSize,POOL_SERVER_ENUM_BUFFER);
if (OutputBuffer == NULL) {
return(STATUS_INSUFFICIENT_RESOURCES);
}
OutputBufferEnd = (LPTSTR)((PCHAR)OutputBuffer+OutputBufferSize);
Context.EntriesRead = 0;
Context.TotalEntries = 0;
Context.TotalBytesNeeded = 0;
Context.Level = Level;
Context.LogonId = LogonId;
Context.OriginalResumeKey = *ResumeKey;
Context.ServerTypeMask = ServerTypeMask;
Context.DomainName = DomainName;
Context.OutputBufferSize = OutputBufferSize;
Context.OutputBuffer = OutputBuffer;
Context.OutputBufferDisplacement =
((PCHAR)OutputBuffer - ((PCHAR)OriginalOutputBuffer - OutputBufferDisplacement));
Context.OutputBufferEnd = OutputBufferEnd;
dlog(DPRT_SRVENUM, ("Enumerate Servers: Buffer: %lx, BufferSize: %lx, BufferEnd: %lx\n",
OutputBuffer, OutputBufferSize, OutputBufferEnd));
if (TransportName == NULL) {
Status = STATUS_INVALID_PARAMETER;
} else {
PTRANSPORT Transport;
Transport = BowserFindTransport(TransportName, EmulatedDomainName );
dprintf(DPRT_REF, ("Called Find transport %lx from BowserEnumerateServers.\n", Transport));
if (Transport == NULL) {
return(STATUS_OBJECT_NAME_NOT_FOUND);
}
dlog(DPRT_SRVENUM,
("%s: %ws: Enumerate Servers: Buffer: %lx, BufferSize: %lx, BufferEnd: %lx\n",
Transport->DomainInfo->DomOemDomainName,
Transport->PagedTransport->TransportName.Buffer,
OutputBuffer, OutputBufferSize, OutputBufferEnd));
Status = EnumerateServersWorker(Transport, &Context);
//
// Dereference the transport..
BowserDereferenceTransport(Transport);
}
*EntriesRead = Context.EntriesRead;
*TotalEntries = Context.TotalEntries;
*TotalBytesNeeded = Context.TotalBytesNeeded;
*ResumeKey = Context.ResumeKey;
try {
RtlCopyMemory(OriginalOutputBuffer,OutputBuffer,OutputBufferSize);
} except (BR_EXCEPTION) {
FREE_POOL(OutputBuffer);
return(GetExceptionCode());
}
FREE_POOL(OutputBuffer);
dlog(DPRT_SRVENUM, ("TotalEntries: %lx EntriesRead: %lx, TotalBytesNeeded: %lx\n", *TotalEntries, *EntriesRead, *TotalBytesNeeded));
if (*EntriesRead == *TotalEntries) {
return STATUS_SUCCESS;
} else {
return STATUS_MORE_ENTRIES;
}
}
NTSTATUS
EnumerateServersWorker(
IN PTRANSPORT Transport,
IN OUT PVOID Ctx
)
/*++
Routine Description:
This routine is the worker routine for GowserGetAnnounceTableSize.
It is called for each of the serviced transports in the bowser and
returns the size needed to enumerate the servers received on each transport.
Arguments:
None.
Return Value:
None.
--*/
{
PENUM_SERVERS_CONTEXT Context = Ctx;
PANNOUNCE_ENTRY Announcement;
NTSTATUS Status;
ULONG AnnouncementIndex;
PPAGED_TRANSPORT PagedTransport = Transport->PagedTransport;
PUNICODE_STRING DomainName;
PAGED_CODE();
LOCK_ANNOUNCE_DATABASE_SHARED(Transport);
if (Context->DomainName == NULL) {
DomainName = &Transport->DomainInfo->DomUnicodeDomainName;
} else {
DomainName = Context->DomainName;
}
try {
PVOID ResumeKey = NULL;
for (AnnouncementIndex = 1,
Announcement = RtlEnumerateGenericTableWithoutSplaying((Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ?
&PagedTransport->DomainTable :
&PagedTransport->AnnouncementTable),
&ResumeKey) ;
Announcement != NULL ;
AnnouncementIndex += 1,
Announcement = RtlEnumerateGenericTableWithoutSplaying((Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ?
&PagedTransport->DomainTable :
&PagedTransport->AnnouncementTable),
&ResumeKey) ) {
//
// If the type mask matches, check the domain supplied to make sure
// that this announcement is acceptable to the caller.
//
//
// If we are doing a domain enumeration, we want to use domains
// received on all names, otherwise we want to use names only
// seen on the domain being queried.
//
if ((AnnouncementIndex > Context->OriginalResumeKey) &&
((Announcement->ServerType & Context->ServerTypeMask) != 0) &&
(Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ||
RtlEqualUnicodeString(DomainName, &Announcement->Name->Name, TRUE))
) {
try {
//
// We have an entry we can return to the user.
//
Context->TotalEntries += 1;
if (PackServerAnnouncement(Context->Level,
Context->ServerTypeMask,
(LPTSTR *)&Context->OutputBuffer,
(LPTSTR *)&Context->OutputBufferEnd,
Context->OutputBufferDisplacement,
Announcement,
&Context->TotalBytesNeeded)) {
Context->EntriesRead += 1;
//
// Set the resume key in the structure to point to
// the last entry we returned.
//
Context->ResumeKey = AnnouncementIndex;
}
} except (BR_EXCEPTION) {
try_return(Status = GetExceptionCode());
}
#if 0
} else {
if (Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ||
Context->ServerTypeMask == SV_TYPE_ALL ) {
KdPrint(("Skipping Announce entry %ws. Index: %ld, ResumeKey: %ld, Domain: %wZ, %wZ\n",
Announcement->ServerName,
AnnouncementIndex,
Context->OriginalResumeKey,
&Announcement->Name->Name,
DomainName));
}
#endif
}
}
try_return(Status = STATUS_SUCCESS);
try_exit: {
#if 0
if (Context->ServerTypeMask == SV_TYPE_ALL) {
if (AnnouncementIndex-1 != RtlNumberGenericTableElements(&Transport->AnnouncementTable) ) {
KdPrint(("Bowser: Announcement index != Number of elements in table (%ld, %ld) on transport %wZ\n", AnnouncementIndex-1, RtlNumberGenericTableElements(&Transport->AnnouncementTable), &Transport->TransportName ));
}
} else if (Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM) {
if (AnnouncementIndex-1 != RtlNumberGenericTableElements(&Transport->DomainTable) ) {
KdPrint(("Bowser: Announcement index != Number of domains in table (%ld, %ld) on transport %wZ\n", AnnouncementIndex-1, RtlNumberGenericTableElements(&Transport->DomainTable), &Transport->TransportName ));
}
}
if (Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM) {
if (Context->TotalEntries != RtlNumberGenericTableElements(&Transport->DomainTable)) {
KdPrint(("Bowser: Returned EntriesRead == %ld, But %ld entries in table on transport %wZ\n", Context->TotalEntries, RtlNumberGenericTableElements(&Transport->DomainTable), &Transport->TransportName ));
}
} else if (Context->ServerTypeMask == SV_TYPE_ALL) {
if (Context->TotalEntries != RtlNumberGenericTableElements(&Transport->AnnouncementTable)) {
KdPrint(("Bowser: Returned EntriesRead == %ld, But %ld entries in table on transport %wZ\n", Context->TotalEntries, RtlNumberGenericTableElements(&Transport->AnnouncementTable), &Transport->TransportName ));
}
}
if (Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM || Context->ServerTypeMask == SV_TYPE_ALL) {
if (Context->EntriesRead <= 20) {
KdPrint(("Bowser: Returned %s: EntriesRead == %ld (%ld/%ld) on transport %wZ. Resume handle: %lx, %lx\n",
(Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ? "domain" : "server"),
Context->EntriesRead,
RtlNumberGenericTableElements(&Transport->AnnouncementTable),
RtlNumberGenericTableElements(&Transport->DomainTable),
&Transport->TransportName,
Context->ResumeKey,
Context->OriginalResumeKey ));
}
if (Context->TotalEntries <= 20) {
KdPrint(("Bowser: Returned %s: TotalEntries == %ld (%ld/%ld) on transport %wZ. Resume handle: %lx, %lx\n",
(Context->ServerTypeMask == SV_TYPE_DOMAIN_ENUM ? "domain" : "server"),
Context->TotalEntries,
RtlNumberGenericTableElements(&Transport->AnnouncementTable),
RtlNumberGenericTableElements(&Transport->DomainTable),
&Transport->TransportName,
Context->ResumeKey,
Context->OriginalResumeKey ));
}
}
#endif
}
} finally {
UNLOCK_ANNOUNCE_DATABASE(Transport);
}
return(Status);
}
BOOLEAN
PackServerAnnouncement (
IN ULONG Level,
IN ULONG ServerTypeMask,
IN OUT LPTSTR *BufferStart,
IN OUT LPTSTR *BufferEnd,
IN ULONG_PTR BufferDisplacment,
IN PANNOUNCE_ENTRY Announcement,
OUT PULONG TotalBytesNeeded
)
/*++
Routine Description:
This routine packs a server announcement into the buffer provided updating
all relevant pointers.
Arguments:
IN ULONG Level - Level of information requested.
IN OUT PCHAR *BufferStart - Supplies the output buffer.
Updated to point to the next buffer
IN OUT PCHAR *BufferEnd - Supplies the end of the buffer. Updated to
point before the start of the
strings being packed.
IN PVOID UsersBufferStart - Supplies the start of the buffer in the users
address space
IN PANNOUNCE_ENTRY Announcement - Supplies the announcement to enumerate.
IN OUT PULONG TotalBytesNeeded - Updated to account for the length of this
entry
Return Value:
BOOLEAN - True if the entry was successfully packed into the buffer.
--*/
{
ULONG BufferSize;
UNICODE_STRING UnicodeNameString, UnicodeCommentString;
PSERVER_INFO_101 ServerInfo = (PSERVER_INFO_101 )*BufferStart;
PAGED_CODE();
switch (Level) {
case 100:
BufferSize = sizeof(SERVER_INFO_100);
break;
case 101:
BufferSize = sizeof(SERVER_INFO_101);
break;
default:
return FALSE;
}
*BufferStart = (LPTSTR)(((PUCHAR)*BufferStart) + BufferSize);
dlog(DPRT_SRVENUM, ("Pack Announcement %ws (%lx) - %ws :", Announcement->ServerName, Announcement, Announcement->ServerComment));
dlog(DPRT_SRVENUM, ("BufferStart: %lx, BufferEnd: %lx\n", ServerInfo, *BufferEnd));
//
// Compute the length of the name.
//
RtlInitUnicodeString(&UnicodeNameString, Announcement->ServerName);
ASSERT (UnicodeNameString.Length <= CNLEN*sizeof(WCHAR));
RtlInitUnicodeString(&UnicodeCommentString, Announcement->ServerComment);
ASSERT (UnicodeCommentString.Length <= LM20_MAXCOMMENTSZ*sizeof(WCHAR));
#if DBG
if (ServerTypeMask == SV_TYPE_DOMAIN_ENUM) {
ASSERT (UnicodeCommentString.Length <= CNLEN*sizeof(WCHAR));
}
#endif
//
// Update the total number of bytes needed for this structure.
//
*TotalBytesNeeded += UnicodeNameString.Length + BufferSize + sizeof(WCHAR);
if (Level == 101) {
*TotalBytesNeeded += UnicodeCommentString.Length + sizeof(WCHAR);
if (ServerTypeMask == SV_TYPE_BACKUP_BROWSER) {
*TotalBytesNeeded += 2;
}
}
if (*BufferStart >= *BufferEnd) {
return FALSE;
}
//
// Assume an OS/2 platform ID, unless an NT server
//
if (Announcement->ServerType & SV_TYPE_NT) {
ServerInfo->sv101_platform_id = PLATFORM_ID_NT;
} else {
ServerInfo->sv101_platform_id = PLATFORM_ID_OS2;
}
ServerInfo->sv101_name = UnicodeNameString.Buffer;
ASSERT (UnicodeNameString.Length / sizeof(WCHAR) <= CNLEN);
if (!BowserPackUnicodeString(
&ServerInfo->sv101_name,
UnicodeNameString.Length,
BufferDisplacment,
*BufferStart,
BufferEnd)) {
dlog(DPRT_SRVENUM, ("Unable to pack name %ws into buffer\n", Announcement->ServerName));
return FALSE;
}
if (Level > 100) {
PUSHORT VersionPointer;
ServerInfo->sv101_version_major = Announcement->ServerVersionMajor;
ServerInfo->sv101_version_minor = Announcement->ServerVersionMinor;
ServerInfo->sv101_type = Announcement->ServerType;
ServerInfo->sv101_comment = UnicodeCommentString.Buffer;
ASSERT (UnicodeCommentString.Length / sizeof(WCHAR) <= LM20_MAXCOMMENTSZ);
if (!BowserPackUnicodeString(
&ServerInfo->sv101_comment,
UnicodeCommentString.Length,
BufferDisplacment,
*BufferStart,
BufferEnd)) {
dlog(DPRT_SRVENUM, ("Unable to pack comment %ws into buffer\n", Announcement->ServerComment));
return FALSE;
}
if (ServerTypeMask == SV_TYPE_BACKUP_BROWSER) {
//
// If we can't fit a ushort into the buffer, return an error.
//
if ((*BufferEnd - *BufferStart) <= sizeof(USHORT)) {
return FALSE;
}
//
// Back the buffer end by the size of a USHORT (to make room for
// this value).
//
(ULONG_PTR)*BufferEnd -= sizeof(USHORT);
VersionPointer = (PUSHORT)*BufferEnd;
*VersionPointer = Announcement->ServerBrowserVersion;
}
}
return TRUE;
}
PVIEW_BUFFER
BowserAllocateViewBuffer(
VOID
)
/*++
Routine Description:
This routine will allocate a view buffer from the view buffer pool.
If it is unable to allocate a buffer, it will allocate the buffer from
non-paged pool (up to the maximum configured by the user).
Arguments:
None.
Return Value:
ViewBuffr - The allocated buffer.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE( BowserDiscardableCodeSection );
ACQUIRE_SPIN_LOCK(&BowserViewBufferListSpinLock, &OldIrql);
if (!IsListEmpty(&BowserViewBufferHead)) {
PLIST_ENTRY Entry = RemoveHeadList(&BowserViewBufferHead);
RELEASE_SPIN_LOCK(&BowserViewBufferListSpinLock, OldIrql);
return CONTAINING_RECORD(Entry, VIEW_BUFFER, Overlay.NextBuffer);
}
if (BowserNumberOfServerAnnounceBuffers <=
BowserData.NumberOfServerAnnounceBuffers) {
PVIEW_BUFFER ViewBuffer = NULL;
BowserNumberOfServerAnnounceBuffers += 1;
RELEASE_SPIN_LOCK(&BowserViewBufferListSpinLock, OldIrql);
ViewBuffer = ALLOCATE_POOL(NonPagedPool, sizeof(VIEW_BUFFER), POOL_VIEWBUFFER);
if (ViewBuffer == NULL) {
ACQUIRE_SPIN_LOCK(&BowserViewBufferListSpinLock, &OldIrql);
BowserNumberOfServerAnnounceBuffers -= 1;
BowserStatistics.NumberOfFailedServerAnnounceAllocations += 1;
RELEASE_SPIN_LOCK(&BowserViewBufferListSpinLock, OldIrql);
return NULL;
}
ViewBuffer->Signature = STRUCTURE_SIGNATURE_VIEW_BUFFER;
ViewBuffer->Size = sizeof(VIEW_BUFFER);
return ViewBuffer;
}
RELEASE_SPIN_LOCK(&BowserViewBufferListSpinLock, OldIrql);
BowserStatistics.NumberOfMissedServerAnnouncements += 1;
// run out of buffers.
return NULL;
}
VOID
BowserFreeViewBuffer(
IN PVIEW_BUFFER Buffer
)
/*++
Routine Description:
This routine will return a view buffer to the view buffer pool.
Arguments:
IN PVIEW_BUFFER Buffer - Supplies the buffer to free
Return Value:
None.
--*/
{
KIRQL OldIrql;
DISCARDABLE_CODE( BowserDiscardableCodeSection );
ASSERT (Buffer->Signature == STRUCTURE_SIGNATURE_VIEW_BUFFER);
ACQUIRE_SPIN_LOCK(&BowserViewBufferListSpinLock, &OldIrql);
InsertTailList(&BowserViewBufferHead, &Buffer->Overlay.NextBuffer);
RELEASE_SPIN_LOCK(&BowserViewBufferListSpinLock, OldIrql);
}
NTSTATUS
BowserpInitializeAnnounceTable(
VOID
)
/*++
Routine Description:
This routine will allocate a transport descriptor and bind the bowser
to the transport.
Arguments:
Return Value:
NTSTATUS - Status of operation.
--*/
{
PAGED_CODE();
InitializeListHead(&BowserViewBufferHead);
//
// Allocate a spin lock to protect the view buffer chain.
//
KeInitializeSpinLock(&BowserViewBufferListSpinLock);
BowserNumberOfServerAnnounceBuffers = 0;
return STATUS_SUCCESS;
}
NTSTATUS
BowserpUninitializeAnnounceTable(
VOID
)
/*++
Routine Description:
Arguments:
Return Value:
NTSTATUS - Status of operation.
--*/
{
PVIEW_BUFFER Buffer;
PAGED_CODE();
//
// Note: We don't need to protect this list while stopping because
// we have already unbound from all the loaded transports, thus no
// other announcements are being processed.
//
while (!IsListEmpty(&BowserViewBufferHead)) {
PLIST_ENTRY Entry = RemoveHeadList(&BowserViewBufferHead);
Buffer = CONTAINING_RECORD(Entry, VIEW_BUFFER, Overlay.NextBuffer);
FREE_POOL(Buffer);
}
ASSERT (IsListEmpty(&BowserViewBufferHead));
BowserNumberOfServerAnnounceBuffers = 0;
return STATUS_SUCCESS;
}
VOID
BowserDeleteGenericTable(
IN PRTL_GENERIC_TABLE GenericTable
)
{
PVOID TableElement;
PAGED_CODE();
//
// Enumerate the elements in the table, deleting them as we go.
//
// KdPrint("Delete Generic Table %lx\n", GenericTable));
for (TableElement = RtlEnumerateGenericTable(GenericTable, TRUE) ;
TableElement != NULL ;
TableElement = RtlEnumerateGenericTable(GenericTable, TRUE)) {
PANNOUNCE_ENTRY Announcement = TableElement;
if (Announcement->BackupLink.Flink != NULL) {
ASSERT (Announcement->BackupLink.Blink != NULL);
ASSERT (Announcement->ServerType & SV_TYPE_BACKUP_BROWSER);
RemoveEntryList(&Announcement->BackupLink);
Announcement->BackupLink.Flink = NULL;
Announcement->BackupLink.Blink = NULL;
}
BowserDereferenceName( Announcement->Name );
RtlDeleteElementGenericTable(GenericTable, TableElement);
}
ASSERT (RtlNumberGenericTableElements(GenericTable) == 0);
}