Leaked source code of windows server 2003
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.
 
 
 
 
 
 

814 lines
20 KiB

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
domain.cxx
Abstract:
Routines implementing a cache of domains and the DC for that domain.
Author:
Cliff Van Dyke (cliffv) 29-June-2001
--*/
#include "pch.hxx"
#include <dsgetdc.h>
#include <lmcons.h>
#include <lmapibuf.h>
#include <windns.h>
//
// Structure definitions
//
// A cached domain
//
typedef struct _AZP_DOMAIN {
//
// Link to the next domain for this AzAuthorizationStore
// Access is serialized by the AzAuthorizationStore->DomainCritSect
//
LIST_ENTRY Next;
//
// Reference count in this structure
// Access is serialized by Interlocked increment/decrement
//
LONG ReferenceCount;
//
// Name of the domain represented by this structure
// Access is serialized by the AZP_DOMAIN->DomainCritSect
// In the case of InitializeFromName we do not have the DNS domain name.
// The boolean is used to distinguish the two cases.
//
AZP_STRING DomainName;
BOOLEAN IsDnsDomainName;
//
// A DCs in that domain
// Access is serialized by the AZP_DOMAIN->DomainCritSect
//
PAZP_DC Dc;
//
// A boolean indicating that the domain is down and the time when it went down.
// Access is serialized by the AZP_DOMAIN->DomainCritSect
//
BOOLEAN DomainIsDown;
LARGE_INTEGER DomainDownStartTime;
SAFE_CRITICAL_SECTION DomainCritSect;
} AZP_DOMAIN, *PAZP_DOMAIN;
PVOID
AzpReferenceDomain(
IN PAZP_AZSTORE AzAuthorizationStore,
IN LPWSTR DomainName,
IN BOOLEAN IsDnsDomainName
)
/*++
Routine Description:
This routine finds the domain structure for a domain. If none exists, it is allocated.
Arguments:
AzAuthorizationStore - The AzAuthorizationStore object for the authz policy database.
DomainName - Name of the domain being referenced.
IsDnsDomainName - Whether this is really a DNS Domain name or NetBios Name.
When we have a token avalaible we have the DNS Domain name. If not, in
the case in which we have initialized the context from Name we only
have a NetBios name.
Return Value:
Returns a pointer to the referenced domain object. The pointer must be dereference by
calling AzpDereferenceDomain.
NULL: memory could not be allocated for the domain.
--*/
{
NTSTATUS Status;
PAZP_DOMAIN Domain = NULL;
PLIST_ENTRY ListEntry;
AZP_STRING DomainNameString;
//
// Initialization
//
ASSERT( AzAuthorizationStore->GenericObject.ObjectType == OBJECT_TYPE_AZAUTHSTORE );
ASSERT( AzAuthorizationStore->DomainCritSectInitialized );
AzpInitString( &DomainNameString, DomainName );
SafeEnterCriticalSection( &AzAuthorizationStore->DomainCritSect );
//
// Walk the list finding an existing domain.
//
for ( ListEntry = AzAuthorizationStore->Domains.Flink ;
ListEntry != &AzAuthorizationStore->Domains ;
ListEntry = ListEntry->Flink) {
Domain = CONTAINING_RECORD( ListEntry,
AZP_DOMAIN,
Next );
if ( AzpEqualStrings( &DomainNameString, &Domain->DomainName )) {
//
// Reference the domain
//
InterlockedIncrement( &Domain->ReferenceCount );
AzPrint(( AZD_DOMREF, "0x%lx %ws (%ld): Domain ref\n", Domain, Domain->DomainName.String, Domain->ReferenceCount ));
//
// Move it to the front of the list
//
RemoveEntryList( &Domain->Next );
InsertHeadList( &AzAuthorizationStore->Domains, &Domain->Next );
break;
}
Domain = NULL;
}
//
// If we didn't find one,
// allocate one.
//
if ( Domain == NULL ) {
Domain = (PAZP_DOMAIN) AzpAllocateHeap( sizeof(AZP_DOMAIN) + DomainNameString.StringSize, "DMDOM" );
if ( Domain != NULL ) {
//
// Initialize the domain
//
RtlZeroMemory( Domain, sizeof(*Domain) );
//
// Initialize the client context critical section
//
Status = SafeInitializeCriticalSection( &Domain->DomainCritSect, SAFE_DOMAIN );
if ( !NT_SUCCESS( Status )) {
AzpFreeHeap( Domain );
Domain = NULL;
} else {
RtlCopyMemory( (Domain+1),
DomainNameString.String,
DomainNameString.StringSize );
Domain->DomainName = DomainNameString;
Domain->DomainName.String = (LPWSTR)(Domain+1);
Domain->IsDnsDomainName = IsDnsDomainName;
// One to return to the caller.
// One for being in the linked list
Domain->ReferenceCount = 2;
InsertHeadList( &AzAuthorizationStore->Domains, &Domain->Next );
}
}
}
SafeLeaveCriticalSection( &AzAuthorizationStore->DomainCritSect );
return (PVOID) Domain;
}
VOID
AzpDereferenceDomain(
IN PVOID DomainHandle
)
/*++
Routine Description:
This routine decrements the reference count on the domain object.
If the reference count reaches zero, the object is deleted.
Arguments:
DomainHandle - Handle to the domain to dereference
Return Value:
None.
--*/
{
PAZP_DOMAIN Domain = (PAZP_DOMAIN) DomainHandle;
ULONG RefCount;
//
// Decrement the reference count
//
RefCount = InterlockedDecrement( &Domain->ReferenceCount );
AzPrint(( AZD_DOMREF, "0x%lx %ws (%ld): Domain deref\n", Domain, Domain->DomainName.String, Domain->ReferenceCount ));
//
// If the object is no longer referenced,
// delete it.
//
if ( RefCount == 0 ) {
if ( Domain->Dc != NULL ) {
AzpDereferenceDc( Domain->Dc );
}
SafeDeleteCriticalSection( &Domain->DomainCritSect );
AzpFreeHeap( Domain );
}
}
VOID
AzpUnlinkDomains(
IN PAZP_AZSTORE AzAuthorizationStore
)
/*++
Routine Description:
This routine unlinks all of the domains from the AzAuthorizationStore.
Arguments:
AzAuthorizationStore - The AzAuthorizationStore object for the authz policy database.
Return Value:
None.
--*/
{
//
// Initialization
//
ASSERT( AzAuthorizationStore->GenericObject.ObjectType == OBJECT_TYPE_AZAUTHSTORE );
ASSERT( AzAuthorizationStore->DomainCritSectInitialized );
SafeEnterCriticalSection( &AzAuthorizationStore->DomainCritSect );
//
// Free the list of domains
//
while ( !IsListEmpty( &AzAuthorizationStore->Domains ) ) {
PLIST_ENTRY ListEntry;
PAZP_DOMAIN Domain;
//
// Remove the entry from the list
//
ListEntry = RemoveHeadList( &AzAuthorizationStore->Domains );
Domain = CONTAINING_RECORD( ListEntry,
AZP_DOMAIN,
Next );
ASSERT( Domain->ReferenceCount == 1 );
AzpDereferenceDomain( Domain );
}
SafeLeaveCriticalSection( &AzAuthorizationStore->DomainCritSect );
}
DWORD
AzpLdapErrorToWin32Error(
IN ULONG LdapStatus
)
/*++
Routine Description:
Map Ldap Error to Win 32 error.
Be a little bit more specific than LdapMapErrorToWin32
Arguments:
LdapStatus - LdapStatus code to map.
Return Value:
Corresponding win 32 status code
--*/
{
//
// Return a consistent status code for DC down
//
switch ( LdapStatus ) {
case LDAP_SERVER_DOWN :
case LDAP_UNAVAILABLE :
case LDAP_BUSY:
return ERROR_NO_SUCH_DOMAIN;
default:
return LdapMapErrorToWin32( LdapStatus );
}
}
DWORD
AzpAllocateDc(
IN PAZP_STRING DcName,
OUT PAZP_DC *RetDc
)
/*++
Routine Description:
This routine allocates a PAZP_DC structure and binds to the DC
Arguments:
DcName - Name of the Dc to allocate the structure for
RetDc - Returns a pointer to a structure representing a DC.
The caller should dereference this structure by calling AzpDereferenceDc
Return Value:
Status of the operation:
NO_ERROR: a DcName has been returned. The caller should try the named DC. If the DC is
expected to be down, the caller should call this routine again.
ERROR_NO_SUCH_DOMAIN: No DC could be found.
Others: resource errors. etc.
--*/
{
DWORD WinStatus;
PAZP_DC Dc = NULL;
ULONG LdapStatus;
LDAP *LdapHandle = NULL;
//
// Initialize the LDAP connection
//
LdapHandle = ldap_init( DcName->String, LDAP_PORT );
if ( LdapHandle == NULL ) {
LdapStatus = LdapGetLastError();
AzPrint(( AZD_ACCESS,
"AzpAllocateDc: ldap_init failed on %ws: %ld: %s\n",
DcName->String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
WinStatus = AzpLdapErrorToWin32Error( LdapStatus );
goto Cleanup;
}
//
// Set our default options
//
WinStatus = AzpADSetDefaultLdapOptions( LdapHandle, DcName->String );
if (WinStatus != NO_ERROR)
{
AzPrint(( AZD_AD,
"AzpAllocateDc: AzpADSetDefaultLdapOptions failed on %ws: %ld\n",
DcName->String,
WinStatus
));
goto Cleanup;
}
//
// Bind to the DC
//
LdapStatus = ldap_bind_s( LdapHandle,
NULL, // No DN of account to authenticate as
NULL, // Default credentials
LDAP_AUTH_NEGOTIATE );
if ( LdapStatus != LDAP_SUCCESS ) {
AzPrint(( AZD_ACCESS,
"AzpAllocateDc: ldap_bind failed on %ws: %ld: %s\n",
DcName->String,
LdapStatus,
ldap_err2stringA( LdapStatus )));
WinStatus = AzpLdapErrorToWin32Error(LdapStatus);
goto Cleanup;
}
//
// Allocate the structure
//
Dc = (PAZP_DC) AzpAllocateHeap( sizeof(AZP_DC) + DcName->StringSize, "DMDC" );
if ( Dc == NULL ) {
WinStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
//
// Fill it in
//
RtlCopyMemory( (Dc+1),
DcName->String,
DcName->StringSize );
Dc->DcName = *DcName;
Dc->DcName.String = (LPWSTR)(Dc+1);
Dc->LdapHandle = LdapHandle;
LdapHandle = NULL;
// One to return to the caller.
Dc->ReferenceCount = 1;
AzPrint(( AZD_DOMREF, "0x%lx %ws (%ld): DC Allocaote\n", Dc, Dc->DcName.String, Dc->ReferenceCount ));
//
// Return it to the caller
//
*RetDc = Dc;
Dc = NULL;
WinStatus = NO_ERROR;
Cleanup:
if ( LdapHandle != NULL ) {
LdapStatus = ldap_unbind( LdapHandle );
ASSERT( LdapStatus == LDAP_SUCCESS );
}
return WinStatus;
}
DWORD
AzpGetDc(
IN PAZP_AZSTORE AzAuthorizationStore,
IN PVOID DomainHandle,
IN OUT PULONG Context,
OUT PAZP_DC *RetDc
)
/*++
Routine Description:
This routine returns the DC for the domain represented by DomainHandle.
Arguments:
AzAuthorizationStore - The AzAuthorizationStore object for the authz policy database.
DomainHandle - A handle to the domain to find a DC for
Context - Specifies the context indicating how hard this routine has tried to find
a DC. The caller should call the routine in a loop. On the first call, the caller
should pass in a pointer DWORD set to zero. On subsequent calls, the caller should
pass in the value returned on the previous pass.
RetDc - Returns a pointer to a structure representing a DC.
The caller should dereference this structure by calling AzpDereferenceDc
Return Value:
Status of the operation:
NO_ERROR: a DcName has been returned. The caller should try the named DC. If the DC is
expected to be down, the caller should call this routine again.
ERROR_NO_SUCH_DOMAIN: No DC could be found.
Others: resource errors. etc.
--*/
{
DWORD WinStatus;
PAZP_DOMAIN Domain = (PAZP_DOMAIN) DomainHandle;
ULONG DsGetDcFlags;
PDOMAIN_CONTROLLER_INFO DomainControllerInfo = NULL;
AZP_STRING CapturedString;
PAZP_DC Dc = NULL;
PAZP_DC TempDc;
//
// Initialization
//
ASSERT( AzAuthorizationStore->GenericObject.ObjectType == OBJECT_TYPE_AZAUTHSTORE );
ASSERT( AzAuthorizationStore->DomainCritSectInitialized );
AzpInitString( &CapturedString, NULL );
SafeEnterCriticalSection( &Domain->DomainCritSect );
*RetDc = NULL;
//
// If the domain is down,
// check to see if it is time to try again.
//
if ( Domain->DomainIsDown ) {
//
// If we haven't waited long enough,
// fail immediately.
//
if ( !AzpTimeHasElapsed( &Domain->DomainDownStartTime,
AzAuthorizationStore->DomainTimeout ) ) {
WinStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// If we have waited long enough,
// try again to find a DC.
//
if ( Domain->Dc != NULL ) {
AzpDereferenceDc( Domain->Dc ); // No use trying this stale DC
Domain->Dc = NULL;
}
Domain->DomainIsDown = FALSE;
}
//
// Loop through the various states internally so our caller doesn't need to
//
for ( ; *Context <= 2 ; (*Context)++ ) {
//
// If this is the first call,
// return the cached DC name.
//
if ( *Context == 0 ) {
//
// If there is no cached value,
// continue the loop to look harder.
//
if ( Domain->Dc == NULL ) {
continue;
}
//
// On the second call, use DsGetDcName.
// On the third call, use DsGetDcName withforce.
//
} else {
if (Domain->IsDnsDomainName) {
DsGetDcFlags = DS_IS_DNS_NAME | DS_IP_REQUIRED;
} else {;
//
// Make sure that this is NT5 domain or higher.
//
DsGetDcFlags = DS_IS_FLAT_NAME | DS_IP_REQUIRED |
DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME;
}
if ( *Context == 2 ) {
DsGetDcFlags |= DS_FORCE_REDISCOVERY;
}
//
// Free the values from the previous iteration
//
if ( DomainControllerInfo != NULL ) {
NetApiBufferFree( DomainControllerInfo );
DomainControllerInfo = NULL;
}
if ( Dc != NULL ) {
AzpDereferenceDc( Dc );
Dc = NULL;
}
AzpFreeString( &CapturedString );
//
// Find a DC in the user's account domain
//
WinStatus = DsGetDcName(
NULL,
Domain->DomainName.String,
NULL, // No guid
NULL, // No site,
DsGetDcFlags,
&DomainControllerInfo );
if ( WinStatus != NO_ERROR ) {
//
// If the DC is down,
// DsGetDcName never lies,
// but map obnoxious status codes to a more likeable one
//
if ( WinStatus == WSAEHOSTUNREACH ||
WinStatus == ERROR_NO_SUCH_DOMAIN ) {
break;
}
goto Cleanup;
}
//
// Capture the DC name into a string structure.
//
AzpFreeString( &CapturedString );
WinStatus = AzpCaptureString( &CapturedString,
DomainControllerInfo->DomainControllerName + 2,
DNS_MAX_NAME_LENGTH,
FALSE ); // Null not OK
if ( WinStatus != NO_ERROR ) {
goto Cleanup;
}
//
// If the DC name is the same as the previous name,
// continue looping to try harder to find a DC.
//
if ( Domain->Dc != NULL &&
AzpEqualStrings( &CapturedString, &Domain->Dc->DcName) ) {
WinStatus = ERROR_NO_SUCH_DOMAIN;
continue;
}
//
// Allocate a structure representing the DC
//
WinStatus = AzpAllocateDc( &CapturedString,
&Dc );
if ( WinStatus != NO_ERROR ) {
if ( WinStatus == ERROR_NO_SUCH_DOMAIN ) {
continue;
}
goto Cleanup;
}
//
// Swap the old/new Dcs
//
TempDc = Domain->Dc;
Domain->Dc = Dc;
Dc = TempDc;
}
//
// Return the found DC to the caller.
//
ASSERT( Domain->Dc != NULL );
ASSERT( Domain->Dc->DcName.StringSize != 0 );
InterlockedIncrement( &Domain->Dc->ReferenceCount );
AzPrint(( AZD_DOMREF, "0x%lx %ws (%ld): DC ref\n", Domain->Dc, Domain->Dc->DcName.String, Domain->Dc->ReferenceCount ));
*RetDc = Domain->Dc;
WinStatus = NO_ERROR;
goto Cleanup;
}
//
// No DC can be found via any mechanism
// Set the negative cache to indicate so.
//
Domain->DomainIsDown = TRUE;
GetSystemTimeAsFileTime( (PFILETIME)&Domain->DomainDownStartTime );
WinStatus = ERROR_NO_SUCH_DOMAIN;
Cleanup:
if ( DomainControllerInfo != NULL ) {
NetApiBufferFree( DomainControllerInfo );
}
AzpFreeString( &CapturedString );
if ( Dc != NULL ) {
AzpDereferenceDc( Dc );
Dc = NULL;
}
SafeLeaveCriticalSection( &Domain->DomainCritSect );
return WinStatus;
}
VOID
AzpDereferenceDc(
IN PAZP_DC Dc
)
/*++
Routine Description:
This routine decrements the reference count on the DC object.
If the reference count reaches zero, the object is deleted.
Arguments:
Dc - Pointer to the DC to dereference
Return Value:
None.
--*/
{
ULONG RefCount;
//
// Decrement the reference count
//
RefCount = InterlockedDecrement( &Dc->ReferenceCount );
AzPrint(( AZD_DOMREF, "0x%lx %ws (%ld): DC deref\n", Dc, Dc->DcName.String, Dc->ReferenceCount ));
//
// If the object is no longer referenced,
// delete it.
//
if ( RefCount == 0 ) {
if ( Dc->LdapHandle != NULL ) {
ULONG LdapStatus;
LdapStatus = ldap_unbind( Dc->LdapHandle );
ASSERT( LdapStatus == LDAP_SUCCESS );
}
AzpFreeHeap( Dc );
}
}