Windows-Server-2003/net/tapi/sp/remotesp/dslookup.cpp

939 lines
23 KiB
C++

/*
* Purpose: C++ API for finding the telephony
* servers in Active Directory
*
*/
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <objbase.h>
#include <activeds.h>
#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;
}