/*++ Copyright (c) 1987-1991 Microsoft Corporation Module Name: getdcnam.c Abstract: NetGetDCName API Author: Ported from Lan Man 2.0 Environment: User mode only. Contains NT-specific code. Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 09-Feb-1989 (PaulC) Created file, to hold NetGetDCName. 18-Apr-1989 (Ericpe) Implemented NetGetDCName. 30-May-1989 (DannyGl) Reduced DosReadMailslot timeout. 07-Jul-1989 (NealF) Use I_NetNameCanonicalize 27-Jul-1989 (WilliamW) Use WIN3 manifest for WIN3.0 compatibility 03-Jan-1990 (WilliamW) canonicalize domain and use I_NetCompareName 08-Jun-1991 (CliffV) Ported to NT 23-Jul-1991 JohnRo Implement downlevel NetGetDCName. --*/ #include #include #include #ifdef _CAIRO_ #define INC_OLE2 #include #endif // _CAIRO_ #include #include // includes lmcons.h, lmaccess.h, netlogon.h, ssi.h, windef.h #include #include #include #include // IF_DEBUG() #include // NAMETYPE_* defines NetpIsRemote(), NIRFLAG_ equates. #include #include #include // SUPPORTS_* defines #include // SV_TYPE_* defines #include // SERVICE_* defines #include #include // NetpLogon routines #include // NetpIsDomainNameValid #include // Netlogon RPC binding cache init routines #include // NetpKdPrint #include // NetpMemoryFree #include #include // RxNetGetDCName(). #include #include #ifdef _CAIRO_ #include #define SECURITY_WIN32 #include #include #endif // _CAIRO_ // // Maintain a cache of the correct answer for the Primary Domain. // DBGSTATIC CRITICAL_SECTION GlobalDCNameCritSect; DBGSTATIC WCHAR GlobalPrimaryDCName[UNCLEN+1]; DBGSTATIC WCHAR GlobalPrimaryDomainName[DNLEN+1]; #define LOCKDOMAINSEM() EnterCriticalSection( &GlobalDCNameCritSect ) #define FREEDOMAINSEM() LeaveCriticalSection( &GlobalDCNameCritSect ) // end global dll data // // Local definitions used throughout this file. // #define DOMAIN_OTHER 0 #define DOMAIN_PRIMARY 1 #ifdef _CAIRO_ WCHAR PrimaryDomainName[DNLEN+1]; #endif // _CAIRO_ NET_API_STATUS DCNameInitialize( VOID ) /*++ Routine Description: Perform per-process initialization. Arguments: None. Return Value: Status of the operation. --*/ { #ifdef _CAIRO_ WCHAR CairoDomainName[MAX_PATH+1]; ULONG DomainNameLength = MAX_PATH; HRESULT Status; #endif // _CAIRO_ // // Initialize the critsect that protects the DCName cache // InitializeCriticalSection( &GlobalDCNameCritSect ); // // Clear the cache. // GlobalPrimaryDCName[0] = '\0'; GlobalPrimaryDomainName[0] = '\0'; // // Initialize the RPC binding cache. // NlBindingAttachDll(); #ifdef _CAIRO_ // // Initialize local domain name cache // Status = DSGetDomainName(CairoDomainName,&DomainNameLength); if (SUCCEEDED(Status)) { CairoDomainToNtDomain(CairoDomainName,PrimaryDomainName); } else { // // BUGBUG: is this the correct thing to do? // PrimaryDomainName[0] = L'\0'; } #endif // _CAIRO_ return NERR_Success; } VOID DCNameClose( VOID ) /*++ Routine Description: Perform per-process cleanup. Arguments: None. Return Value: None. --*/ { // // Delete the critsect that protects the DCName cache // DeleteCriticalSection( &GlobalDCNameCritSect ); // // Shutdown the RPC binding cache. // NlBindingDetachDll(); } NET_API_STATUS DCNameValidate( IN LPWSTR ServerName, IN LPWSTR DomainName, OUT LPBYTE *Buffer ) /*++ Routine Description: Ensure the named server is the PDC of the named domain. Arguments: ServerName -- Suggest PDC server name (with leading \\'s). DomainName -- Domain that ServerName is PDC of. Buffer - Returns a pointer to an allcated buffer containing the servername of the PDC of the domain. The server name is prefixed by \\. The buffer should be deallocated using NetApiBufferFree. Return Value: NERR_Success -- Server is PDC of specified domain. Other sundry status codes. --*/ { NET_API_STATUS NetStatus; PWKSTA_INFO_100 WkstaInfo100 = NULL; PSERVER_INFO_101 ServerInfo101 = NULL; // // Ensure the specified server if it is a PDC. // // The call to ServerGetInfo below could be replaced with a call // to UserModalsGet at level 1, which would provide a sort of // referral information in case the machine is no longer a DC. // In the case that the machine is no longer the primary, // at this point we could potentially make use of the information // that the machine we just queried sent us about who it thinks // is its primary machine. Using this referral could save us the // broadcast that follows, but who knows how long the referral // chain could get. This could be modified later. // NetStatus = NetServerGetInfo( ServerName, 101, (LPBYTE *)&ServerInfo101 ); if ( NetStatus != NERR_Success ) { goto Cleanup; } if ( (ServerInfo101->sv101_type & SV_TYPE_DOMAIN_CTRL) == 0 ) { NetStatus = NERR_DCNotFound; goto Cleanup; } // // Ensure this PDC is still controlling the domain we think it should be. // NetStatus = NetWkstaGetInfo( ServerName, 100, (LPBYTE * )&WkstaInfo100); if ( NetStatus != NERR_Success ) { goto Cleanup; } if ( I_NetNameCompare( NULL, DomainName, WkstaInfo100->wki100_langroup, NAMETYPE_DOMAIN, 0L) != 0 ) { NetStatus = NERR_DCNotFound; goto Cleanup; } // // Allocate a buffer to return to the caller and fill it in // NetStatus = NetapipBufferAllocate( (wcslen(ServerName) + 1) * sizeof(WCHAR), (LPVOID *) Buffer ); if ( NetStatus != NERR_Success ) { IF_DEBUG( LOGON ) { NetpKdPrint(( "NetGetDCName: cannot allocate response buffer.\n")); } goto Cleanup; } wcscpy((LPWSTR)*Buffer, ServerName ); NetStatus = NERR_Success; Cleanup: if ( ServerInfo101 != NULL ) { NetApiBufferFree( ServerInfo101 ); } if ( WkstaInfo100 != NULL ) { NetApiBufferFree( WkstaInfo100 ); } return NetStatus; } #ifdef _CAIRO_ NET_API_STATUS NET_API_FUNCTION NetGetCairoDCName( IN LPWSTR DomainName, OUT LPBYTE *Buffer ) /*++ Routine Description: If the requested domain is the local domain, finds the domain controller Arguments: DomainName - name of domain (null for primary domain) Buffer - Returns a pointer to an allcated buffer containing the servername of a DC of the domain. The server name is prefixed by \\. The buffer should be deallocated using NetApiBufferFree. Returns: NERR_Success - Found a DC successfully NERR_DCNotFound - the domain requested was the local domain but no DCs could be found ERROR_NO_LOGON_SERVERS - the domain was not the local domain and no DCs could be found --*/ { HRESULT hrRet; NET_API_STATUS NetStatus; PDomainKdcInfo pDomainInfo; ULONG Index; if ((DomainName == NULL) || (!_wcsicmp(DomainName,PrimaryDomainName))) { hrRet = FindDomainController( NULL, 0, // unused address type &pDomainInfo ); } else { return(NERR_DCNotFound); } if (FAILED(hrRet)) { return(hrRet); } for (Index = 0; Index < pDomainInfo->KdcInfo[0].AddressCount ; Index++ ) { if (pDomainInfo->KdcInfo[0].KdcAddress[Index].AddressType == ADDRESS_TYPE_NETBIOS) { NetStatus = NetapipBufferAllocate( pDomainInfo->KdcInfo[0].KdcAddress[Index].cbAddressString + 2 * sizeof(WCHAR), (PVOID *) Buffer); if (NetStatus != NERR_Success) { hrRet = ERROR_NOT_ENOUGH_MEMORY; } else { wcscpy((LPWSTR) *Buffer,L"\\\\"); wcscat((LPWSTR) *Buffer, (LPWSTR) pDomainInfo->KdcInfo[0].KdcAddress[Index].AddressString); hrRet = NERR_Success; } } } FreeContextBuffer(pDomainInfo); return(hrRet); } #endif // _CAIRO_ NET_API_STATUS NET_API_FUNCTION NetGetDCName ( IN LPCWSTR ServerName OPTIONAL, IN LPCWSTR DomainName OPTIONAL, OUT LPBYTE *Buffer ) /*++ Routine Description: Get the name of the primary domain controller for a domain. Arguments: ServerName - name of remote server (null for local) DomainName - name of domain (null for primary) Buffer - Returns a pointer to an allcated buffer containing the servername of the PDC of the domain. The server name is prefixed by \\. The buffer should be deallocated using NetApiBufferFree. Return Value: NERR_Success - Success. Buffer contains PDC name prefixed by \\. NERR_DCNotFound No DC found for this domain. ERROR_INVALID_NAME Badly formed domain name --*/ { NET_API_STATUS NetStatus = 0; // // Points to the actual domain to query. // LPWSTR DomainToQuery; DWORD WhichDomain = DOMAIN_OTHER; DWORD Version; LPWSTR UnicodeComputerName = NULL; LPWSTR PrimaryDomainName = NULL; BOOLEAN IsWorkgroupName; // // API SECURITY - Anyone can call anytime. No code required. // // // Check if API is to be remoted, and handle downlevel case if so. // if ( (ServerName != NULL) && ( ServerName[0] != '\0') ) { TCHAR UncCanonServerName[UNCLEN+1]; DWORD LocalOrRemote; NetStatus = NetpIsRemote( (LPWSTR) ServerName, // uncanon server name & LocalOrRemote, UncCanonServerName, // output: canon NIRFLAG_MAPLOCAL // flags: map null to local name ); if (NetStatus != NERR_Success) { goto Cleanup; } if (LocalOrRemote == ISREMOTE) { // // Do the RPC call with an exception handler since RPC will raise an // exception if anything fails. It is up to us to figure out what // to do once the exception is raised. // NET_REMOTE_TRY_RPC // // Call RPC version of the API. // *Buffer = NULL; NetStatus = NetrGetDCName( (LPWSTR) ServerName, (LPWSTR) DomainName, (LPWSTR *)Buffer ); NET_REMOTE_RPC_FAILED( "NetGetDCName", UncCanonServerName, NetStatus, NET_REMOTE_FLAG_NORMAL, SERVICE_NETLOGON ) // // BUGBUG: Check if it's really a downlevel machine! // NetStatus = RxNetGetDCName( UncCanonServerName, (LPWSTR) DomainName, (LPBYTE *) Buffer // may be allocated ); NET_REMOTE_END goto Cleanup; } // // Must be explicit reference to local machine. Fall through and // handle it. // } // // Validate the DomainName // if (( DomainName != NULL ) && ( DomainName[0] != '\0' )) { if ( !NetpIsDomainNameValid((LPWSTR)DomainName) ) { NetStatus = NERR_DCNotFound; goto Cleanup; } } // // Determine the PrimaryDomainName // NetStatus = NetpGetDomainNameEx( &PrimaryDomainName, &IsWorkgroupName ); if ( NetStatus != NERR_Success ) { IF_DEBUG( LOGON ) { NetpKdPrint(( "NetGetDCName: cannot call NetpGetDomainName: %ld\n", NetStatus)); } goto Cleanup; } // // If the given domain is NULL, the NULL string or matches // the our domain, check for cache validity and make the // query domain our domain. // if ( (DomainName == NULL) || (DomainName[0] == '\0') || (I_NetNameCompare( NULL, (LPWSTR) DomainName, PrimaryDomainName, NAMETYPE_DOMAIN, 0L) == 0) ) { // // if the current primary domain is not the same as the one we // have cached, refresh the one in the cache and void the cached // primary DC name. // LOCKDOMAINSEM(); if (I_NetNameCompare( NULL, PrimaryDomainName, GlobalPrimaryDomainName, NAMETYPE_DOMAIN, 0L) != 0 ) { wcsncpy( GlobalPrimaryDomainName, PrimaryDomainName, DNLEN); GlobalPrimaryDomainName[DNLEN] = '\0'; GlobalPrimaryDCName[0] = '\0'; } FREEDOMAINSEM(); // // Remember which domain to query. // DomainToQuery = PrimaryDomainName; WhichDomain = DOMAIN_PRIMARY; // // This is a request on some non-NULL other domain. // } else { DomainToQuery = (LPWSTR) DomainName; } // // If the query domain is the primary domain AND // the primary DC name is cached // request the named DC to confirm that it is still the DC. // if ( WhichDomain == DOMAIN_PRIMARY ) { LOCKDOMAINSEM(); if (GlobalPrimaryDCName[0] != '\0') { WCHAR CachedPrimaryDCName[UNCLEN+1]; wcscpy(CachedPrimaryDCName, GlobalPrimaryDCName); // Don't keep the cache locked while we validate FREEDOMAINSEM(); NetStatus = DCNameValidate( CachedPrimaryDCName, DomainToQuery, Buffer ); if ( NetStatus == NERR_Success ) { goto Cleanup; } LOCKDOMAINSEM(); GlobalPrimaryDCName[0] = '\0'; } FREEDOMAINSEM(); } // // If we get here, it means that we need to broadcast to find the name // of the DC for the given domain, if there is one. DomainToQuery // points to the name of the domain to ask about. First we open a mailslot // to get the response. We send the request and listen for the response. // // // Pick out the computername from the Workstation information // NetStatus = NetpGetComputerName( &UnicodeComputerName ); if ( NetStatus != NERR_Success ) { IF_DEBUG( LOGON ) { NetpKdPrint(( "NetGetDCName: cannot call NetpGetComputerName: %ld\n", NetStatus)); } goto Cleanup; } // // Broadcast to the domain to get the primary DC name. // // If we're querying our primary domain, // we know this isn't a lanman domain. So we can optimize. // If this machine is a member of a workgroup, // don't optimize since there might be a LANMAN PDC in the domain. // NetStatus = NetpLogonGetDCName( UnicodeComputerName, DomainToQuery, (WhichDomain == DOMAIN_PRIMARY && !IsWorkgroupName) ? NETLOGON_PRIMARY_DOMAIN : 0, (LPWSTR *)Buffer, &Version ); if ( NetStatus == NERR_Success ) { goto CacheIt; } IF_DEBUG( LOGON ) { NetpKdPrint(("NetGetDCName: Error from NetpLogonGetDCName: %ld\n", NetStatus)); } switch (NetStatus) { case ERROR_ACCESS_DENIED: case ERROR_BAD_NETPATH: case NERR_NetNotStarted: case NERR_WkstaNotStarted: case NERR_ServerNotStarted: case NERR_BrowserNotStarted: case NERR_ServiceNotInstalled: case NERR_BadTransactConfig: goto Cleanup; } // // If none of the methods have succeeded, // Just return DcNotFound // NetStatus = NERR_DCNotFound; goto Cleanup; // // Cache the response. // CacheIt: if ( WhichDomain == DOMAIN_PRIMARY ) { LOCKDOMAINSEM(); wcsncpy(GlobalPrimaryDCName, (LPWSTR)*Buffer, UNCLEN); GlobalPrimaryDCName[UNCLEN] = '\0'; FREEDOMAINSEM(); } NetStatus = NERR_Success; Cleanup: // // Cleanup all locally used resources // if ( PrimaryDomainName != NULL ) { NetApiBufferFree( PrimaryDomainName ); } if ( UnicodeComputerName != NULL ) { NetApiBufferFree( UnicodeComputerName ); } return NetStatus; } NET_API_STATUS NET_API_FUNCTION NetGetAnyDCName ( IN LPCWSTR ServerName OPTIONAL, IN LPCWSTR DomainName OPTIONAL, OUT LPBYTE *Buffer ) /*++ Routine Description: Get the name of the any domain controller for a domain that is directly trusted by ServerName. If ServerName is a standalone Windows NT Workstation or standalone Windows NT Server, no DomainName is valid. If ServerName is a Windows NT Workstation that is a member of a domain or a Windows NT Server member server, the DomainName must the the domain ServerName is a member of. If ServerName is a Windows NT Server domain controller, the DomainName must be one of the domains trusted by the domain the server is a controller for. The domain controller found is guaranteed to have been up at one point during this API call. Arguments: ServerName - name of remote server (null for local) DomainName - name of domain (null for primary domain) Buffer - Returns a pointer to an allcated buffer containing the servername of a DC of the domain. The server name is prefixed by \\. The buffer should be deallocated using NetApiBufferFree. Return Value: ERROR_SUCCESS - Success. Buffer contains DC name prefixed by \\. ERROR_NO_LOGON_SERVERS - No DC could be found ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain. ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is broken. ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is broken or the password is broken. ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper domain controller of the specified domain. --*/ { NET_API_STATUS NetStatus; #ifdef _CAIRO_ // // Try a Cairo domain first // NetStatus = NetGetCairoDCName( (LPWSTR) DomainName,Buffer); if (NetStatus != NERR_DCNotFound) { return(NetStatus); } #endif // _CAIRO_ // // Do the RPC call with an exception handler since RPC will raise an // exception if anything fails. It is up to us to figure out what // to do once the exception is raised. // RpcTryExcept { *Buffer = NULL; // Force RPC to allocate // // Call RPC version of the API. // NetStatus = NetrGetAnyDCName( (LPWSTR) ServerName, (LPWSTR) DomainName, (LPWSTR *) Buffer ); } RpcExcept( EXCEPTION_EXECUTE_HANDLER ) { NetStatus = RpcExceptionCode(); } RpcEndExcept; IF_DEBUG( LOGON ) { NetpKdPrint(("NetGetAnyDCName rc = %lu 0x%lx\n", NetStatus, NetStatus)); } return NetStatus; }