/*++ Copyright (c) 1991-1992 Microsoft Corporation Module Name: srvenum.c Abstract: This module contains the worker routine for the NetServerEnum API implemented by the Workstation service. Author: Rita Wong (ritaw) 25-Mar-1991 Revision History: --*/ #include "precomp.h" #pragma hdrstop NET_API_STATUS NET_API_FUNCTION BrNetServerEnum( IN PNETWORK Network OPTIONAL, IN LPCWSTR ClientName OPTIONAL, IN ULONG Level, IN DWORD PreferedMaximumLength, OUT PVOID *Buffer, OUT LPDWORD EntriesRead, OUT LPDWORD TotalEntries, IN DWORD ServerType, IN LPCWSTR Domain, IN LPCWSTR FirstNameToReturn OPTIONAL ); NET_API_STATUS BrRetrieveServerListForMaster( IN PNETWORK Network, IN OUT PVOID *Buffer, OUT PDWORD EntriesRead, OUT PDWORD TotalEntries, IN DWORD Level, IN DWORD ServerType, IN DWORD PreferedMaximumLength, IN BOOLEAN LocalListOnly, IN LPTSTR ClientName, IN LPTSTR DomainName, IN LPCWSTR FirstNameToReturn ); NET_API_STATUS BrRetrieveServerListForBackup( IN PNETWORK Network, IN OUT PVOID *Buffer, OUT PDWORD EntriesRead, OUT PDWORD TotalEntries, IN DWORD Level, IN DWORD ServerType, IN DWORD PreferedMaximumLength, IN LPCWSTR FirstNameToReturn ); // // This points to XsConvertServerEnumBuffer, which is dynamically loaded from // xactsrv.dll when required // XS_CONVERT_SERVER_ENUM_BUFFER_FUNCTION *pXsConvertServerEnumBuffer = NULL; NET_API_STATUS NET_API_FUNCTION I_BrowserrServerEnum( IN LPTSTR ServerName OPTIONAL, IN LPTSTR TransportName OPTIONAL, IN LPTSTR ClientName OPTIONAL, IN OUT LPSERVER_ENUM_STRUCT InfoStruct, IN DWORD PreferedMaximumLength, OUT LPDWORD TotalEntries, IN DWORD ServerType, IN LPTSTR Domain, IN OUT LPDWORD ResumeHandle OPTIONAL ) /*++ Routine Description: This function is the NetServerEnum entry point in the Workstation service. Arguments: ServerName - Supplies the name of server to execute this function TransportName - Supplies the name of xport on which to enumerate servers InfoStruct - This structure supplies the level of information requested, returns a pointer to the buffer allocated by the Workstation service which contains a sequence of information structure of the specified information level, and returns the number of entries read. The buffer pointer is set to NULL if return code is not NERR_Success or ERROR_MORE_DATA, or if EntriesRead returned is 0. The EntriesRead value is only valid if the return code is NERR_Success or ERROR_MORE_DATA. PreferedMaximumLength - Supplies the number of bytes of information to return in the buffer. If this value is MAXULONG, we will try to return all available information if there is enough memory resource. TotalEntries - Returns the total number of entries available. This value is returned only if the return code is NERR_Success or ERROR_MORE_DATA. ServerType - Supplies the type of server to enumerate. Domain - Supplies the name of one of the active domains to enumerate the servers from. If NULL, servers from the primary domain, logon domain and other domains are enumerated. ResumeHandle - Supplies and returns the point to continue with enumeration. Return Value: NET_API_STATUS - NERR_Success or reason for failure. --*/ { NET_API_STATUS NetStatus; NetStatus = I_BrowserrServerEnumEx( ServerName, TransportName, ClientName, InfoStruct, PreferedMaximumLength, TotalEntries, ServerType, Domain, NULL ); // NULL FirstNameToReturn if (ARGUMENT_PRESENT(ResumeHandle)) { *ResumeHandle = 0; } return NetStatus; } NET_API_STATUS NET_API_FUNCTION I_BrowserrServerEnumEx( IN LPTSTR ServerName OPTIONAL, IN LPTSTR TransportName OPTIONAL, IN LPTSTR ClientName OPTIONAL, IN OUT LPSERVER_ENUM_STRUCT InfoStruct, IN DWORD PreferedMaximumLength, OUT LPDWORD TotalEntries, IN DWORD ServerType, IN LPTSTR Domain, IN LPTSTR FirstNameToReturnArg ) /*++ Routine Description: This function is the NetServerEnum entry point in the Workstation service. Arguments: ServerName - Supplies the name of server to execute this function TransportName - Supplies the name of xport on which to enumerate servers InfoStruct - This structure supplies the level of information requested, returns a pointer to the buffer allocated by the Workstation service which contains a sequence of information structure of the specified information level, and returns the number of entries read. The buffer pointer is set to NULL if return code is not NERR_Success or ERROR_MORE_DATA, or if EntriesRead returned is 0. The EntriesRead value is only valid if the return code is NERR_Success or ERROR_MORE_DATA. PreferedMaximumLength - Supplies the number of bytes of information to return in the buffer. If this value is MAXULONG, we will try to return all available information if there is enough memory resource. TotalEntries - Returns the total number of entries available. This value is returned only if the return code is NERR_Success or ERROR_MORE_DATA. ServerType - Supplies the type of server to enumerate. Domain - Supplies the name of one of the active domains to enumerate the servers from. If NULL, servers from the primary domain, logon domain and other domains are enumerated. FirstNameToReturnArg - Supplies the name of the first domain or server entry to return. The caller can use this parameter to implement a resume handle of sorts by passing the name of the last entry returned on a previous call. (Notice that the specified entry will, also, be returned on this call unless it has since been deleted.) Pass NULL to start with the first entry available. Return Value: NET_API_STATUS - NERR_Success or reason for failure. --*/ { NET_API_STATUS status; PVOID Buffer = NULL; ULONG EntriesRead; BOOLEAN NetworkLocked = FALSE; PNETWORK Network = NULL; UNICODE_STRING NetworkName; WCHAR FirstNameToReturn[DNLEN+1]; PDOMAIN_INFO DomainInfo = NULL; #if DBG DWORD StartTickCount, EndTickCount; #endif UNREFERENCED_PARAMETER(ServerName); #if DBG StartTickCount = GetTickCount(); #endif BrPrint( (BR_CLIENT_OP, "I_BrowserrServerEnumEx: called by client <%ws>, for domain <%ws>, type <%lx>, firstNameToReturn <%ws>, on Transport <%ws>\n", ClientName, Domain, ServerType, FirstNameToReturnArg, TransportName )); if (!ARGUMENT_PRESENT(TransportName)) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } if (!ARGUMENT_PRESENT(InfoStruct)) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } if ( InfoStruct->Level == 101 && !InfoStruct->ServerInfo.Level101 ) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } else if ( InfoStruct->Level == 100 && !InfoStruct->ServerInfo.Level100 ) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } #ifdef ENABLE_PSEUDO_BROWSER // // Pseudo Server returns nothing // if (BrInfo.PseudoServerLevel == BROWSER_PSEUDO) { if (InfoStruct->Level == 101) { InfoStruct->ServerInfo.Level101->Buffer = NULL; InfoStruct->ServerInfo.Level101->EntriesRead = 0; } else { InfoStruct->ServerInfo.Level100->Buffer = NULL; InfoStruct->ServerInfo.Level100->EntriesRead = 0; } status = ERROR_SUCCESS; goto Cleanup; } #endif // // Find the requested domain. // DomainInfo = BrFindDomain( Domain, TRUE ); if ( DomainInfo == NULL) { status = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Find the requested network // RtlInitUnicodeString(&NetworkName, TransportName); Network = BrFindNetwork( DomainInfo, &NetworkName); if (Network == NULL) { BrPrint(( BR_CRITICAL, "%ws: %ws: BrowserrServerEnum: Network not found.\n", Domain, TransportName)); status = ERROR_FILE_NOT_FOUND; goto Cleanup; } // // If the caller has asked us to use the alternate transport, // do so. // if ((ServerType != SV_TYPE_ALL) && (ServerType & SV_TYPE_ALTERNATE_XPORT) ) { // // If this transport has an alternate network, then actually // query the alternate name, not the real one. // if (Network->AlternateNetwork != NULL) { PNETWORK TempNetwork = Network; Network = Network->AlternateNetwork; if ( !BrReferenceNetwork( Network )) { Network = TempNetwork; status = ERROR_INVALID_PARAMETER; goto Cleanup; } BrDereferenceNetwork( TempNetwork ); BrPrint((BR_CLIENT_OP, "I_BrowserrServerEnumEx: for client <%ws>, querying alternate network <%ws> instead of <%ws>\n", ClientName, Network->NetworkName.Buffer, Network->AlternateNetwork->NetworkName.Buffer )); } else { status = ERROR_INVALID_PARAMETER; goto Cleanup; } ServerType &= ~SV_TYPE_ALTERNATE_XPORT; if (ServerType == 0) { ServerType = SV_TYPE_ALL; } } if (!LOCK_NETWORK_SHARED(Network)) { status = NERR_InternalError; goto Cleanup; } NetworkLocked = TRUE; if (!(Network->Role & (ROLE_BACKUP | ROLE_MASTER))) { // // If this is a wannish transport, // and the caller is asking for "local list", // try to find another wannish transport that is a master browser. // // The domain master browser doesn't have any control over which // transport he comes in on. If he picks a disabled transport, // this code will find the enabled transport. // // There are cases where there is more than one wannish master browser // transport on this machine. In that case, BrNetServerEnum merges the // local lists for all wannish transports to ensure that all list are returned // to the domain master browser // if ( (Network->Flags & NETWORK_WANNISH) != 0 && (ServerType == SV_TYPE_LOCAL_LIST_ONLY || ServerType == (SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM) ) ) { PNETWORK TempNetwork; TempNetwork = BrFindWannishMasterBrowserNetwork( DomainInfo ); if ( TempNetwork == NULL ) { BrPrint(( BR_CRITICAL, "%ws: %ws: Browse request received from %ws, but not backup or master\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, ClientName)); status = ERROR_REQ_NOT_ACCEP; goto Cleanup; } // // Ditch the old network. // UNLOCK_NETWORK(Network); NetworkLocked = FALSE; BrDereferenceNetwork( Network ); Network = TempNetwork; // // Use the new network // if (!LOCK_NETWORK_SHARED(Network)) { status = NERR_InternalError; goto Cleanup; } NetworkLocked = TRUE; BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Is wannish IP Network found for %ws\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, ClientName)); } else { BrPrint(( BR_CRITICAL, "%ws: %ws: Browse request received from %ws, but not backup or master\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, ClientName)); status = ERROR_REQ_NOT_ACCEP; goto Cleanup; } } // // Canonicalize the FirstNameToReturn. // if (ARGUMENT_PRESENT(FirstNameToReturnArg) && *FirstNameToReturnArg != L'\0') { if ( I_NetNameCanonicalize( NULL, (LPWSTR) FirstNameToReturnArg, FirstNameToReturn, sizeof(FirstNameToReturn), NAMETYPE_WORKGROUP, LM2X_COMPATIBLE ) != NERR_Success) { status = ERROR_INVALID_PARAMETER; goto Cleanup; } } else { FirstNameToReturn[0] = L'\0'; } status = BrNetServerEnum(Network, ClientName, InfoStruct->Level, PreferedMaximumLength, &Buffer, &EntriesRead, TotalEntries, ServerType, Domain, FirstNameToReturn ); // // Return output parameters other than output buffer from request packet. // if (status == NERR_Success || status == ERROR_MORE_DATA) { if (InfoStruct->Level == 101) { InfoStruct->ServerInfo.Level101->Buffer = (PSERVER_INFO_101) Buffer; InfoStruct->ServerInfo.Level101->EntriesRead = EntriesRead; } else { InfoStruct->ServerInfo.Level100->Buffer = (PSERVER_INFO_100) Buffer; InfoStruct->ServerInfo.Level100->EntriesRead = EntriesRead; } } Cleanup: if (NetworkLocked) { UNLOCK_NETWORK(Network); } if ( Network != NULL ) { #if DBG EndTickCount = GetTickCount(); BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Browse request for %ws took %ld milliseconds\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, ClientName, EndTickCount - StartTickCount)); #endif BrDereferenceNetwork( Network ); } if ( DomainInfo != NULL ) { BrDereferenceDomain( DomainInfo ); } return status; } WORD I_BrowserServerEnumForXactsrv( IN LPCWSTR TransportName OPTIONAL, IN LPCWSTR ClientName OPTIONAL, IN ULONG Level, IN USHORT ClientLevel, IN PVOID ClientBuffer, IN WORD BufferLength, IN DWORD PreferedMaximumLength, OUT LPDWORD EntriesFilled, OUT LPDWORD TotalEntries, IN DWORD ServerType, IN LPCWSTR Domain, IN LPCWSTR FirstNameToReturnArg OPTIONAL, OUT PWORD Converter ) /*++ Routine Description: This function is a private entrypoint for Xactsrv that bypasses RPC entirely. Arguments: TransportName - Supplies the name of xport on which to enumerate servers ClientName - Supplies the name of the client that requested the data Level - Level of data requested. ClientLevel - Level requested by the client. ClientBuffer - Output buffer allocated to hold the buffer. BufferLength - Size of ClientBuffer PreferedMaximumLength - Prefered maximum size of Client buffer if we are going to use the NT form of the buffer OUT LPDWORD EntriesFilled - The entries packed into ClientBuffer OUT LPDWORD TotalEntries - The total # of entries available. IN DWORD ServerType - Server type mask. IN LPTSTR Domain - Domain to query OUT PWORD Converter - Magic constant from Xactsrv that allows the client to convert the response buffer. Return Value: WORD - NERR_Success or reason for failure. --*/ { NET_API_STATUS status; BOOLEAN networkLocked = FALSE; PNETWORK network = NULL; UNICODE_STRING networkName; PDOMAIN_INFO DomainInfo = NULL; PVOID buffer = NULL; DWORD entriesRead; PCACHED_BROWSE_RESPONSE response = NULL; WCHAR FirstNameToReturn[DNLEN+1]; #if DBG DWORD startTickCount, endTickCount; startTickCount = GetTickCount(); #endif try { BrPrint( (BR_CLIENT_OP, "I_BrowserServerEnunForXactsrv: called by client <%ws>, domain <%ws>, type <%lx>, firstNameToReturn <%ws>, on Transport <%ws>\n", ClientName, Domain, ServerType, FirstNameToReturnArg, TransportName )); // // If the browser isn't up and we get one of these calls, fail the API // immediately. // if (BrGlobalData.Status.dwCurrentState != SERVICE_RUNNING) { BrPrint(( BR_CRITICAL, "Browse request from %ws received, but browser not running\n", ClientName)); try_return(status = NERR_ServiceNotInstalled); } if (!ARGUMENT_PRESENT(TransportName)) { try_return(status = ERROR_INVALID_PARAMETER); } // // Find the requested domain. // DomainInfo = BrFindDomain( (LPWSTR) Domain, TRUE ); if ( DomainInfo == NULL) { try_return(status = ERROR_NO_SUCH_DOMAIN); } // // Look up the transport. // RtlInitUnicodeString(&networkName, TransportName); BrPrint(( BR_SERVER_ENUM, "%ws: %ws: NetServerEnum: Look up network for %ws\n", Domain, TransportName, ClientName)); network = BrFindNetwork( DomainInfo, &networkName); // // If it returns NULL, the network was not found. // In that case just pick a network randomly and enumerate for that network // This is a workaround for RAID bug 614688. // The situation is that the request comes over NetbiosSMB and so // networkName points to that, and browser is not registered on that transport. // if ( network == NULL) { EnterCriticalSection(&NetworkCritSect); if ( ServicedNetworks.Flink != &ServicedNetworks ) { // If the list is not empty, just pick the first network network = CONTAINING_RECORD(ServicedNetworks.Flink, NETWORK, NextNet); network->ReferenceCount ++; } LeaveCriticalSection(&NetworkCritSect); } if (network == NULL) { BrPrint(( BR_CRITICAL, "%ws: %ws: Network not found.\n", Domain, TransportName)); try_return(status = ERROR_FILE_NOT_FOUND); } // // If the caller has asked us to use the alternate transport, // do so. // if ((ServerType != SV_TYPE_ALL) && (ServerType & SV_TYPE_ALTERNATE_XPORT) ) { // // If this transport has an alternate network, then actually // query the alternate name, not the real one. // if (network->AlternateNetwork != NULL) { PNETWORK TempNetwork = network; network = network->AlternateNetwork; if ( !BrReferenceNetwork( network )) { network = TempNetwork; try_return(status = ERROR_INVALID_PARAMETER); } BrDereferenceNetwork( TempNetwork ); } else { try_return(status = ERROR_INVALID_PARAMETER); } ServerType &= ~SV_TYPE_ALTERNATE_XPORT; if (ServerType == 0) { ServerType = SV_TYPE_ALL; } } if (!LOCK_NETWORK_SHARED(network)) { try_return(status = NERR_InternalError); } networkLocked = TRUE; BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Network found for %ws\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer, ClientName)); if (!(network->Role & (ROLE_BACKUP | ROLE_MASTER))) { // // If this is a wannish transport, // and the caller is asking for "local list", // try to find another wannish transport that is a master browser. // // The domain master browser doesn't have any control over which // transport he comes in on. If he picks a disabled transport, // this code will find the enabled transport. // // There are cases where there is more than one wannish master browser // transport on this machine. In that case, BrNetServerEnum merges the // local lists for all wannish transports to ensure that all list are returned // to the domain master browser // if ( (network->Flags & NETWORK_WANNISH) != 0 && (ServerType == SV_TYPE_LOCAL_LIST_ONLY || ServerType == (SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM) ) ) { PNETWORK TempNetwork; TempNetwork = BrFindWannishMasterBrowserNetwork( DomainInfo ); if ( TempNetwork == NULL ) { BrPrint(( BR_CRITICAL, "%ws: %ws: Browse request received from %ws, but not backup or master\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer, ClientName)); try_return(status = ERROR_REQ_NOT_ACCEP); } // // Ditch the old network. // UNLOCK_NETWORK(network); networkLocked = FALSE; BrDereferenceNetwork( network ); network = TempNetwork; // // Use the new network // if (!LOCK_NETWORK_SHARED(network)) { try_return(status = NERR_InternalError); } networkLocked = TRUE; BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Is wannish IP Network found for %ws\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer, ClientName)); } else { BrPrint(( BR_CRITICAL, "%ws: %ws: Browse request received from %ws, but not backup or master\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer, ClientName)); try_return(status = ERROR_REQ_NOT_ACCEP); } } // // If we weren't able to find a cached response buffer, or // if the cached response didn't have a buffer allocated for it, // we need to process the browse request. // if (network->Role & ROLE_MASTER) { // // Check to see if we should flush the cache at this time. If // we should, we need to release the network and re-acquire it // exclusively, because we use the network lock to protect // enumerations from flushes. // if ((BrCurrentSystemTime() - network->TimeCacheFlushed) > BrInfo.DriverQueryFrequency) { UNLOCK_NETWORK(network); networkLocked = FALSE; if (!LOCK_NETWORK(network)) { try_return(status = NERR_InternalError); } networkLocked = TRUE; // // We're running on a master browser, and we found a cached browse // request. See if we can safely use this cached value, or if we // should go to the driver for it. // EnterCriticalSection(&network->ResponseCacheLock); if ((BrCurrentSystemTime() - network->TimeCacheFlushed) > BrInfo.DriverQueryFrequency) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Flushing cache\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer )); network->TimeCacheFlushed = BrCurrentSystemTime(); BrAgeResponseCache( network ); } LeaveCriticalSection(&network->ResponseCacheLock); } } // // Canonicalize the FirstNameToReturn. // if (ARGUMENT_PRESENT(FirstNameToReturnArg) && *FirstNameToReturnArg != L'\0') { if ( I_NetNameCanonicalize( NULL, (LPWSTR) FirstNameToReturnArg, FirstNameToReturn, sizeof(FirstNameToReturn), NAMETYPE_WORKGROUP, LM2X_COMPATIBLE ) != NERR_Success) { try_return(status = ERROR_INVALID_PARAMETER); } } else { FirstNameToReturn[0] = L'\0'; } if (!ARGUMENT_PRESENT(Domain) || !STRICMP(Domain, network->DomainInfo->DomUnicodeDomainName)) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Look up 0x%x/%d/%x.\n", Domain, TransportName, ServerType, ClientLevel, BufferLength)); // // This request is for our primary domain. Look up a cached response // entry. If none is found for this request, allocate a new one and // return it. // BrPrint( (BR_CLIENT_OP, "I_BrowserServerEnunForXactsrv: try to look up a cached response for our primary domain <%ws> on network <%ws>\n", Domain, network->NetworkName.Buffer )); response = BrLookupAndAllocateCachedEntry( network, ServerType, BufferLength, ClientLevel, FirstNameToReturn ); } EnterCriticalSection(&network->ResponseCacheLock); if ((response == NULL) || (response->Buffer == NULL)) { LeaveCriticalSection(&network->ResponseCacheLock); BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Cached entry not found, or hit count too low. Retrieve actual list for %ws\n", Domain, TransportName, ClientName)); BrPrint( (BR_CLIENT_OP, "I_BrowserServerEnunForXactsrv: retrieving actual list for client <%ws>, domain <%ws> on network <%ws>\n", ClientName, Domain, network->NetworkName.Buffer )); status = BrNetServerEnum(network, ClientName, Level, PreferedMaximumLength, &buffer, &entriesRead, TotalEntries, ServerType, Domain, FirstNameToReturn ); if (status == NERR_Success || status == ERROR_MORE_DATA) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Convert NT buffer to Xactsrv buffer for %ws\n", Domain, TransportName, ClientName )); if( pXsConvertServerEnumBuffer == NULL ) { // // It doesn't really matter if several threads do this simultaneously. // We are never going to unload this library anyway. But we are delaying // the load of the library until now to speed up system boot and init. // HMODULE hLibrary = LoadLibrary( L"xactsrv.dll" ); if( hLibrary != NULL ) { pXsConvertServerEnumBuffer = (XS_CONVERT_SERVER_ENUM_BUFFER_FUNCTION *)GetProcAddress( hLibrary, "XsConvertServerEnumBuffer" ); } } if( pXsConvertServerEnumBuffer != NULL ) { status = pXsConvertServerEnumBuffer( buffer, entriesRead, TotalEntries, ClientLevel, ClientBuffer, BufferLength, EntriesFilled, Converter); } else { status = GetLastError(); } if (status == NERR_Success || status == ERROR_MORE_DATA) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Conversion done for %ws\n", Domain, TransportName, ClientName )); EnterCriticalSection(&network->ResponseCacheLock); if ((response != NULL) && (response->Buffer == NULL) && (response->TotalHitCount >= BrInfo.CacheHitLimit)) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Save contents of server list for 0x%x/%d/%x.\n", Domain, TransportName, ServerType, ClientLevel, BufferLength)); response->Buffer = MIDL_user_allocate(BufferLength); if ( response->Buffer != NULL ) { // // We successfully allocated the buffer, now copy // our response into the buffer and save away the // other useful stuff about the request. // RtlCopyMemory(response->Buffer, ClientBuffer, BufferLength); response->EntriesRead = *EntriesFilled; response->TotalEntries = *TotalEntries; response->Converter = *Converter; response->Status = (WORD)status; } } LeaveCriticalSection(&network->ResponseCacheLock); } } } else { ASSERT (response); ASSERT (response->Buffer); ASSERT (response->Size == BufferLength); BrPrint(( BR_SERVER_ENUM, "Cache hit. Use contents of server list for 0x%x/%d/%x.\n", Domain, TransportName, ServerType, ClientLevel, BufferLength)); // // Copy over the cached response from the response cache into the // users response buffer. // RtlCopyMemory(ClientBuffer, response->Buffer, BufferLength); // // Also copy over the other useful stuff that the client will // want to know about. // *EntriesFilled = response->EntriesRead; *TotalEntries = response->TotalEntries; *Converter = response->Converter; status = response->Status; LeaveCriticalSection(&network->ResponseCacheLock); } try_exit:NOTHING; } finally { if (networkLocked) { UNLOCK_NETWORK(network); } // // If a buffer was allocated, free it. // if (buffer != NULL) { MIDL_user_free(buffer); } if ( network != NULL ) { #if DBG endTickCount = GetTickCount(); BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Browse request for %ws took %ld milliseconds\n", network->DomainInfo->DomUnicodeDomainName, network->NetworkName.Buffer, ClientName, endTickCount - startTickCount)); #endif BrDereferenceNetwork( network ); } if ( DomainInfo != NULL ) { BrDereferenceDomain( DomainInfo ); } } return (WORD)status; } NET_API_STATUS NET_API_FUNCTION BrNetServerEnum( IN PNETWORK Network OPTIONAL, IN LPCWSTR ClientName OPTIONAL, IN ULONG Level, IN DWORD PreferedMaximumLength, OUT PVOID *Buffer, OUT LPDWORD EntriesRead, OUT LPDWORD TotalEntries, IN DWORD ServerType, IN LPCWSTR Domain, IN LPCWSTR FirstNameToReturn OPTIONAL ) /*++ Routine Description: This function is the real worker for the NetServerEnum entry point in the Workstation service. Arguments: ServerName - Supplies the name of server to execute this function TransportName - Supplies the name of xport on which to enumerate servers InfoStruct - This structure supplies the level of information requested, returns a pointer to the buffer allocated by the Workstation service which contains a sequence of information structure of the specified information level, and returns the number of entries read. The buffer pointer is set to NULL if return code is not NERR_Success or ERROR_MORE_DATA, or if EntriesRead returned is 0. The EntriesRead value is only valid if the return code is NERR_Success or ERROR_MORE_DATA. PreferedMaximumLength - Supplies the number of bytes of information to return in the buffer. If this value is MAXULONG, we will try to return all available information if there is enough memory resource. TotalEntries - Returns the total number of entries available. This value is returned only if the return code is NERR_Success or ERROR_MORE_DATA. ServerType - Supplies the type of server to enumerate. Domain - Supplies the name of one of the active domains to enumerate the servers from. If NULL, servers from the primary domain, logon domain and other domains are enumerated. FirstNameToReturn - Supplies the name of the first server or domain to return to the caller. If NULL, enumeration begins with the very first entry. Passed name must be the canonical form of the name. Return Value: NET_API_STATUS - NERR_Success or reason for failure. --*/ { NET_API_STATUS status; DWORD DomainNameSize = 0; TCHAR DomainName[DNLEN + 1]; BOOLEAN NetworkLocked = TRUE; BOOLEAN LocalListOnly = FALSE; PVOID DoubleHopLocalList = NULL; BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Retrieve browse list for %lx for client %ws\n", Domain, Network->NetworkName.Buffer, ServerType, ClientName)); EnterCriticalSection(&BrowserStatisticsLock); if (ServerType == SV_TYPE_DOMAIN_ENUM) { NumberOfDomainEnumerations += 1; } else if (ServerType == SV_TYPE_ALL) { NumberOfServerEnumerations += 1; } else { NumberOfOtherEnumerations += 1; } LeaveCriticalSection(&BrowserStatisticsLock); // // Only levels 100 and 101 are valid // if ((Level != 100) && (Level != 101)) { return ERROR_INVALID_LEVEL; } #ifdef ENABLE_PSEUDO_BROWSER // // Pseudo Server shortcut // if ( BrInfo.PseudoServerLevel == BROWSER_PSEUDO ) { *Buffer = NULL; *EntriesRead = 0; *TotalEntries = 0; return NERR_Success; } #endif if (ARGUMENT_PRESENT(Domain)) { // // NAMETYPE_WORKGROUP allows spaces. // if ( I_NetNameCanonicalize( NULL, (LPWSTR) Domain, DomainName, (DNLEN + 1) * sizeof(TCHAR), NAMETYPE_WORKGROUP, 0 ) != NERR_Success) { return ERROR_INVALID_DOMAINNAME; } DomainNameSize = STRLEN(DomainName) * sizeof(WCHAR); } try { if (ServerType != SV_TYPE_ALL) { // // If the user has specified SV_TYPE_LOCAL_LIST_ONLY, we // will only accept SV_TYPE_DOMAIN_ENUM or 0 as legal bits // otherwise, we return an error. // if (ServerType & SV_TYPE_LOCAL_LIST_ONLY) { LocalListOnly = TRUE; // BrPrint(( BR_SERVER_ENUM, "Retrieve local list for %ws\n", ClientName)); // // Turn off the LOCAL_LIST_ONLY bit. // ServerType &= ~SV_TYPE_LOCAL_LIST_ONLY; // // Check the remaining bits. The only two legal values // are SV_TYPE_DOMAIN_ENUM and 0 // if (ServerType != SV_TYPE_DOMAIN_ENUM) { if (ServerType == 0) { ServerType = SV_TYPE_ALL; } else { try_return(status = ERROR_INVALID_FUNCTION); } } // // If we aren't a master browser, blow this request // off, since we don't have a local list. // if (!(Network->Role & ROLE_MASTER)) { BrPrint(( BR_CRITICAL, "%ws: %ws: Local list request received from %ws, but not master\n", Domain, Network->NetworkName.Buffer, ClientName)); try_return(status = ERROR_REQ_NOT_ACCEP); } } else if (ServerType & SV_TYPE_DOMAIN_ENUM) { if (ServerType != SV_TYPE_DOMAIN_ENUM) { try_return(status = ERROR_INVALID_FUNCTION); } } } if (ARGUMENT_PRESENT(Domain) && STRICMP(Domain, Network->DomainInfo->DomUnicodeDomainName)) { PINTERIM_ELEMENT DomainEntry; LPWSTR MasterName = NULL; BrPrint(( BR_SERVER_ENUM, "Non local domain %ws - Check for double hop\n", Domain)); if ( Network->Role & ROLE_MASTER ) { if ( Network->DomainList.EntriesRead != 0 ) { DomainEntry = LookupInterimServerList(&Network->DomainList, (LPWSTR) Domain); if (DomainEntry != NULL) { MasterName = DomainEntry->Comment; } } else { ULONG i; PSERVER_INFO_101 DomainInfo; DWORD DoubleHopEntriesRead = 0; DWORD DoubleHopTotalEntries = 0; status = BrGetLocalBrowseList(Network, NULL, 101, SV_TYPE_DOMAIN_ENUM, &DoubleHopLocalList, &DoubleHopEntriesRead, &DoubleHopTotalEntries); for (i = 0 , DomainInfo = DoubleHopLocalList ; i < DoubleHopEntriesRead ; i += 1) { if (!_wcsicmp(Domain, DomainInfo->sv101_name)) { MasterName = DomainInfo->sv101_comment; break; } DomainInfo += 1; } } } else { ULONG i; PSERVER_INFO_101 DomainInfo; // // We're running on a backup browser. We want to find the // name of the master browser by looking in the backup // server list for this network. // for (i = 0 , DomainInfo = Network->BackupDomainList ; i < Network->TotalBackupDomainListEntries ; i += 1) { if (!_wcsicmp(Domain, DomainInfo->sv101_name)) { MasterName = DomainInfo->sv101_comment; break; } DomainInfo += 1; } } // // If we couldn't find a master name, bail out right now. // if (MasterName == NULL || *MasterName == UNICODE_NULL) { try_return(status = ERROR_NO_BROWSER_SERVERS_FOUND); } // We also check if the MasterName is actually our current domain name // There is a deadlock scenario that is being hit that is causing this // to be the case which results in stalled SRV threads if ( 0 == STRICMP(MasterName, Network->DomainInfo->DomUnicodeDomainName)) { Domain = NULL; goto get_local_list; } // // If the master for this domain isn't listed as our // current machine, remote the API to that server. // if (STRICMP(MasterName, Network->DomainInfo->DomUnicodeComputerName)) { WCHAR RemoteComputerName[UNLEN+1]; // // Build the name of the remote computer. // STRCPY(RemoteComputerName, TEXT("\\\\")); STRCAT(RemoteComputerName, MasterName); ASSERT (NetworkLocked); UNLOCK_NETWORK(Network); NetworkLocked = FALSE; BrPrint(( BR_SERVER_ENUM, "Double hop to %ws on %ws\n", RemoteComputerName, Network->NetworkName.Buffer)); BrPrint(( BR_CLIENT_OP, "BrNetServerEnum: for client <%ws>, remoting the call to computer <%ws> on network <%ws>\n", ClientName, RemoteComputerName, Network->NetworkName.Buffer )); status = RxNetServerEnum(RemoteComputerName, Network->NetworkName.Buffer, Level, (LPBYTE *)Buffer, PreferedMaximumLength, EntriesRead, TotalEntries, ServerType, Domain, FirstNameToReturn ); BrPrint(( BR_SERVER_ENUM, "Double hop done\n")); if (!LOCK_NETWORK_SHARED (Network)) { try_return(status = NERR_InternalError); } NetworkLocked = TRUE; try_return(status); } } get_local_list: ASSERT (NetworkLocked); if (!ARGUMENT_PRESENT(Domain)) { STRCPY(DomainName, Network->DomainInfo->DomUnicodeDomainName); } // // If we are running on the master browser, we want to retrieve the // local list, merge it with our interim server list, and // return that to the user. // if (Network->Role & ROLE_MASTER) { BrPrint(( BR_CLIENT_OP, "BrNetServerEnum: retrieving local server list as master browser for client <%ws> on network <%ws>\n", ClientName, Network->NetworkName.Buffer )); status = BrRetrieveServerListForMaster(Network, Buffer, EntriesRead, TotalEntries, Level, ServerType, PreferedMaximumLength, LocalListOnly, (LPWSTR) ClientName, DomainName, FirstNameToReturn ); } else { BrPrint(( BR_CLIENT_OP, "BrNetServerEnum: retrieving local server list as backup browser for client <%ws> on network <%ws>\n", ClientName, Network->NetworkName.Buffer )); status = BrRetrieveServerListForBackup(Network, Buffer, EntriesRead, TotalEntries, Level, ServerType, PreferedMaximumLength, FirstNameToReturn ); } try_return(status); try_exit:NOTHING; } finally { #if DBG if (status == NERR_Success || status == ERROR_MORE_DATA) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Returning Browse list for %lx with %ld entries for %ws\n", Domain, Network->NetworkName.Buffer, ServerType, *EntriesRead, ClientName)); } else { BrPrint(( BR_CRITICAL, "%ws: %ws: Failing I_BrowserServerEnum: %ld for client %ws\n", Domain, Network->NetworkName.Buffer, status, ClientName)); } #endif // // If we used the local driver's list to retrieve the list of // domains, free up the buffer. // if (DoubleHopLocalList != NULL) { MIDL_user_free(DoubleHopLocalList); } ASSERT (NetworkLocked); } return status; } VOID TrimServerList( IN DWORD Level, IN OUT LPBYTE *Buffer, IN OUT LPDWORD EntriesRead, IN OUT LPDWORD TotalEntries, IN LPCWSTR FirstNameToReturn ) /*++ Routine Description: This routine trims any name from Buffer that's less than FirstNameToReturn. The routine is implemented by moving the fixed part of the kept entries to the beginning of the allocated buffer. Thay way, the pointer contained in the entries need not be relocated and the original buffer can still be used. Arguments: Level - Level of information in the buffer. Buffer - Pointer to address of buffer to trim. Returns null (and deallocates the buffer) if all the entries are trimmed. EntriesRead - Pointer to number of entries in the buffer. Returns the number of entries remaining in the buffer after triming. TotalEntries - Pointer to number of entries available from server. Returns a number reduced by the number of trimmed entries. FirstNameToReturn - Supplies the name of the first server or domain to return to the caller. If NULL, enumeration begins with the very first entry. Passed name must be the canonical form of the name. Return Value: Returns the error code for the operation. --*/ { DWORD EntryCount; DWORD EntryNumber; DWORD FixedSize; LPBYTE CurrentEntry; // // Early out if there's nothing to do. // if ( FirstNameToReturn == NULL || *FirstNameToReturn == L'\0' || *EntriesRead == 0 ) { return; } // // Compute the size of each entry. // switch (Level) { case 100: FixedSize = sizeof(SERVER_INFO_100); break; case 101: FixedSize = sizeof(SERVER_INFO_101); break; default: NetpAssert( FALSE ); return; } // // Finding the first entry to return // EntryCount = *EntriesRead; for ( EntryNumber=0; EntryNumber< EntryCount; EntryNumber++ ) { LPSERVER_INFO_100 ServerEntry = (LPSERVER_INFO_100)( *Buffer + FixedSize * EntryNumber); // // Found the first entry to return. // if ( STRCMP( ServerEntry->sv100_name, FirstNameToReturn ) >= 0 ) { // // If we're returning the whole list, // simply return. // if ( ServerEntry == (LPSERVER_INFO_100)(*Buffer) ) { return; } // // Copy the remaining entries to the beginning of the buffer. // (Yes, this is an overlapping copy) // RtlMoveMemory( *Buffer, ServerEntry, (*EntriesRead) * FixedSize ); return; } // // Account for skipped entries. // *EntriesRead -= 1; *TotalEntries -= 1; } // // If no entries should be returned, // deallocate the buffer. // NetApiBufferFree( *Buffer ); *Buffer = NULL; ASSERT ( *EntriesRead == 0 ); return; } // TrimServerList // // Context for building a local list of all the Wannish transports. // typedef struct _BR_LOCAL_LIST_CONTEXT { LPWSTR DomainName; DWORD Level; DWORD ServerType; PVOID Buffer; NET_API_STATUS NetStatus; DWORD EntriesRead; DWORD TotalEntries; BOOLEAN AtLeastOneWorked; INTERIM_SERVER_LIST InterimServerList; } BR_LOCAL_LIST_CONTEXT, *PBR_LOCAL_LIST_CONTEXT; NET_API_STATUS BrGetWannishLocalList( IN PNETWORK Network, IN PVOID Context ) /*++ Routine Description: Worker function to get the local list for all WANNISH networks and merge them together. Arguments: Network - The current network to do. Context - Context that the accumulated list is built into. Return Value: NET_API_STATUS - NERR_Success or reason for failure. --*/ { NET_API_STATUS NetStatus; PBR_LOCAL_LIST_CONTEXT LocalListContext = (PBR_LOCAL_LIST_CONTEXT) Context; PVOID Buffer = NULL; DWORD EntriesRead; DWORD TotalEntries; // // If this isn't a wannish network, // ignore it. if ((Network->Flags & NETWORK_WANNISH) == 0 ) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: isn't a wannish network. (ignored)\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer )); return NO_ERROR; } // // Get the local list from this transport. // BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Get local list from wannish network.\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer )); NetStatus = BrGetLocalBrowseList( Network, LocalListContext->DomainName, LocalListContext->Level, LocalListContext->ServerType, &Buffer, &EntriesRead, &TotalEntries); if ( NetStatus != NO_ERROR && NetStatus != ERROR_MORE_DATA ) { BrPrint(( BR_CRITICAL, "%ws: %ws: Get local list from wannish network failed: %ld.\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, NetStatus )); // // If no transport has worked yet, save this as the new default status. // (But keep on going) // if ( !LocalListContext->AtLeastOneWorked ) { LocalListContext->NetStatus = NetStatus; } NetStatus = NO_ERROR; goto Cleanup; } LocalListContext->AtLeastOneWorked = TRUE; LocalListContext->NetStatus = NetStatus; // // If no data was returned, // we're done. // if ( EntriesRead == 0 ) { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Get local list from wannish network got zero entries.\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer )); NetStatus = NO_ERROR; goto Cleanup; } // // If a prior network already returned some entries, // build an interim list to merge both lists into. // if ( LocalListContext->Buffer != NULL ) { NetStatus = MergeServerList( &LocalListContext->InterimServerList, LocalListContext->Level, LocalListContext->Buffer, LocalListContext->EntriesRead, LocalListContext->TotalEntries ); MIDL_user_free(LocalListContext->Buffer); LocalListContext->Buffer = NULL; LocalListContext->EntriesRead = 0; LocalListContext->TotalEntries = 0;; if ( NetStatus != NO_ERROR ) { BrPrint(( BR_CRITICAL, "%ws: %ws: merge local list from wannish network failed: %ld.\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, NetStatus )); goto Cleanup; } } // // If there is an interim server list, // add the new entries to it. // if ( LocalListContext->InterimServerList.TotalEntries != 0 ) { NetStatus = MergeServerList( &LocalListContext->InterimServerList, LocalListContext->Level, Buffer, EntriesRead, TotalEntries ); if ( NetStatus != NO_ERROR ) { BrPrint(( BR_CRITICAL, "%ws: %ws: merge local list from wannish network failed: %ld.\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, NetStatus )); goto Cleanup; } // // If this is the first network to return entries, // simply save the list. // // This saves us from having to convert a list to an interim list just // to have to convert it back in those cases where we have a single wannish // network. // } else { LocalListContext->Buffer = Buffer; LocalListContext->EntriesRead = EntriesRead; LocalListContext->TotalEntries = TotalEntries; Buffer = NULL; } NetStatus = NO_ERROR; Cleanup: if ( Buffer != NULL ) { MIDL_user_free(Buffer); } return NetStatus; } NET_API_STATUS BrRetrieveServerListForMaster( IN PNETWORK Network, IN OUT PVOID *Buffer, OUT PDWORD EntriesRead, OUT PDWORD TotalEntries, IN DWORD Level, IN DWORD ServerType, IN DWORD PreferedMaximumLength, IN BOOLEAN LocalListOnly, IN LPTSTR ClientName, IN LPTSTR DomainName, IN LPCWSTR FirstNameToReturn ) { BOOLEAN GetLocalList = FALSE; NET_API_STATUS status; BOOLEAN EarlyOut = FALSE; // // Determine if it is appropriate that we should early out after retrieving // the list of servers (or domains) from the bowser. // // We will early out on the following critieria: // // 1) If we are requested to retrieve the local list on a Wannish xport // 2) If the transport isn't a wannish transport, or // 3) If this domain isn't our primary domain (in which case it's an // "otherdomain" request). // if (LocalListOnly || STRICMP(DomainName, Network->DomainInfo->DomUnicodeDomainName)) { EarlyOut = TRUE; } else if (!(Network->Flags & NETWORK_WANNISH)) { // // This isn't a wannish transport. We need to see if we've been // master long enough for the driver to have retrieved a reasonable // list. // // The server and domain table will be emptied when the first master // browser timer is run. This will happen 15 minutes after we // become the master, so we will use our services list for the // first 15 minutes the browser is master. // if (ServerType == SV_TYPE_DOMAIN_ENUM) { if (Network->DomainList.EntriesRead == 0) { EarlyOut = TRUE; } } else { if (Network->BrowseTable.EntriesRead == 0) { EarlyOut = TRUE; } } } if (EarlyOut) { GetLocalList = TRUE; } else if (ServerType == SV_TYPE_ALL) { if ((BrCurrentSystemTime() - Network->LastBowserServerQueried) > BrInfo.DriverQueryFrequency ) { GetLocalList = TRUE; } } else if (ServerType == SV_TYPE_DOMAIN_ENUM) { if ((BrCurrentSystemTime() - Network->LastBowserDomainQueried) > BrInfo.DriverQueryFrequency ) { GetLocalList = TRUE; } } else { GetLocalList = TRUE; } if (GetLocalList && !EarlyOut) { // // If we're retriving the list from the driver, and can't early out // this request, this means that we will be merging this server list // with an interim server list. This means that we will be modifying // the network structure, and thus that we need to re-lock the network // structure. // UNLOCK_NETWORK(Network); if (!LOCK_NETWORK(Network)) { return NERR_InternalError; } // // Now re-test to see if another thread came in and retrieved the // list from the driver while we had the network unlocked. If so, // we don't want to get the local list any more. // // Otherwise, we want to update the last query time. // if (ServerType == SV_TYPE_ALL) { if ((BrCurrentSystemTime() - Network->LastBowserServerQueried) > BrInfo.DriverQueryFrequency ) { Network->LastBowserServerQueried = BrCurrentSystemTime(); } else { GetLocalList = FALSE; } } else if (ServerType == SV_TYPE_DOMAIN_ENUM) { if ((BrCurrentSystemTime() - Network->LastBowserDomainQueried) > BrInfo.DriverQueryFrequency ) { Network->LastBowserDomainQueried = BrCurrentSystemTime(); } else { GetLocalList = FALSE; } } else { Network->LastBowserServerQueried = BrCurrentSystemTime(); } } // // If we're supposed to retrieve the local server list, retrieve it. // if (GetLocalList) { DWORD ServerTypeForLocalList; // // Calculate the server type to use when retrieving the list from // the driver. // if (LocalListOnly || (ServerType == SV_TYPE_DOMAIN_ENUM) || !(Network->Flags & NETWORK_WANNISH)) { // // If we are retrieving the local list, or this is a non-wannish // transport, or we are asking for domains, we want to // keep the callers server type. // ServerTypeForLocalList = ServerType; } else { // // This must be a wannish transport, ask for all servers (we know // that we're not asking for domains, since we checked that // above). // // We ask for all the servers because it's possible that a servers // role has changed. // ASSERT (Network->Flags & NETWORK_WANNISH); ServerTypeForLocalList = SV_TYPE_ALL; } BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Get local browse list for %ws\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, ClientName)); // // If this is a call from the Domain Master Browser to get the local list, // and this is a wannish transport, // get the local list on all wannish transports. // if ( LocalListOnly && (Network->Flags & NETWORK_WANNISH) != 0 ) { BR_LOCAL_LIST_CONTEXT LocalListContext; // // Build a context to use to gather the server lists from all // wannish transports // LocalListContext.DomainName = DomainName; LocalListContext.Level = Level; LocalListContext.ServerType = ServerTypeForLocalList; LocalListContext.Buffer = NULL; LocalListContext.NetStatus = NO_ERROR; LocalListContext.EntriesRead = 0; LocalListContext.TotalEntries = 0; LocalListContext.AtLeastOneWorked = FALSE; (VOID) InitializeInterimServerList(&LocalListContext.InterimServerList, NULL, NULL, NULL, NULL); // // Gather the lists // status = BrEnumerateNetworks( BrGetWannishLocalList, &LocalListContext ); if ( status == NO_ERROR ) { // // If multiple networks were found, // pack the interim list. // if ( LocalListContext.InterimServerList.TotalEntries != 0 ) { status = PackServerList( &LocalListContext.InterimServerList, LocalListContext.Level, LocalListContext.ServerType, PreferedMaximumLength, Buffer, EntriesRead, TotalEntries, (LPWSTR)FirstNameToReturn ); // // If just a single network was found, // just use it. // } else { status = LocalListContext.NetStatus; *Buffer = LocalListContext.Buffer; *EntriesRead = LocalListContext.EntriesRead; *TotalEntries = LocalListContext.TotalEntries; } } (VOID) UninitializeInterimServerList( &LocalListContext.InterimServerList ); } else { status = BrGetLocalBrowseList( Network, DomainName, ( EarlyOut ? Level : 101 ), ServerTypeForLocalList, Buffer, EntriesRead, TotalEntries); } // BrPrint(( BR_SERVER_ENUM, "List retrieved. %ld entries, %ld total\n", *EntriesRead, *TotalEntries)); // // If we're supposed to early-out this request (or if there was // an error), do so now. // if (EarlyOut || (status != NERR_Success && status != ERROR_MORE_DATA)) { // // If we're returning an early out list, // truncate the complete list returned from the kernel. // // This saves us from having to modify the kernel interface and untangle // the code above. // if ( status == NERR_Success || status == ERROR_MORE_DATA ) { TrimServerList( Level, (LPBYTE *)Buffer, EntriesRead, TotalEntries, FirstNameToReturn ); } BrPrint(( BR_SERVER_ENUM, "Early out for %ws with %ld servers. Don't merge server list.\n", ClientName, *EntriesRead)); return status; } if (status == NERR_Success || status == ERROR_MORE_DATA) { if (*EntriesRead != 0) { // // Merge the local list with the list we got from the // master or from the domain master. // BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Merge %d entries in server list for %ws \n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, *EntriesRead, ClientName)); status = MergeServerList((ServerType == SV_TYPE_DOMAIN_ENUM ? &Network->DomainList : &Network->BrowseTable), 101, *Buffer, *EntriesRead, *TotalEntries ); } } } // // We've merged the local list into the appropriate interim table, // now free it up. // if (*EntriesRead != 0) { MIDL_user_free(*Buffer); } status = PackServerList((ServerType == SV_TYPE_DOMAIN_ENUM ? &Network->DomainList : &Network->BrowseTable), Level, ServerType, PreferedMaximumLength, Buffer, EntriesRead, TotalEntries, FirstNameToReturn ); return status; } NET_API_STATUS BrRetrieveServerListForBackup( IN PNETWORK Network, IN OUT PVOID *Buffer, OUT PDWORD EntriesRead, OUT PDWORD TotalEntries, IN DWORD Level, IN DWORD ServerType, IN DWORD PreferedMaximumLength, IN LPCWSTR FirstNameToReturn ) { PSERVER_INFO_101 ServerList, ClientServerInfo; ULONG EntriesInList; ULONG TotalEntriesInList; ULONG EntrySize; ULONG BufferSize; LPTSTR BufferEnd; BOOLEAN ReturnWholeList = FALSE; BOOLEAN TrimmingNames; BOOLEAN BufferFull = FALSE; // see bug 427656 // // If we are not running as a master, we want to use our stored // server list to figure out what the client gets. // if (ServerType == SV_TYPE_DOMAIN_ENUM) { ServerList = Network->BackupDomainList; TotalEntriesInList = EntriesInList = Network->TotalBackupDomainListEntries; ReturnWholeList = TRUE; } else { ServerList = Network->BackupServerList; TotalEntriesInList = EntriesInList = Network->TotalBackupServerListEntries; if (ServerType == SV_TYPE_ALL) { ReturnWholeList = TRUE; } } // // Figure out the largest buffer we have to allocate to hold this // server info. // if (Level == 101) { if (PreferedMaximumLength == MAXULONG) { if (ServerType == SV_TYPE_DOMAIN_ENUM) { BufferSize = (sizeof(SERVER_INFO_101) + (CNLEN+1 + CNLEN+1)*sizeof(TCHAR)) * EntriesInList; } else { BufferSize = (sizeof(SERVER_INFO_101) + (CNLEN+1 + LM20_MAXCOMMENTSZ+1)*sizeof(TCHAR)) * EntriesInList; } } else { BufferSize = PreferedMaximumLength; } EntrySize = sizeof(SERVER_INFO_101); } else { if (PreferedMaximumLength == MAXULONG) { BufferSize = (sizeof(SERVER_INFO_100) + (CNLEN+1)*sizeof(TCHAR)) * EntriesInList; } else { BufferSize = PreferedMaximumLength; } EntrySize = sizeof(SERVER_INFO_100); } *Buffer = MIDL_user_allocate(BufferSize); if (*Buffer == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } BufferEnd = (LPTSTR)((ULONG_PTR)*Buffer+BufferSize); ClientServerInfo = *Buffer; *TotalEntries = 0; *EntriesRead = 0; // // While there are still entries to process.... // TrimmingNames = (FirstNameToReturn != NULL && *FirstNameToReturn != L'\0'); while (EntriesInList) { EntriesInList -= 1; // // If this entry is appropriate to be packed, // if ( (ServerList->sv101_type & ServerType) && (!TrimmingNames || STRCMP( ServerList->sv101_name, FirstNameToReturn ) >= 0 ) ) { TrimmingNames = FALSE; // // Indicate one more entry in the list. // *TotalEntries += 1; // // If we can fit this entry before the buffer end, // pack in the information into the buffer. // if ( !BufferFull && (ULONG_PTR)ClientServerInfo+EntrySize <= (ULONG_PTR)BufferEnd) { // // Copy over the platform ID and computer name. // ClientServerInfo->sv101_platform_id = ServerList->sv101_platform_id; ClientServerInfo->sv101_name = ServerList->sv101_name; if (NetpPackString(&ClientServerInfo->sv101_name, (LPBYTE)((PCHAR)ClientServerInfo)+EntrySize, &BufferEnd)) { if (Level == 101) { ClientServerInfo->sv101_version_major = ServerList->sv101_version_major; ClientServerInfo->sv101_version_minor = ServerList->sv101_version_minor; ClientServerInfo->sv101_type = ServerList->sv101_type; ClientServerInfo->sv101_comment = ServerList->sv101_comment; if (NetpPackString(&ClientServerInfo->sv101_comment, (LPBYTE)((PCHAR)ClientServerInfo)+EntrySize, &BufferEnd)) { *EntriesRead += 1; } else { BufferFull = TRUE; } } else { *EntriesRead += 1; } } else { BufferFull = TRUE; } ClientServerInfo = (PSERVER_INFO_101)((PCHAR)ClientServerInfo+EntrySize); } else { // // If we're returning the entire list, we can // early out now, since there's no point in continuing. // if (ReturnWholeList) { *TotalEntries = TotalEntriesInList; break; } BufferFull = TRUE; } } ServerList += 1; } // // If we weren't able to pack all the entries into the list, // return ERROR_MORE_DATA // BrPrint( (BR_CLIENT_OP, "BrRetrieveServerListForBackup: returning from the stored server list for network <%ws>\n", Network->NetworkName.Buffer )); if (*EntriesRead != *TotalEntries) { return ERROR_MORE_DATA; } else { return NERR_Success; } } NET_API_STATUS I_BrowserrResetStatistics ( IN LPTSTR servername OPTIONAL ) { NET_API_STATUS Status = NERR_Success; ULONG BufferSize; // // Perform access validation on the caller. // Status = NetpAccessCheck( BrGlobalBrowserSecurityDescriptor, // Security descriptor BROWSER_CONTROL_ACCESS, // Desired access &BrGlobalBrowserInfoMapping ); // Generic mapping if ( Status != NERR_Success) { BrPrint((BR_CRITICAL, "I_BrowserrResetStatistics failed NetpAccessCheck\n" )); return ERROR_ACCESS_DENIED; } EnterCriticalSection(&BrowserStatisticsLock); NumberOfServerEnumerations = 0; NumberOfDomainEnumerations = 0; NumberOfOtherEnumerations = 0; NumberOfMissedGetBrowserListRequests = 0; // // Reset the driver's statistics as well. // if (!DeviceIoControl(BrDgReceiverDeviceHandle, IOCTL_LMDR_RESET_STATISTICS, NULL, 0, NULL, 0, &BufferSize, NULL)) { // // The API failed, return the error. // Status = GetLastError(); } LeaveCriticalSection(&BrowserStatisticsLock); return Status; } NET_API_STATUS I_BrowserrQueryStatistics ( IN LPTSTR servername OPTIONAL, OUT LPBROWSER_STATISTICS *Statistics ) { NET_API_STATUS Status = NERR_Success; BOWSER_STATISTICS BowserStatistics; ULONG BufferSize; // // Perform access validation on the caller. // Status = NetpAccessCheck( BrGlobalBrowserSecurityDescriptor, // Security descriptor BROWSER_QUERY_ACCESS, // Desired access &BrGlobalBrowserInfoMapping ); // Generic mapping if ( Status != NERR_Success) { BrPrint((BR_CRITICAL, "I_BrowserrQueryStatistics failed NetpAccessCheck\n" )); return ERROR_ACCESS_DENIED; } *Statistics = MIDL_user_allocate(sizeof(BROWSER_STATISTICS)); if (*Statistics == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } EnterCriticalSection(&BrowserStatisticsLock); if (!DeviceIoControl(BrDgReceiverDeviceHandle, IOCTL_LMDR_QUERY_STATISTICS, NULL, 0, &BowserStatistics, sizeof(BowserStatistics), &BufferSize, NULL)) { // // The API failed, return the error. // Status = GetLastError(); } else { if (BufferSize != sizeof(BOWSER_STATISTICS)) { Status = ERROR_INSUFFICIENT_BUFFER; } else { (*Statistics)->StatisticsStartTime = BowserStatistics.StartTime; (*Statistics)->NumberOfServerAnnouncements = BowserStatistics.NumberOfServerAnnouncements; (*Statistics)->NumberOfDomainAnnouncements = BowserStatistics.NumberOfDomainAnnouncements; (*Statistics)->NumberOfElectionPackets = BowserStatistics.NumberOfElectionPackets; (*Statistics)->NumberOfMailslotWrites = BowserStatistics.NumberOfMailslotWrites; (*Statistics)->NumberOfGetBrowserServerListRequests = BowserStatistics.NumberOfGetBrowserServerListRequests; (*Statistics)->NumberOfMissedServerAnnouncements = BowserStatistics.NumberOfMissedServerAnnouncements; (*Statistics)->NumberOfMissedMailslotDatagrams = BowserStatistics.NumberOfMissedMailslotDatagrams; (*Statistics)->NumberOfMissedGetBrowserServerListRequests = BowserStatistics.NumberOfMissedGetBrowserServerListRequests + NumberOfMissedGetBrowserListRequests; (*Statistics)->NumberOfFailedServerAnnounceAllocations = BowserStatistics.NumberOfFailedServerAnnounceAllocations; (*Statistics)->NumberOfFailedMailslotAllocations = BowserStatistics.NumberOfFailedMailslotAllocations; (*Statistics)->NumberOfFailedMailslotReceives = BowserStatistics.NumberOfFailedMailslotReceives; (*Statistics)->NumberOfFailedMailslotWrites = BowserStatistics.NumberOfFailedMailslotWrites; (*Statistics)->NumberOfFailedMailslotOpens = BowserStatistics.NumberOfFailedMailslotOpens; (*Statistics)->NumberOfDuplicateMasterAnnouncements = BowserStatistics.NumberOfDuplicateMasterAnnouncements; (*Statistics)->NumberOfIllegalDatagrams = BowserStatistics.NumberOfIllegalDatagrams; // // Now fill in the local statistics. // (*Statistics)->NumberOfServerEnumerations = NumberOfServerEnumerations; (*Statistics)->NumberOfDomainEnumerations = NumberOfDomainEnumerations; (*Statistics)->NumberOfOtherEnumerations = NumberOfOtherEnumerations; } } LeaveCriticalSection(&BrowserStatisticsLock); return Status; } // // Browser request response cache management logic. // PCACHED_BROWSE_RESPONSE BrLookupAndAllocateCachedEntry( IN PNETWORK Network, IN DWORD ServerType, IN WORD Size, IN DWORD Level, IN LPCWSTR FirstNameToReturn ) /*++ Routine Description: This function will look up (and allocate if appropriate) a cached browse response for this browse. Enter with the Network Locked shared or exclusive. Arguments: IN PNETWORK Network - Network to allocate entry on. IN DWORD ServerType - Server type bits for request. IN WORD Size, - Users buffer size for request. IN WORD Level - Level of request. FirstNameToReturn - Supplies the name of the first domain or server entry to return. The caller can use this parameter to implement a resume handle of sorts by passing the name of the last entry returned on a previous call. (Notice that the specified entry will, also, be returned on this call unless it has since been deleted.) Passed name must be the canonical form of the name. This entry is never NULL. It may be a pointer to an empty string to indicate the enumeration starts at the beginning of the list. Return Value: PCACHED_BROWSE_RESPONSE - NULL or a cached response for the request. --*/ { PLIST_ENTRY entry; PCACHED_BROWSE_RESPONSE response; // // If we have more cached responses than we are allowed, // remove the last entry from the list and free it. // if (Network->NumberOfCachedResponses > BrInfo.NumberOfCachedResponses) { // // We need to release the network and re-acquire it // exclusively, because we use the network lock to protect // enumerations from deletions. // UNLOCK_NETWORK(Network); if (LOCK_NETWORK(Network)) { EnterCriticalSection(&Network->ResponseCacheLock); if (Network->NumberOfCachedResponses > BrInfo.NumberOfCachedResponses) { PLIST_ENTRY LastEntry = RemoveTailList(&Network->ResponseCache); response = CONTAINING_RECORD(LastEntry, CACHED_BROWSE_RESPONSE, Next); Network->NumberOfCachedResponses -= 1; response->Next.Flink = NULL; response->Next.Blink = NULL; // // Free the last cached entry. // BrDestroyCacheEntry( response ); response = NULL; } LeaveCriticalSection(&Network->ResponseCacheLock); } } // // Search the list of responses for this one. // EnterCriticalSection(&Network->ResponseCacheLock); for (entry = Network->ResponseCache.Flink ; entry != &Network->ResponseCache ; entry = entry->Flink ) { response = CONTAINING_RECORD(entry, CACHED_BROWSE_RESPONSE, Next); // // If this response cache entry matches the incoming request, // we can increment the hit count for this entry and return it. // if (response->Level == Level && response->ServerType == ServerType && response->Size == Size && wcscmp( response->FirstNameToReturn, FirstNameToReturn ) == 0) { // // This response exactly matches the request. // // Bump its hit count and move it to the head of the cache. // response->HitCount += 1; response->TotalHitCount += 1; // // Remove this entry from its current location in the list and // move it to the head of the list. // RemoveEntryList(&response->Next); InsertHeadList(&Network->ResponseCache, &response->Next); BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Found cache entry 0x%x/%d/%x H:%d T:%d\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, response->ServerType, response->Level, response->Size, response->HitCount, response->TotalHitCount )); BrPrint( (BR_CLIENT_OP, "BrLookupAndAllocateCachedEntry: returning cached entry for domain <%ws>, network <%ws>\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer )); LeaveCriticalSection(&Network->ResponseCacheLock); return response; } } // // We've walked our entire cache and have been unable to find // a response that matches our request. // // Allocate a new response cache entry and hook it into the cache. // response = BrAllocateResponseCacheEntry(Network, ServerType, Size, Level, FirstNameToReturn ); LeaveCriticalSection(&Network->ResponseCacheLock); return response; } VOID BrAgeResponseCache( IN PNETWORK Network ) /*++ Routine Description: This function will age response cache entries for a network. We scan the response cache, and every entry that has a cached response will be tossed. In addition, any entry that has had less than the cache hit limit number of hits since the past scan will also be removed. Arguments: IN PNETWORK Network - Network to age entries on. Return Value: None. --*/ { PLIST_ENTRY entry; EnterCriticalSection(&Network->ResponseCacheLock); try { for (entry = Network->ResponseCache.Flink ; entry != &Network->ResponseCache ; entry = entry->Flink ) { PCACHED_BROWSE_RESPONSE response = CONTAINING_RECORD(entry, CACHED_BROWSE_RESPONSE, Next); // // If this response didn't have a hit count high enough during // the previous run to justify keeping it around, blow it away. // if (response->HitCount < BrInfo.CacheHitLimit) { response->LowHitCount += 1; } // // If we have CacheHitLimit iterations of low hits, then // flush the entry from the cache. // if (response->LowHitCount > BrInfo.CacheHitLimit) { PLIST_ENTRY nextentry = entry->Blink; BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Flush cache entry for 0x%x/%d/%x H:%d T:%d\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, response->ServerType, response->Level, response->Size, response->HitCount, response->TotalHitCount )); Network->NumberOfCachedResponses -= 1; RemoveEntryList(entry); entry->Flink = NULL; entry->Blink = NULL; BrDestroyCacheEntry(response); entry = nextentry; // // Null out the pointer to make sure we don't use it again. // response = NULL; } else { BrPrint(( BR_SERVER_ENUM, "%ws: %ws: Retain cache entry 0x%x/%d/%x H:%d T:%d\n", Network->DomainInfo->DomUnicodeDomainName, Network->NetworkName.Buffer, response->ServerType, response->Level, response->Size, response->HitCount, response->TotalHitCount )); // // We ALWAYS blow away the response buffer during an age pass. // MIDL_user_free( response->Buffer ); response->Buffer = NULL; // // Reset the hit count for this entry for this pass. // response->HitCount = 0; } } } finally { LeaveCriticalSection(&Network->ResponseCacheLock); } } PCACHED_BROWSE_RESPONSE BrAllocateResponseCacheEntry( IN PNETWORK Network, IN DWORD ServerType, IN WORD Size, IN DWORD Level, IN LPCWSTR FirstNameToReturn ) /*++ Routine Description: This function will allocate a new browse response cache entry. Arguments: IN PNETWORK Network - Network to allocate entry on. IN DWORD ServerType - Server type bits for request. IN WORD Size, - Users buffer size for request. IN WORD Level - Level of request. FirstNameToReturn - FirstNameCached Return Value: PCACHED_BROWSE_RESPONSE - NULL or a cached response for the request. NOTE: This is called with the network response cache locked. --*/ { PCACHED_BROWSE_RESPONSE response; response = MIDL_user_allocate( sizeof( CACHED_BROWSE_RESPONSE ) ); if ( response == NULL ) { return NULL; } // // Flag the information for this response. // response->ServerType = ServerType; response->Size = Size; response->Level = Level; // // Initialize the other fields in the response. // response->Buffer = NULL; response->HitCount = 0; response->TotalHitCount = 0; response->LowHitCount = 0; response->Status = NERR_Success; wcscpy( response->FirstNameToReturn, FirstNameToReturn ); Network->NumberOfCachedResponses += 1; // // We hook this response into the tail of the cache. We do this // because we assume that this request won't be used frequently. If // it is, it will move to the head of the cache naturally. // InsertTailList(&Network->ResponseCache, &response->Next); return response; } NET_API_STATUS BrDestroyCacheEntry( IN PCACHED_BROWSE_RESPONSE CacheEntry ) /*++ Routine Description: This routine destroys an individual response cache entry. Arguments: IN PCACHED_BROWSE_RESPONSE CacheEntry - Entry to destroy. Return Value: NET_API_STATUS - NERR_Success --*/ { ASSERT (CacheEntry->Next.Flink == NULL); ASSERT (CacheEntry->Next.Blink == NULL); if (CacheEntry->Buffer != NULL) { MIDL_user_free(CacheEntry->Buffer); } MIDL_user_free(CacheEntry); return NERR_Success; } NET_API_STATUS BrDestroyResponseCache( IN PNETWORK Network ) /*++ Routine Description: This routine destroys the entire response cache for a supplied network. Arguments: IN PNETWORK Network - Network to allocate entry on. Return Value: NET_API_STATUS - NERR_Success --*/ { while (!IsListEmpty(&Network->ResponseCache)) { PCACHED_BROWSE_RESPONSE cacheEntry; PLIST_ENTRY entry = RemoveHeadList(&Network->ResponseCache); entry->Flink = NULL; entry->Blink = NULL; cacheEntry = CONTAINING_RECORD(entry, CACHED_BROWSE_RESPONSE, Next); Network->NumberOfCachedResponses -= 1; BrDestroyCacheEntry(cacheEntry); } ASSERT (Network->NumberOfCachedResponses == 0); return NERR_Success; } NET_API_STATUS NetrBrowserStatisticsGet ( IN LPTSTR servername OPTIONAL, IN DWORD Level, IN OUT LPBROWSER_STATISTICS_STRUCT InfoStruct ) { // // And return success. // return(NERR_Success); } NET_API_STATUS NetrBrowserStatisticsClear ( IN LPTSTR servername OPTIONAL ) { // // And return success. // return(NERR_Success); } #if DBG NET_API_STATUS I_BrowserrDebugCall ( IN LPTSTR servername OPTIONAL, IN DWORD DebugCode, IN DWORD OptionalValue ) { NET_API_STATUS Status = STATUS_SUCCESS; // // Perform access validation on the caller. // Status = NetpAccessCheck( BrGlobalBrowserSecurityDescriptor, // Security descriptor BROWSER_CONTROL_ACCESS, // Desired access &BrGlobalBrowserInfoMapping ); // Generic mapping if ( Status != NERR_Success) { BrPrint((BR_CRITICAL, "I_BrowserrDebugCall failed NetpAccessCheck\n" )); return ERROR_ACCESS_DENIED; } switch (DebugCode) { case BROWSER_DEBUG_BREAK_POINT: DbgBreakPoint(); break; case BROWSER_DEBUG_DUMP_NETWORKS: BrDumpNetworks(); break; case BROWSER_DEBUG_SET_DEBUG: BrInfo.BrowserDebug |= OptionalValue; BrPrint(( BR_INIT, "Setting browser trace to %lx\n", BrInfo.BrowserDebug)); break; case BROWSER_DEBUG_CLEAR_DEBUG: BrInfo.BrowserDebug &= ~OptionalValue; BrPrint(( BR_INIT, "Setting browser trace to %lx\n", BrInfo.BrowserDebug)); break; case BROWSER_DEBUG_TRUNCATE_LOG: Status = BrTruncateLog(); break; default: BrPrint(( BR_CRITICAL, "Unknown debug callout %lx\n", DebugCode)); DbgBreakPoint(); break; } return Status; } NET_API_STATUS I_BrowserrDebugTrace ( IN LPTSTR servername OPTIONAL, IN LPSTR String ) { NET_API_STATUS Status; // // Perform access validation on the caller. // Status = NetpAccessCheck( BrGlobalBrowserSecurityDescriptor, // Security descriptor BROWSER_CONTROL_ACCESS, // Desired access &BrGlobalBrowserInfoMapping ); // Generic mapping if ( Status != NERR_Success) { BrPrint((BR_CRITICAL, "I_BrowserrDebugTrace failed NetpAccessCheck\n" )); return ERROR_ACCESS_DENIED; } // // Stick the string parameter into the browser log. // BrowserTrace( BR_UTIL, "%s", String); // // And return success. // return(NERR_Success); } #else NET_API_STATUS I_BrowserrDebugCall ( IN LPTSTR servername OPTIONAL, IN DWORD DebugCode, IN DWORD OptionalValue ) { return(ERROR_NOT_SUPPORTED); } NET_API_STATUS I_BrowserrDebugTrace ( IN LPTSTR servername OPTIONAL, IN LPSTR String ) { return(ERROR_NOT_SUPPORTED); } #endif