windows-nt/Source/XPSP1/NT/net/ias/providers/ntuser/ldap/ntdomain.cpp
2020-09-26 16:20:57 +08:00

366 lines
8 KiB
C++

///////////////////////////////////////////////////////////////////////////////
//
// FILE
//
// ntdomain.cpp
//
// SYNOPSIS
//
// Defines the clas NTDomain.
//
// MODIFICATION HISTORY
//
// 05/07/1998 Original version.
// 06/23/1998 Changes to DCLocator. Use ntldap constants.
// 07/13/1998 Clean up header file dependencies.
// 02/18/1999 Connect by DNS name not address.
// 03/10/1999 Cache mixed-mode and native-mode connections.
// 03/12/1999 Do not perform I/O from constructor.
// 04/14/1999 Specify domain and server when opening a connection.
// 09/14/1999 Always specify timeout for LDAP searches.
//
///////////////////////////////////////////////////////////////////////////////
#include <ias.h>
#include <iasutil.h>
#include <iaslsa.h>
#include <iasntds.h>
#include <lm.h>
#include <ldapcxn.h>
#include <limits.h>
#include <ntdomain.h>
//////////
// Attributes of interest.
//////////
const WCHAR NT_MIXED_DOMAIN[] = L"nTMixedDomain";
//////////
// Default search filter.
//////////
const WCHAR ANY_OBJECT[] = L"(objectclass=*)";
//////////
// Domain attributes that we need.
//////////
const PCWSTR DOMAIN_ATTRS[] = {
NT_MIXED_DOMAIN,
NULL
};
//////////
// Utility function for getting the current system time as a 64-bit integer.
//////////
inline DWORDLONG GetSystemTimeAsDWORDLONG() throw ()
{
ULARGE_INTEGER ft;
GetSystemTimeAsFileTime((LPFILETIME)&ft);
return ft.QuadPart;
}
//////////
// Number of 100 nsec intervals in one second.
//////////
const DWORDLONG ONE_SECOND = 10000000ui64;
//////////
// Defaults for poll interval and retry interval.
//////////
DWORDLONG NTDomain::pollInterval = 60 * 60 * ONE_SECOND;
DWORDLONG NTDomain::retryInterval = 1 * 60 * ONE_SECOND;
inline NTDomain::NTDomain(PWSTR domainName)
: refCount(1),
name(domainName),
mode(MODE_UNKNOWN),
connection(NULL),
status(NO_ERROR),
expiry(0)
{ }
inline NTDomain::~NTDomain()
{
if (connection) { connection->Release(); }
delete[] name;
}
inline BOOL NTDomain::isExpired() throw ()
{
return GetSystemTimeAsDWORDLONG() >= expiry;
}
inline BOOL NTDomain::isConnected() throw ()
{
if (connection && connection->isDisabled())
{
closeConnection();
}
return connection != NULL;
}
void NTDomain::Release() throw ()
{
if (!InterlockedDecrement(&refCount))
{
delete this;
}
}
DWORD NTDomain::getConnection(LDAPConnection** cxn) throw ()
{
Lock();
// Is it time to try for a new connection ?
if (!isConnected() && isExpired())
{
findServer();
}
// Return the current connection ...
if (*cxn = connection) { (*cxn)->AddRef(); }
// ... and status to the caller.
DWORD retval = status;
Unlock();
return retval;
}
NTDomain::Mode NTDomain::getMode() throw ()
{
Lock();
if (isExpired())
{
if (isConnected())
{
readDomainMode();
}
else
{
findServer();
}
}
Mode retval = mode;
Unlock();
return retval;
}
NTDomain* NTDomain::createInstance(PCWSTR name) throw ()
{
// We copy the domain name here, so that we don't have to throw an
// exception from the constructor.
PWSTR nameCopy = ias_wcsdup(name);
if (!nameCopy) { return NULL; }
return new (std::nothrow) NTDomain(nameCopy);
}
void NTDomain::openConnection(
PCWSTR domain,
PCWSTR server
) throw ()
{
closeConnection();
IASTracePrintf("Opening LDAP connection to %S.", server);
status = LDAPConnection::createInstance(
domain,
server,
&connection
);
if (status == ERROR_ACCESS_DENIED)
{
IASTraceString("Access denied -- purging Kerberos ticket cache.");
IASPurgeTicketCache();
IASTracePrintf("Retrying LDAP connection to %S.", server);
status = LDAPConnection::createInstance(
domain,
server,
&connection
);
}
if (status == NO_ERROR) { readDomainMode(); }
if (status == NO_ERROR)
{
IASTraceString("LDAP connect succeeded.");
}
else
{
IASTraceFailure("LDAP connect", status);
}
}
void NTDomain::closeConnection() throw ()
{
if (connection)
{
connection->Release();
connection = NULL;
}
expiry = 0;
}
void NTDomain::findServer() throw ()
{
// First try to get a DC from the cache.
PDOMAIN_CONTROLLER_INFO dci1 = NULL;
status = IASGetDcName(
name,
DS_DIRECTORY_SERVICE_PREFERRED,
&dci1
);
if (status == NO_ERROR)
{
if (dci1->Flags & DS_DS_FLAG)
{
openConnection(
dci1->DomainName,
dci1->DomainControllerName + 2
);
}
else
{
// No DS. We'll treat this as if IASGetDcName failed.
NetApiBufferFree(dci1);
dci1 = NULL;
status = ERROR_DS_NOT_INSTALLED;
}
}
// If the cached DC failed, try again with the force flag.
if (status != NO_ERROR)
{
PDOMAIN_CONTROLLER_INFO dci2;
DWORD err = IASGetDcName(
name,
DS_DIRECTORY_SERVICE_PREFERRED |
DS_FORCE_REDISCOVERY,
&dci2
);
if (err == NO_ERROR)
{
if (dci2->Flags & DS_DS_FLAG)
{
// Don't bother connecting unless this is a different DC than we
// tried above.
if (!dci1 ||
wcscmp(
dci1->DomainControllerName,
dci2->DomainControllerName
))
{
openConnection(
dci2->DomainName,
dci2->DomainControllerName + 2
);
}
}
else
{
status = ERROR_DS_NOT_INSTALLED;
}
NetApiBufferFree(dci2);
}
else
{
status = err;
}
}
NetApiBufferFree(dci1);
/////////
// Process the result of our 'find'.
/////////
if (status == ERROR_DS_NOT_INSTALLED)
{
mode = MODE_NT4;
expiry = GetSystemTimeAsDWORDLONG() + pollInterval;
}
else if (status != NO_ERROR)
{
expiry = GetSystemTimeAsDWORDLONG() + retryInterval;
}
else if (mode == MODE_NATIVE)
{
expiry = _UI64_MAX;
}
else
{
// mode == MODE_MIXED
expiry = GetSystemTimeAsDWORDLONG() + pollInterval;
}
}
void NTDomain::readDomainMode() throw ()
{
LDAPMessage* res = NULL;
ULONG ldapError = ldap_search_ext_sW(
*connection,
const_cast<PWCHAR>(connection->getBase()),
LDAP_SCOPE_BASE,
const_cast<PWCHAR>(ANY_OBJECT),
const_cast<PWCHAR*>(DOMAIN_ATTRS),
0,
NULL,
NULL,
&LDAPConnection::SEARCH_TIMEOUT,
0,
&res
);
// We have to check two error codes.
if (ldapError == LDAP_SUCCESS)
{
ldapError = res->lm_returncode;
}
if (ldapError == LDAP_SUCCESS)
{
PWCHAR* vals = ldap_get_valuesW(
*connection,
ldap_first_entry(*connection, res),
const_cast<PWCHAR>(NT_MIXED_DOMAIN)
);
if (vals && *vals)
{
mode = wcstoul(*vals, NULL, 10) ? MODE_MIXED : MODE_NATIVE;
}
else
{
status = ERROR_ACCESS_DENIED;
}
ldap_value_freeW(vals);
}
else
{
status = LdapMapErrorToWin32(ldapError);
}
ldap_msgfree(res);
if (status != NO_ERROR)
{
closeConnection();
}
}