/////////////////////////////////////////////////////////////////////////////// // // 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 #include #include #include #include #include #include #include ////////// // 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(connection->getBase()), LDAP_SCOPE_BASE, const_cast(ANY_OBJECT), const_cast(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(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); connection->TraceFailure("ldap_get_valuesW", ldapError ); } ldap_msgfree(res); if (status != NO_ERROR) { closeConnection(); } }