/* * Purpose: C++ API for finding the telephony * servers in Active Directory * */ #ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #include #include #include #include "tspi.h" #include "tapi.h" #include "dslookup.h" #include "utils.h" #include "tchar.h" const TCHAR gszNoDSQuery[] = TEXT("NoDSQuery"); const TCHAR gszStatusActive[] = TEXT("S{Active}"); const TCHAR gszTTLWithBrace[] = TEXT("TTL{"); // // Utility functions // // // GetIntFromString // Utility function used for retrieving TTL information // Parameters: // sz - String that contains the integer // dwDigits - Number of digits to convert // int GetIntFromString (LPTSTR & sz, DWORD dwDigits) { int iRet = 0; while (*sz != 0 && dwDigits) { iRet = ((iRet << 3) + (iRet << 1)) + // IRet * 10 + (*sz - '0'); ++sz; --dwDigits; } return iRet; } // // Rules: // The following codes are not thread safe, the caller // needs to be concious about synchronization. // Currently theses are only used in remotesp.tsp // /********************************************************** * Get TAPI servers list from the registry *********************************************************/ DWORD gdwCurServerNum = 0; HKEY ghRegistry = NULL; BOOL RegOpenServerLookup( HKEY hRegistry ) { if (NULL != ghRegistry) { // Already have a search in progress or the // caller did not close the last search. return FALSE; } else { ghRegistry = hRegistry; return TRUE; } } BOOL RegGetNextServer( LPTSTR pszServerName, DWORD dwSize ) { DWORD dwRet; LOG((TL_TRACE, "GetNextServer entered")); TCHAR szServerN[24]; DWORD dwType; wsprintf(szServerN, TEXT("Server%d"), gdwCurServerNum++); LOG((TL_INFO, "RegGetNextServer: Getting server %d from reg", gdwCurServerNum-1)); dwRet = RegQueryValueEx( ghRegistry, szServerN, 0, &dwType, (LPBYTE) pszServerName, &dwSize ); if (ERROR_SUCCESS != dwRet) { LOG((TL_INFO, "Got last server")); LOG((TL_TRACE, "GetNextServer exited")); return FALSE; } LOG((TL_TRACE, "GetNextServer exited")); return TRUE; } BOOL RegCloseLookup( void ) { LOG((TL_INFO, "Closing directory lookup")); ghRegistry = NULL; gdwCurServerNum = 0; return TRUE; } /********************************************************** * Enumerate published telephony servers *********************************************************/ typedef struct _TAPISRV_LOOKUP_CTX { ADS_SEARCH_HANDLE hDirSearch; IDirectorySearch * pDirSearch; } TAPISRV_LOOKUP_CTX, *PTAPISRV_LOOKUP_CTX; // gszTapisrvGuid needs to be consistant with server\dspub.cpp const WCHAR gszTapisrvGuid[] = L"keywords=B1A37774-E3F7-488E-ADBFD4DB8A4AB2E5"; // // GetGC // // Retrieve the IDirectorySearch of the Global Catalog (GC) // for SCP maintenance / discovery // HRESULT GetGC (IDirectorySearch ** ppGC) { HRESULT hr = S_OK; IEnumVARIANT * pEnum = NULL; IADsContainer * pCont = NULL; VARIANT var; IDispatch * pDisp = NULL; ULONG lFetch; // Set IDirectorySearch pointer to NULL. *ppGC = NULL; // First, bind to the GC: namespace container object. The "real" GC DN // is a single immediate child of the GC: namespace, which must // be obtained using enumeration. hr = ADsOpenObject( TEXT("GC:"), NULL, NULL, ADS_SECURE_AUTHENTICATION, //Use Secure Authentication IID_IADsContainer, (void**)&pCont ); if (FAILED(hr)) { LOG((TL_ERROR, "ADsOpenObject failed: 0x%x\n", hr)); goto cleanup; } // Fetch an enumeration interface for the GC container. hr = ADsBuildEnumerator(pCont, &pEnum); if (FAILED(hr)) { LOG((TL_ERROR, "ADsBuildEnumerator failed: 0x%x\n", hr)); goto cleanup; } //Now enumerate. There's only one child of the GC: object. hr = ADsEnumerateNext(pEnum, 1, &var, &lFetch); if (FAILED(hr)) { LOG((TL_ERROR, "ADsEnumerateNext failed: 0x%x\n", hr)); goto cleanup; } if (( hr == S_OK ) && ( lFetch == 1 ) ) { pDisp = V_DISPATCH(&var); hr = pDisp->QueryInterface( IID_IDirectorySearch, (void**)ppGC); } cleanup: if (pEnum) { ADsFreeEnumerator(pEnum); } if (pCont) { pCont->Release(); } if (pDisp) { (pDisp)->Release(); } return hr; } // // DsOpenServerLookup // Start the operation of tapisrv server lookup // pctx - The lookup context // HRESULT DsOpenServerLookup( PTAPISRV_LOOKUP_CTX pctx ) { HRESULT hr = S_OK; ADS_SEARCHPREF_INFO SearchPref[3]; BOOL bInited = FALSE; DWORD dwPref; WCHAR *szAttribs[]={ L"distinguishedName" }; if (pctx == NULL) { hr = E_INVALIDARG; goto ExitHere; } pctx->hDirSearch = NULL; pctx->pDirSearch = NULL; hr = CoInitializeEx (NULL, COINIT_MULTITHREADED); if (FAILED (hr)) { goto ExitHere; } bInited = TRUE; // Get the global catalog hr = GetGC (&pctx->pDirSearch); if (FAILED (hr) || pctx->pDirSearch == NULL) { goto ExitHere; } // Set up the search. We want to do a deep search. // Note that we are not expecting thousands of objects // in this example, so we will ask for 10 rows / page. dwPref=sizeof(SearchPref)/sizeof(ADS_SEARCHPREF_INFO); SearchPref[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE; SearchPref[0].vValue.dwType = ADSTYPE_INTEGER; SearchPref[0].vValue.Integer = ADS_SCOPE_SUBTREE; SearchPref[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE; SearchPref[1].vValue.dwType = ADSTYPE_INTEGER; SearchPref[1].vValue.Integer = 10; SearchPref[2].dwSearchPref = ADS_SEARCHPREF_TIME_LIMIT; SearchPref[2].vValue.dwType = ADSTYPE_INTEGER; SearchPref[2].vValue.Integer = 5 * 60; // 5 minute search timeout hr = pctx->pDirSearch->SetSearchPreference(SearchPref, dwPref); if (FAILED(hr)) { LOG((TL_ERROR, "Failed to set search prefs: hr:0x%x\n", hr)); goto ExitHere; } // Now execute the search hr = pctx->pDirSearch->ExecuteSearch( (LPWSTR)gszTapisrvGuid, szAttribs, sizeof(szAttribs) / sizeof(WCHAR *), &pctx->hDirSearch ); ExitHere: if (FAILED(hr) && pctx != NULL) { if (pctx->pDirSearch) { pctx->pDirSearch->Release(); } } if (FAILED(hr) && bInited) { CoUninitialize (); } return hr; } // // DsGetNextServer // // Return the next server name (in ANSI since that is what the // RPC subsystem uses) // // returns S_FALSE if no more server to enumerate // HRESULT DsGetNextServer( PTAPISRV_LOOKUP_CTX pctx, LPTSTR pszServerName, DWORD dwSize ) { HRESULT hr = S_OK; ADS_SEARCH_COLUMN Col; TCHAR szDN[MAX_PATH]; WCHAR *szAttribs[]={ L"serviceDNSName", L"serviceBindingInformation", }; ADS_ATTR_INFO *pPropEntries = NULL; DWORD dwNumAttrGot; IDirectoryObject * pSCP = NULL; int i; LPWSTR wsz; BOOL bCheckedBinding; if (pctx == NULL || pctx->pDirSearch == NULL || pctx->hDirSearch == NULL || dwSize < sizeof(WCHAR)) { hr = E_INVALIDARG; goto ExitHere; } hr = pctx->pDirSearch->GetNextRow(pctx->hDirSearch); if (SUCCEEDED (hr) && hr != S_ADS_NOMORE_ROWS) { hr = pctx->pDirSearch->GetColumn( pctx->hDirSearch, L"distinguishedName", &Col ); if (FAILED (hr)) { goto ExitHere; } _tcscpy (szDN, TEXT("LDAP://")); _tcsncpy ( szDN + _tcslen (szDN), Col.pADsValues->CaseExactString, sizeof(szDN)/sizeof(TCHAR) - _tcslen (szDN) ); pctx->pDirSearch->FreeColumn(&Col); hr = ADsGetObject ( szDN, IID_IDirectoryObject, (void **)&pSCP ); if (FAILED(hr)) { LOG((TL_ERROR, "DsGetNextServer: ADsGetObject %S failed", szDN)); hr = S_FALSE; // return a non-critical error so that we // ignore this server but don't abandon the search goto ExitHere; } LOG((TL_TRACE, "DsGetNextServer: ADsGetObject %S succeeded", szDN)); hr = pSCP->GetObjectAttributes ( szAttribs, sizeof(szAttribs) / sizeof(WCHAR *), &pPropEntries, &dwNumAttrGot ); if (FAILED(hr) || dwNumAttrGot != sizeof(szAttribs) / sizeof(WCHAR *)) { LOG((TL_ERROR, "DsGetNextServer: GetObjectAttributes %S failed", szDN)); hr = S_FALSE; goto ExitHere; } LOG((TL_TRACE, "DsGetNextServer: GetObjectAttributes %S succeeded", szDN)); bCheckedBinding = FALSE; for (i=0;i<(int)dwNumAttrGot;i++) { if (_tcsicmp(TEXT("serviceDNSName"), pPropEntries[i].pszAttrName) ==0 && (pPropEntries[i].pADsValues->dwType == ADSTYPE_CASE_EXACT_STRING || pPropEntries[i].pADsValues->dwType == ADSTYPE_CASE_IGNORE_STRING)) { _tcsncpy ( pszServerName, pPropEntries[i].pADsValues->CaseExactString, dwSize/sizeof(TCHAR) ); pszServerName[dwSize/sizeof(TCHAR) - 1] = '\0'; if (bCheckedBinding) { break; } } else if (_tcsicmp(TEXT("serviceBindingInformation"), pPropEntries[i].pszAttrName) ==0 && (pPropEntries[i].pADsValues->dwType == ADSTYPE_CASE_EXACT_STRING || pPropEntries[i].pADsValues->dwType == ADSTYPE_CASE_IGNORE_STRING)) { SYSTEMTIME st; FILETIME ft1, ft2; bCheckedBinding = TRUE; wsz = pPropEntries[i].pADsValues->CaseExactString; wsz = wcsstr (wsz, gszStatusActive); if (wsz == NULL) { // No server status information or server is not active // ignore this server LOG((TL_ERROR, "DsGetNextServer: %S No server status information", szDN)); hr = S_FALSE; break; } wsz += _tcslen(gszStatusActive); // skip "S{Active}" itself wsz = wcsstr (wsz, gszTTLWithBrace); if (wsz == NULL) { // No TTL found, corrupt serviceBindingInformation, ignore LOG((TL_ERROR, "DsGetNextServer: %S No TTL found", szDN)); hr = S_FALSE; break; } wsz += _tcslen (gszTTLWithBrace); // skip "TTL{" // // The following codes parses the TTL information // created in server\dspub.cpp. They need to be // consistant. The current format is 5 digits for // year & 3 digits for milliseconds, 2 digits for // the remaining // st.wYear = (WORD) GetIntFromString (wsz, 5); st.wMonth = (WORD) GetIntFromString (wsz, 2); st.wDay = (WORD) GetIntFromString (wsz, 2), st.wHour = (WORD) GetIntFromString (wsz, 2); st.wMinute = (WORD) GetIntFromString (wsz, 2); st.wSecond = (WORD) GetIntFromString (wsz, 2); st.wMilliseconds = (WORD) GetIntFromString (wsz, 3); SystemTimeToFileTime (&st, &ft1); GetSystemTimeAsFileTime (&ft2); if (CompareFileTime (&ft1, &ft2) < 0) { // The TapiSCP object has passed its TTL, ignore hr = S_FALSE; LOG((TL_ERROR, "DsGetNextServer: %S The TapiSCP object has passed its TTL", szDN)); break; } } } if (i == (int) dwNumAttrGot) { // Did not find an attribute hr = S_FALSE; } } ExitHere: if (pSCP) pSCP->Release(); if (pPropEntries) FreeADsMem(pPropEntries); return hr; } // // DsCloseLookup // Close the TAPI DS published server lookup identified by pctx // HRESULT DsCloseLookup( PTAPISRV_LOOKUP_CTX pctx ) { if (pctx && pctx->pDirSearch && pctx->hDirSearch) { pctx->pDirSearch->CloseSearchHandle(pctx->hDirSearch); } if (pctx && pctx->pDirSearch) { pctx->pDirSearch->Release(); } CoUninitialize (); return S_OK; } /********************************************************** * Get TAPI servers list remotesp.tsp should contact * Servers include those specified in registry through * tcmsetup.exe and those servers published in the DS *********************************************************/ typedef struct _SERVER_LOOKUP_ENTRY { TCHAR szServer[MAX_PATH]; BOOL bFromReg; } SERVER_LOOKUP_ENTRY, *PSERVER_LOOKUP_ENTRY; typedef struct _SERVER_LOOKUP { DWORD dwTotalEntries; DWORD dwUsedEntries; SERVER_LOOKUP_ENTRY * aEntries; } SERVER_LOOKUP, *PSERVER_LOOKUP; SERVER_LOOKUP gLookup; DWORD gdwCurIndex; // // AddEntry : return FALSE if failed; otherwise, return true // BOOL AddEntry ( LPTSTR szServer, BOOL bFromReg ) { LPTSTR psz; if (gLookup.dwUsedEntries >= gLookup.dwTotalEntries) { PSERVER_LOOKUP_ENTRY pNew; pNew = (PSERVER_LOOKUP_ENTRY) DrvAlloc ( sizeof(SERVER_LOOKUP_ENTRY) * (gLookup.dwTotalEntries + 5) ); if (pNew == NULL) { return FALSE; } if (gLookup.dwUsedEntries > 0) { CopyMemory ( pNew, gLookup.aEntries, sizeof(SERVER_LOOKUP_ENTRY) * gLookup.dwTotalEntries ); } if (gLookup.aEntries) { DrvFree (gLookup.aEntries); } gLookup.aEntries = pNew; gLookup.dwTotalEntries += 5; } wcsncpy ( gLookup.aEntries[gLookup.dwUsedEntries].szServer, szServer, sizeof(gLookup.aEntries[gLookup.dwUsedEntries].szServer)/sizeof(TCHAR) ); psz = _tcschr(gLookup.aEntries[gLookup.dwUsedEntries].szServer, TEXT('.')); if (psz != NULL) { *psz = 0; } gLookup.aEntries[gLookup.dwUsedEntries].bFromReg = bFromReg; ++gLookup.dwUsedEntries; return TRUE; } BOOL IsServerInListOrSelf ( LPTSTR szServer ) { int i; TCHAR szServer1[MAX_PATH + 1]; LPTSTR psz; BOOL bRet = FALSE; _tcsncpy (szServer1, szServer, MAX_PATH); szServer1[MAX_PATH] = 0; // A computer name might be DNS name like comp1.microsoft.com // only compare the computer name psz = _tcschr(szServer1, TEXT('.')); if (psz != NULL) { *psz = 0; } for (i = 0; i < (int)gLookup.dwUsedEntries; ++i) { if (_tcsicmp (szServer1, gLookup.aEntries[i].szServer) == 0) { bRet = TRUE; break; } } if (!bRet) { TCHAR szSelf[MAX_PATH]; DWORD dwSize = sizeof(szSelf); if (GetComputerName (szSelf, &dwSize)) { if (_tcsicmp (szServer1, szSelf) == 0) { bRet = TRUE; } } } return bRet; } BOOL OpenServerLookup ( HKEY hRegistry ) { BOOL bRet = TRUE; TCHAR szServer[MAX_PATH]; TAPISRV_LOOKUP_CTX ctx; HRESULT hr; DWORD dwNoDSQuery = 0; DWORD dwSize = sizeof(dwNoDSQuery); gLookup.dwTotalEntries = 0; gLookup.dwUsedEntries = 0; gLookup.aEntries = NULL; // // First add the computer from registry // if (RegOpenServerLookup (hRegistry)) { while (RegGetNextServer (szServer, sizeof(szServer))) { if (!IsServerInListOrSelf (szServer)) { AddEntry (szServer, TRUE); } } RegCloseLookup (); } if (hRegistry != NULL) { if (ERROR_SUCCESS != RegQueryValueEx ( hRegistry, gszNoDSQuery, NULL, NULL, (LPBYTE)&dwNoDSQuery, &dwSize )) { dwNoDSQuery = 0; } } // // Next add the computer from DS unless disabled // if (dwNoDSQuery == 0) { if (DsOpenServerLookup (&ctx) == S_OK) { while (SUCCEEDED(hr = DsGetNextServer (&ctx,szServer, sizeof(szServer)))) { if (hr == S_ADS_NOMORE_ROWS) { break; } else if (hr != S_OK) { continue; // Server needs to be ignored } if (szServer[0] != 0 && !IsServerInListOrSelf (szServer)) { AddEntry (szServer, FALSE); } } DsCloseLookup (&ctx); } } gdwCurIndex = 0; return TRUE; } BOOL GetNextServer ( LPSTR szServer, DWORD dwSize, BOOL * pbReg ) { BOOL bRet = TRUE; DWORD dwRet; if (gdwCurIndex >= gLookup.dwUsedEntries) { bRet = FALSE; goto ExitHere; } if (pbReg != NULL) { *pbReg = gLookup.aEntries[gdwCurIndex].bFromReg; } dwRet = WideCharToMultiByte( GetACP(), 0, gLookup.aEntries[gdwCurIndex].szServer, -1, szServer, dwSize, 0, NULL ); if (dwRet == 0) { bRet = FALSE; goto ExitHere; } ++gdwCurIndex; ExitHere: return bRet; } BOOL CloseLookup ( void ) { if (gLookup.aEntries) { DrvFree (gLookup.aEntries); } gLookup.aEntries = NULL; gLookup.dwTotalEntries = 0; gLookup.dwUsedEntries = 0; gdwCurIndex = 0; return TRUE; } HRESULT SockStartup ( RSPSOCKET * pSocket ) { HRESULT hr = S_OK; BOOL bCleanup = FALSE; WSADATA wsadata; WORD wVersionRequested = MAKEWORD( 2, 2 ); if (pSocket == NULL) { hr = LINEERR_INVALPARAM; goto ExitHere; } ZeroMemory (pSocket, sizeof(RSPSOCKET)); bCleanup = TRUE; ZeroMemory (pSocket, sizeof(RSPSOCKET)); pSocket->hWS2 = LoadLibrary (TEXT("ws2_32.dll")); if (pSocket->hWS2 == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); goto ExitHere; } pSocket->pFnWSAStartup = (PFNWSASTARTUP)GetProcAddress ( pSocket->hWS2, "WSAStartup" ); pSocket->pFnWSACleanup = (PFNWSACLEANUP)GetProcAddress ( pSocket->hWS2, "WSACleanup" ); pSocket->pFngethostbyname = (PFNGETHOSTBYNAME)GetProcAddress( pSocket->hWS2, "gethostbyname" ); if (pSocket->pFnWSAStartup == NULL || pSocket->pFnWSACleanup == NULL || pSocket->pFngethostbyname == NULL) { hr = LINEERR_OPERATIONFAILED; goto ExitHere; } pSocket->hICMP = LoadLibrary (TEXT("icmp.dll")); if (pSocket->hICMP == NULL) { hr = HRESULT_FROM_WIN32(GetLastError()); goto ExitHere; } pSocket->pFnIcmpCreateFile = (PFNICMPCREATEFILE)GetProcAddress ( pSocket->hICMP, "IcmpCreateFile" ); pSocket->pFnIcmpCloseHandle = (PFNICMPCLOSEHANDLE)GetProcAddress ( pSocket->hICMP, "IcmpCloseHandle" ); pSocket->pFnIcmpSendEcho = (PFNICMPSENDECHO)GetProcAddress ( pSocket->hICMP, "IcmpSendEcho" ); if (pSocket->pFnIcmpCreateFile == NULL || pSocket->pFnIcmpCloseHandle == NULL || pSocket->pFnIcmpSendEcho == NULL) { hr = LINEERR_OPERATIONFAILED; goto ExitHere; } hr = (*pSocket->pFnWSAStartup)( wVersionRequested, &wsadata ); if(FAILED(hr)) { goto ExitHere; } pSocket->IcmpHandle = (*pSocket->pFnIcmpCreateFile)(); if (pSocket->IcmpHandle == INVALID_HANDLE_VALUE) { (*pSocket->pFnWSACleanup)(); hr = LINEERR_OPERATIONFAILED; } ExitHere: if (hr != S_OK && bCleanup) { if (pSocket->hWS2 != NULL) { FreeLibrary (pSocket->hWS2); } if (pSocket->hICMP != NULL) { FreeLibrary (pSocket->hICMP); } ZeroMemory (pSocket, sizeof(RSPSOCKET)); } return hr; } #define MAX_PACKET_SIZE 256 #define PING_TIMEOUT 1000 HRESULT SockIsServerResponding ( RSPSOCKET * pSocket, char * szServer ) { HRESULT hr = S_OK; unsigned long inetAddr; HOSTENT * pHost; BOOL bRet; CHAR ReplyBuf[MAX_PACKET_SIZE]; // Validate parameters if (pSocket == NULL || pSocket->hWS2 == NULL || pSocket->hICMP == NULL || pSocket->IcmpHandle == NULL || pSocket->IcmpHandle == INVALID_HANDLE_VALUE) { hr = LINEERR_INVALPARAM; goto ExitHere; } // Get the server IP address pHost = (*pSocket->pFngethostbyname)(szServer); if (pHost == NULL) { hr = LINEERR_OPERATIONFAILED; goto ExitHere; } inetAddr = *(unsigned long *)pHost->h_addr; // Ping the server bRet = (*pSocket->pFnIcmpSendEcho)( pSocket->IcmpHandle, inetAddr, 0, 0, 0, (LPVOID)ReplyBuf, sizeof(ReplyBuf), PING_TIMEOUT ); if (!bRet || ((PICMP_ECHO_REPLY)ReplyBuf)->Address != inetAddr) { hr = S_FALSE; } ExitHere: return hr; } HRESULT SockShutdown ( RSPSOCKET * pSocket ) { if (pSocket != NULL) { if (pSocket->IcmpHandle != INVALID_HANDLE_VALUE && pSocket->IcmpHandle != NULL) { (*pSocket->pFnIcmpCloseHandle)(pSocket->IcmpHandle); } if (pSocket->hICMP != NULL) { FreeLibrary (pSocket->hICMP); } if (pSocket->hWS2 != NULL) { (*pSocket->pFnWSACleanup)(); FreeLibrary (pSocket->hWS2); } ZeroMemory (pSocket, sizeof(RSPSOCKET)); } return S_OK; }