1562 lines
33 KiB
C++
1562 lines
33 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996
|
|
//
|
|
// File: cgenobj.cxx
|
|
//
|
|
// Contents: Microsoft ADs LDAP Provider Generic Object
|
|
//
|
|
//
|
|
// History: 08-30-96 yihsins Created.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
#include "ldap.hxx"
|
|
#pragma hdrstop
|
|
|
|
// Class CLDAPRootDSE
|
|
|
|
|
|
DEFINE_IDispatch_Implementation(CLDAPRootDSE)
|
|
DEFINE_IADs_Implementation(CLDAPRootDSE)
|
|
|
|
CLDAPRootDSE::CLDAPRootDSE():
|
|
_pPropertyCache( NULL ),
|
|
_pDispMgr( NULL ),
|
|
_pszLDAPServer(NULL),
|
|
_pszLDAPDn(NULL),
|
|
_pLdapHandle( NULL )
|
|
{
|
|
VariantInit(&_vFilter);
|
|
VariantInit(&_vHints);
|
|
|
|
ENLIST_TRACKING(CLDAPRootDSE);
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::CreateRootDSE(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
BSTR LdapClassName,
|
|
CCredentials& Credentials,
|
|
DWORD dwObjectState,
|
|
REFIID riid,
|
|
void **ppvObj
|
|
)
|
|
{
|
|
CLDAPRootDSE FAR * pGenObject = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
hr = AllocateGenObject(Credentials, &pGenObject);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pGenObject->InitializeCoreObject(
|
|
Parent,
|
|
CommonName,
|
|
LdapClassName,
|
|
CLSID_LDAPGenObject,
|
|
dwObjectState
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
pGenObject->_ADsPath,
|
|
&pGenObject->_pszLDAPServer,
|
|
&pGenObject->_pszLDAPDn,
|
|
&pGenObject->_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
//
|
|
// At this point update the info in the property cache
|
|
//
|
|
pGenObject->_pPropertyCache->SetObjInformation(
|
|
&(pGenObject->_Credentials),
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = LdapOpenObject(
|
|
pGenObject->_pszLDAPServer,
|
|
NULL,
|
|
&(pGenObject->_pLdapHandle),
|
|
pGenObject->_Credentials,
|
|
pGenObject->_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (Credentials.GetAuthFlags() & ADS_AUTH_RESERVED) {
|
|
//
|
|
// From Umi so we need to return UMI Object not RootDSE.
|
|
//
|
|
hr = ((CCoreADsObject*)pGenObject)->InitUmiObject(
|
|
IntfPropsGeneric,
|
|
pGenObject->_pPropertyCache,
|
|
(IADs*) pGenObject,
|
|
(IADs*) pGenObject,
|
|
riid,
|
|
ppvObj,
|
|
&(pGenObject->_Credentials),
|
|
pGenObject->_dwPort,
|
|
pGenObject->_pszLDAPServer,
|
|
NULL,
|
|
pGenObject->_pLdapHandle
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
RRETURN(S_OK);
|
|
|
|
}
|
|
|
|
hr = pGenObject->QueryInterface(riid, ppvObj);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pGenObject->Release();
|
|
|
|
RRETURN(hr);
|
|
|
|
error:
|
|
|
|
*ppvObj = NULL;
|
|
delete pGenObject;
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
CLDAPRootDSE::~CLDAPRootDSE( )
|
|
{
|
|
VariantClear(&_vFilter);
|
|
VariantClear(&_vHints);
|
|
|
|
if ( _pLdapHandle )
|
|
{
|
|
LdapCloseObject(_pLdapHandle);
|
|
_pLdapHandle = NULL;
|
|
}
|
|
|
|
if (_pszLDAPServer) {
|
|
FreeADsStr(_pszLDAPServer);
|
|
_pszLDAPServer = NULL;
|
|
}
|
|
|
|
if (_pszLDAPDn) {
|
|
FreeADsStr(_pszLDAPDn);
|
|
_pszLDAPDn = NULL;
|
|
}
|
|
|
|
delete _pDispMgr;
|
|
|
|
delete _pPropertyCache;
|
|
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::QueryInterface(REFIID iid, LPVOID FAR* ppv)
|
|
{
|
|
if (ppv == NULL) {
|
|
RRETURN(E_POINTER);
|
|
}
|
|
|
|
if (IsEqualIID(iid, IID_IUnknown))
|
|
{
|
|
*ppv = (IADs FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IADs))
|
|
{
|
|
*ppv = (IADs FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IDispatch))
|
|
{
|
|
*ppv = (IADs FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IADsPropertyList))
|
|
{
|
|
*ppv = (IADsPropertyList FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IADsObjectOptions)) {
|
|
*ppv = (IADsObjectOptions FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_ISupportErrorInfo))
|
|
{
|
|
*ppv = (ISupportErrorInfo FAR *) this;
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::SetInfo()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
|
|
//
|
|
// No concept of creating RootDSE objects
|
|
// Any DS must have a RootDSE object
|
|
//
|
|
|
|
hr = E_FAIL;
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// If the create succeded, set the object type to bound
|
|
//
|
|
|
|
SetObjectState(ADS_OBJECT_BOUND);
|
|
|
|
}else {
|
|
|
|
hr = LDAPSetObject();
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
error:
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::LDAPSetObject()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPModW **aMod = NULL;
|
|
BOOL fNTSecDes = FALSE;
|
|
SECURITY_INFORMATION NewSeInfo;
|
|
|
|
|
|
hr = _pPropertyCache->LDAPMarshallProperties(
|
|
&aMod,
|
|
&fNTSecDes,
|
|
&NewSeInfo
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if ( aMod == NULL ) // There are no changes that needs to be modified
|
|
RRETURN(S_OK);
|
|
|
|
hr = LdapModifyS(
|
|
_pLdapHandle,
|
|
NULL,
|
|
aMod
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
// We are successful at this point,
|
|
// So, clean up the flags in the cache so the same operation
|
|
// won't be repeated on the next SetInfo()
|
|
|
|
_pPropertyCache->ClearAllPropertyFlags();
|
|
|
|
error:
|
|
|
|
if (aMod) {
|
|
|
|
if ( *aMod )
|
|
FreeADsMem( *aMod );
|
|
|
|
FreeADsMem( aMod );
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::InterfaceSupportsErrorInfo(THIS_ REFIID riid)
|
|
{
|
|
if (IsEqualIID(riid, IID_IADs) ||
|
|
IsEqualIID(riid, IID_IADsPropertyList)) {
|
|
RRETURN(S_OK);
|
|
} else {
|
|
RRETURN(S_FALSE);
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::GetInfo()
|
|
{
|
|
RRETURN(GetInfo(GETINFO_FLAG_EXPLICIT));
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::GetInfo(
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId = ADSTYPE_CASE_IGNORE_STRING;
|
|
LDAPMessage *res = NULL;
|
|
|
|
if (dwFlags == GETINFO_FLAG_IMPLICIT_AS_NEEDED) {
|
|
if (_pPropertyCache->getGetInfoFlag()) {
|
|
//
|
|
// Nothing to do in this case.
|
|
//
|
|
RRETURN(S_OK);
|
|
}
|
|
}
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
hr = E_ADS_OBJECT_UNBOUND;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = LdapSearchS(
|
|
_pLdapHandle,
|
|
NULL,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
NULL,
|
|
0,
|
|
&res
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if ( dwFlags == GETINFO_FLAG_EXPLICIT )
|
|
{
|
|
// If this is an explicit GetInfo,
|
|
// delete the old cache and start a new cache from scratch.
|
|
|
|
_pPropertyCache->flushpropertycache();
|
|
}
|
|
|
|
hr = _pPropertyCache->LDAPUnMarshallPropertiesAs(
|
|
_pszLDAPServer,
|
|
_pLdapHandle,
|
|
res,
|
|
dwSyntaxId,
|
|
(dwFlags == GETINFO_FLAG_EXPLICIT) ?
|
|
TRUE :
|
|
FALSE,
|
|
_Credentials
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
_pPropertyCache->setGetInfoFlag();
|
|
|
|
error:
|
|
|
|
if (res) {
|
|
|
|
LdapMsgFree( res );
|
|
}
|
|
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::GetInfoEx(THIS_ VARIANT vProperties, long lnReserved)
|
|
{
|
|
RRETURN_EXP_IF_ERR(E_NOTIMPL);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLDAPRootDSE::AllocateGenObject(
|
|
CCredentials& Credentials,
|
|
CLDAPRootDSE ** ppGenObject
|
|
)
|
|
{
|
|
CLDAPRootDSE FAR * pGenObject = NULL;
|
|
CAggregatorDispMgr FAR * pDispMgr = NULL;
|
|
CPropertyCache FAR * pPropertyCache = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
pGenObject = new CLDAPRootDSE();
|
|
if (pGenObject == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pDispMgr = new CAggregatorDispMgr(Credentials);
|
|
if (pDispMgr == NULL) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pDispMgr->LoadTypeInfoEntry(
|
|
LIBID_ADs,
|
|
IID_IADs,
|
|
(IADs *)pGenObject,
|
|
DISPID_REGULAR
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pDispMgr->LoadTypeInfoEntry(
|
|
LIBID_ADs,
|
|
IID_IADsPropertyList,
|
|
(IADsPropertyList *)pGenObject,
|
|
DISPID_VALUE
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pDispMgr->LoadTypeInfoEntry(
|
|
LIBID_ADs,
|
|
IID_IADsObjectOptions,
|
|
(IADsObjectOptions *)pGenObject,
|
|
DISPID_VALUE
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = CPropertyCache::createpropertycache(
|
|
(CCoreADsObject FAR *) pGenObject,
|
|
(IGetAttributeSyntax *) pGenObject,
|
|
&pPropertyCache
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pDispMgr->RegisterPropertyCache(pPropertyCache);
|
|
|
|
pGenObject->_Credentials = Credentials;
|
|
pGenObject->_pPropertyCache = pPropertyCache;
|
|
pGenObject->_pDispMgr = pDispMgr;
|
|
*ppGenObject = pGenObject;
|
|
|
|
RRETURN(hr);
|
|
|
|
error:
|
|
delete pDispMgr;
|
|
delete pGenObject;
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Get(
|
|
THIS_ BSTR bstrName,
|
|
VARIANT FAR* pvProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// For some folks who have no clue what they are doing.
|
|
//
|
|
if (!pvProp) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
//
|
|
// retrieve data object from cache; if one exists
|
|
//
|
|
|
|
if ( GetObjectState() == ADS_OBJECT_UNBOUND ) {
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
bstrName,
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility
|
|
if (!ldapSrcObjects.pLdapObjects && SUCCEEDED(hr)) {
|
|
hr = E_FAIL;
|
|
}
|
|
} else {
|
|
|
|
hr = _pPropertyCache->getproperty(
|
|
bstrName,
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility
|
|
if (!ldapSrcObjects.pLdapObjects && SUCCEEDED(hr)) {
|
|
hr = E_ADS_PROPERTY_NOT_FOUND;
|
|
}
|
|
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// translate the Ldap objects to variants
|
|
//
|
|
|
|
if ( ldapSrcObjects.dwCount == 1 ) {
|
|
|
|
hr = LdapTypeToVarTypeCopy(
|
|
_pszLDAPServer,
|
|
_Credentials,
|
|
ldapSrcObjects.pLdapObjects,
|
|
dwSyntaxId,
|
|
pvProp
|
|
);
|
|
} else {
|
|
|
|
hr = LdapTypeToVarTypeCopyConstruct(
|
|
_pszLDAPServer,
|
|
_Credentials,
|
|
ldapSrcObjects,
|
|
dwSyntaxId,
|
|
pvProp
|
|
);
|
|
}
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Put(
|
|
THIS_ BSTR bstrName,
|
|
VARIANT vProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId = 0;
|
|
|
|
DWORD dwIndex = 0;
|
|
LDAPOBJECTARRAY ldapDestObjects;
|
|
|
|
DWORD dwNumValues = 0;
|
|
VARIANT * pVarArray = NULL;
|
|
VARIANT * pvProp = NULL;
|
|
VARIANT vDefProp;
|
|
|
|
VariantInit(&vDefProp);
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapDestObjects);
|
|
|
|
//
|
|
// A VT_BYREF|VT_VARIANT may expand to a VT_VARIANT|VT_ARRAY.
|
|
// We should dereference a VT_BYREF|VT_VARIANT once and see
|
|
// what's inside.
|
|
//
|
|
pvProp = &vProp;
|
|
if (V_VT(pvProp) == (VT_BYREF|VT_VARIANT)) {
|
|
pvProp = V_VARIANTREF(&vProp);
|
|
}
|
|
|
|
if ((V_VT(pvProp) == (VT_VARIANT|VT_ARRAY|VT_BYREF)) ||
|
|
(V_VT(pvProp) == (VT_VARIANT|VT_ARRAY))) {
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
vProp,
|
|
&pVarArray,
|
|
&dwNumValues
|
|
);
|
|
// returns E_FAIL if vProp is invalid
|
|
if (hr == E_FAIL)
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
pvProp = pVarArray;
|
|
|
|
}
|
|
else {
|
|
|
|
//
|
|
// If this is a single VT_BYREF of a basic type, we dereference
|
|
// it once.
|
|
//
|
|
if (V_ISBYREF(pvProp)) {
|
|
hr = VariantCopyInd(&vDefProp, pvProp);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
pvProp = &vDefProp;
|
|
}
|
|
|
|
dwNumValues = 1;
|
|
}
|
|
|
|
//
|
|
// check if the variant maps to the syntax of this property
|
|
//
|
|
|
|
hr = GetLdapSyntaxFromVariant(
|
|
pvProp,
|
|
&dwSyntaxId,
|
|
_pszLDAPServer,
|
|
bstrName,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
if ( dwNumValues > 0 )
|
|
{
|
|
hr = VarTypeToLdapTypeCopyConstruct(
|
|
_pszLDAPServer,
|
|
_Credentials,
|
|
dwSyntaxId,
|
|
pvProp,
|
|
dwNumValues,
|
|
&ldapDestObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
//
|
|
// Find this property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->findproperty(
|
|
bstrName,
|
|
&dwIndex
|
|
);
|
|
|
|
//
|
|
// If this property does not exist in the
|
|
// cache, add this property into the cache.
|
|
//
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
hr = _pPropertyCache->addproperty( bstrName );
|
|
|
|
//
|
|
// If dwNumValues == 0 ( delete the property ) but couldn't find
|
|
// the property, or if the add operation fails, return the error.
|
|
//
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
}
|
|
|
|
//
|
|
// Now update the property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->putproperty(
|
|
bstrName,
|
|
PROPERTY_UPDATE,
|
|
dwSyntaxId,
|
|
ldapDestObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapDestObjects );
|
|
|
|
if (pVarArray) {
|
|
|
|
DWORD i = 0;
|
|
|
|
for (i = 0; i < dwNumValues; i++) {
|
|
VariantClear(pVarArray + i);
|
|
}
|
|
FreeADsMem(pVarArray);
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::GetEx(
|
|
THIS_ BSTR bstrName,
|
|
VARIANT FAR* pvProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// For those who know no not what they do
|
|
//
|
|
if (!pvProp) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
//
|
|
// retrieve data object from cache; if one exists
|
|
//
|
|
|
|
if ( GetObjectState() == ADS_OBJECT_UNBOUND ) {
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
bstrName,
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility
|
|
if (!ldapSrcObjects.pLdapObjects && SUCCEEDED(hr)) {
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
} else {
|
|
|
|
hr = _pPropertyCache->getproperty(
|
|
bstrName,
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility
|
|
if (!ldapSrcObjects.pLdapObjects && SUCCEEDED(hr)) {
|
|
hr = E_ADS_PROPERTY_NOT_FOUND;
|
|
}
|
|
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// translate the Ldap objects to variants
|
|
//
|
|
|
|
hr = LdapTypeToVarTypeCopyConstruct(
|
|
_pszLDAPServer,
|
|
_Credentials,
|
|
ldapSrcObjects,
|
|
dwSyntaxId,
|
|
pvProp
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::PutEx(
|
|
THIS_ long lnControlCode,
|
|
BSTR bstrName,
|
|
VARIANT vProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId = 0;
|
|
DWORD dwFlags = 0;
|
|
|
|
DWORD dwIndex = 0;
|
|
LDAPOBJECTARRAY ldapDestObjects;
|
|
|
|
DWORD dwNumValues = 0;
|
|
VARIANT * pVarArray = NULL;
|
|
VARIANT * pvProp = NULL;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapDestObjects);
|
|
|
|
switch ( lnControlCode ) {
|
|
case ADS_PROPERTY_CLEAR:
|
|
dwFlags = PROPERTY_DELETE;
|
|
break;
|
|
|
|
case ADS_PROPERTY_APPEND:
|
|
dwFlags = PROPERTY_ADD;
|
|
break;
|
|
|
|
|
|
case ADS_PROPERTY_UPDATE:
|
|
dwFlags = PROPERTY_UPDATE;
|
|
break;
|
|
|
|
default:
|
|
RRETURN(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
if ( dwFlags != PROPERTY_DELETE )
|
|
{
|
|
//
|
|
// A VT_BYREF|VT_VARIANT may expand to a VT_VARIANT|VT_ARRAY.
|
|
// We should dereference a VT_BYREF|VT_VARIANT once and see
|
|
// what's inside.
|
|
//
|
|
pvProp = &vProp;
|
|
if (V_VT(pvProp) == (VT_BYREF|VT_VARIANT)) {
|
|
pvProp = V_VARIANTREF(&vProp);
|
|
}
|
|
|
|
if ((V_VT(pvProp) == (VT_VARIANT|VT_ARRAY|VT_BYREF)) ||
|
|
(V_VT(pvProp) == (VT_VARIANT|VT_ARRAY))) {
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
*pvProp,
|
|
&pVarArray,
|
|
&dwNumValues
|
|
);
|
|
// returns E_FAIL if *pvProp is invalid
|
|
if (hr == E_FAIL)
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
pvProp = pVarArray;
|
|
|
|
} else {
|
|
|
|
hr = E_FAIL;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
//
|
|
// check if the variant maps to the syntax of this property
|
|
//
|
|
|
|
//
|
|
// check if the variant maps to the syntax of this property
|
|
//
|
|
|
|
hr = GetLdapSyntaxFromVariant(
|
|
pvProp,
|
|
&dwSyntaxId,
|
|
_pszLDAPServer,
|
|
bstrName,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if ( dwNumValues > 0 )
|
|
{
|
|
hr = VarTypeToLdapTypeCopyConstruct(
|
|
_pszLDAPServer,
|
|
_Credentials,
|
|
dwSyntaxId,
|
|
pvProp,
|
|
dwNumValues,
|
|
&ldapDestObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Find this property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->findproperty(
|
|
bstrName,
|
|
&dwIndex
|
|
);
|
|
|
|
//
|
|
// If this property does not exist in the
|
|
// cache, add this property into the cache.
|
|
//
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
hr = _pPropertyCache->addproperty( bstrName );
|
|
|
|
//
|
|
// If dwNumValues == 0 ( delete the property ) but couldn't find
|
|
// the property, or if the add operation fails, return the error.
|
|
//
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
//
|
|
// Now update the property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->putproperty(
|
|
bstrName,
|
|
dwFlags,
|
|
dwSyntaxId,
|
|
ldapDestObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapDestObjects );
|
|
|
|
if (pVarArray) {
|
|
|
|
DWORD i = 0;
|
|
|
|
for (i = 0; i < dwNumValues; i++) {
|
|
VariantClear(pVarArray + i);
|
|
}
|
|
FreeADsMem(pVarArray);
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::get_PropertyCount(
|
|
THIS_ long FAR *plCount
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
if (!plCount) {
|
|
RRETURN(E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
if (_pPropertyCache) {
|
|
|
|
hr = _pPropertyCache->get_PropertyCount((PDWORD)plCount);
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Next(
|
|
THIS_ VARIANT FAR *pVariant
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
DWORD dwSyntaxId = 0;
|
|
DWORD dwNumValues = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
IDispatch * pDispatch = NULL;
|
|
DWORD dwNumAdsValues = 0;
|
|
DWORD dwAdsType = 0;
|
|
DWORD dwPropStatus = 0;
|
|
DWORD dwCtrlCode = 0;
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
|
|
if (!pVariant) {
|
|
RRETURN(E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
if(!_pPropertyCache->index_valid())
|
|
RRETURN_EXP_IF_ERR(E_FAIL);
|
|
|
|
//
|
|
// retreive the item with current idex; unboundgetproperty()
|
|
// returns E_ADS_PROPERTY_NOT_FOUND if index out of bound
|
|
//
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
_pPropertyCache->get_CurrentIndex(),
|
|
&dwSyntaxId,
|
|
&dwPropStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwPropStatus);
|
|
|
|
|
|
//
|
|
// translate the LDAP objects to variants
|
|
//
|
|
|
|
hr = ConvertLdapValuesToVariant(
|
|
_pPropertyCache->get_CurrentPropName(),
|
|
&ldapSrcObjects,
|
|
dwSyntaxId,
|
|
dwCtrlCode,
|
|
pVariant,
|
|
_pszLDAPServer,
|
|
&_Credentials
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
error:
|
|
|
|
//
|
|
// - goto next one even if error to avoid infinite looping at a property
|
|
// which we cannot convert (e.g. schemaless server property.)
|
|
// - do not return the result of Skip() as current operation does not
|
|
// depend on the success of Skip().
|
|
//
|
|
|
|
Skip(1);
|
|
|
|
LdapTypeFreeLdapObjects(&ldapSrcObjects);
|
|
|
|
if (FAILED(hr)) {
|
|
V_VT(pVariant) = VT_ERROR;
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Skip(
|
|
THIS_ long cElements
|
|
)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
|
|
hr = _pPropertyCache->skip_propindex(
|
|
cElements
|
|
);
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Reset(
|
|
|
|
)
|
|
{
|
|
_pPropertyCache->reset_propindex();
|
|
|
|
RRETURN(S_OK);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::ResetPropertyItem(THIS_ VARIANT varEntry)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwIndex = 0;
|
|
|
|
switch (V_VT(&varEntry)) {
|
|
|
|
case VT_BSTR:
|
|
|
|
hr = _pPropertyCache->findproperty(
|
|
V_BSTR(&varEntry),
|
|
&dwIndex
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
break;
|
|
|
|
case VT_I4:
|
|
dwIndex = V_I4(&varEntry);
|
|
break;
|
|
|
|
|
|
case VT_I2:
|
|
dwIndex = V_I2(&varEntry);
|
|
break;
|
|
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = _pPropertyCache->deleteproperty(
|
|
dwIndex
|
|
);
|
|
error:
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::GetPropertyItem(
|
|
THIS_ BSTR bstrName,
|
|
LONG lnType,
|
|
VARIANT * pVariant
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
DWORD dwNumValues = 0;
|
|
|
|
DWORD dwUserSyntaxId = 0;
|
|
DWORD dwStatus = 0;
|
|
DWORD dwCtrlCode = 0;
|
|
|
|
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// retrieve data object from cache; do NOT retreive from server
|
|
//
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
bstrName,
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// translate the Ldap objects to variants
|
|
//
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwStatus);
|
|
|
|
hr = ConvertLdapValuesToVariant(
|
|
bstrName,
|
|
&ldapSrcObjects,
|
|
dwSyntaxId,
|
|
dwCtrlCode,
|
|
pVariant,
|
|
_pszLDAPServer,
|
|
&_Credentials
|
|
);
|
|
|
|
error:
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::PutPropertyItem(
|
|
THIS_ VARIANT varData
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwFlags = 0;
|
|
|
|
DWORD dwIndex = 0;
|
|
DWORD dwControlCode = 0;
|
|
LDAPOBJECTARRAY ldapDestObjects;
|
|
WCHAR szPropertyName[MAX_PATH];
|
|
DWORD dwSyntaxId = 0;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapDestObjects);
|
|
|
|
hr = ConvertVariantToLdapValues(
|
|
varData,
|
|
szPropertyName,
|
|
&dwControlCode,
|
|
&ldapDestObjects,
|
|
&dwSyntaxId,
|
|
_pszLDAPServer,
|
|
&_Credentials,
|
|
_dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
switch ( dwControlCode ) {
|
|
|
|
case ADS_PROPERTY_CLEAR:
|
|
|
|
//
|
|
// Clears an entire property
|
|
//
|
|
|
|
dwFlags = PROPERTY_DELETE;
|
|
break;
|
|
|
|
case ADS_PROPERTY_UPDATE:
|
|
|
|
//
|
|
// Updates the entire property
|
|
//
|
|
|
|
dwFlags = PROPERTY_UPDATE;
|
|
break;
|
|
|
|
case ADS_PROPERTY_APPEND:
|
|
//
|
|
// Appends a set of values to the property
|
|
//
|
|
|
|
break;
|
|
|
|
case ADS_PROPERTY_DELETE:
|
|
//
|
|
// Delete a value(s) from the property
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Find this property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->findproperty(
|
|
szPropertyName,
|
|
&dwIndex
|
|
);
|
|
|
|
//
|
|
// If this property does not exist in the
|
|
// cache, add this property into the cache.
|
|
//
|
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
hr = _pPropertyCache->addproperty( szPropertyName );
|
|
|
|
//
|
|
// If dwNumValues == 0 ( delete the property ) but couldn't find
|
|
// the property, or if the add operation fails, return the error.
|
|
//
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
//
|
|
// Now update the property in the cache
|
|
//
|
|
|
|
hr = _pPropertyCache->putproperty(
|
|
szPropertyName,
|
|
dwFlags,
|
|
dwSyntaxId,
|
|
ldapDestObjects
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapDestObjects );
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::Item(THIS_ VARIANT varIndex, VARIANT * pVariant)
|
|
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
PADSVALUE pAdsValues = NULL;
|
|
DWORD dwNumValues = 0;
|
|
DWORD dwNumAdsValues = 0;
|
|
DWORD dwAdsType = 0;
|
|
DWORD dwStatus = 0;
|
|
DWORD dwCtrlCode = 0;
|
|
VARIANT *pvVar = &varIndex;
|
|
LPWSTR szPropName = NULL;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// retrieve data object from cache; if one exis
|
|
//
|
|
|
|
if (V_VT(pvVar) == (VT_BYREF|VT_VARIANT)) {
|
|
//
|
|
// The value is being passed in byref so we need to
|
|
// deref it for vbs stuff to work
|
|
//
|
|
pvVar = V_VARIANTREF(&varIndex);
|
|
}
|
|
|
|
switch (V_VT(pvVar)) {
|
|
|
|
case VT_BSTR:
|
|
//
|
|
// retrieve data object from cache; if one exists
|
|
//
|
|
|
|
if ( GetObjectState() == ADS_OBJECT_UNBOUND ) {
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
V_BSTR(pvVar),
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility -- nothing done, you
|
|
// should be able to get an item marked as delete.
|
|
|
|
} else {
|
|
|
|
hr = _pPropertyCache->getproperty(
|
|
V_BSTR(pvVar),
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility -- nothing done,
|
|
// you should be able to get an item marked as delete.
|
|
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
szPropName = V_BSTR(pvVar);
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwStatus);
|
|
break;
|
|
|
|
case VT_I4:
|
|
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
(DWORD)V_I4(pvVar),
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility -- nothing done, you
|
|
// should be able to get an item marked as delte.
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
szPropName = _pPropertyCache->get_PropName(
|
|
(DWORD)V_I4(pvVar)
|
|
);
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwStatus);
|
|
break;
|
|
|
|
|
|
case VT_I2:
|
|
|
|
hr = _pPropertyCache->unboundgetproperty(
|
|
(DWORD)V_I2(pvVar),
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
// For backward compatibility -- nothing done, you
|
|
// should be able to get an item marked as delete.
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
szPropName = _pPropertyCache->get_PropName(
|
|
(DWORD)V_I2(pvVar)
|
|
);
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwStatus);
|
|
break;
|
|
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
}
|
|
|
|
//
|
|
// translate the Ldap objects to variants
|
|
//
|
|
|
|
dwCtrlCode = MapPropCacheFlagToControlCode(dwStatus);
|
|
|
|
hr = ConvertLdapValuesToVariant(
|
|
szPropName,
|
|
&ldapSrcObjects,
|
|
dwSyntaxId,
|
|
dwCtrlCode,
|
|
pVariant,
|
|
_pszLDAPServer,
|
|
&_Credentials
|
|
);
|
|
|
|
error:
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::PurgePropertyList()
|
|
{
|
|
_pPropertyCache->flushpropertycache();
|
|
RRETURN(S_OK);
|
|
}
|
|
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::GetInfo(
|
|
LPWSTR szPropertyName,
|
|
DWORD dwSyntaxId,
|
|
BOOL fExplicit
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPMessage *res = NULL;
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
hr = E_ADS_OBJECT_UNBOUND;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = LdapSearchS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
NULL,
|
|
0,
|
|
&res
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if ( fExplicit )
|
|
{
|
|
// If this is an explicit GetInfo,
|
|
// delete the old cache and start a new cache from scratch.
|
|
|
|
_pPropertyCache->flushpropertycache();
|
|
}
|
|
|
|
hr = _pPropertyCache->LDAPUnMarshallPropertiesAs(
|
|
_pszLDAPServer,
|
|
_pLdapHandle,
|
|
res,
|
|
ADSTYPE_CASE_IGNORE_STRING,
|
|
fExplicit,
|
|
_Credentials
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
_pPropertyCache->setGetInfoFlag();
|
|
|
|
|
|
error:
|
|
|
|
if (res) {
|
|
|
|
LdapMsgFree( res );
|
|
}
|
|
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
//
|
|
// Needed for dynamic dispid's in the property cache.
|
|
//
|
|
HRESULT
|
|
CLDAPRootDSE::GetAttributeSyntax(
|
|
LPWSTR szPropertyName,
|
|
PDWORD pdwSyntaxId
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
hr = LdapGetSyntaxOfAttributeOnServer(
|
|
_pszLDAPServer,
|
|
szPropertyName,
|
|
pdwSyntaxId,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// IADsObjecOptions methods
|
|
//
|
|
|
|
//
|
|
// Unlike the cgenobj GetOption implementation, this will support
|
|
// only a subset of the flags - mutual auth status being the only one.
|
|
//
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::GetOption(
|
|
THIS_ long lnControlCode,
|
|
VARIANT FAR* pvProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
ULONG ulFlags = 0;
|
|
CtxtHandle hCtxtHandle;
|
|
DWORD dwErr = 0;
|
|
|
|
VariantInit(pvProp);
|
|
|
|
switch (lnControlCode) {
|
|
|
|
case ADS_OPTION_MUTUAL_AUTH_STATUS :
|
|
|
|
dwErr = ldap_get_option(
|
|
_pLdapHandle->LdapHandle,
|
|
LDAP_OPT_SECURITY_CONTEXT,
|
|
(void *) &hCtxtHandle
|
|
);
|
|
if (dwErr) {
|
|
BAIL_ON_FAILURE(hr = E_FAIL);
|
|
}
|
|
|
|
//DSCLIENT
|
|
|
|
#if (!defined(WIN95))
|
|
|
|
dwErr = QueryContextAttributesWrapper(
|
|
&hCtxtHandle,
|
|
SECPKG_ATTR_FLAGS,
|
|
(void *) &ulFlags
|
|
);
|
|
if (dwErr) {
|
|
BAIL_ON_FAILURE(hr = HRESULT_FROM_WIN32(dwErr));
|
|
}
|
|
#else
|
|
ulFlags = 0;
|
|
#endif
|
|
pvProp->vt = VT_I4;
|
|
pvProp->lVal = ulFlags;
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
error:
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPRootDSE::SetOption(
|
|
THIS_ long lnControlCode,
|
|
VARIANT vProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwOptVal = 0;
|
|
VARIANT *pvProp = NULL;
|
|
|
|
//
|
|
// To make sure we handle variant by refs correctly.
|
|
//
|
|
pvProp = &vProp;
|
|
if (V_VT(pvProp) == (VT_BYREF|VT_VARIANT)) {
|
|
pvProp = V_VARIANTREF(&vProp);
|
|
}
|
|
|
|
switch (lnControlCode) {
|
|
|
|
case ADS_PRIVATE_OPTION_KEEP_HANDLES :
|
|
|
|
hr = LdapcKeepHandleAround(_pLdapHandle);
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|