/* File: sdo.c Function to interact with the SDO's Paul Mayfield, 5/7/98 */ #include #include #include #include #include #include "sdoias.h" #include "sdolib.h" #include "sdowrap.h" #include "dialinusr.h" const DWORD dwFramed = RAS_RST_FRAMED; const DWORD dwFramedCallback = RAS_RST_FRAMEDCALLBACK; #define SDO_ERROR(e) \ ((HRESULT_FACILITY((e)) == FACILITY_WIN32) ? HRESULT_CODE((e)) : (e)); #define SDO_PROPERTY_IS_EMPTY(_pVar) (V_VT((_pVar)) == VT_EMPTY) // Definitions #define SDO_MAX_AUTHS 7 DWORD SdoSetProfileToForceEncryption( IN HANDLE hSdo, IN HANDLE hProfile, IN BOOL bStrong); // // Sends debug trace and returns the given error // DWORD SdoTraceEx (DWORD dwErr, LPSTR pszTrace, ...) { #if DBG va_list arglist; char szBuffer[1024], szTemp[1024]; va_start(arglist, pszTrace); vsprintf(szTemp, pszTrace, arglist); va_end(arglist); sprintf(szBuffer, "Sdo: %s", szTemp); OutputDebugStringA(szBuffer); #endif return dwErr; } // // Allocation routine for sdo functions // PVOID SdoAlloc ( IN DWORD dwSize, IN BOOL bZero) { return LocalAlloc ((bZero) ? LPTR : LMEM_FIXED, dwSize); } // // Free routine for sdo functions // VOID SdoFree ( IN PVOID pvData) { LocalFree (pvData); } // // Releases any resources aquired by loading the SDO library. // DWORD SdoUnloadLibrary ( IN HANDLE hData) { return NO_ERROR; } // // Loads the library that utilizes SDO's // DWORD SdoLoadLibrary ( IN HANDLE hData) { return NO_ERROR; } typedef struct _tagSDOINFO { BOOL bComCleanup; } SDOINFO; // // Initialize and cleanup the sdo library // DWORD SdoInit ( OUT PHANDLE phSdo) { DWORD dwErr = NO_ERROR; HRESULT hr = S_OK; SDOINFO* pInfo = NULL; BOOL bCom = FALSE; SdoTraceEx (0, "SdoInit: entered.\n"); //For whistler bug 397815 //We have to modify the CoIntialize() and CoUnitialize() //to avoid AV in rasdlg!netDbClose() // // Validate parameters // if ( NULL == phSdo ) { return ERROR_INVALID_PARAMETER; } // Initialize // *phSdo = NULL; do { // Load in the sdo library dwErr = SdoLoadLibrary(NULL); if (NO_ERROR != dwErr ) { SdoTraceEx(dwErr, "SdoInit: unabled to load library\n"); break; } // Initialize Com // hr = CoInitializeEx (NULL, COINIT_MULTITHREADED); if ( RPC_E_CHANGED_MODE == hr ) { hr = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); } if (FAILED(hr)) { dwErr = HRESULT_CODE(hr); break; } bCom = TRUE; pInfo = SdoAlloc(sizeof(SDOINFO), TRUE); if (pInfo == NULL) { dwErr = ERROR_NOT_ENOUGH_MEMORY; break; } pInfo->bComCleanup = bCom; *phSdo = (HANDLE)pInfo; } while (FALSE); // Cleanup // { if ( NO_ERROR!= dwErr ) { if (pInfo) { SdoFree(pInfo); } if (bCom) { CoUninitialize(); } } } return dwErr; } // // Frees resources held by the SDO library DWORD SdoCleanup ( IN HANDLE hSdo) { DWORD dwErr; SDOINFO* pInfo = (SDOINFO*)hSdo; SdoTraceEx (0, "SdoCleanup: entered.\n"); if ( NULL == pInfo ) { return ERROR_INVALID_PARAMETER; } // Unload the sdo library if ((dwErr = SdoUnloadLibrary(NULL)) != NO_ERROR) SdoTraceEx (dwErr, "SdoCleanup: %x on unload.\n", dwErr); // Unititialize com if (pInfo->bComCleanup) { CoUninitialize(); } SdoFree(pInfo); return NO_ERROR; } // // Connects to an SDO server // DWORD SdoConnect ( IN HANDLE hSdo, IN PWCHAR pszServer, IN BOOL bLocal, OUT PHANDLE phServer) { BSTR bstrComputer = NULL; HRESULT hr; SdoTraceEx (0, "SdoConnect: entered %S, %d\n", pszServer, bLocal); // Prepare a correctly formatted version of the server // name -- NULL for local, no "\\" for remote. if (pszServer) { WCHAR pszLocalComputer[1024]; DWORD dwSize = sizeof(pszLocalComputer) / sizeof(WCHAR); if (*pszServer == 0) bstrComputer = NULL; else if (*pszServer == '\\') { bstrComputer = SysAllocString(pszServer + 2); if (bstrComputer == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } } else { bstrComputer = SysAllocString(pszServer); if (bstrComputer == NULL) { return ERROR_NOT_ENOUGH_MEMORY; } } if ((bstrComputer) && (GetComputerName(pszLocalComputer, &dwSize))) { if (lstrcmpi (pszLocalComputer, bstrComputer) == 0) { SysFreeString(bstrComputer); bstrComputer = NULL; } } } else bstrComputer = NULL; hr = SdoWrapOpenServer( bstrComputer, bLocal, phServer); if (FAILED (hr)) SdoTraceEx (0, "SdoConnect: %x on OpenServer(%S) \n", hr, bstrComputer); if (bstrComputer) SysFreeString(bstrComputer); if (FAILED (hr)) return hr; return NO_ERROR; } // // Disconnects from an SDO server // DWORD SdoDisconnect ( IN HANDLE hSdo, IN HANDLE hServer) { SdoTraceEx (0, "SdoDisconnect: entered\n"); return SdoWrapCloseServer(hServer); } // // Opens an Sdo user for manipulation // DWORD SdoOpenUser( IN HANDLE hSdo, IN HANDLE hServer, IN PWCHAR pszUser, OUT PHANDLE phUser) { DWORD dwErr; BSTR bstrUser; // Initailize the strings for COM bstrUser = SysAllocString(pszUser); if (bstrUser == NULL) return ERROR_NOT_ENOUGH_MEMORY; // Open the user's Sdo object dwErr = SdoWrapOpenUser( hServer, bstrUser, phUser); if (dwErr != NO_ERROR) SdoTraceEx (0, "SdoOpenUser: %x on OpenUser(%S)\n", dwErr, bstrUser); // Cleanup SysFreeString(bstrUser); if (dwErr != NO_ERROR) return dwErr; return NO_ERROR; } // // Closes an Sdo user // DWORD SdoCloseUser( IN HANDLE hSdo, IN HANDLE hUser) { if (hUser != NULL) return SdoWrapClose(hUser); return ERROR_INVALID_PARAMETER; } // // Commits an Sdo user // DWORD SdoCommitUser( IN HANDLE hSdo, IN HANDLE hUser, IN BOOL bCommit) { if (hUser != NULL) { return SdoWrapCommit(hUser, bCommit); } return ERROR_INVALID_PARAMETER; } // // SDO equivalent of MprAdminUserGetInfo // DWORD SdoUserGetInfo ( IN HANDLE hSdo, IN HANDLE hUser, IN DWORD dwLevel, OUT LPBYTE pRasUser) { RAS_USER_0* pUserInfo = (RAS_USER_0*)pRasUser; VARIANT var, vCallback, vSavedCb; DWORD dwErr, dwCallback; HRESULT hr; // Validate -- we only handle level 0 if ((!hUser) || (dwLevel != 0 && dwLevel != 1) || (!pUserInfo)) return ERROR_INVALID_PARAMETER; // Initialize pUserInfo->bfPrivilege = 0; dwCallback = RAS_RST_FRAMED; // Read in the service type VariantInit (&var); hr = SdoWrapGetAttr( hUser, PROPERTY_USER_SERVICE_TYPE, &var); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserGetInfo: %x on GetAttr ST\n", hr); } // If the service type doesn't exist, return // set defaults. if (SDO_PROPERTY_IS_EMPTY(&var)) { pUserInfo->bfPrivilege |= RASPRIV_NoCallback; wcscpy (pUserInfo->wszPhoneNumber, L""); } else { // Assign the callback flags from the service type dwCallback = V_I4(&var); } VariantClear (&var); // Readin the dialin flag hr = SdoWrapGetAttr( hUser, PROPERTY_USER_ALLOW_DIALIN, &var); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserGetInfo: %x on GetAttr DI\n", hr); } if (dwLevel == 1) { if (SDO_PROPERTY_IS_EMPTY(&var)) { pUserInfo->bfPrivilege |= RASPRIV_DialinPolicy; } else if ((V_VT(&var) == VT_BOOL) && (V_BOOL(&var) == VARIANT_TRUE)) { pUserInfo->bfPrivilege |= RASPRIV_DialinPrivilege; } } else if ((V_VT(&var) == VT_BOOL) && (V_BOOL(&var) == VARIANT_TRUE)) { pUserInfo->bfPrivilege |= RASPRIV_DialinPrivilege; } // Read in the callback number and saved callback number VariantInit(&vCallback); VariantInit(&vSavedCb); hr = SdoWrapGetAttr( hUser, PROPERTY_USER_RADIUS_CALLBACK_NUMBER, &vCallback); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserGetInfo: %x on GetAttr CB\n", hr); } hr = SdoWrapGetAttr( hUser, PROPERTY_USER_SAVED_RADIUS_CALLBACK_NUMBER, &vSavedCb); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserGetInfo: %x on GetAttr SCB\n", hr); } // If there was a callback number, then this is definately, // admin assigned callback if ( (V_VT(&vCallback) == VT_BSTR) && (V_BSTR(&vCallback)) ) { pUserInfo->bfPrivilege |= RASPRIV_AdminSetCallback; } // Otherwise, the service type will tell us whether we have // caller settable callback or none. else { if (dwCallback == RAS_RST_FRAMEDCALLBACK) pUserInfo->bfPrivilege |= RASPRIV_CallerSetCallback; else pUserInfo->bfPrivilege |= RASPRIV_NoCallback; } // Now, assign the callback number accordingly if (pUserInfo->bfPrivilege & RASPRIV_AdminSetCallback) { wcscpy (pUserInfo->wszPhoneNumber, V_BSTR(&vCallback)); } else if ((V_VT(&vSavedCb) == VT_BSTR) && (V_BSTR(&vSavedCb))) { wcscpy (pUserInfo->wszPhoneNumber, V_BSTR(&vSavedCb)); } else { wcscpy (pUserInfo->wszPhoneNumber, L""); } VariantClear (&vSavedCb); VariantClear (&vCallback); return NO_ERROR; } // // SDO equivalent of MprAdminUserSetInfo // DWORD SdoUserSetInfo ( IN HANDLE hSdo, IN HANDLE hUser, IN DWORD dwLevel, IN LPBYTE pRasUser) { RAS_USER_0* pUserInfo = (RAS_USER_0*)pRasUser; DWORD dwErr, dwCallback, dwCallbackId, dwSize, dwCbType; VARIANT var; HRESULT hr; // Validate -- we only handle level 0 if ((!hUser) || (dwLevel != 0 && dwLevel != 1) || (!pUserInfo)) return ERROR_INVALID_PARAMETER; // Initialize VariantInit (&var); dwCallback = 0; // Assign dialin flags if (!!(pUserInfo->bfPrivilege & RASPRIV_DialinPrivilege)) { V_VT(&var) = VT_BOOL; V_BOOL(&var) = VARIANT_TRUE; } else { V_VT(&var) = VT_BOOL; V_BOOL(&var) = VARIANT_FALSE; } if (dwLevel == 1) { if (!!(pUserInfo->bfPrivilege & RASPRIV_DialinPolicy)) { V_VT(&var) = VT_EMPTY; } } hr = SdoWrapPutAttr( hUser, PROPERTY_USER_ALLOW_DIALIN, &var); if (FAILED (hr)) { SdoTraceEx (hr, "SdoUserSetInfo: %x on PutAttr DI\n", hr); } VariantClear(&var); // Assign the callback mode and read in the // callback number dwCbType = VT_EMPTY; if (pUserInfo->bfPrivilege & RASPRIV_AdminSetCallback) { dwCbType = VT_I4; dwCallback = RAS_RST_FRAMEDCALLBACK; dwCallbackId = PROPERTY_USER_RADIUS_CALLBACK_NUMBER; } else if (pUserInfo->bfPrivilege & RASPRIV_CallerSetCallback) { dwCbType = VT_I4; dwCallback = RAS_RST_FRAMEDCALLBACK; dwCallbackId = PROPERTY_USER_SAVED_RADIUS_CALLBACK_NUMBER; } else { dwCbType = VT_EMPTY; dwCallback = RAS_RST_FRAMED; dwCallbackId = PROPERTY_USER_SAVED_RADIUS_CALLBACK_NUMBER; } // Write out the callback number if (wcslen (pUserInfo->wszPhoneNumber) > 0) { V_VT(&var) = VT_BSTR; V_BSTR(&var) = SysAllocString (pUserInfo->wszPhoneNumber); if (V_BSTR(&var) == NULL) { return E_OUTOFMEMORY; } hr = SdoWrapPutAttr(hUser, dwCallbackId, &var); SysFreeString (V_BSTR(&var)); if (FAILED (hr)) return SdoTraceEx (hr, "SdoUserSetInfo: %x on PutAttr CB\n", hr); } // Write out the callback policy VariantInit(&var); V_VT(&var) = (USHORT)dwCbType; if (V_VT(&var) != VT_EMPTY) { V_I4(&var) = dwCallback; } hr = SdoWrapPutAttr(hUser, PROPERTY_USER_SERVICE_TYPE, &var); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserSetInfo: %x on PutAttr ST\n", hr); } // Remove the appropriate callback attribute dwCallbackId = (dwCallbackId == PROPERTY_USER_RADIUS_CALLBACK_NUMBER) ? PROPERTY_USER_SAVED_RADIUS_CALLBACK_NUMBER : PROPERTY_USER_RADIUS_CALLBACK_NUMBER; hr = SdoWrapRemoveAttr(hUser, dwCallbackId); if (FAILED (hr)) { return SdoTraceEx (hr, "SdoUserSetInfo: %x on RemoveAttr CB\n", hr); } return NO_ERROR; } // // Opens the default profile // DWORD SdoOpenDefaultProfile( IN HANDLE hSdo, IN HANDLE hServer, OUT PHANDLE phProfile) { SdoTraceEx (0, "SdoOpenDefaultProfile: entered\n"); if (phProfile == NULL) return ERROR_INVALID_PARAMETER; return SdoWrapOpenDefaultProfile(hServer, phProfile); } // // Closes a profile // DWORD SdoCloseProfile( IN HANDLE hSdo, IN HANDLE hProfile) { SdoTraceEx (0, "SdoCloseProfile: entered\n"); if (hProfile == NULL) return ERROR_INVALID_PARAMETER; return SdoWrapCloseProfile(hProfile); } // // Converts a 1 demensional safe array of variant dwords // into an a array of dwords and a count // HRESULT SdoConvertSafeArrayDw ( IN SAFEARRAY * pArray, OUT LPDWORD lpdwAuths, OUT LPDWORD lpdwAuthCount) { LONG lDim, lLBound, lRBound, lCount, i; HRESULT hr; VARIANT var; // Validate if (!pArray || !lpdwAuths || !lpdwAuthCount) return ERROR_INVALID_PARAMETER; // Verify dimensions lDim = (DWORD)SafeArrayGetDim(pArray); if (lDim != 1) return ERROR_INVALID_PARAMETER; // Get the bounds hr = SafeArrayGetLBound(pArray, 1, &lLBound); if (FAILED (hr)) return hr; hr = SafeArrayGetUBound(pArray, 1, &lRBound); if (FAILED (hr)) return hr; lCount = (lRBound - lLBound) + 1; *lpdwAuthCount = (DWORD)lCount; if (lCount == 0) return NO_ERROR; // Loop through for (i = 0; i < lCount; i++) { hr = SafeArrayGetElement(pArray, &i, (VOID*)&var); if (FAILED (hr)) continue; lpdwAuths[i] = V_I4(&var); } return S_OK; } // // Converts a 1 demensional array of dwords to a // safe array of variant dwords. // HRESULT SdoCovertDwToSafeArray( IN SAFEARRAY ** ppArray, OUT LPDWORD lpdwAuths, OUT DWORD dwAuthCount) { HRESULT hr; SAFEARRAY * pArray; SAFEARRAYBOUND rgsabound[1]; LONG i; VARIANT var; // Validate if (!lpdwAuths || !ppArray) return E_INVALIDARG; // Create the new array rgsabound[0].lLbound = 0; rgsabound[0].cElements = dwAuthCount; pArray = SafeArrayCreate(VT_VARIANT, 1, rgsabound); // Fill in the array values for (i = 0; i < (LONG)dwAuthCount; i++) { hr = SafeArrayGetElement(pArray, &i, (VOID*)&var); if (FAILED (hr)) continue; V_VT(&var) = VT_I4; V_I4(&var) = lpdwAuths[i]; hr = SafeArrayPutElement(pArray, &i, (VOID*)&var); if (FAILED (hr)) return hr; } *ppArray = pArray; return S_OK; } // // Sets data in the profile. // DWORD SdoSetProfileData( IN HANDLE hSdo, IN HANDLE hProfile, IN DWORD dwFlags) { DWORD dwAuthCount, dwAuths[SDO_MAX_AUTHS]; VARIANT varEp, varEt, varAt; HRESULT hr; SdoTraceEx (0, "SdoSetProfileData: entered\n"); if ((dwFlags & MPR_USER_PROF_FLAG_FORCE_STRONG_ENCRYPTION) || (dwFlags & MPR_USER_PROF_FLAG_FORCE_ENCRYPTION)) { return SdoSetProfileToForceEncryption( hSdo, hProfile, !!(dwFlags & MPR_USER_PROF_FLAG_FORCE_STRONG_ENCRYPTION)); } // Initialize VariantInit (&varEp); VariantInit (&varEt); VariantInit (&varAt); do { // Set the encryption policy V_VT(&varEp) = VT_I4; if (dwFlags & MPR_USER_PROF_FLAG_SECURE) { V_I4(&varEp) = RAS_EP_REQUIRE; } else { V_I4(&varEp) = RAS_EP_ALLOW; } // Set the encryption type V_VT(&varEt) = VT_I4; if (dwFlags & MPR_USER_PROF_FLAG_SECURE) { V_I4(&varEt) = (RAS_ET_BASIC | RAS_ET_STRONGEST | RAS_ET_STRONG); } else { V_I4(&varEt) = (RAS_ET_BASIC | RAS_ET_STRONGEST | RAS_ET_STRONG); } // Set the authentication types if (dwFlags & MPR_USER_PROF_FLAG_SECURE) { dwAuthCount = 4; dwAuths[0] = IAS_AUTH_MSCHAP; dwAuths[1] = IAS_AUTH_MSCHAP2; dwAuths[2] = IAS_AUTH_MSCHAP_CPW; dwAuths[3] = IAS_AUTH_MSCHAP2_CPW; } else { dwAuthCount = 5; dwAuths[0] = IAS_AUTH_MSCHAP; dwAuths[1] = IAS_AUTH_MSCHAP2; dwAuths[2] = IAS_AUTH_PAP; dwAuths[3] = IAS_AUTH_MSCHAP_CPW; dwAuths[4] = IAS_AUTH_MSCHAP2_CPW; } V_VT(&varAt) = VT_ARRAY | VT_VARIANT; hr = SdoCovertDwToSafeArray( &(V_ARRAY(&varAt)), dwAuths, dwAuthCount); if (FAILED (hr)) { break; } // Set the values in the profile hr = SdoWrapSetProfileValues( hProfile, &varEp, &varEt, &varAt); if (FAILED (hr)) { break; } } while (FALSE); // Cleanup { VariantClear(&varEp); VariantClear(&varEt); VariantClear(&varAt); } return SDO_ERROR(hr); } // // Sets a profile to force strong encryption // DWORD SdoSetProfileToForceEncryption( IN HANDLE hSdo, IN HANDLE hProfile, IN BOOL bStrong) { VARIANT varEp, varEt; HRESULT hr = S_OK; SdoTraceEx (0, "SdoSetProfileToForceEncryption: entered (%d)\n", !!bStrong); // Initialize VariantInit (&varEp); VariantInit (&varEt); do { // Set the encryption policy V_VT(&varEp) = VT_I4; V_I4(&varEp) = RAS_EP_REQUIRE; // Set the encryption type V_VT(&varEt) = VT_I4; if (bStrong) { V_I4(&varEt) = RAS_ET_STRONGEST; } else { V_I4(&varEt) = RAS_ET_BASIC | RAS_ET_STRONG | RAS_ET_STRONGEST; } // Write out the values // Set the values in the profile hr = SdoWrapSetProfileValues( hProfile, &varEp, &varEt, NULL); if (FAILED (hr)) { break; } } while (FALSE); // Cleanup { VariantClear(&varEp); VariantClear(&varEt); } return SDO_ERROR(hr); } // // Read information from the given profile // DWORD SdoGetProfileData( IN HANDLE hSdo, IN HANDLE hProfile, OUT LPDWORD lpdwFlags) { VARIANT varEp, varEt, varAt; HRESULT hr = S_OK; DWORD dwEncPolicy, dwAuthCount, dwAuths[SDO_MAX_AUTHS], i, dwEncType; SdoTraceEx (0, "SdoGetProfileData: entered\n"); // Initialize ZeroMemory(dwAuths, sizeof(dwAuths)); VariantInit(&varEp); VariantInit(&varEt); VariantInit(&varAt); do { // Read in the encryption values hr = SdoWrapGetProfileValues(hProfile, &varEp, &varEt, &varAt); if (FAILED (hr)) { break; } // Parse the encryption policy if (SDO_PROPERTY_IS_EMPTY(&varEp)) { dwEncPolicy = RAS_DEF_ENCRYPTIONPOLICY; } else { dwEncPolicy = V_I4(&varEp); } // Parse the encryption type if (SDO_PROPERTY_IS_EMPTY(&varEt)) { dwEncType = RAS_DEF_ENCRYPTIONTYPE; } else { dwEncType = V_I4(&varEt); } // Parse in the allowed authentication types if (SDO_PROPERTY_IS_EMPTY(&varAt)) { dwAuthCount = 1; dwAuths[0] = RAS_DEF_AUTHENTICATIONTYPE; } else { hr = SdoConvertSafeArrayDw ( V_ARRAY(&varAt), dwAuths, &dwAuthCount); if (FAILED (hr)) { break; } } // If the encryption type has been mucked with // then we can't tell if we're secure. if (dwEncType != (RAS_ET_STRONG | RAS_ET_STRONGEST | RAS_ET_BASIC)) { *lpdwFlags = MPR_USER_PROF_FLAG_UNDETERMINED; } else { // If the encryption policy forces encryption // then we're secure if the only authentication // types are MSCHAP v1 or 2. if (dwEncPolicy == RAS_EP_REQUIRE) { *lpdwFlags = MPR_USER_PROF_FLAG_SECURE; for (i = 0; i < dwAuthCount; i++) { if ((dwAuths[i] != IAS_AUTH_MSCHAP) && (dwAuths[i] != IAS_AUTH_MSCHAP2) && (dwAuths[i] != IAS_AUTH_MSCHAP_CPW) && (dwAuths[i] != IAS_AUTH_MSCHAP2_CPW)) { *lpdwFlags = MPR_USER_PROF_FLAG_UNDETERMINED; } } } // We know that we're not secure all authentication // types are allowed else { if ( (dwAuthCount >= 3) && (dwAuthCount <= 5)) { *lpdwFlags = 0; for (i = 0; i < dwAuthCount; i++) { if ((dwAuths[i] != IAS_AUTH_MSCHAP) && (dwAuths[i] != IAS_AUTH_MSCHAP2) && (dwAuths[i] != IAS_AUTH_MSCHAP_CPW) && (dwAuths[i] != IAS_AUTH_MSCHAP2_CPW) && (dwAuths[i] != IAS_AUTH_PAP)) { *lpdwFlags = MPR_USER_PROF_FLAG_UNDETERMINED; } } } else { *lpdwFlags = MPR_USER_PROF_FLAG_UNDETERMINED; } } } } while (FALSE); // Cleanup { VariantClear(&varEp); VariantClear(&varEt); VariantClear(&varAt); } return SDO_ERROR(hr); }