/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * * -*- mode: c, c-basic-offset: 4 -*- */ /* * Copyright Likewise Software 2004-2008 * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General * Public License along with this program. If not, see * . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ /* * Copyright (C) Likewise Software. All rights reserved. * * Module Name: * * dcinfo.c * * Abstract: * * Likewise Site Manager * * Domain Controller Info API (Server) * * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com) * Sriram Nambakam (snambakam@likewisesoftware.com) * Kyle Stemen (kstemen@likewisesoftware.com) * Brian Dunstan (bdunstan@likewisesoftware.com) * */ #include "includes.h" static DWORD LWNetSrvGetDCTimeFromDC( IN PCSTR pszDCName, OUT PLWNET_UNIX_TIME_T pDCTime, OUT PBOOLEAN pbIsNetworkError ); DWORD LWNetSrvGetDCName( IN PCSTR pszServerName, IN PCSTR pszDnsDomainName, IN OPTIONAL PCSTR pszSiteName, IN PCSTR pszPrimaryDomain, IN DWORD dwDsFlags, IN DWORD dwBlackListCount, IN PSTR* ppszAddressBlackList, OUT PLWNET_DC_INFO* ppDcInfo ) // // TODO: Remove server name param. Technically, the server-size should not // take a server name. That's for the client to use to figure out // where to dispatch the request (as an RPC, in that case). // // TODO: Add parameter for GUID-based lookups. // { DWORD dwError = 0; LWNET_UNIX_TIME_T now = 0; LWNET_UNIX_TIME_T lastPinged = 0; LWNET_UNIX_TIME_T lastDiscovered = 0; BOOLEAN isBackoffToWritableDc = FALSE; LWNET_UNIX_TIME_T lastBackoffToWritableDc = 0; PLWNET_DC_INFO pDcInfo = NULL; PLWNET_DC_INFO pNewDcInfo = NULL; PDNS_SERVER_INFO pServerArray = NULL; DWORD dwServerCount = 0; DWORD dwIndex = 0; BOOLEAN bFailedFindWritable = FALSE; BOOLEAN bUpdateCache = FALSE; BOOLEAN bUpdateKrb5Affinity = FALSE; LWNET_LOG_INFO("Looking for a DC in domain '%s', site '%s' with flags %X", LWNET_SAFE_LOG_STRING(pszDnsDomainName), LWNET_SAFE_LOG_STRING(pszSiteName), dwDsFlags); if (!IsNullOrEmptyString(pszServerName)) { LWNET_LOG_ERROR("Cannot specify computer name: '%s'", pszServerName); dwError = ERROR_INVALID_PARAMETER; BAIL_ON_LWNET_ERROR(dwError); } for (dwIndex = 0; dwIndex < dwBlackListCount; dwIndex++) { if (!ppszAddressBlackList[dwIndex]) { dwError = ERROR_INVALID_PARAMETER; BAIL_ON_LWNET_ERROR(dwError); } } // Look up in cache if (!(dwDsFlags & DS_FORCE_REDISCOVERY)) { dwError = LWNetCacheQuery(pszDnsDomainName, pszSiteName, dwDsFlags, &pDcInfo, &lastDiscovered, &lastPinged, &isBackoffToWritableDc, &lastBackoffToWritableDc); BAIL_ON_LWNET_ERROR(dwError); // If in background mode, we do not care about any expiration if (dwDsFlags & DS_BACKGROUND_ONLY) { dwError = pDcInfo ? 0 : ERROR_NO_SUCH_DOMAIN; BAIL_ON_LWNET_ERROR(dwError); goto error; } dwError = LWNetGetSystemTime(&now); BAIL_ON_LWNET_ERROR(dwError); // Check whether a negative cache applies if (!pDcInfo && (lastDiscovered > 0)) { if ((now - lastDiscovered) <= LWNetConfigGetNegativeCacheTimeoutSeconds()) { dwError = ERROR_NO_SUCH_DOMAIN; BAIL_ON_LWNET_ERROR(dwError); } } // ISSUE-2008/07/03-Perhaps cache on pszClientSiteName too... // Actually, it may be worthwhile to have that take place // under the hood inside the caching code itself. // If we found something in the cache, we may need to ping it. if (pDcInfo) { if (isBackoffToWritableDc && (now - lastBackoffToWritableDc) > LWNetConfigGetWritableRediscoveryTimeoutSeconds()) { // Need to re-affinitize LWNET_SAFE_FREE_DC_INFO(pDcInfo); } // Note that we only explicitly verify writability wrt re-affinitization. else if (!LWNetSrvIsMatchingDcInfo(pDcInfo, dwDsFlags)) { // Cannot use these results. LWNET_SAFE_FREE_DC_INFO(pDcInfo); } else if ((now - lastPinged) > LWNetConfigGetPingAgainTimeoutSeconds()) { DNS_SERVER_INFO serverInfo; serverInfo.pszName = pDcInfo->pszDomainControllerName; serverInfo.pszAddress = pDcInfo->pszDomainControllerAddress; while (serverInfo.pszName && serverInfo.pszName[0] == '\\') serverInfo.pszName++; while (serverInfo.pszAddress && serverInfo.pszAddress[0] == '\\') serverInfo.pszAddress++; dwError = LWNetSrvPingCLdapArray(pszDnsDomainName, dwDsFlags, &serverInfo, 1, 1, 0, &pNewDcInfo, &bFailedFindWritable); if (!dwError) { // We need to update the cache to reflect the ping time // and perhaps extend the life of the backoff to writable // DC flag. bUpdateCache = TRUE; dwError = LWNetGetSystemTime(&now); BAIL_ON_LWNET_ERROR(dwError); lastPinged = now; if ((dwDsFlags & DS_WRITABLE_REQUIRED) && isBackoffToWritableDc) { // update the time lastBackoffToWritableDc = now; } LWNET_SAFE_FREE_DC_INFO(pDcInfo); pDcInfo = pNewDcInfo; pNewDcInfo = 0; } else { // now we need to bottom out and find us new info. LWNET_SAFE_FREE_DC_INFO(pDcInfo); } } else { // cached data is fine dwError = 0; // check whether we need to update the backoff time if ((dwDsFlags & DS_WRITABLE_REQUIRED) && isBackoffToWritableDc && (now - lastBackoffToWritableDc) > LWNetConfigGetWritableTimestampMinimumChangeSeconds()) { // We need to update cache to extend the life of the // backoff to writable DC flag. bUpdateCache = TRUE; dwError = LWNetGetSystemTime(&now); BAIL_ON_LWNET_ERROR(dwError); lastBackoffToWritableDc = now; } } } } for (dwIndex = 0; pDcInfo && dwIndex < dwBlackListCount; dwIndex++) { if (!strcmp(pDcInfo->pszDomainControllerAddress, ppszAddressBlackList[dwIndex])) { LWNET_SAFE_FREE_DC_INFO(pDcInfo); } } if (!pDcInfo) { dwError = LWNetSrvGetDCNameDiscover(pszDnsDomainName, pszSiteName, pszPrimaryDomain, dwDsFlags, dwBlackListCount, ppszAddressBlackList, &pDcInfo, &pServerArray, &dwServerCount, &bFailedFindWritable); BAIL_ON_LWNET_ERROR(dwError); // Do not update the cache if a black list is passed in. Otherwise, // callers would have too much control over which DC is affinitized. // Also do not update if looking for a writeable DC since Windows // (Vista) does not appear to update its cache in that case either. if ((dwBlackListCount == 0) && LWNetSrvIsAffinitizableRequestFlags(dwDsFlags)) { // We need to update the cache with this entry. bUpdateCache = TRUE; dwError = LWNetGetSystemTime(&now); BAIL_ON_LWNET_ERROR(dwError); lastDiscovered = now; lastPinged = now; isBackoffToWritableDc = bFailedFindWritable; lastBackoffToWritableDc = isBackoffToWritableDc ? now : 0; // Will only affinitize for KDC/LDAP (i.e., not PDC/GC) and if the site is the same. if (!(dwDsFlags & (DS_PDC_REQUIRED | DS_GC_SERVER_REQUIRED)) && (IsNullOrEmptyString(pszSiteName) || !strcasecmp(pDcInfo->pszDCSiteName, pszSiteName))) { bUpdateKrb5Affinity = TRUE; } } } // Handle updates if (bUpdateCache) { dwError = LWNetCacheUpdate(pszDnsDomainName, pszSiteName, dwDsFlags, lastDiscovered, lastPinged, isBackoffToWritableDc, lastBackoffToWritableDc, pDcInfo); BAIL_ON_LWNET_ERROR(dwError); } if (bUpdateKrb5Affinity) { dwError = LWNetKrb5UpdateAffinity( pszDnsDomainName, pDcInfo, pServerArray, dwServerCount); BAIL_ON_LWNET_ERROR(dwError); } error: LWNET_SAFE_FREE_MEMORY(pServerArray); LWNET_SAFE_FREE_DC_INFO(pNewDcInfo); if (dwError) { LWNET_SAFE_FREE_DC_INFO(pDcInfo); } *ppDcInfo = pDcInfo; return dwError; } DWORD LWNetSrvGetDCList( IN PCSTR pszDnsDomainName, IN OPTIONAL PCSTR pszSiteName, IN DWORD dwDsFlags, OUT PLWNET_DC_ADDRESS* ppDcList, OUT PDWORD pdwDcCount ) { DWORD dwError = 0; PDNS_SERVER_INFO pServerArray = NULL; DWORD dwServerCount = 0; PLWNET_DC_ADDRESS pDcList = NULL; DWORD i = 0; dwError = LWNetDnsSrvQuery( pszDnsDomainName, pszSiteName, dwDsFlags, &pServerArray, &dwServerCount); BAIL_ON_LWNET_ERROR(dwError); dwError = LWNetAllocateMemory( sizeof(pDcList[0]) * dwServerCount, (PVOID*)&pDcList); BAIL_ON_LWNET_ERROR(dwError); for (i = 0; i < dwServerCount; i++) { if (pServerArray[i].pszName) { dwError = LWNetAllocateString(pServerArray[i].pszName, &pDcList[i].pszDomainControllerName); BAIL_ON_LWNET_ERROR(dwError); } if (pServerArray[i].pszAddress) { dwError = LWNetAllocateString(pServerArray[i].pszAddress, &pDcList[i].pszDomainControllerAddress); BAIL_ON_LWNET_ERROR(dwError); } } error: if (dwError) { if (pDcList) { LWNetFreeDCList(pDcList, dwServerCount); pDcList = NULL; } dwServerCount = 0; } LWNET_SAFE_FREE_MEMORY(pServerArray); *ppDcList = pDcList; *pdwDcCount = dwServerCount; return dwError; } DWORD LWNetSrvGetDomainController( IN PCSTR pszDomainFQDN, OUT PSTR* ppszDomainControllerFQDN ) { DWORD dwError = 0; PSTR pszDomainControllerFQDN = NULL; PLWNET_DC_INFO pDcInfo = NULL; dwError = LWNetSrvGetDCName(NULL, pszDomainFQDN, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED, 0, NULL, &pDcInfo); BAIL_ON_LWNET_ERROR(dwError); dwError = LWNetAllocateString(pDcInfo->pszDomainControllerName, &pszDomainControllerFQDN); BAIL_ON_LWNET_ERROR(dwError); error: LWNET_SAFE_FREE_DC_INFO(pDcInfo); if (dwError) { LWNET_SAFE_FREE_STRING(pszDomainControllerFQDN); } *ppszDomainControllerFQDN = pszDomainControllerFQDN; return dwError; } DWORD LWNetSrvGetDCTime( IN PCSTR pszDomainFQDN, OUT PLWNET_UNIX_TIME_T pDCTime ) { DWORD dwError = 0; PLWNET_DC_INFO pDcInfo = NULL; PSTR pszDCTime = NULL; LWNET_UNIX_TIME_T result = 0; BOOLEAN bIsNetworkError = FALSE; BAIL_ON_INVALID_POINTER(pDCTime); LWNET_LOG_INFO("Determining the current time for domain '%s'", LWNET_SAFE_LOG_STRING(pszDomainFQDN)); dwError = LWNetSrvGetDCName( NULL, pszDomainFQDN, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED, 0, NULL, &pDcInfo); BAIL_ON_LWNET_ERROR(dwError); dwError = LWNetSrvGetDCTimeFromDC( pDcInfo->pszDomainControllerName, &result, &bIsNetworkError); if (bIsNetworkError) { LWNET_SAFE_FREE_DC_INFO(pDcInfo); dwError = LWNetSrvGetDCName( NULL, pszDomainFQDN, NULL, NULL, DS_DIRECTORY_SERVICE_REQUIRED | DS_FORCE_REDISCOVERY, 0, NULL, &pDcInfo); BAIL_ON_LWNET_ERROR(dwError); dwError = LWNetSrvGetDCTimeFromDC( pDcInfo->pszDomainControllerName, &result, &bIsNetworkError); BAIL_ON_LWNET_ERROR(dwError); } else { BAIL_ON_LWNET_ERROR(dwError); } error: LWNET_SAFE_FREE_DC_INFO(pDcInfo); LWNET_SAFE_FREE_STRING(pszDCTime); if (dwError) { memset(&result, 0, sizeof(result)); } *pDCTime = result; return dwError; } static DWORD LWNetSrvGetDCTimeFromDC( IN PCSTR pszDCName, OUT PLWNET_UNIX_TIME_T pDCTime, OUT PBOOLEAN pbIsNetworkError ) { DWORD dwError = 0; BOOLEAN bIsNetworkError = FALSE; PSTR pszDCTime = NULL; struct tm dcTime = {0}; time_t ttDcTimeUTC = 0; LDAPMessage* pMessage = NULL; HANDLE hDirectory = (HANDLE)NULL; PSTR ppszAttributeList[] = { "currentTime", NULL }; LWNET_UNIX_TIME_T result = 0; #ifndef HAVE_TIMEGM struct tm utcTm = {0}; struct tm utcTmAdjusted = {0}; struct tm dcTimeCopy = {0}; time_t now = -1; #endif BAIL_ON_INVALID_POINTER(pDCTime); dwError = LwCLdapOpenDirectory(pszDCName, &hDirectory); if (dwError) { LWNET_LOG_ERROR( "Failed ldap open on %s error=%d\n", pszDCName, dwError); } BAIL_ON_LWNET_ERROR(dwError); dwError = LwLdapBindDirectoryAnonymous(hDirectory); if (dwError) { LWNET_LOG_ERROR( "Failed ldap bind on %s error=%d\n", pszDCName, dwError); } BAIL_ON_LWNET_ERROR(dwError); dwError = LwLdapDirectorySearchEx( hDirectory, "", LDAP_SCOPE_BASE, "(objectclass=*)", ppszAttributeList, NULL, 0, &pMessage); if (dwError) { LWNET_LOG_ERROR( "Failed ldap search on %s error=%d\n", pszDCName, dwError); } BAIL_ON_LWNET_ERROR(dwError); dwError = LwLdapGetString(hDirectory, pMessage, "currentTime", &pszDCTime); BAIL_ON_LWNET_ERROR(dwError); dwError = LWNetCrackLdapTime(pszDCTime, &dcTime); if (dwError) { if (dwError == EINVAL) { dwError = ERROR_INVALID_TIME; } BAIL_ON_LWNET_ERROR(dwError); } #ifdef HAVE_TIMEGM ttDcTimeUTC = timegm(&dcTime); #else // We need to convert a universal time in a time structure to // seconds since the epoch but the only function that is available // assumes it's a local time and applies the timezone offset. We // have to determine what the offset is for today's date which could // vary depending on whether daylight savings time is in effect and // adjust the results. It is possible that the local time and the DC // time are on opposite sides of the daylight savings time change // boundary so the result could be off by as much as an hour and may // may need to be adjusted again. Note that we're saving a copy of // dcTime because mktime has a tendency to modify the structure. now = time(NULL); gmtime_r(&now, &utcTm); dcTimeCopy = dcTime; ttDcTimeUTC = mktime(&dcTime) + now - mktime(&utcTm); gmtime_r(&ttDcTimeUTC, &utcTmAdjusted); if (utcTmAdjusted.tm_hour == 0 && dcTimeCopy.tm_hour != 0) { ttDcTimeUTC += (dcTimeCopy.tm_hour - 24) * 60 * 60; } else if (utcTmAdjusted.tm_hour != 0 && dcTimeCopy.tm_hour == 0) { ttDcTimeUTC += (24 - utcTmAdjusted.tm_hour) * 60 * 60; } else { ttDcTimeUTC += (dcTimeCopy.tm_hour - utcTmAdjusted.tm_hour) * 60 * 60; } ttDcTimeUTC += (dcTimeCopy.tm_min - utcTmAdjusted.tm_min) * 60; #endif result = ttDcTimeUTC; error: LWNET_SAFE_FREE_STRING(pszDCTime); if (hDirectory) { LwLdapCloseDirectory(hDirectory); } if (pMessage) { ldap_msgfree(pMessage); } if (dwError) { memset(&result, 0, sizeof(result)); switch (dwError) { case LW_ERROR_LDAP_SERVER_DOWN: case LW_ERROR_LDAP_TIMEOUT: case LW_ERROR_LDAP_SERVER_UNAVAILABLE: case LW_ERROR_LDAP_CONNECT_ERROR: bIsNetworkError = TRUE; break; } } *pDCTime = result; *pbIsNetworkError = bIsNetworkError; return dwError; }