/*++ Copyright (c) 1997 Microsoft Corporation Module Name: ds.c Abstract: This file contains all the routines used in interfacing with the DS. We go to the DS go make sure we are not a Rogue server, and also to retrieve all configuration information. Author: Shirish Koti (koti) 26-May-1997 Environment: User Mode - Win32 Revision History: --*/ // // System Includes // #define INC_OLE2 #include #include #include #include "activeds.h" #include "adsi.h" #if DBG #define DBGPRINT printf #else #define DBGPRINT #endif #include #include #include #include #include "adsi.h" #include #include #include DWORD ValidateService( LPWSTR lpwDomain, // domain name LPWSTR lpwUserName, // usually NULL LPWSTR lpwPassword, // usually NULL DWORD dwAuthFlags, // ?? LPWSTR lpwObjectPath, // where our things are stored (e.g. DHCP) LPWSTR lpwSrchFilter, // objects we're looking for (objectClass==DHCP) LPWSTR *ppwAttrName, // name of the attribute we're looking for LPWSTR lpwAttrVal // value of the attribute we're looking for ); LPWSTR lpwGlbNamingContextString = DHCPDS_NAMING_CONTEXT; DWORD DhcpDSGetDomainAndRoot( LPWSTR * ppwDomainName, LPWSTR * ppwRootName, BOOLEAN * fIsStandAlone ) /*++ Routine Description: This function gets the DS Domain and the root of the enterprise that this machine is a member of. NOTE: Memory is allocated to hold strings pointed to by *ppwDomainName and *ppwRootName, and the caller must free that memory. One of ppwDomainName or ppwRootName can be NULL, to indicate the caller doesn't want that info. (e.g. only root is needed, not domain) Arguments: ppwDomainName - pointer to a string that will hold domain name ppwRootName - pointer to a string that will hold the root of enterprise fIsStandAlone - pointer to a boolean: TRUE if this server is Standalone Return Value: Result of the operation --*/ { DWORD dwError; LPTSTR NetbiosDomNamePtr=NULL; LPTSTR pDomain=NULL; LPWSTR lpwRetDomainName=NULL; LPWSTR lpwRetRootName=NULL; BOOLEAN IsWorkgroup=TRUE; PDOMAIN_CONTROLLER_INFO pDCInfo = NULL; // initialize, in case we bail out if (ppwDomainName) { *ppwDomainName = NULL; } if (ppwRootName) { *ppwRootName = NULL; } *fIsStandAlone = FALSE; // // first, find out which domain we are a member of. // dwError = NetpGetDomainNameExEx( &NetbiosDomNamePtr, &pDomain, &IsWorkgroup); // // if NetpGetDomainNameExEx failed, we shouldn't proceed // if (dwError != NO_ERROR) { DBGPRINT("DhcpGetDSDomain: NetpGetDomainNameExEx failed (%ld)\n",dwError); return(dwError); } // we don't care about this domain name: free the buffer if (NetbiosDomNamePtr) { NetApiBufferFree(NetbiosDomNamePtr); } // if this is a standalone server, we're done here if (IsWorkgroup) { DBGPRINT("DhcpGetDSDomain: server is part of a workgroup\n"); *fIsStandAlone = TRUE; if (pDomain) { NetApiBufferFree(pDomain); } return(NO_ERROR); } // if pDomain is NULL, it means that the DNS domain isn't configured. // For the purpose of rogue-detection, we will assume it to be a workgroup // though in the strictest sense that's not true. if (pDomain == NULL) { DBGPRINT("DhcpGetDSDomain: pDomain is NULL: assuming part of workgroup\n"); *fIsStandAlone = TRUE; return(NO_ERROR); } // // if the caller wants DomainName, allocate space, and copy it in // if (ppwDomainName) { lpwRetDomainName = (LPWSTR)LocalAlloc( LPTR, (wcslen(pDomain)+1)*sizeof(WCHAR) ); if (lpwRetDomainName == NULL) { DBGPRINT("DhcpGetDSDomain: malloc 1 failed (%ld)\n", (wcslen(pDomain)+1)*sizeof(WCHAR)); dwError = ERROR_NOT_ENOUGH_MEMORY; goto DhcpGetDSDomain_ErrExit; } // copy the name in wcscpy(lpwRetDomainName, pDomain); *ppwDomainName = lpwRetDomainName; } // // See if the caller wants DS Root as well // get the info about this domain controller (the only thing we want // from all the good info in pDCInfo is the DS root name (DnsForestName field) // if (ppwRootName) { dwError = DsGetDcName( NULL, // server name pDomain, // domain name NULL, // domain guid NULL, // site name DS_IS_DNS_NAME, &pDCInfo); if (dwError != NO_ERROR) { DBGPRINT("DhcpGetDSDomain: DsGetDcName failed %ld\n",dwError); goto DhcpGetDSDomain_ErrExit; } // // RootName: allocate space, and copy the root name in // lpwRetRootName = (LPWSTR)LocalAlloc( LPTR, (wcslen(pDCInfo->DnsForestName)+1)*sizeof(WCHAR) ); if (lpwRetRootName == NULL) { DBGPRINT("DhcpGetDSDomain: malloc 2 failed (%ld)\n", (wcslen(pDCInfo->DnsForestName)+1)*sizeof(WCHAR)); dwError = ERROR_NOT_ENOUGH_MEMORY; goto DhcpGetDSDomain_ErrExit; } // copy the name in wcscpy(lpwRetRootName, pDCInfo->DnsForestName); *ppwRootName = lpwRetRootName; NetApiBufferFree(pDCInfo); } NetApiBufferFree(pDomain); return(NO_ERROR); DhcpGetDSDomain_ErrExit: DBGPRINT("DhcpGetDSDomain: executing error path, dwError = %ld\n",dwError); // // free the things that were given to us (or we took!) // if (pDomain) { NetApiBufferFree(pDomain); } if (pDCInfo) { NetApiBufferFree(pDCInfo); } if (lpwRetDomainName) { LocalFree(lpwRetDomainName); } if (lpwRetRootName) { LocalFree(lpwRetRootName); } if (ppwDomainName) { *ppwDomainName = NULL; } if (ppwRootName) { *ppwRootName = NULL; } return(dwError); } //BeginExport(function) //DOC DhcpDSValidateServer validates the server in the DS by looking for the server //DOC object and checking to see if the given IpAddress is present in the DS. //DOC This calls the other helper routine in DhcpDs.dll to do the real work. //DOC Returns one of //DOC DHCPDSERR_DS_OPERATION_FAILED //DOC DHCPDSERR_ENTRY_NOT_FOUND //DOC DHCPDSERR_ENTRY_FOUND //DOC DHCPDSERR_SERVER_IS_STANDALONE DWORD DhcpDSValidateServer( // validate in DS IN LPWSTR lpwDomain, // OPTIONAL NULL ==> Default domain IN DWORD *lpIpAddress, // one of the IpAddresses that must exist in DS IN ULONG dwIpAddressCount, // # of ip addresses supplied.. IN LPWSTR lpwUserName, // OPTIONAL NULL ==> m/c account IN LPWSTR lpwPassword, // OPTIONAL password to use IN DWORD dwAuthFlags // MUST BE ZERO? ) //EndExport(function) { DWORD Error; BOOL Found; BOOL IsStandAlone; IsStandAlone = FALSE; Found = FALSE; Error = DhcpDsValidateService // check to validate for dhcp ( lpwDomain, lpIpAddress, dwIpAddressCount, lpwUserName, lpwPassword, dwAuthFlags, &Found, &IsStandAlone ); if( ERROR_SUCCESS != Error ) return DHCPDSERR_DS_OPERATION_FAILED; if( IsStandAlone ) return DHCPDSERR_SERVER_IS_STANDALONE; return Found? DHCPDSERR_ENTRY_FOUND : DHCPDSERR_ENTRY_NOT_FOUND; } DWORD DhcpDSMapDomainToCNPath( LPWSTR lpwDomain, LPWSTR lpwUserName, LPWSTR lpwPassword, DWORD dwAuthFlags, LPWSTR *ppwCNPath ) /*++ Routine Description: This function retrieves the full path to the Configuration Container, given the name of the domain. If the domain name is not specified, server's domain name is assumed. NOTE: memory is allocated for ppwCNPath on successful return. Caller must free this memory. Arguments: lpwDomain - domain name in which to validate the service. Can be NULL to indicate the server's domain lpwUserName - account name to use during DS lookup. Usually, NULL (default) lpwPassword - password for the above user dwAuthFlags - flags to use ppwCNPath - pointer to string containing the path, on return. Return Value: Result of the operation --*/ { HRESULT hr=S_OK; LPWSTR pCanonName; LPWSTR lpwLocalDomain=NULL; LPWSTR lpwDomainToContact=NULL; LPWSTR lpwDCName; HANDLE handle = NULL; PDOMAIN_CONTROLLER_INFO pDCInfo = NULL; DWORD dwCanonNameLen; PADS_ATTR_INFO pAttributeEntries=NULL; DWORD dwNumAttributesReturned=0; BOOLEAN fIsStandAlone; BOOLEAN fCNStringFound; DWORD dwRetCode; DWORD index; *ppwCNPath = NULL; lpwDomainToContact = lpwDomain; // // if domain name is not supplied, find local domain first // if (!lpwDomain) { dwRetCode = DhcpDSGetDomainAndRoot( &lpwLocalDomain, NULL, &fIsStandAlone); if (dwRetCode != NO_ERROR) { DBGPRINT("DhcpDSMap..CNPath: GetLocalDom.. failed (0x%x)\n",dwRetCode); return(dwRetCode); } // // are we a standalone server? if so, return error since there is no // local domain (and the caller didn't specify a domain) // if (fIsStandAlone) { DBGPRINT("DhcpDSMap..CNPath: standalone has no domain!\n"); return(DHCPDSERR_SERVER_IS_STANDALONE); } lpwDomainToContact = lpwLocalDomain; } // // first, find a DC for this domain // dwRetCode = DsGetDcName( NULL, // server name lpwDomainToContact, // domain name NULL, // domain guid NULL, // site name DS_IS_DNS_NAME, // what flag(s) to use? &pDCInfo); // if we first obtained local domain, free it here if (lpwLocalDomain != NULL) { LocalFree((PCHAR)lpwLocalDomain); } if (dwRetCode != NO_ERROR) { DBGPRINT("MapDomainToCNPath: DsGetDcName failed %ld\n",dwRetCode); return(dwRetCode); } lpwDCName = pDCInfo->DomainControllerName; // skip the two leading '\' lpwDCName += 2; // also, skip the trailing '.' lpwDCName[wcslen(lpwDCName)-1] = 0; dwCanonNameLen = sizeof(DHCPDS_ROOTDSE_PRE) + (wcslen(lpwDCName)+1)*sizeof(WCHAR) + sizeof(DHCPDS_ROOTDSE_POST) + sizeof(WCHAR); pCanonName = (LPWSTR)LocalAlloc(LPTR, dwCanonNameLen); if (pCanonName == NULL) { DBGPRINT("DhcpDSMapDomainToCNPath: malloc 1 failed (%ld)\n",dwCanonNameLen); NetApiBufferFree(pDCInfo); return(ERROR_NOT_ENOUGH_MEMORY); } // copy the LDAP:// string wcscpy(pCanonName, DHCPDS_ROOTDSE_PRE); // append the server name wcscat(pCanonName, lpwDCName); // then append the RootDSE part wcscat(pCanonName, DHCPDS_ROOTDSE_POST); // don't need this nomore: free it now NetApiBufferFree(pDCInfo); // now, open the RootDSE "object" hr = ADSIOpenDSObject( pCanonName, lpwUserName, lpwPassword, dwAuthFlags, &handle ); // free this first: don't need this nomore LocalFree((PCHAR)pCanonName); if (FAILED(hr)) { DBGPRINT("ADSIOpenDSObject (RootDSE) failed with 0x%x\n",hr); return(DHCPDSERR_DS_OPERATION_FAILED); } // now, get all the attributes on this object hr = ADSIGetObjectAttributes( handle, &lpwGlbNamingContextString, 1, &pAttributeEntries, &dwNumAttributesReturned); if (FAILED(hr)) { DBGPRINT("ADSIGetObjectAttributes (RootDSE) failed with 0x%x\n",hr); dwRetCode = DHCPDSERR_DS_OPERATION_FAILED; goto DhcpDSMapDomainToCNPath_Exit; } fCNStringFound = FALSE; // // now, of all the values returned, find the one that we are // interested in (the one starts with CN=Configuration,) // for (index=0; indexdwNumValues; index++) { if (pAttributeEntries->pADsValues[index].dwType != ADSTYPE_CASE_IGNORE_STRING) { continue; } if (wcsncmp(pAttributeEntries->pADsValues[index].CaseIgnoreString, DHCPDS_CN_STRING, wcslen(DHCPDS_CN_STRING)) == 0) { fCNStringFound = TRUE; break; } } if (!fCNStringFound) { DBGPRINT("MapDomainToCNPath: %d values returned, but not ours\n", pAttributeEntries->dwNumValues); dwRetCode = DHCPDSERR_DS_OPERATION_FAILED; goto DhcpDSMapDomainToCNPath_Exit; } dwCanonNameLen = (wcslen(pAttributeEntries->pADsValues[index].CaseIgnoreString) + 1) * sizeof(WCHAR); (*ppwCNPath) = (LPWSTR)LocalAlloc(LPTR, dwCanonNameLen); if ((*ppwCNPath) == NULL) { DBGPRINT("MapDomainToCNPath: malloc 2 failed (%ld)\n",dwCanonNameLen); dwRetCode = ERROR_NOT_ENOUGH_MEMORY; goto DhcpDSMapDomainToCNPath_Exit; } // now, copy the-path-to-Configuration string wcscpy((*ppwCNPath),pAttributeEntries->pADsValues[index].CaseIgnoreString); dwRetCode = 0; DhcpDSMapDomainToCNPath_Exit: // free pAttributeEntries if (pAttributeEntries) { FreeADsMem(pAttributeEntries); } ADSICloseDSObject( handle ); return(dwRetCode); } DWORD ValidateService( LPWSTR lpwDomain, LPWSTR lpwUserName, LPWSTR lpwPassword, DWORD dwAuthFlags, LPWSTR lpwObjectPath, LPWSTR lpwSrchFilter, LPWSTR *ppwAttrName, LPWSTR lpwAttrVal ) /*++ Routine Description: This function determines if the given attribute (ipaddress in case of DHCP) is present in the list of authorized entities (DHCP Servers) on the DS enterprise. Arguments: lpwDomain - domain name in which to validate the service. Can be NULL to indicate the server's domain lpwUserName - account name to use during DS lookup. Usually, NULL (default) lpwPassword - password for the above user dwAuthFlags - flags to use lpwObjectPath - path to where the "entities" are stored lpwSrchFilter - the filter to use to get our "entities" (e.g.(objectClass==DHCPClass) ppwAttrName - name of the attribute we're looking for (e.g. IpAddress) lpwAttrVal - value of the attribute (e.g. the server's ipaddress to validate) Return Value: Result of the operation --*/ { HRESULT hr=S_OK; HANDLE handle = NULL; ADS_SEARCH_HANDLE hSearchHandle=NULL; ADS_SEARCH_COLUMN Column; DWORD dwRetCode; DWORD i; LPWSTR lpwCNPath=NULL; LPWSTR lpwFullDSPath=NULL; DWORD dwLen; BOOLEAN fObjectFound=FALSE; dwRetCode = DHCPDSERR_ENTRY_NOT_FOUND; // // first, we need to get the full ads path to our container within the // configuration container, from the domain name // dwRetCode = DhcpDSMapDomainToCNPath( lpwDomain, lpwUserName, lpwPassword, dwAuthFlags, &lpwCNPath); if (dwRetCode != 0) { DBGPRINT("ValidateService: MapDomainToCNPath failed with 0x%x\n",dwRetCode); goto ValidateService_Exit; } dwLen = (wcslen(lpwObjectPath)+1)*sizeof(WCHAR) + (wcslen(lpwCNPath)+1)*sizeof(WCHAR); lpwFullDSPath = (LPWSTR)LocalAlloc(LPTR, dwLen); if (lpwFullDSPath == NULL) { DBGPRINT("ValidateService: malloc failed (%ld)\n",dwLen); dwRetCode = ERROR_NOT_ENOUGH_MEMORY; goto ValidateService_Exit; } // copy the lpwObjectPath (e.g. LDAP://CN=DHCP) string wcscpy(lpwFullDSPath, lpwObjectPath); // now, concatenate the path to the configuration container wcscat(lpwFullDSPath, lpwCNPath); hr = ADSIOpenDSObject( lpwFullDSPath, lpwUserName, lpwPassword, dwAuthFlags, &handle ); if (FAILED(hr)) { DBGPRINT("ValidateService: ADSIOpenDSObject failed with 0x%x\n",hr); dwRetCode = DHCPDSERR_DS_OPERATION_FAILED; goto ValidateService_Exit; } hr = ADSIExecuteSearch( handle, lpwSrchFilter, ppwAttrName, -1, // ok to always pass this? &hSearchHandle ); if (FAILED(hr)) { DBGPRINT("ADSIExecuteSearch failed with 0x%x\n",hr); dwRetCode = DHCPDSERR_DS_OPERATION_FAILED; goto ValidateService_Exit; } hr = ADSIGetNextRow( handle, hSearchHandle ); if (FAILED(hr)) { DBGPRINT("ADSIGetNextRow failed with 0x%x\n",hr); dwRetCode = DHCPDSERR_DS_OPERATION_FAILED; goto ValidateService_Exit; } while (hr != S_ADS_NOMORE_ROWS && !fObjectFound) { // // get the values for the attribute requested (e.g. ipaddress) // hr = ADSIGetColumn( handle, hSearchHandle, ppwAttrName[0], // for now, only first one is needed &Column ); if (SUCCEEDED(hr)) { // // if the attribute is multivalued, compare all the values to see // if we find the one we want // for (i=0; i < Column.dwNumValues; i++) { if (_wcsnicmp( Column.pADsValues[i].CaseIgnoreString, lpwAttrVal, wcslen(lpwAttrVal)) == 0) { fObjectFound = TRUE; break; } } // free the column ADSIFreeColumn( handle, &Column); } // // it's strange if this attribute is not defined for this object since // it's one of our objects. But, nevertheless, just skip it // else { DBGPRINT("ADSIGetColumn failed with hr = 0x%x\n",hr); } // move to the next object hr = ADSIGetNextRow( handle, hSearchHandle ); } dwRetCode = (fObjectFound)? DHCPDSERR_ENTRY_FOUND : DHCPDSERR_ENTRY_NOT_FOUND; ValidateService_Exit: if (hSearchHandle) { ADSICloseSearchHandle( handle, hSearchHandle); } if (handle) { ADSICloseDSObject( handle ); } if (lpwCNPath) { LocalFree((PCHAR)lpwCNPath); } if (lpwFullDSPath) { LocalFree((PCHAR)lpwFullDSPath); } return(dwRetCode) ; } DWORD DhcpDSSameEnterprise( LPWSTR lpwDomainName, LPWSTR lpwRootName, BOOLEAN *fIsSameEnterprise ) /*++ Routine Description: This function checks if the given domain is part of the given DS Tree (in other words, we want to find out if this domain belongs to another enterprise) Arguments: lpwDomainName - name of the domain in question lpwRootName - root of the DS Tree to test against fIsSameEnterprise - on return, TRUE or FALSE Return Value: result of the operation --*/ { PDOMAIN_CONTROLLER_INFO pDCInfo = NULL; DWORD dwRetCode; // // until we can definitely verify that the root of the given domain is // the same as the one that is supplied, we return FALSE. // (*fIsSameEnterprise) = FALSE; if (!lpwRootName) { DBGPRINT("DhcpDSSameEnterprise: C'mon, don't pass me a NULL!\n"); return(0); } dwRetCode = DsGetDcName( NULL, // server name lpwDomainName, // domain name NULL, // domain guid NULL, // site name DS_IS_DNS_NAME, // what flag(s) to use? &pDCInfo); if (dwRetCode != NO_ERROR) { DBGPRINT("DhcpDSSameEnterprise: DsGetDcName failed (%ld)\n",dwRetCode); return(dwRetCode); } // moment of truth: are the two roots the same? (*fIsSameEnterprise) = (DnsCompareName(lpwRootName, pDCInfo->DnsForestName) != 0); NetApiBufferFree(pDCInfo); return(0); }