You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4490 lines
109 KiB
4490 lines
109 KiB
//----------------------------------------------------------------------------
|
|
//
|
|
// 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
|
|
|
|
#ifdef __cplusplus
|
|
extern "C"
|
|
#else
|
|
extern
|
|
#endif
|
|
HRESULT
|
|
AdsTypeToPropVariant2(
|
|
PADSVALUE pAdsValues,
|
|
DWORD dwNumValues,
|
|
VARIANT * pVariant,
|
|
LPWSTR pszServerName,
|
|
CCredentials* pCredentials,
|
|
BOOL fNTDSType
|
|
);
|
|
|
|
#ifdef __cplusplus
|
|
extern "C"
|
|
#else
|
|
extern
|
|
#endif
|
|
HRESULT
|
|
PropVariantToAdsType2(
|
|
PVARIANT pVariant,
|
|
DWORD dwNumVariant,
|
|
PADSVALUE *ppAdsValues,
|
|
PDWORD pdwNumValues,
|
|
LPWSTR pszServerName,
|
|
CCredentials* pCredentials,
|
|
BOOL fNTDSType
|
|
);
|
|
|
|
//
|
|
// Helper routine that handles setting the sticky server private
|
|
// option when the input is an array.
|
|
//
|
|
HRESULT
|
|
SetStickyServerWithDomain(
|
|
PVARIANT pvProp
|
|
);
|
|
|
|
// Class CLDAPGenObject
|
|
|
|
DEFINE_IDispatch_ExtMgr_Implementation(CLDAPGenObject)
|
|
DEFINE_IADs_Shorter_Implementation(CLDAPGenObject)
|
|
|
|
//
|
|
// This is a useful function
|
|
//
|
|
HRESULT GetIntegerFromVariant(
|
|
VARIANT* pvProp,
|
|
DWORD* pdwValue
|
|
);
|
|
|
|
typedef struct _classeshierarchylist {
|
|
LPWSTR pszClassName;
|
|
struct _classeshierarchylist *pNext;
|
|
} ClassesHierarchyList, *PClassesHierarchyList;
|
|
|
|
|
|
//
|
|
// Helper to Trace The Tree for a class
|
|
//
|
|
HRESULT
|
|
TraceTreeForClass(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
LPWSTR pszClassName,
|
|
CCredentials& Credentials,
|
|
PWCHAR **pppszNameArr,
|
|
PLONG plnNumElements
|
|
);
|
|
|
|
HRESULT
|
|
AddToClassesList(
|
|
VARIANT vBstrVal,
|
|
LPWSTR *ppszCurClass,
|
|
PClassesHierarchyList *pClassListhead,
|
|
PLONG plnNumItems
|
|
);
|
|
|
|
|
|
CLDAPGenObject::CLDAPGenObject():
|
|
_pExtMgr(NULL),
|
|
_pPropertyCache( NULL ),
|
|
_pDispMgr( NULL ),
|
|
_pszLDAPServer(NULL),
|
|
_pszLDAPDn(NULL),
|
|
_pLdapHandle( NULL ),
|
|
_dwOptReferral((DWORD) LDAP_CHASE_EXTERNAL_REFERRALS),
|
|
_dwPageSize(99),
|
|
_dwCorePropStatus(0),
|
|
_fRangeRetrieval(FALSE),
|
|
_pSid(NULL),
|
|
_dwSidLength(0),
|
|
_dwPasswordPort((DWORD) -1),
|
|
_dwPasswordMethod(ADS_PASSWORD_ENCODE_REQUIRE_SSL),
|
|
_fPasswordPortSet(FALSE)
|
|
{
|
|
VariantInit(&_vFilter);
|
|
VariantInit(&_vHints);
|
|
|
|
_seInfo = OWNER_SECURITY_INFORMATION
|
|
| GROUP_SECURITY_INFORMATION
|
|
| DACL_SECURITY_INFORMATION;
|
|
|
|
_fExplicitSecurityMask = FALSE;
|
|
|
|
LdapInitializeSearchPreferences(&_SearchPref, TRUE);
|
|
|
|
ENLIST_TRACKING(CLDAPGenObject);
|
|
}
|
|
|
|
//
|
|
// fClassDefaulted indicates if the class has been defaulted
|
|
// because of a fast bind flag or otherwise.
|
|
//
|
|
HRESULT
|
|
CLDAPGenObject::CreateGenericObject(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
BSTR LdapClassName,
|
|
CCredentials& Credentials,
|
|
DWORD dwObjectState,
|
|
REFIID riid,
|
|
void **ppvObj,
|
|
BOOL fClassDefaulted,
|
|
BOOL fNoQI // defaulted to FALSE
|
|
)
|
|
{
|
|
|
|
//
|
|
// Call into the fully featured Create
|
|
//
|
|
LPWSTR pszClassNames[2];
|
|
HRESULT hr = S_OK;
|
|
PWCHAR *ppListOfNames;
|
|
LONG lnNumNames = 0;
|
|
|
|
pszClassNames[0] = LdapClassName;
|
|
pszClassNames[1] = NULL;
|
|
|
|
hr = TraceTreeForClass(
|
|
Parent,
|
|
CommonName,
|
|
LdapClassName,
|
|
Credentials,
|
|
&ppListOfNames,
|
|
&lnNumNames
|
|
);
|
|
|
|
if (FAILED(hr)) {
|
|
//
|
|
// Default to just the class name given
|
|
//
|
|
RRETURN(CreateGenericObject(
|
|
Parent,
|
|
CommonName,
|
|
pszClassNames,
|
|
1,
|
|
Credentials,
|
|
dwObjectState,
|
|
riid,
|
|
ppvObj,
|
|
fClassDefaulted,
|
|
fNoQI
|
|
)
|
|
);
|
|
}
|
|
else {
|
|
//
|
|
// Create with all the classes specified
|
|
//
|
|
hr = CreateGenericObject(
|
|
Parent,
|
|
CommonName,
|
|
ppListOfNames,
|
|
lnNumNames,
|
|
Credentials,
|
|
dwObjectState,
|
|
riid,
|
|
ppvObj,
|
|
fClassDefaulted,
|
|
fNoQI
|
|
);
|
|
|
|
for (long i = 0; i < lnNumNames; i++) {
|
|
if (ppListOfNames[i]) {
|
|
FreeADsStr(ppListOfNames[i]);
|
|
}
|
|
}
|
|
|
|
if (ppListOfNames) {
|
|
FreeADsMem(ppListOfNames);
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Function: CLDAPGenObject::CreateGenericObject
|
|
//
|
|
// Synopsis: Does all the work and actually creates the object.
|
|
// Difference from the overlaoded member being that it accepts an
|
|
// array of values for the class name so that it can load
|
|
// extensions for all the classes in the inheritance hierarchy.
|
|
//
|
|
// Arguments:
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
HRESULT
|
|
CLDAPGenObject::CreateGenericObject(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
LPWSTR LdapClassNames[],
|
|
long lnNumClasses,
|
|
CCredentials& Credentials,
|
|
DWORD dwObjectState,
|
|
REFIID riid,
|
|
void **ppvObj,
|
|
BOOL fClassDefaulted,
|
|
BOOL fNoQI // will return cgenobject if set to TRUE, defaulted FALSE
|
|
)
|
|
{
|
|
CLDAPGenObject FAR * pGenObject = NULL;
|
|
HRESULT hr = S_OK;
|
|
DWORD dwCtr = 0;
|
|
LPWSTR pszBaseClassName = GET_BASE_CLASS(LdapClassNames, lnNumClasses);
|
|
|
|
hr = AllocateGenObject(pszBaseClassName, Credentials, &pGenObject);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
ADsAssert(pGenObject->_pDispMgr);
|
|
|
|
|
|
pGenObject->_Credentials = Credentials;
|
|
|
|
|
|
hr = pGenObject->InitializeCoreObject(
|
|
Parent,
|
|
CommonName,
|
|
pszBaseClassName,
|
|
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
|
|
//
|
|
hr = pGenObject->_pPropertyCache->SetObjInformation(
|
|
&(pGenObject->_Credentials),
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Create and Load 3rd party extensions and the extension mgr.
|
|
// This should be done after initilaization of core and generic object s.t
|
|
// 1) an extension writer can access info on IADs (e.g _Parent) etc
|
|
// during extension creation, if he wants to.
|
|
// 2) we shouldn't waste the effort to create and load extension
|
|
// objects if fialure in the aggregator's creation
|
|
//
|
|
|
|
hr = CADsExtMgr::CreateExtMgr(
|
|
(IADs *)pGenObject,
|
|
pGenObject->_pDispMgr,
|
|
LdapClassNames,
|
|
lnNumClasses,
|
|
&(pGenObject->_Credentials),
|
|
&pGenObject->_pExtMgr
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
ADsAssert(pGenObject->_pExtMgr);
|
|
|
|
hr = LdapOpenObject(
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_pszLDAPDn,
|
|
&(pGenObject->_pLdapHandle),
|
|
pGenObject->_Credentials,
|
|
pGenObject->_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (!fClassDefaulted) {
|
|
//
|
|
// Update the status to reflect we do not need to go
|
|
// on the wire to get the class
|
|
//
|
|
pGenObject->_dwCorePropStatus |= LDAP_CLASS_VALID;
|
|
}
|
|
|
|
|
|
if (fNoQI) {
|
|
*ppvObj = (void *) pGenObject;
|
|
}
|
|
else if (Credentials.GetAuthFlags() & ADS_AUTH_RESERVED) {
|
|
//
|
|
// Call is from umi so we need to create the umi object.
|
|
//
|
|
hr = ((CCoreADsObject*)pGenObject)->InitUmiObject(
|
|
IntfPropsGeneric,
|
|
pGenObject->_pPropertyCache,
|
|
(IADs *) pGenObject,
|
|
(IADs *) pGenObject,
|
|
IID_IUnknown,
|
|
ppvObj,
|
|
&(pGenObject->_Credentials),
|
|
pGenObject->_dwPort,
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_pszLDAPDn,
|
|
pGenObject->_pLdapHandle,
|
|
pGenObject->_pExtMgr
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
RRETURN(S_OK);
|
|
|
|
} else {
|
|
//
|
|
// Need the appropriate interface.
|
|
//
|
|
hr = pGenObject->QueryInterface(riid, ppvObj);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pGenObject->Release();
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
RRETURN(hr);
|
|
|
|
error:
|
|
|
|
*ppvObj = NULL;
|
|
delete pGenObject;
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
// Function: CLDAPGenObject::CreateGenericObject (static constructor), this
|
|
// constructor is used by Umi Searches only.
|
|
//
|
|
// Synopsis: This routine uses the other static constructor routines
|
|
// depending on the objectClass information available in the passed
|
|
// in ldapMsg. The state of the newly created object reflects this
|
|
// information. The newly created object is then prepopulated with
|
|
// the attributes in the ldapMsg. The return value from this routine
|
|
// is the newly created object (interface asked on this object).
|
|
//
|
|
// Arguments: Parent - Path to the parent of the newly created object.
|
|
// CommonName - Name of new object to create.
|
|
// Credentials - Standard credentials object.
|
|
// dwObjectState- Bound/unbound (always bound in this case).
|
|
// ldapHandle - Handle used in the deciphering search info.
|
|
// pldapMsg - The Ldao msg with the attributes.
|
|
// riid - IID requested on newly created object.
|
|
// ppvObj - Return value.
|
|
//
|
|
//
|
|
// Returns: S_OK, or any appropriate error code.
|
|
//
|
|
// Modifies: ppvObj contains newly created object with attributes.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HRESULT
|
|
CLDAPGenObject::CreateGenericObject(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
CCredentials& Credentials,
|
|
DWORD dwObjectState,
|
|
PADSLDP ldapHandle,
|
|
LDAPMessage *pldapMsg,
|
|
REFIID riid,
|
|
void **ppvObj
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
TCHAR **aValues = NULL;
|
|
int nCount;
|
|
CLDAPGenObject *pGenObject = NULL;
|
|
|
|
hr = LdapGetValues(
|
|
ldapHandle,
|
|
pldapMsg,
|
|
L"objectClass",
|
|
&aValues,
|
|
&nCount
|
|
);
|
|
|
|
if (FAILED(hr) || !aValues) {
|
|
//
|
|
// We do not have the objectClass
|
|
//
|
|
hr = CLDAPGenObject::CreateGenericObject(
|
|
Parent,
|
|
CommonName,
|
|
L"Top",
|
|
Credentials,
|
|
dwObjectState,
|
|
riid,
|
|
(void **) &pGenObject,
|
|
TRUE, // class is defaulted
|
|
TRUE // No QI
|
|
);
|
|
|
|
}
|
|
else {
|
|
//
|
|
// We have the info we need for the constructor with class name
|
|
//
|
|
hr = CLDAPGenObject::CreateGenericObject(
|
|
Parent,
|
|
CommonName,
|
|
aValues,
|
|
nCount,
|
|
Credentials,
|
|
dwObjectState,
|
|
riid,
|
|
(void **) &pGenObject,
|
|
FALSE, // objectClass is not defaulted
|
|
TRUE // No QI
|
|
);
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Now we need to prepopulate the object.
|
|
//
|
|
hr = pGenObject->_pPropertyCache->
|
|
LDAPUnMarshallProperties2(
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_pLdapHandle,
|
|
pldapMsg,
|
|
FALSE, // this is not an explicit getinfo.
|
|
pGenObject->_Credentials,
|
|
&pGenObject->_fRangeRetrieval
|
|
);
|
|
//
|
|
// Should we really fail if we could not unmarshall properties ???
|
|
///
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// This call is always from UMI, so now we need to get the
|
|
// outer umiObject and return that.
|
|
//
|
|
hr = ((CCoreADsObject*)pGenObject)->InitUmiObject(
|
|
IntfPropsGeneric,
|
|
pGenObject->_pPropertyCache,
|
|
(IADs *) pGenObject,
|
|
(IADs *) pGenObject,
|
|
riid,
|
|
ppvObj,
|
|
&(pGenObject->_Credentials),
|
|
pGenObject->_dwPort,
|
|
pGenObject->_pszLDAPServer,
|
|
pGenObject->_pszLDAPDn,
|
|
pGenObject->_pLdapHandle,
|
|
pGenObject->_pExtMgr
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Only thing remaining is to get a list of the attributes fetched
|
|
// into this object. That way we wont go on the wire for them. This
|
|
// can be done once the GetInfoEx related bug is fixed.
|
|
//
|
|
|
|
error :
|
|
|
|
if (pGenObject && FAILED(hr)) {
|
|
delete pGenObject;
|
|
*ppvObj = NULL;
|
|
}
|
|
|
|
//
|
|
// Need to free the Ldap values in all cases.
|
|
//
|
|
if (aValues) {
|
|
LdapValueFree(aValues);
|
|
aValues = NULL;
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
CLDAPGenObject::~CLDAPGenObject( )
|
|
{
|
|
|
|
//
|
|
// last to be created - first to be unloaded
|
|
//
|
|
|
|
delete _pExtMgr;
|
|
|
|
VariantClear(&_vFilter);
|
|
VariantClear(&_vHints);
|
|
|
|
if ( _pLdapHandle )
|
|
{
|
|
LdapCloseObject(_pLdapHandle);
|
|
_pLdapHandle = NULL;
|
|
}
|
|
|
|
if (_pszLDAPServer) {
|
|
FreeADsStr(_pszLDAPServer);
|
|
_pszLDAPServer = NULL;
|
|
}
|
|
|
|
if (_pszLDAPDn) {
|
|
FreeADsStr(_pszLDAPDn);
|
|
_pszLDAPDn = NULL;
|
|
}
|
|
|
|
if(_pSid)
|
|
{
|
|
FreeADsMem(_pSid);
|
|
_pSid = NULL;
|
|
}
|
|
|
|
delete _pDispMgr;
|
|
|
|
delete _pPropertyCache;
|
|
|
|
//
|
|
// Free the sort keys if applicable.
|
|
//
|
|
if (_SearchPref._pSortKeys) {
|
|
for (DWORD dwCtr = 0; dwCtr < _SearchPref._nSortKeys; dwCtr++) {
|
|
if (_SearchPref._pSortKeys[dwCtr].sk_attrtype)
|
|
FreeADsStr(_SearchPref._pSortKeys[dwCtr].sk_attrtype);
|
|
}
|
|
FreeADsMem(_SearchPref._pSortKeys);
|
|
}
|
|
|
|
//
|
|
// Free the VLV information if applicable
|
|
//
|
|
if (_SearchPref._pVLVInfo) {
|
|
|
|
if (_SearchPref._pVLVInfo->ldvlv_attrvalue) {
|
|
|
|
if (_SearchPref._pVLVInfo->ldvlv_attrvalue->bv_val) {
|
|
FreeADsMem(_SearchPref._pVLVInfo->ldvlv_attrvalue->bv_val);
|
|
}
|
|
|
|
FreeADsMem(_SearchPref._pVLVInfo->ldvlv_attrvalue);
|
|
}
|
|
|
|
if (_SearchPref._pVLVInfo->ldvlv_context) {
|
|
|
|
if (_SearchPref._pVLVInfo->ldvlv_context->bv_val) {
|
|
FreeADsMem(_SearchPref._pVLVInfo->ldvlv_context->bv_val);
|
|
}
|
|
|
|
FreeADsMem(_SearchPref._pVLVInfo->ldvlv_context);
|
|
}
|
|
|
|
FreeADsMem(_SearchPref._pVLVInfo);
|
|
}
|
|
|
|
//
|
|
// Free the attribute scoped query information if applicable
|
|
//
|
|
if (_SearchPref._pAttribScoped) {
|
|
|
|
FreeADsStr(_SearchPref._pAttribScoped);
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::QueryInterface(REFIID iid, LPVOID FAR* ppv)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (ppv == NULL) {
|
|
RRETURN(E_POINTER);
|
|
}
|
|
|
|
if (IsEqualIID(iid, IID_IUnknown))
|
|
{
|
|
*ppv = (IADs FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IADsContainer))
|
|
{
|
|
*ppv = (IADsContainer 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_IDirectoryObject))
|
|
{
|
|
*ppv = (IDirectoryObject FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IDirectorySearch))
|
|
{
|
|
*ppv = (IDirectorySearch FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IDirectorySchemaMgmt))
|
|
{
|
|
*ppv = (IDirectorySchemaMgmt 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_IADsDeleteOps))
|
|
{
|
|
*ppv = (IADsDeleteOps FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_ISupportErrorInfo))
|
|
{
|
|
*ppv = (ISupportErrorInfo FAR *) this;
|
|
}
|
|
else if (IsEqualIID(iid, IID_IADsObjOptPrivate))
|
|
{
|
|
*ppv = (IADsObjOptPrivate *) this;
|
|
}
|
|
else if (_pExtMgr)
|
|
{
|
|
|
|
RRETURN(hr = _pExtMgr->QueryInterface(iid, ppv));
|
|
|
|
}else {
|
|
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
/* ISupportErrorInfo method */
|
|
HRESULT
|
|
CLDAPGenObject::InterfaceSupportsErrorInfo(THIS_ REFIID riid)
|
|
{
|
|
if (IsEqualIID(riid, IID_IADs) ||
|
|
IsEqualIID(riid, IID_IADsPropertyList) ||
|
|
IsEqualIID(riid, IID_IADsContainer) ||
|
|
#if 0
|
|
IsEqualIID(riid, IID_IDirectoryObject) ||
|
|
IsEqualIID(riid, IID_IDirectorySearch) ||
|
|
IsEqualIID(riid, IID_IDirectorySchemaMgmt) ||
|
|
#endif
|
|
IsEqualIID(riid, IID_IADsObjectOptions)) {
|
|
|
|
RRETURN(S_OK);
|
|
|
|
}
|
|
else {
|
|
|
|
RRETURN(S_FALSE);
|
|
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPGenObject::SetInfo()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
|
|
hr = LDAPCreateObject();
|
|
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
|
|
CLDAPGenObject::LDAPSetObject()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPModW **aMod = NULL;
|
|
BOOL fNTSecDes=FALSE;
|
|
DWORD dwSecDescType = ADSI_LDAPC_SECDESC_NONE;
|
|
BOOL fModifyDone = FALSE;
|
|
SECURITY_INFORMATION NewSeInfo=0;
|
|
|
|
|
|
SECURITY_INFORMATION SeInfo = _seInfo;
|
|
|
|
BYTE berValue[8];
|
|
|
|
memset(berValue, 0, 8);
|
|
|
|
berValue[0] = 0x30; // Start sequence tag
|
|
berValue[1] = 0x03; // Length in bytes of following
|
|
berValue[2] = 0x02; // Actual value this and next 2
|
|
berValue[3] = 0x01;
|
|
berValue[4] = (BYTE) ((ULONG)SeInfo);
|
|
|
|
LDAPControl SeInfoControl =
|
|
{
|
|
LDAP_SERVER_SD_FLAGS_OID_W,
|
|
{
|
|
5, (PCHAR) berValue
|
|
},
|
|
TRUE
|
|
};
|
|
|
|
LDAPControl ModifyControl =
|
|
{
|
|
LDAP_SERVER_PERMISSIVE_MODIFY_OID_W,
|
|
{
|
|
0, NULL
|
|
},
|
|
FALSE
|
|
};
|
|
|
|
PLDAPControl ServerControls[2] =
|
|
{
|
|
&SeInfoControl,
|
|
NULL
|
|
};
|
|
|
|
PLDAPControl ServerControlsOnlyModify[2] =
|
|
{
|
|
&ModifyControl,
|
|
NULL
|
|
};
|
|
|
|
PLDAPControl ServerControlsAll[3] =
|
|
{
|
|
&SeInfoControl,
|
|
&ModifyControl,
|
|
NULL
|
|
};
|
|
|
|
BOOL fServerIsAD = FALSE;
|
|
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
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);
|
|
|
|
//
|
|
// Sometimes the call to Marshall might contain the SD but NewSeInfo
|
|
// might not have been updated suitable because of failures not sever
|
|
// enough to warrant failing the entire operation.
|
|
//
|
|
if (!_fExplicitSecurityMask
|
|
&& fNTSecDes
|
|
&& (NewSeInfo != INVALID_SE_VALUE)
|
|
) {
|
|
berValue[4] = (BYTE) ((ULONG)NewSeInfo);
|
|
}
|
|
|
|
//
|
|
// Find out if server is AD.
|
|
//
|
|
hr = ReadServerSupportsIsADControl(
|
|
_pszLDAPServer,
|
|
&fServerIsAD,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
if (FAILED(hr)) {
|
|
//
|
|
// Assume it is not AD and continue, there is no
|
|
// good reason for this to fail on AD.
|
|
//
|
|
fServerIsAD = FALSE;
|
|
}
|
|
|
|
if (fNTSecDes) {
|
|
|
|
hr = ReadSecurityDescriptorControlType(
|
|
_pszLDAPServer,
|
|
&dwSecDescType,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
if (SUCCEEDED(hr) && (dwSecDescType == ADSI_LDAPC_SECDESC_NT)) {
|
|
|
|
hr = LdapModifyExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
aMod,
|
|
fServerIsAD ?
|
|
(PLDAPControl *) &ServerControlsAll :
|
|
(PLDAPControl *) &ServerControls,
|
|
NULL
|
|
);
|
|
|
|
fModifyDone = TRUE;
|
|
|
|
|
|
} // SecDesc type == NT
|
|
} // if modifySecDes
|
|
|
|
if (!fModifyDone) {
|
|
|
|
if (fServerIsAD) {
|
|
//
|
|
// Need to send the additional control that says it is
|
|
// ok to clear the values on attributes that do not
|
|
// have any values.
|
|
//
|
|
hr = LdapModifyExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
aMod,
|
|
(PLDAPControl *) &ServerControlsOnlyModify,
|
|
NULL
|
|
);
|
|
}
|
|
else {
|
|
hr = LdapModifyS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
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->ClearMarshalledProperties();
|
|
_pPropertyCache->ClearAllPropertyFlags();
|
|
|
|
_pPropertyCache->DeleteSavingEntry();
|
|
|
|
|
|
error:
|
|
|
|
if (aMod) {
|
|
|
|
if ( *aMod )
|
|
FreeADsMem( *aMod );
|
|
|
|
FreeADsMem( aMod );
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPGenObject::LDAPCreateObject()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPModW **aMod = NULL;
|
|
DWORD dwIndex = 0;
|
|
BOOL fNTSecDes= FALSE;
|
|
BOOL fAddDone = FALSE;
|
|
DWORD dwSecDescType = ADSI_LDAPC_SECDESC_NONE;
|
|
SECURITY_INFORMATION SeInfo = _seInfo;
|
|
SECURITY_INFORMATION NewSeInfo=0;
|
|
|
|
BYTE berValue[8];
|
|
|
|
memset(berValue, 0, 8);
|
|
|
|
berValue[0] = 0x30; // Start sequence tag
|
|
berValue[1] = 0x03; // Length in bytes of following
|
|
berValue[2] = 0x02; // Actual value this and next 2
|
|
berValue[3] = 0x01;
|
|
berValue[4] = (BYTE) ((ULONG)SeInfo);
|
|
|
|
LDAPControl SeInfoControl =
|
|
{
|
|
LDAP_SERVER_SD_FLAGS_OID_W,
|
|
{
|
|
5, (PCHAR) berValue
|
|
},
|
|
TRUE
|
|
};
|
|
|
|
PLDAPControl ServerControls[2] =
|
|
{
|
|
&SeInfoControl,
|
|
NULL
|
|
};
|
|
|
|
if ( _pPropertyCache->findproperty( TEXT("objectClass"), &dwIndex )
|
|
== E_ADS_PROPERTY_NOT_FOUND )
|
|
{
|
|
VARIANT v;
|
|
|
|
VariantInit(&v);
|
|
v.vt = VT_BSTR;
|
|
V_BSTR(&v) = _SchemaClass;
|
|
|
|
hr = Put( TEXT("objectClass"), v );
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = _pPropertyCache->LDAPMarshallProperties(
|
|
&aMod,
|
|
&fNTSecDes,
|
|
&NewSeInfo
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (!_fExplicitSecurityMask && NewSeInfo) {
|
|
berValue[4] = (BYTE) ((ULONG)NewSeInfo);
|
|
}
|
|
|
|
if (fNTSecDes == TRUE) {
|
|
//
|
|
// If we support the SD control, then we use
|
|
// the add ext method.
|
|
//
|
|
if (fNTSecDes) {
|
|
|
|
|
|
hr = ReadSecurityDescriptorControlType(
|
|
_pszLDAPServer,
|
|
&dwSecDescType,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
if (SUCCEEDED(hr) && (dwSecDescType == ADSI_LDAPC_SECDESC_NT)) {
|
|
|
|
hr = LdapAddExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
aMod,
|
|
(PLDAPControl *)&ServerControls,
|
|
NULL
|
|
);
|
|
|
|
fAddDone = TRUE;
|
|
|
|
} // SecDesc type == NT
|
|
}
|
|
} // if SecDesc needs to be sent.
|
|
|
|
|
|
if (!fAddDone) {
|
|
//
|
|
// Call add s
|
|
//
|
|
hr = LdapAddS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
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
|
|
CLDAPGenObject::GetInfoEx(
|
|
THIS_ VARIANT vProperties,
|
|
long lnReserved
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPMessage *res = NULL;
|
|
VARIANT *vVarArray = NULL;
|
|
DWORD dwNumVariants = 0;
|
|
PWSTR *ppszStringArray = NULL;
|
|
DWORD dwOptions = 0;
|
|
int ldaperr = 0;
|
|
DWORD dwCtr = 0;
|
|
DWORD dwSecDescType = 0;
|
|
BOOL fSearchDone = FALSE;
|
|
|
|
DWORD dwControls = 0;
|
|
PLDAPControl *ppServerControls = NULL;
|
|
DWORD dwCurControl = 0;
|
|
PLDAPControl pQuotaControl = NULL;
|
|
PBERVAL pBerVal = NULL;
|
|
|
|
|
|
SECURITY_INFORMATION SeInfo = _seInfo;
|
|
|
|
BYTE berValue[8];
|
|
|
|
memset(berValue, 0, 8);
|
|
|
|
berValue[0] = 0x30; // Start sequence tag
|
|
berValue[1] = 0x03; // Length in bytes of following
|
|
berValue[2] = 0x02; // Actual value this and next 2
|
|
berValue[3] = 0x01;
|
|
berValue[4] = (BYTE)((ULONG)SeInfo);
|
|
|
|
LDAPControl SeInfoControl =
|
|
{
|
|
LDAP_SERVER_SD_FLAGS_OID_W,
|
|
{
|
|
5, (PCHAR) berValue
|
|
},
|
|
TRUE
|
|
};
|
|
|
|
|
|
UNREFERENCED_PARAMETER(lnReserved);
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
hr = E_ADS_OBJECT_UNBOUND;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
vProperties,
|
|
&vVarArray,
|
|
&dwNumVariants
|
|
);
|
|
// returns E_FAIL if vProperties is invalid
|
|
if (hr == E_FAIL)
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = ConvertVariantArrayToLDAPStringArray(
|
|
vVarArray,
|
|
&ppszStringArray,
|
|
dwNumVariants
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
while ((dwCtr < dwNumVariants) && (dwSecDescType == 0)) {
|
|
if (_wcsicmp(ppszStringArray[dwCtr], L"ntSecurityDescriptor") == 0) {
|
|
dwSecDescType = 1;
|
|
}
|
|
|
|
dwCtr++;
|
|
}
|
|
|
|
//
|
|
// Do not bother doing this if secdesc has not been
|
|
// explicitly requested.
|
|
//
|
|
if (dwSecDescType) {
|
|
|
|
//
|
|
// If the server is V3, we want to use controls in case
|
|
// the security descriptor has to be retrieved
|
|
//
|
|
ldaperr = ldap_get_option(
|
|
_pLdapHandle->LdapHandle,
|
|
LDAP_OPT_VERSION,
|
|
&dwOptions
|
|
);
|
|
|
|
if (dwOptions == LDAP_VERSION3) {
|
|
|
|
hr = ReadSecurityDescriptorControlType(
|
|
_pszLDAPServer,
|
|
&dwSecDescType,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (dwSecDescType == ADSI_LDAPC_SECDESC_NT) {
|
|
|
|
dwControls++;
|
|
|
|
|
|
}
|
|
}
|
|
} // sec desc requested
|
|
|
|
if(_pSid && _dwSidLength)
|
|
{
|
|
dwControls++;
|
|
}
|
|
|
|
if(dwControls)
|
|
{
|
|
ppServerControls = (PLDAPControl *) AllocADsMem( sizeof(PLDAPControl) * (dwControls+1) );
|
|
if (!ppServerControls)
|
|
{
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
if(dwSecDescType == ADSI_LDAPC_SECDESC_NT)
|
|
{
|
|
ppServerControls[dwCurControl++] = &SeInfoControl;
|
|
}
|
|
|
|
if(_pSid && _dwSidLength)
|
|
{
|
|
// add quota control
|
|
pQuotaControl = (LDAPControl *) AllocADsMem(sizeof(LDAPControl));
|
|
|
|
if (!pQuotaControl) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
hr = BerEncodingQuotaControl((PBYTE)_pSid, _dwSidLength, &pBerVal);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pQuotaControl->ldctl_oid = LDAP_SERVER_QUOTA_CONTROL_OID_W;
|
|
pQuotaControl->ldctl_value.bv_len = pBerVal->bv_len;
|
|
|
|
pQuotaControl->ldctl_value.bv_val = (PCHAR) pBerVal->bv_val;
|
|
pQuotaControl->ldctl_iscritical = TRUE;
|
|
|
|
ppServerControls[dwCurControl++] = pQuotaControl;
|
|
|
|
|
|
}
|
|
|
|
ppServerControls[dwCurControl] = NULL;
|
|
|
|
hr = LdapSearchExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
ppszStringArray,
|
|
0,
|
|
ppServerControls,
|
|
NULL,
|
|
NULL,
|
|
10000,
|
|
&res
|
|
);
|
|
|
|
fSearchDone = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// At this point even if it is a vesion 3 DS, we might still
|
|
// need to call the normal search routine as all version 3
|
|
// DS's need not necessarily support controls - Ex: Exchange.
|
|
//
|
|
|
|
if (!fSearchDone) {
|
|
|
|
hr = LdapSearchS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
ppszStringArray,
|
|
0,
|
|
&res
|
|
);
|
|
|
|
fSearchDone = TRUE;
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// This is an explicit GetInfo[Ex], but we don't want to flush the
|
|
// property cache. For example, if we have [A B C] in the cache, and
|
|
// we request [C D E], we want to end up with [A B C D E] in the cache.
|
|
//
|
|
// But we do want to tell LDAPUnMarshallProperties that this is an
|
|
// explicit call; using the same example, we want the server's value of
|
|
// C in the cache after this call, not the old value.
|
|
//
|
|
hr = _pPropertyCache->LDAPUnMarshallProperties2(
|
|
_pszLDAPServer,
|
|
_pLdapHandle,
|
|
res,
|
|
TRUE, // fExplicit
|
|
_Credentials,
|
|
&_fRangeRetrieval
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
for(DWORD i = 0; i < dwNumVariants; i++) {
|
|
_pPropertyCache->AddSavingEntry(ppszStringArray[i]);
|
|
}
|
|
|
|
error:
|
|
if (res)
|
|
LdapMsgFree(res);
|
|
|
|
if (ppszStringArray) {
|
|
for (DWORD i = 0; i < dwNumVariants; i++)
|
|
if (ppszStringArray[i])
|
|
FreeADsStr(ppszStringArray[i]);
|
|
FreeADsMem(ppszStringArray);
|
|
}
|
|
|
|
if (vVarArray) {
|
|
for (dwCtr = 0; dwCtr < dwNumVariants; dwCtr++) {
|
|
VariantClear(vVarArray + dwCtr);
|
|
}
|
|
FreeADsMem(vVarArray);
|
|
}
|
|
|
|
// free the LDAP server control
|
|
if(pBerVal)
|
|
{
|
|
ber_bvfree(pBerVal);
|
|
}
|
|
|
|
if(pQuotaControl)
|
|
{
|
|
FreeADsMem(pQuotaControl);
|
|
}
|
|
|
|
if(ppServerControls)
|
|
{
|
|
FreeADsMem(ppServerControls);
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLDAPGenObject::GetInfo()
|
|
{
|
|
_fRangeRetrieval = FALSE;
|
|
RRETURN(GetInfo(GETINFO_FLAG_EXPLICIT));
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPGenObject::GetInfo(
|
|
DWORD dwFlags
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LDAPMessage *res = NULL;
|
|
int ldaperr = 0;
|
|
DWORD dwOptions = 0;
|
|
DWORD dwSecDescType = 0;
|
|
BOOL fSearchDone = FALSE;
|
|
|
|
|
|
SECURITY_INFORMATION SeInfo = _seInfo;
|
|
|
|
BYTE berValue[8];
|
|
|
|
memset(berValue, 0, 8);
|
|
|
|
berValue[0] = 0x30; // Start sequence tag
|
|
berValue[1] = 0x03; // Length in bytes of following
|
|
berValue[2] = 0x02; // Actual value this and next 2
|
|
berValue[3] = 0x01;
|
|
berValue[4] = (BYTE)((ULONG)SeInfo);
|
|
|
|
LDAPControl SeInfoControl =
|
|
{
|
|
LDAP_SERVER_SD_FLAGS_OID_W,
|
|
{
|
|
5, (PCHAR) berValue
|
|
},
|
|
TRUE
|
|
};
|
|
|
|
PLDAPControl ServerControls[2] =
|
|
{
|
|
&SeInfoControl,
|
|
NULL
|
|
};
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
if (dwFlags == GETINFO_FLAG_IMPLICIT_AS_NEEDED) {
|
|
if (_pPropertyCache->getGetInfoFlag()) {
|
|
//
|
|
// We are done there is nothing to do.
|
|
//
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
if (GetObjectState() == ADS_OBJECT_UNBOUND) {
|
|
hr = E_ADS_OBJECT_UNBOUND;
|
|
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();
|
|
}
|
|
|
|
// modified from LdapSearchS to LdapSearchExtS to get all attributes
|
|
// including SecurityDescriptor by one call
|
|
|
|
ldaperr = ldap_get_option(
|
|
_pLdapHandle->LdapHandle,
|
|
LDAP_OPT_VERSION,
|
|
&dwOptions
|
|
);
|
|
|
|
if (dwOptions == LDAP_VERSION3) {
|
|
|
|
hr = ReadSecurityDescriptorControlType(
|
|
_pszLDAPServer,
|
|
&dwSecDescType,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (dwSecDescType == ADSI_LDAPC_SECDESC_NT) {
|
|
|
|
hr = LdapSearchExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
NULL, // modified to NULL for all attributes
|
|
0,
|
|
(PLDAPControl *)&ServerControls,
|
|
NULL,
|
|
NULL,
|
|
10000,
|
|
&res
|
|
);
|
|
|
|
fSearchDone = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If the fSearchDone flags is not set, then the server
|
|
// probably did not support the SecDesc control and we need to
|
|
// just a search not extended.
|
|
//
|
|
if (!fSearchDone) {
|
|
|
|
hr = LdapSearchS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
TEXT("(objectClass=*)"),
|
|
NULL,
|
|
0,
|
|
&res
|
|
);
|
|
|
|
fSearchDone = TRUE;
|
|
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = _pPropertyCache->LDAPUnMarshallProperties2(
|
|
_pszLDAPServer,
|
|
_pLdapHandle,
|
|
res,
|
|
(dwFlags == GETINFO_FLAG_EXPLICIT) ?
|
|
TRUE : FALSE,
|
|
_Credentials,
|
|
&_fRangeRetrieval
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
_pPropertyCache->setGetInfoFlag();
|
|
|
|
error:
|
|
|
|
if (res) {
|
|
|
|
LdapMsgFree( res );
|
|
}
|
|
|
|
if (_pPropertyCache) {
|
|
Reset();
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_GUID(THIS_ BSTR FAR* retval)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
ULONG ulLength = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
LPWSTR pszTempStr = NULL;
|
|
WCHAR pszSmallStr[5];
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
if (_dwObjectState == ADS_OBJECT_UNBOUND) {
|
|
RRETURN(E_ADS_OBJECT_UNBOUND);
|
|
}
|
|
|
|
if (!(_dwCorePropStatus & LDAP_GUID_VALID)) {
|
|
|
|
//
|
|
// Get the property from the server (implicit getinfo)
|
|
// and update the property in the
|
|
//
|
|
hr = _pPropertyCache->getproperty(
|
|
L"objectGUID",
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if ((ldapSrcObjects.dwCount == 0)
|
|
|| (ldapSrcObjects.dwCount > 1)) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_PROPERTY_NOT_FOUND);
|
|
}
|
|
|
|
ulLength = LDAPOBJECT_BERVAL_LEN(ldapSrcObjects.pLdapObjects);
|
|
|
|
pszTempStr = (LPWSTR) AllocADsMem((ulLength * 2 + 1) * sizeof(WCHAR));
|
|
|
|
if (!pszTempStr) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
for (DWORD dwCtr = 0; dwCtr < ulLength; dwCtr++) {
|
|
wsprintf(pszSmallStr, L"%02x", (BYTE) LDAPOBJECT_BERVAL_VAL(ldapSrcObjects.pLdapObjects)[dwCtr]);
|
|
wcscat(pszTempStr, pszSmallStr);
|
|
}
|
|
|
|
wcscat(pszTempStr, L"\0");
|
|
|
|
if (_ADsGuid) {
|
|
ADsFreeString(_ADsGuid);
|
|
_ADsGuid = NULL;
|
|
}
|
|
|
|
hr = ADsAllocString(pszTempStr, &_ADsGuid);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
_dwCorePropStatus |= LDAP_GUID_VALID;
|
|
}
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
if (pszTempStr) {
|
|
FreeADsMem(pszTempStr);
|
|
}
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
RRETURN(get_CoreGUID(retval));
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPGenObject::get_Parent(BSTR * retval)
|
|
{
|
|
|
|
HRESULT hr;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
LPWSTR pszTempStr = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
LPWSTR pszADsParent = NULL;
|
|
LPWSTR pszADsCommon = NULL;
|
|
|
|
BOOL fGCNameSpace = FALSE;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
if (FAILED(hr = ValidateOutParameter(retval))){
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
if ( (wcslen(_Name) > wcslen(L"<GUID=")) &&
|
|
(_wcsnicmp(_Name, L"<GUID=", wcslen(L"<GUID=")) == 0)) {
|
|
|
|
//
|
|
// Replace the guid with the distinguishedName
|
|
//
|
|
|
|
//
|
|
// Get the property from the server (implicit getinfo)
|
|
//
|
|
hr = _pPropertyCache->getproperty(
|
|
L"distinguishedName",
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (ldapSrcObjects.dwCount == 0) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_PROPERTY_NOT_FOUND);
|
|
}
|
|
|
|
pszTempStr = LDAPOBJECT_STRING(ldapSrcObjects.pLdapObjects);
|
|
|
|
|
|
//
|
|
// Break it down into parent name & common name portions
|
|
//
|
|
|
|
// _ADsPath could not be NULL
|
|
if(!_wcsnicmp(L"GC:", _ADsPath, wcslen(L"GC:")))
|
|
{
|
|
fGCNameSpace = TRUE;
|
|
}
|
|
|
|
hr = BuildADsPathFromLDAPPath2(
|
|
(_pszLDAPServer ? TRUE : FALSE),
|
|
fGCNameSpace ? L"GC:" : L"LDAP:",
|
|
_pszLDAPServer,
|
|
_dwPort,
|
|
pszTempStr,
|
|
&pszADsPath
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsParentPath(pszADsPath,
|
|
&pszADsParent,
|
|
&pszADsCommon);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = ADsAllocString(pszADsParent, retval);
|
|
|
|
}
|
|
else {
|
|
|
|
hr = get_CoreParent(retval);
|
|
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
if (pszADsPath)
|
|
FreeADsMem(pszADsPath);
|
|
|
|
if (pszADsParent)
|
|
FreeADsMem(pszADsParent);
|
|
|
|
if (pszADsCommon)
|
|
FreeADsMem(pszADsCommon);
|
|
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
HRESULT
|
|
CLDAPGenObject::get_Name(BSTR * retval)
|
|
{
|
|
HRESULT hr;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
LPWSTR pszTempStr = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
LPWSTR pszADsParent = NULL;
|
|
LPWSTR pszADsCommon = NULL;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
if (FAILED(hr = ValidateOutParameter(retval))){
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
if ( (wcslen(_Name) > wcslen(L"<WKGUID=")) &&
|
|
(_wcsnicmp(_Name, L"<WKGUID=", wcslen(L"<WKGUID=")) == 0)) {
|
|
|
|
//
|
|
// Replace the name with the DN
|
|
//
|
|
hr = ADsAllocString(_pszLDAPDn, retval);
|
|
|
|
}
|
|
else if ( (wcslen(_Name) > wcslen(L"<GUID=")) &&
|
|
(_wcsnicmp(_Name, L"<GUID=", wcslen(L"<GUID=")) == 0)) {
|
|
|
|
//
|
|
// Replace the guid with the distinguishedName
|
|
//
|
|
|
|
//
|
|
// Get the property from the server (implicit getinfo)
|
|
//
|
|
hr = _pPropertyCache->getproperty(
|
|
L"distinguishedName",
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (ldapSrcObjects.dwCount == 0) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_PROPERTY_NOT_FOUND);
|
|
}
|
|
|
|
pszTempStr = LDAPOBJECT_STRING(ldapSrcObjects.pLdapObjects);
|
|
|
|
|
|
//
|
|
// Break it down into parent name & common name portions
|
|
//
|
|
hr = BuildADsPathFromLDAPPath2(
|
|
(_pszLDAPServer ? TRUE : FALSE),
|
|
L"LDAP:", // does not matter, just a place holder
|
|
_pszLDAPServer,
|
|
_dwPort, // doesn't matter
|
|
pszTempStr,
|
|
&pszADsPath
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsParentPath(pszADsPath,
|
|
&pszADsParent,
|
|
&pszADsCommon);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = ADsAllocString(pszADsCommon, retval);
|
|
|
|
}
|
|
else {
|
|
hr = ADsAllocString(_Name, retval);
|
|
}
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
if (pszADsPath)
|
|
FreeADsMem(pszADsPath);
|
|
|
|
if (pszADsParent)
|
|
FreeADsMem(pszADsParent);
|
|
|
|
if (pszADsCommon)
|
|
FreeADsMem(pszADsCommon);
|
|
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_Class(THIS_ BSTR FAR* retval)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwSyntaxId;
|
|
DWORD dwStatus = 0;
|
|
ULONG ulNumVals = 0;
|
|
LDAPOBJECTARRAY ldapSrcObjects;
|
|
PLDAPOBJECT pLdapObject = NULL;
|
|
LPWSTR pszTempStr = NULL;
|
|
|
|
LDAPOBJECTARRAY_INIT(ldapSrcObjects);
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
//
|
|
// Need to go on wire only if object is bound as in this
|
|
// object is not being create now
|
|
//
|
|
if (!(_dwCorePropStatus & LDAP_CLASS_VALID)
|
|
&& _dwObjectState != ADS_OBJECT_UNBOUND) {
|
|
|
|
//
|
|
// Get the property from the server (implicit getinfo)
|
|
// and update the property in the
|
|
//
|
|
hr = _pPropertyCache->getproperty(
|
|
L"objectClass",
|
|
&dwSyntaxId,
|
|
&dwStatus,
|
|
&ldapSrcObjects
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (ldapSrcObjects.dwCount == 0) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_PROPERTY_NOT_FOUND);
|
|
}
|
|
|
|
|
|
ulNumVals = ldapSrcObjects.dwCount;
|
|
|
|
pLdapObject = ldapSrcObjects.pLdapObjects;
|
|
//
|
|
// Try and see if we need the first or the last value
|
|
//
|
|
if (_wcsicmp(LDAPOBJECT_STRING(pLdapObject + (ulNumVals - 1)),
|
|
L"Top")== 0) {
|
|
|
|
pszTempStr = LDAPOBJECT_STRING(
|
|
pLdapObject + 0
|
|
);
|
|
} else {
|
|
|
|
pszTempStr = LDAPOBJECT_STRING(
|
|
pLdapObject + (ulNumVals - 1)
|
|
);
|
|
|
|
}
|
|
|
|
if (_SchemaClass) {
|
|
ADsFreeString(_SchemaClass);
|
|
_SchemaClass = NULL;
|
|
}
|
|
|
|
hr = ADsAllocString(pszTempStr, &_SchemaClass);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
_dwCorePropStatus |= LDAP_CLASS_VALID;
|
|
}
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( &ldapSrcObjects );
|
|
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
RRETURN(get_CoreADsClass(retval));
|
|
}
|
|
|
|
RRETURN(hr);
|
|
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_Schema(THIS_ BSTR FAR* retval)
|
|
{
|
|
BSTR bstrTemp = NULL;
|
|
|
|
//
|
|
// We call the get_Class method because that will take care
|
|
// of all the work we need to do in the event that we need
|
|
// read the information from the server. It makes sense to do
|
|
// that rather than repeat the code here.
|
|
//
|
|
HRESULT hr = get_Class(&bstrTemp);
|
|
|
|
if (FAILED(hr)) {
|
|
RRETURN(hr);
|
|
}
|
|
|
|
if (bstrTemp) {
|
|
SysFreeString(bstrTemp);
|
|
}
|
|
|
|
RRETURN(get_CoreSchema(retval));
|
|
}
|
|
|
|
/* IADsContainer methods */
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_Count(long FAR* retval)
|
|
{
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
RRETURN_EXP_IF_ERR(E_NOTIMPL);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_Filter(THIS_ VARIANT FAR* pVar)
|
|
{
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
VariantInit(pVar);
|
|
hr = VariantCopy(pVar, &_vFilter);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::put_Filter(THIS_ VARIANT Var)
|
|
{
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
VariantClear(&_vFilter);
|
|
|
|
hr = VariantCopy(&_vFilter, &Var);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get_Hints(THIS_ VARIANT FAR* pVar)
|
|
{
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
VariantInit(pVar);
|
|
hr = VariantCopy(pVar, &_vHints);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::put_Hints(THIS_ VARIANT Var)
|
|
{
|
|
HRESULT hr;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
VariantClear(&_vHints);
|
|
|
|
hr = VariantCopy(&_vHints, &Var);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::GetObject(
|
|
THIS_ BSTR ClassName,
|
|
BSTR RelativeName,
|
|
IDispatch * FAR* ppObject
|
|
)
|
|
{
|
|
TCHAR *pszBuffer = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
IADs *pADs = NULL;
|
|
BSTR bstrClass = NULL;
|
|
|
|
BSTR bstrParent = NULL;
|
|
BSTR bstrName = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
if (!RelativeName || !*RelativeName) {
|
|
RRETURN_EXP_IF_ERR(E_ADS_UNKNOWN_OBJECT);
|
|
}
|
|
|
|
|
|
//
|
|
// Build an ADsPath from get_Parent/get_Name
|
|
// (which always returns useful LDAP-style values)
|
|
// rather than using _ADsPath, which could be
|
|
// of the <GUID=....> form
|
|
//
|
|
hr = get_Parent(&bstrParent);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = get_Name(&bstrName);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsPathFromParent(bstrParent,
|
|
bstrName,
|
|
&pszADsPath);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
//
|
|
// Tack on the path component of the child
|
|
// object being retrieved
|
|
//
|
|
hr = BuildADsPathFromParent(
|
|
pszADsPath,
|
|
RelativeName,
|
|
&pszBuffer
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = ::GetObject(
|
|
pszBuffer,
|
|
_Credentials,
|
|
(LPVOID *)ppObject
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Check the class name only if we are not in umi land. In umi
|
|
// land, we will fail the QI for the IID_IADs interface.
|
|
//
|
|
if(ClassName && !(_Credentials.GetAuthFlags() & ADS_AUTH_RESERVED)) {
|
|
hr = (*ppObject)->QueryInterface(
|
|
IID_IADs,
|
|
(void **)&pADs
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pADs->get_Class(&bstrClass);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
#ifdef WIN95
|
|
if (_wcsicmp( bstrClass, ClassName )) {
|
|
#else
|
|
if (CompareStringW(
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
bstrClass,
|
|
-1,
|
|
ClassName,
|
|
-1
|
|
) != CSTR_EQUAL) {
|
|
#endif
|
|
(*ppObject)->Release();
|
|
*ppObject = NULL;
|
|
BAIL_ON_FAILURE(hr=E_ADS_UNKNOWN_OBJECT);
|
|
}
|
|
}
|
|
|
|
error:
|
|
|
|
if (bstrParent) {
|
|
ADsFreeString(bstrParent);
|
|
}
|
|
|
|
if (bstrName) {
|
|
ADsFreeString(bstrName);
|
|
}
|
|
|
|
if (pszADsPath) {
|
|
FreeADsMem(pszADsPath);
|
|
}
|
|
|
|
if ( pADs ) {
|
|
pADs->Release();
|
|
}
|
|
|
|
if ( pszBuffer )
|
|
FreeADsMem( pszBuffer );
|
|
|
|
if ( bstrClass ) {
|
|
SysFreeString( bstrClass );
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::get__NewEnum(
|
|
THIS_ IUnknown * FAR* retval
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IUnknown FAR* punkEnum=NULL;
|
|
IEnumVARIANT * penum = NULL;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
*retval = NULL;
|
|
|
|
hr = CLDAPGenObjectEnum::Create(
|
|
(CLDAPGenObjectEnum **)&penum,
|
|
_ADsPath,
|
|
_pLdapHandle,
|
|
this,
|
|
_vFilter,
|
|
_Credentials,
|
|
_dwOptReferral,
|
|
_dwPageSize
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = penum->QueryInterface(
|
|
IID_IUnknown,
|
|
(VOID FAR* FAR*)retval
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (penum) {
|
|
penum->Release();
|
|
}
|
|
|
|
RRETURN(NOERROR);
|
|
|
|
error:
|
|
|
|
if (penum) {
|
|
delete penum;
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::Create(
|
|
THIS_ BSTR ClassName,
|
|
BSTR RelativeName,
|
|
IDispatch * FAR* ppObject
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL fValid = FALSE;
|
|
|
|
BSTR bstrParent = NULL;
|
|
BSTR bstrName = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
//
|
|
// No null or empty names for class or rdn.
|
|
//
|
|
if (!ClassName || !*ClassName
|
|
|| !RelativeName || !*RelativeName) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
|
|
//
|
|
// Build an ADsPath from get_Parent/get_Name
|
|
// (which always returns useful LDAP-style values)
|
|
// rather than using _ADsPath, which could be
|
|
// of the <GUID=....> form
|
|
//
|
|
hr = get_Parent(&bstrParent);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = get_Name(&bstrName);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsPathFromParent(bstrParent,
|
|
bstrName,
|
|
&pszADsPath);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = CLDAPGenObject::CreateGenericObject(
|
|
pszADsPath,
|
|
RelativeName,
|
|
ClassName,
|
|
_Credentials,
|
|
ADS_OBJECT_UNBOUND,
|
|
IID_IADs,
|
|
(void **) ppObject
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
if (bstrParent) {
|
|
ADsFreeString(bstrParent);
|
|
}
|
|
|
|
if (bstrName) {
|
|
ADsFreeString(bstrName);
|
|
}
|
|
|
|
if (pszADsPath) {
|
|
FreeADsMem(pszADsPath);
|
|
}
|
|
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::Delete(
|
|
THIS_ BSTR bstrClassName,
|
|
BSTR bstrRelativeName
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BSTR bstrParent = NULL;
|
|
BSTR bstrName = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
BSTR bstrAbsoluteName = NULL;
|
|
TCHAR *pszLDAPServer = NULL;
|
|
TCHAR *pszLDAPDn = NULL;
|
|
LPTSTR *aValues = NULL;
|
|
int nCount = 0;
|
|
DWORD dwPort = 0;
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
//
|
|
// Check to see if the RelativeName is non empty
|
|
//
|
|
if (bstrRelativeName == NULL) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
|
|
//
|
|
// Build an ADsPath from get_Parent/get_Name
|
|
// (which always returns useful LDAP-style values)
|
|
// rather than using _ADsPath, which could be
|
|
// of the <GUID=....> form
|
|
//
|
|
hr = get_Parent(&bstrParent);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = get_Name(&bstrName);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsPathFromParent(bstrParent,
|
|
bstrName,
|
|
&pszADsPath);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
//
|
|
// Tack on the path component of the child object to be
|
|
// deleted
|
|
//
|
|
hr = BuildADsPath(
|
|
pszADsPath,
|
|
bstrRelativeName,
|
|
&bstrAbsoluteName
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
bstrAbsoluteName,
|
|
&pszLDAPServer,
|
|
&pszLDAPDn,
|
|
&dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Compare the class names only if one is given. Null
|
|
// can be used to speed up the delete operation.
|
|
//
|
|
if (bstrClassName) {
|
|
//
|
|
// Validate the class name first
|
|
//
|
|
hr = LdapReadAttribute(
|
|
pszLDAPServer,
|
|
pszLDAPDn,
|
|
TEXT("objectClass"),
|
|
&aValues,
|
|
&nCount,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
if ( nCount > 0 )
|
|
{
|
|
if ( _tcsicmp( bstrClassName, GET_BASE_CLASS( aValues, nCount) ) != 0 )
|
|
{
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
hr = LdapDeleteS(
|
|
_pLdapHandle,
|
|
pszLDAPDn
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
if (bstrParent) {
|
|
ADsFreeString(bstrParent);
|
|
}
|
|
|
|
if (bstrName) {
|
|
ADsFreeString(bstrName);
|
|
}
|
|
|
|
if (pszADsPath) {
|
|
FreeADsMem(pszADsPath);
|
|
}
|
|
|
|
if ( bstrAbsoluteName ) {
|
|
ADsFreeString( bstrAbsoluteName );
|
|
}
|
|
|
|
if ( pszLDAPServer ) {
|
|
FreeADsStr( pszLDAPServer );
|
|
}
|
|
|
|
if (pszLDAPDn) {
|
|
|
|
FreeADsStr(pszLDAPDn);
|
|
}
|
|
|
|
if ( aValues ) {
|
|
LdapValueFree( aValues );
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::CopyHere(
|
|
THIS_ BSTR SourceName,
|
|
BSTR NewName,
|
|
IDispatch * FAR* ppObject
|
|
)
|
|
{
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
/*
|
|
RRETURN(CopyObject( SourceName,
|
|
_ADsPath,
|
|
_pLdapHandle,
|
|
_pSchemaInfo,
|
|
NewName,
|
|
ppObject ) );
|
|
|
|
*/
|
|
RRETURN_EXP_IF_ERR(E_NOTIMPL);
|
|
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::MoveHere(
|
|
THIS_ BSTR SourceName,
|
|
BSTR NewName,
|
|
IDispatch * FAR* ppObject
|
|
)
|
|
{
|
|
LPWSTR pszLDAPServer= NULL;
|
|
LPWSTR pszLDAPDn = NULL;
|
|
|
|
BSTR bstrParent = NULL;
|
|
BSTR bstrName = NULL;
|
|
LPWSTR pszADsPath = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
LPWSTR pszSrcParent= NULL;
|
|
LPWSTR pszCN = NULL;
|
|
LPWSTR pszRelativeName = NULL;
|
|
BSTR bstrDestADsPath = NULL;
|
|
LPWSTR pszDestLDAPServer = NULL;
|
|
LPWSTR pszDestLDAPDn = NULL;
|
|
LPWSTR pszLDAPServerTemp = NULL;
|
|
LPWSTR pszLDAPSourceDnParent = NULL;
|
|
DWORD dwPort = 0;
|
|
LPBOOL lpBoolVal = NULL;
|
|
|
|
// Variables need to conver wide char to ANSI format
|
|
DWORD dwOptions = 0;
|
|
int intErr = 0;
|
|
LPSTR pANSIServer = NULL;
|
|
DWORD dwDestLen = 0;
|
|
DWORD dwTempLen = 0;
|
|
PADSLDP pSourceLD = NULL;
|
|
BOOL fTryXDom = FALSE;
|
|
|
|
LPWSTR pszDestLDAPRelativeName = NULL;
|
|
|
|
LDAPControl RenExtInfoControl =
|
|
{
|
|
LDAP_SERVER_CROSSDOM_MOVE_TARGET_OID_W,
|
|
{
|
|
0, NULL
|
|
},
|
|
TRUE
|
|
};
|
|
|
|
PLDAPControl ServerControls[2] =
|
|
{
|
|
&RenExtInfoControl,
|
|
NULL
|
|
};
|
|
|
|
//
|
|
// Make sure that the last error is reset
|
|
//
|
|
Macro_ClearADsLastError(L"LDAP Provider");
|
|
|
|
//
|
|
// Build the source paths of the object
|
|
// being moved
|
|
//
|
|
hr = BuildADsParentPath(
|
|
SourceName,
|
|
&pszSrcParent,
|
|
&pszCN
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
SourceName,
|
|
&pszLDAPServer,
|
|
&pszLDAPDn,
|
|
&dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (!pszSrcParent) {
|
|
//
|
|
// If we cannot get the parent, then we cannot move this object.
|
|
//
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
//
|
|
// We need the dn to the parent of the object being moved.
|
|
//
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
pszSrcParent,
|
|
&pszLDAPServerTemp,
|
|
&pszLDAPSourceDnParent,
|
|
&dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
//
|
|
// set the right value of relative distinguished name
|
|
// use the name given by the user if given at all
|
|
// otherwise use the name of the source
|
|
//
|
|
if (NewName != NULL) {
|
|
pszRelativeName = NewName;
|
|
|
|
} else {
|
|
pszRelativeName = pszCN;
|
|
}
|
|
|
|
|
|
//
|
|
// Build an ADsPath from get_Parent/get_Name
|
|
// (which always returns useful LDAP-style values)
|
|
// rather than using _ADsPath, which could be
|
|
// of the <GUID=....> form
|
|
//
|
|
hr = get_Parent(&bstrParent);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = get_Name(&bstrName);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildADsPathFromParent(
|
|
bstrParent,
|
|
bstrName,
|
|
&pszADsPath
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Build the destination ADsPath of the object being moved.
|
|
//
|
|
hr = BuildADsPath(
|
|
pszADsPath,
|
|
pszRelativeName,
|
|
&bstrDestADsPath
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
bstrDestADsPath,
|
|
&pszDestLDAPServer,
|
|
&pszDestLDAPDn,
|
|
&dwPort
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// ADSI needs to escape /, but on server side, / is not a special character. So if someone passes
|
|
// in rdn but has ADSI type escaping, we need to convert it to ldap type path, otherwise server will
|
|
// reject this kind of rdn
|
|
//
|
|
hr = GetLDAPTypeName(
|
|
pszRelativeName,
|
|
&pszDestLDAPRelativeName
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// LdapModDNS uses ldap_modrdn2_s. This function is used to
|
|
// rename the object not to really move it. If the path of this
|
|
// container and the parentDN of the object being moved here are
|
|
// not the same, we need to use ldapRenameExt instead.
|
|
//
|
|
#ifdef WIN95
|
|
if (!_wcsicmp(_pszLDAPDN, pszLDAPSourceDnParent)) {
|
|
#else
|
|
if (CompareStringW(
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
_pszLDAPDn,
|
|
-1,
|
|
pszLDAPSourceDnParent,
|
|
-1
|
|
) == CSTR_EQUAL ) {
|
|
#endif
|
|
|
|
//
|
|
// They have the same parent, so we can use LdapModDnS.
|
|
//
|
|
hr = LdapModDnS(
|
|
_pLdapHandle,
|
|
pszLDAPDn,
|
|
pszDestLDAPRelativeName,
|
|
TRUE
|
|
);
|
|
}
|
|
else {
|
|
//
|
|
// Since the object is not in this container, we need to use
|
|
// the renameExt call.
|
|
//
|
|
hr = LdapRenameExtS(
|
|
_pLdapHandle,
|
|
pszLDAPDn,
|
|
pszDestLDAPRelativeName,
|
|
_pszLDAPDn,
|
|
TRUE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
// if there was an error it maybe because the move should
|
|
// be across the domains.
|
|
if (FAILED(hr)) {
|
|
|
|
intErr = ldap_get_option(
|
|
_pLdapHandle->LdapHandle,
|
|
LDAP_OPT_VERSION,
|
|
&dwOptions
|
|
);
|
|
|
|
//
|
|
// Only if server is V3 and if the server names are not
|
|
// the same will we attempt the extended rename operation.
|
|
// is there are a better way to decide ?
|
|
//
|
|
|
|
if (!pszDestLDAPServer) {
|
|
//
|
|
// This object does not have a server, we should
|
|
// read it and then use that as the target server.
|
|
//
|
|
|
|
// If this call succeeds, it will copy the servername
|
|
// to the ptr, even if it fails, there should not be problem
|
|
// of memory leak
|
|
//
|
|
GetActualHostName(&pszDestLDAPServer);
|
|
|
|
} // if !pszDestLDAPServer
|
|
|
|
//
|
|
// Try the XDom move only if we have no match on the server
|
|
// names. Note that there is small chance that we will call
|
|
// crossdom when both are serverless but that should be rare
|
|
// as the ModDN call should have succeeded.
|
|
// If the source server is NULL, LDAPOpenObject will handle
|
|
// that case. The target can never be NULL for XDOm.
|
|
//
|
|
if (dwOptions == LDAP_VERSION3 && pszDestLDAPServer) {
|
|
if (!pszLDAPServer) {
|
|
//
|
|
// One server is set the other is not.
|
|
//
|
|
fTryXDom = TRUE;
|
|
}
|
|
#ifdef WIN95
|
|
else if (_wcsicmp(pszLDAPServer, pszDestLDAPServer)) {
|
|
#else
|
|
else if (CompareStringW(
|
|
LOCALE_SYSTEM_DEFAULT,
|
|
NORM_IGNORECASE,
|
|
pszLDAPServer,
|
|
-1,
|
|
pszDestLDAPServer,
|
|
-1
|
|
) != CSTR_EQUAL ) {
|
|
#endif
|
|
//
|
|
// Only if both the servers are different.
|
|
//
|
|
fTryXDom = TRUE;
|
|
}
|
|
}
|
|
|
|
if (fTryXDom) {
|
|
//
|
|
// The move request has to go to the server that has
|
|
// the object not the target server.
|
|
//
|
|
dwDestLen = _tcslen(pszDestLDAPServer);
|
|
dwTempLen = LdapUnicodeToUTF8(
|
|
pszDestLDAPServer,
|
|
dwDestLen,
|
|
NULL,
|
|
0
|
|
);
|
|
// pszDestLDAPServer will not be NULL here
|
|
if(!dwTempLen)
|
|
{
|
|
// keep the previous behavior
|
|
|
|
// note that there has to be a valid hr from the
|
|
// previous LdapModDN call to be here.
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
pANSIServer = (LPSTR)LocalAlloc(LPTR, dwTempLen + 1);
|
|
|
|
|
|
if (!pANSIServer) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
intErr = LdapUnicodeToUTF8(
|
|
pszDestLDAPServer,
|
|
dwDestLen,
|
|
pANSIServer,
|
|
dwTempLen + 1
|
|
);
|
|
|
|
/*
|
|
intErr = WideCharToMultiByte(
|
|
CP_OEMCP,
|
|
WC_DEFAULTCHAR,
|
|
pszDestLDAPServer,
|
|
dwDestLen,
|
|
pANSIDestServer,
|
|
dwDestLen * 2,
|
|
NULL,
|
|
lpBoolVal
|
|
);
|
|
*/
|
|
|
|
if (!intErr) {
|
|
// note that there has to be a valid hr from the
|
|
// previous LdapModDN call to be here.
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
*(pANSIServer+dwTempLen) = '\0';
|
|
|
|
|
|
// Update the control information
|
|
RenExtInfoControl.ldctl_value.bv_len = strlen(pANSIServer);
|
|
RenExtInfoControl.ldctl_value.bv_val = pANSIServer;
|
|
|
|
CCredentials Credentials = _Credentials;
|
|
|
|
//
|
|
// Add request delegation flag
|
|
//
|
|
Credentials.SetAuthFlags(
|
|
_Credentials.GetAuthFlags()
|
|
| ADS_USE_DELEGATION
|
|
);
|
|
|
|
|
|
hr = LdapOpenObject(
|
|
pszLDAPServer,
|
|
pszLDAPDn,
|
|
&pSourceLD,
|
|
Credentials,
|
|
dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = LdapRenameExtS(
|
|
pSourceLD,
|
|
pszLDAPDn,
|
|
pszDestLDAPRelativeName,
|
|
_pszLDAPDn,
|
|
TRUE,
|
|
ServerControls,
|
|
NULL
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// We do not need to pass the class name - even if we do it
|
|
// is not used
|
|
//
|
|
hr = GetObject(
|
|
NULL,
|
|
pszDestLDAPRelativeName,
|
|
(IDispatch **)ppObject
|
|
);
|
|
|
|
|
|
error:
|
|
|
|
if (bstrParent) {
|
|
ADsFreeString(bstrParent);
|
|
}
|
|
|
|
if (bstrName) {
|
|
ADsFreeString(bstrName);
|
|
}
|
|
|
|
if (pszADsPath) {
|
|
FreeADsMem(pszADsPath);
|
|
}
|
|
|
|
if(pszSrcParent){
|
|
FreeADsStr(pszSrcParent);
|
|
}
|
|
|
|
if(pszCN){
|
|
FreeADsStr(pszCN);
|
|
}
|
|
|
|
if(bstrDestADsPath){
|
|
ADsFreeString(bstrDestADsPath );
|
|
}
|
|
|
|
if(pszDestLDAPServer){
|
|
FreeADsStr(pszDestLDAPServer);
|
|
}
|
|
|
|
if(pszDestLDAPDn){
|
|
FreeADsStr(pszDestLDAPDn);
|
|
}
|
|
|
|
|
|
if(pszLDAPServer){
|
|
FreeADsStr(pszLDAPServer);
|
|
}
|
|
|
|
if(pszLDAPDn){
|
|
FreeADsStr(pszLDAPDn);
|
|
}
|
|
|
|
if (pszLDAPServerTemp) {
|
|
FreeADsStr(pszLDAPServerTemp);
|
|
}
|
|
|
|
if (pszLDAPSourceDnParent) {
|
|
FreeADsStr(pszLDAPSourceDnParent);
|
|
}
|
|
|
|
if (pANSIServer) {
|
|
LocalFree(pANSIServer);
|
|
}
|
|
|
|
if (pSourceLD) {
|
|
LdapCloseObject(pSourceLD);
|
|
}
|
|
|
|
if (pszDestLDAPRelativeName) {
|
|
FreeADsStr(pszDestLDAPRelativeName);
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLDAPGenObject::AllocateGenObject(
|
|
LPWSTR pszClassName,
|
|
CCredentials &Credentials,
|
|
CLDAPGenObject ** ppGenObject
|
|
)
|
|
{
|
|
CLDAPGenObject FAR * pGenObject = NULL;
|
|
CAggregatorDispMgr FAR * pDispMgr = NULL;
|
|
CPropertyCache FAR * pPropertyCache = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
pGenObject = new CLDAPGenObject();
|
|
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_IADsContainer,
|
|
(IADsContainer *)pGenObject,
|
|
DISPID_NEWENUM
|
|
);
|
|
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_REGULAR
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pDispMgr->LoadTypeInfoEntry(
|
|
LIBID_ADs,
|
|
IID_IADsDeleteOps,
|
|
(IADsDeleteOps *)pGenObject,
|
|
DISPID_REGULAR
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = CPropertyCache::createpropertycache(
|
|
(CCoreADsObject FAR *) pGenObject,
|
|
(IGetAttributeSyntax *)pGenObject,
|
|
&pPropertyCache
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pDispMgr->RegisterPropertyCache(pPropertyCache);
|
|
|
|
|
|
pGenObject->_pPropertyCache = pPropertyCache;
|
|
pGenObject->_pDispMgr = pDispMgr;
|
|
*ppGenObject = pGenObject;
|
|
|
|
RRETURN(hr);
|
|
|
|
error:
|
|
delete pPropertyCache;
|
|
delete pDispMgr;
|
|
delete pGenObject;
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
|
|
}
|
|
|
|
//
|
|
// IADsObjOptPrivate methods, IADsObjectOptions are wrapped
|
|
// around this.
|
|
//
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::GetOption(
|
|
DWORD dwOption,
|
|
void *pValue
|
|
)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
CtxtHandle hCtxtHandle;
|
|
DWORD dwErr = 0;
|
|
ULONG ulFlags = 0;
|
|
DWORD dwPropertyStatus;
|
|
LONG lUserAccountCtrlVal;
|
|
|
|
if (!pValue) {
|
|
RRETURN_EXP_IF_ERR(E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
switch( dwOption ) {
|
|
|
|
case LDP_CACHE_ENTRY:
|
|
*((PADSLDP *) pValue) = _pLdapHandle;
|
|
break;
|
|
|
|
case LDAP_HANDLE:
|
|
*((LDAP **) pValue) = _pLdapHandle ? _pLdapHandle->LdapHandle : (LDAP *) NULL;
|
|
break;
|
|
|
|
case LDAP_SERVER:
|
|
//
|
|
// pValue here is expected to be a pointer to LPWSTR
|
|
//
|
|
hr = GetActualHostName((LPWSTR *)pValue);
|
|
break;
|
|
|
|
case LDAP_DN:
|
|
*((LPWSTR *) pValue) = _pszLDAPDn;
|
|
break;
|
|
|
|
case LDAP_CHASE_REFERRALS:
|
|
*((DWORD *) pValue) = _dwOptReferral;
|
|
break;
|
|
|
|
case LDAP_PAGESIZE:
|
|
*((DWORD *) pValue) = _dwPageSize;
|
|
break;
|
|
|
|
case LDAP_SECURITY_MASK:
|
|
*((SECURITY_INFORMATION*) pValue) = _seInfo;
|
|
break;
|
|
|
|
case LDAP_MUTUAL_AUTH_STATUS:
|
|
dwErr = ldap_get_option(
|
|
_pLdapHandle->LdapHandle,
|
|
LDAP_OPT_SECURITY_CONTEXT,
|
|
(void *) &hCtxtHandle
|
|
);
|
|
if (dwErr) {
|
|
BAIL_ON_FAILURE(hr = E_FAIL);
|
|
}
|
|
|
|
#if (!defined(WIN95))
|
|
dwErr = QueryContextAttributesWrapper(
|
|
&hCtxtHandle,
|
|
SECPKG_ATTR_FLAGS,
|
|
(void *) &ulFlags
|
|
);
|
|
if (dwErr) {
|
|
if (dwErr == SEC_E_INVALID_HANDLE) {
|
|
//
|
|
// This will happen when SSL is used for certain.
|
|
//
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
}
|
|
else {
|
|
hr = HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
#else
|
|
ulFlags = 0;
|
|
#endif
|
|
|
|
*((ULONG *) pValue) = ulFlags;
|
|
break;
|
|
|
|
case LDAP_MEMBER_HAS_RANGE :
|
|
*((BOOL *) pValue) = _fRangeRetrieval;
|
|
break;
|
|
|
|
case LDAP_USERACCOUNTCONTROL :
|
|
hr = get_LONG_Property(this, TEXT("userAccountControl"), &lUserAccountCtrlVal );
|
|
// if it failed, we treat it as user does not want to explicitly unset the UF_LOCKOUT bit
|
|
if(FAILED(hr))
|
|
{
|
|
*((BOOL *) pValue) = FALSE;
|
|
break;
|
|
}
|
|
|
|
// user does not want to unset the UF_LOCKOUT flag
|
|
if(lUserAccountCtrlVal & UF_LOCKOUT)
|
|
{
|
|
*((BOOL *) pValue) = FALSE;
|
|
break;
|
|
}
|
|
|
|
hr = _pPropertyCache->GetPropertyFlag(L"userAccountControl", &dwPropertyStatus);
|
|
|
|
if(FAILED(hr))
|
|
*((BOOL *) pValue) = FALSE;
|
|
else if(dwPropertyStatus == PROPERTY_UPDATE || dwPropertyStatus == PROPERTY_ADD)
|
|
*((BOOL *) pValue) = TRUE;
|
|
else
|
|
*((BOOL *) pValue) = FALSE;
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
*((DWORD *) pValue) = 0;
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
|
|
}
|
|
|
|
error:
|
|
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::SetOption(
|
|
DWORD dwOption,
|
|
void *pValue
|
|
)
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
int ldaperr = 0;
|
|
void *ldapOption = 0;
|
|
|
|
switch (dwOption) {
|
|
|
|
case LDAP_CHASE_REFERRALS:
|
|
|
|
switch (*((DWORD *)pValue) ) {
|
|
|
|
case ADS_CHASE_REFERRALS_NEVER:
|
|
_dwOptReferral = (DWORD) (DWORD_PTR) LDAP_OPT_OFF;
|
|
break;
|
|
|
|
case ADS_CHASE_REFERRALS_SUBORDINATE:
|
|
_dwOptReferral = LDAP_CHASE_SUBORDINATE_REFERRALS;
|
|
break;
|
|
|
|
case ADS_CHASE_REFERRALS_EXTERNAL:
|
|
_dwOptReferral = LDAP_CHASE_EXTERNAL_REFERRALS;
|
|
break;
|
|
|
|
case ADS_CHASE_REFERRALS_ALWAYS:
|
|
_dwOptReferral = (DWORD) (DWORD_PTR) LDAP_OPT_ON;
|
|
break;
|
|
|
|
default:
|
|
RRETURN_EXP_IF_ERR(E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
break;
|
|
|
|
case LDAP_PAGESIZE:
|
|
_dwPageSize = *((DWORD *)pValue);
|
|
break;
|
|
|
|
case LDAP_SECURITY_MASK:
|
|
_seInfo = *((SECURITY_INFORMATION *) pValue);
|
|
_fExplicitSecurityMask = TRUE;
|
|
break;
|
|
|
|
default:
|
|
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
RRETURN_EXP_IF_ERR( hr );
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// IADsObjecOptions methods - wrapper around IADsObjOptPrivate
|
|
//
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::GetOption(
|
|
THIS_ long lnControlCode,
|
|
VARIANT FAR* pvProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPWSTR pszServerTemp = NULL;
|
|
|
|
ULONG ulMutualAuth = 0;
|
|
|
|
VariantInit(pvProp);
|
|
|
|
switch (lnControlCode) {
|
|
|
|
case ADS_OPTION_SERVERNAME:
|
|
|
|
hr = GetOption(LDAP_SERVER, (void *) &pszServerTemp);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pvProp->vt = VT_BSTR;
|
|
|
|
hr = ADsAllocString(
|
|
pszServerTemp,
|
|
&(pvProp->bstrVal)
|
|
);
|
|
break;
|
|
|
|
case ADS_OPTION_SECURITY_MASK:
|
|
//
|
|
// No need to call GetOpion at least not now
|
|
//
|
|
pvProp->vt = VT_I4;
|
|
pvProp->lVal = (ULONG) _seInfo;
|
|
break;
|
|
|
|
case ADS_OPTION_REFERRALS :
|
|
pvProp->vt = VT_I4;
|
|
|
|
switch (_dwOptReferral) {
|
|
|
|
case ((DWORD) (DWORD_PTR)LDAP_OPT_OFF) :
|
|
pvProp->lVal = ADS_CHASE_REFERRALS_NEVER;
|
|
break;
|
|
|
|
case LDAP_CHASE_SUBORDINATE_REFERRALS :
|
|
pvProp->lVal = ADS_CHASE_REFERRALS_SUBORDINATE;
|
|
break;
|
|
|
|
case LDAP_CHASE_EXTERNAL_REFERRALS :
|
|
pvProp->lVal = ADS_CHASE_REFERRALS_EXTERNAL;
|
|
break;
|
|
|
|
case ((DWORD) (DWORD_PTR)LDAP_OPT_ON) :
|
|
pvProp->lVal = ADS_CHASE_REFERRALS_ALWAYS;
|
|
break;
|
|
|
|
default:
|
|
pvProp->lVal = 0;
|
|
hr = E_ADS_PROPERTY_INVALID;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_PAGE_SIZE :
|
|
pvProp->vt = VT_I4;
|
|
pvProp->lVal = (ULONG) _dwPageSize;
|
|
break;
|
|
|
|
case ADS_OPTION_MUTUAL_AUTH_STATUS :
|
|
hr = GetOption(LDAP_MUTUAL_AUTH_STATUS, (void *) &ulMutualAuth);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pvProp->vt = VT_I4;
|
|
pvProp->lVal = ulMutualAuth;
|
|
break;
|
|
|
|
case ADS_OPTION_PASSWORD_PORTNUMBER :
|
|
pvProp->vt = VT_I4;
|
|
if(_fPasswordPortSet)
|
|
{
|
|
// user explicitly set the password
|
|
pvProp->lVal = _dwPasswordPort;
|
|
}
|
|
else
|
|
{
|
|
// we need to use the default value
|
|
if(_dwPasswordMethod == ADS_PASSWORD_ENCODE_REQUIRE_SSL)
|
|
pvProp->lVal = 636;
|
|
else
|
|
pvProp->lVal = 389;
|
|
}
|
|
break;
|
|
|
|
case ADS_OPTION_PASSWORD_METHOD :
|
|
pvProp->vt = VT_I4;
|
|
pvProp->lVal = _dwPasswordMethod;
|
|
break;
|
|
|
|
default:
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
}
|
|
|
|
error :
|
|
|
|
if (pszServerTemp) {
|
|
FreeADsStr(pszServerTemp);
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CLDAPGenObject::SetOption(
|
|
THIS_ long lnControlCode,
|
|
VARIANT vProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwOptVal = 0;
|
|
VARIANT *pvProp = NULL;
|
|
LPWSTR pszServerTemp = NULL;
|
|
PSID pSidTemp = NULL;
|
|
DWORD dwSidSizeTemp;
|
|
BOOL fNTDS = TRUE;
|
|
|
|
//
|
|
// 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_OPTION_REFERRALS :
|
|
|
|
hr = GetIntegerFromVariant(pvProp, &dwOptVal);
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = SetOption(LDAP_CHASE_REFERRALS, (void *) &dwOptVal);
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_PAGE_SIZE :
|
|
|
|
hr = GetIntegerFromVariant(pvProp, &dwOptVal);
|
|
|
|
if (SUCCEEDED(hr))
|
|
hr = SetOption(LDAP_PAGESIZE, (void *) &dwOptVal);
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_SECURITY_MASK :
|
|
|
|
hr = GetIntegerFromVariant(pvProp, &dwOptVal);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
_seInfo = (SECURITY_INFORMATION) dwOptVal;
|
|
_fExplicitSecurityMask = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_QUOTA :
|
|
|
|
// validate the parameter
|
|
if(pvProp->vt == VT_EMPTY)
|
|
{
|
|
// user just wants to set the sid as himself
|
|
|
|
// Free existing memory if necessary
|
|
if(_pSid)
|
|
{
|
|
FreeADsMem(_pSid);
|
|
|
|
_pSid = NULL;
|
|
_dwSidLength = 0;
|
|
}
|
|
|
|
}
|
|
else if(pvProp->vt == VT_BSTR)
|
|
{
|
|
if(pvProp->bstrVal && *(pvProp->bstrVal) != L'\0')
|
|
{
|
|
// get the server name
|
|
hr = GetOption(LDAP_SERVER, (void *) &pszServerTemp);
|
|
if(FAILED(hr))
|
|
break;
|
|
|
|
hr = ReadServerType(pszServerTemp,
|
|
&_Credentials,
|
|
&fNTDS
|
|
);
|
|
if(FAILED(hr))
|
|
break;
|
|
|
|
// convert the trustee to sid
|
|
hr = ConvertTrusteeToSid(pszServerTemp,
|
|
_Credentials,
|
|
pvProp->bstrVal,
|
|
&pSidTemp,
|
|
&dwSidSizeTemp,
|
|
fNTDS
|
|
);
|
|
if(FAILED(hr))
|
|
break;
|
|
|
|
// Free existing memory if necessary
|
|
if(_pSid)
|
|
{
|
|
FreeADsMem(_pSid);
|
|
}
|
|
|
|
_pSid = pSidTemp;
|
|
_dwSidLength = dwSidSizeTemp;
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
// user just wants to set the sid as himself
|
|
|
|
// Free existing memory if necessary
|
|
if(_pSid)
|
|
{
|
|
FreeADsMem(_pSid);
|
|
|
|
_pSid = NULL;
|
|
_dwSidLength = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
}
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_PASSWORD_PORTNUMBER:
|
|
hr = GetIntegerFromVariant(pvProp, &dwOptVal);
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
_dwPasswordPort = dwOptVal;
|
|
_fPasswordPortSet = TRUE;
|
|
}
|
|
|
|
break;
|
|
|
|
case ADS_OPTION_PASSWORD_METHOD:
|
|
hr = GetIntegerFromVariant(pvProp, &dwOptVal);
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
if(dwOptVal == ADS_PASSWORD_ENCODE_REQUIRE_SSL || dwOptVal == ADS_PASSWORD_ENCODE_CLEAR)
|
|
{
|
|
_dwPasswordMethod = dwOptVal;
|
|
}
|
|
else
|
|
{
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case ADS_PRIVATE_OPTION_SPECIFIC_SERVER :
|
|
|
|
//
|
|
// If it is just a VT_BSTR, then this is old.
|
|
// If this is an array, then it should also
|
|
// have the domain information.
|
|
//
|
|
if (pvProp->vt == VT_BSTR) {
|
|
|
|
if (gpszStickyDomainName) {
|
|
FreeADsStr(gpszStickyDomainName);
|
|
gpszStickyDomainName = NULL;
|
|
}
|
|
|
|
if (gpszStickyServerName) {
|
|
FreeADsStr(gpszStickyServerName);
|
|
gpszStickyServerName = NULL;
|
|
}
|
|
//
|
|
// Set for LDAP layer
|
|
//
|
|
if (pvProp->bstrVal) {
|
|
gpszStickyServerName = AllocADsStr(pvProp->bstrVal);
|
|
|
|
if (!gpszStickyServerName) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
hr = LdapcSetStickyServer(NULL, pvProp->bstrVal);
|
|
}
|
|
}
|
|
else if ((pvProp->vt & VT_ARRAY)) {
|
|
hr = SetStickyServerWithDomain(pvProp);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
}
|
|
|
|
// release the memory
|
|
if (pszServerTemp) {
|
|
FreeADsStr(pszServerTemp);
|
|
}
|
|
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
ConvertVariantToVariantArray(
|
|
VARIANT varData,
|
|
VARIANT ** ppVarArray,
|
|
DWORD * pdwNumValues
|
|
)
|
|
{
|
|
DWORD dwNumValues = 0;
|
|
VARIANT * pVarArray = NULL;
|
|
HRESULT hr = S_OK;
|
|
VARIANT * pvarData = NULL;
|
|
|
|
*ppVarArray = NULL;
|
|
*pdwNumValues = 0;
|
|
|
|
//
|
|
// Although no known automation controller passes
|
|
// VT_VARIANT|VT_BYREF into get_/put_ function
|
|
// as a reference to an array, we carry out the following
|
|
// check for extra safety.
|
|
//
|
|
pvarData = &varData;
|
|
if (V_VT(pvarData) == (VT_VARIANT|VT_BYREF)) {
|
|
pvarData = V_VARIANTREF(&varData);
|
|
}
|
|
|
|
if ((V_VT(pvarData) == (VT_VARIANT|VT_ARRAY|VT_BYREF)) ||
|
|
(V_VT(pvarData) == (VT_VARIANT|VT_ARRAY))) {
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
*pvarData,
|
|
&pVarArray,
|
|
&dwNumValues
|
|
);
|
|
// returns E_FAIL if *pvarData is invalid
|
|
if (hr == E_FAIL)
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
} else {
|
|
|
|
pVarArray = NULL;
|
|
dwNumValues = 0;
|
|
}
|
|
|
|
*ppVarArray = pVarArray;
|
|
*pdwNumValues = dwNumValues;
|
|
|
|
error:
|
|
RRETURN(hr);
|
|
}
|
|
|
|
void
|
|
FreeVariantArray(
|
|
VARIANT * pVarArray,
|
|
DWORD dwNumValues
|
|
)
|
|
{
|
|
if (pVarArray) {
|
|
|
|
DWORD i = 0;
|
|
|
|
for (i = 0; i < dwNumValues; i++) {
|
|
VariantClear(pVarArray + i);
|
|
}
|
|
FreeADsMem(pVarArray);
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT
|
|
ConvertVariantToLdapValues(
|
|
VARIANT varData,
|
|
LPWSTR* ppszPropertyName,
|
|
PDWORD pdwControlCode,
|
|
LDAPOBJECTARRAY * pldapDestObjects,
|
|
PDWORD pdwSyntaxId,
|
|
LPWSTR pszServer,
|
|
CCredentials* pCredentials,
|
|
DWORD dwPort
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IADsPropertyEntry * pPropEntry = NULL;
|
|
IDispatch * pDispatch = NULL;
|
|
BSTR bstrPropName = NULL;
|
|
DWORD dwControlCode = 0;
|
|
DWORD dwAdsType = 0;
|
|
VARIANT varValues;
|
|
VARIANT * pVarArray = NULL;
|
|
DWORD dwNumValues = 0;
|
|
PADSVALUE pAdsValues = NULL;
|
|
DWORD dwAdsValues = 0;
|
|
PVARIANT pVar = NULL;
|
|
BOOL fNTDSType = TRUE;
|
|
BOOL fGenTime = FALSE;
|
|
DWORD dwServerSyntaxId = 0;
|
|
|
|
*ppszPropertyName = NULL;
|
|
|
|
|
|
if (V_VT(&varData) != VT_DISPATCH) {
|
|
|
|
if (V_VT(&varData) != (VT_VARIANT | VT_BYREF)) {
|
|
|
|
RRETURN (hr = DISP_E_TYPEMISMATCH);
|
|
}
|
|
else {
|
|
|
|
pVar = V_VARIANTREF(&varData);
|
|
|
|
if (pVar == NULL || V_VT(pVar) != VT_DISPATCH) {
|
|
|
|
RRETURN (hr = DISP_E_TYPEMISMATCH);
|
|
}
|
|
else {
|
|
pDispatch = V_DISPATCH(pVar);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pDispatch = V_DISPATCH(&varData);
|
|
}
|
|
|
|
|
|
VariantInit(&varValues);
|
|
|
|
hr = pDispatch->QueryInterface(
|
|
IID_IADsPropertyEntry,
|
|
(void **)&pPropEntry
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = pPropEntry->get_Name(&bstrPropName);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
// property name should not be NULL
|
|
if(!bstrPropName)
|
|
{
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
*ppszPropertyName= AllocADsStr(bstrPropName);
|
|
if(!(*ppszPropertyName))
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
hr = pPropEntry->get_ControlCode((long *)&dwControlCode);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pPropEntry->get_ADsType((long *)&dwAdsType);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pPropEntry->get_Values(&varValues);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = ConvertVariantToVariantArray(
|
|
varValues,
|
|
&pVarArray,
|
|
&dwNumValues
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (dwNumValues) {
|
|
//
|
|
// At this point it is probably cheaper to read the
|
|
// server type and send the values to the conversion
|
|
// routines than to check if this is a securityDescriptor
|
|
// by walking the PropertyEntry and then looking at the variant.
|
|
// There is a good chance that this information is already cached.
|
|
//
|
|
|
|
hr = ReadServerType(
|
|
pszServer,
|
|
pCredentials,
|
|
&fNTDSType
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
|
|
hr = PropVariantToAdsType2(
|
|
pVarArray,
|
|
dwNumValues,
|
|
&pAdsValues,
|
|
&dwAdsValues,
|
|
pszServer,
|
|
pCredentials,
|
|
fNTDSType
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (dwAdsType == ADSTYPE_UTC_TIME) {
|
|
//
|
|
// See if this is a GenTime on the server
|
|
//
|
|
HRESULT hr;
|
|
hr = LdapGetSyntaxOfAttributeOnServer(
|
|
pszServer,
|
|
*ppszPropertyName,
|
|
&dwServerSyntaxId,
|
|
*pCredentials,
|
|
dwPort
|
|
);
|
|
|
|
if (FAILED(hr)) {
|
|
hr = S_OK;
|
|
}
|
|
else if (dwServerSyntaxId == LDAPTYPE_GENERALIZEDTIME) {
|
|
fGenTime = TRUE;
|
|
}
|
|
}
|
|
|
|
hr = AdsTypeToLdapTypeCopyConstruct(
|
|
pAdsValues,
|
|
dwAdsValues,
|
|
pldapDestObjects,
|
|
pdwSyntaxId,
|
|
fGenTime
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
}
|
|
|
|
*pdwControlCode = dwControlCode;
|
|
|
|
cleanup:
|
|
|
|
if (bstrPropName) {
|
|
ADsFreeString(bstrPropName);
|
|
}
|
|
|
|
if (pAdsValues) {
|
|
AdsTypeFreeAdsObjects(
|
|
pAdsValues,
|
|
dwNumValues
|
|
);
|
|
}
|
|
|
|
if (pVarArray) {
|
|
|
|
FreeVariantArray(
|
|
pVarArray,
|
|
dwAdsValues
|
|
);
|
|
}
|
|
|
|
if (pPropEntry) {
|
|
|
|
pPropEntry->Release();
|
|
}
|
|
|
|
VariantClear(&varValues);
|
|
|
|
RRETURN(hr);
|
|
|
|
error:
|
|
|
|
LdapTypeFreeLdapObjects( pldapDestObjects );
|
|
|
|
if(*ppszPropertyName)
|
|
{
|
|
FreeADsStr(*ppszPropertyName);
|
|
*ppszPropertyName = NULL;
|
|
}
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
HRESULT
|
|
MapAdsTypeToLdapType(
|
|
BSTR bstrPropName,
|
|
DWORD dwAdsType,
|
|
PDWORD pdwLdapType
|
|
)
|
|
{
|
|
RRETURN(S_OK);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
ConvertLdapValuesToVariant(
|
|
BSTR bstrPropName,
|
|
LDAPOBJECTARRAY * pldapSrcObjects,
|
|
DWORD dwLdapType,
|
|
DWORD dwControlCode,
|
|
PVARIANT pVarProp,
|
|
LPWSTR pszServer,
|
|
CCredentials* pCredentials
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
PADSVALUE pAdsValues = NULL;
|
|
DWORD dwNumAdsValues = 0;
|
|
DWORD dwNumValues = 0;
|
|
DWORD dwAdsType = 0;
|
|
VARIANT varData;
|
|
IDispatch * pDispatch = NULL;
|
|
BOOL fNTDS = TRUE;
|
|
|
|
VariantInit(&varData);
|
|
VariantClear(&varData);
|
|
VariantInit(pVarProp);
|
|
|
|
// pldaSrcObject should never be null
|
|
ADsAssert(pldapSrcObjects);
|
|
|
|
if (dwControlCode != ADS_PROPERTY_DELETE ) {
|
|
|
|
hr = LdapTypeToAdsTypeCopyConstruct(
|
|
*pldapSrcObjects,
|
|
dwLdapType,
|
|
&pAdsValues,
|
|
&dwNumAdsValues,
|
|
&dwAdsType
|
|
);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
dwNumValues = pldapSrcObjects->dwCount;
|
|
|
|
//
|
|
// if the property is a security descriptor
|
|
// we need to set the server type also
|
|
//
|
|
|
|
if (dwAdsType == ADSTYPE_NT_SECURITY_DESCRIPTOR) {
|
|
|
|
hr = ReadServerType(
|
|
pszServer,
|
|
pCredentials,
|
|
&fNTDS
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = AdsTypeToPropVariant2(
|
|
pAdsValues,
|
|
dwNumValues,
|
|
&varData,
|
|
pszServer,
|
|
pCredentials,
|
|
fNTDS
|
|
);
|
|
|
|
} else {
|
|
|
|
hr = AdsTypeToPropVariant(
|
|
pAdsValues,
|
|
dwNumValues,
|
|
&varData
|
|
);
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
} else {
|
|
|
|
// We could not convert the data type
|
|
// This maybe because we have the invalid data type
|
|
// so we will just return the variant data as null
|
|
// and also set the data type to 0 or invalid in that case
|
|
if (dwLdapType == LDAPTYPE_UNKNOWN) {
|
|
dwAdsType = 0;
|
|
hr = S_OK;
|
|
} else {
|
|
// since the datatpye was valid, we should
|
|
// send this back to the user.
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
}
|
|
} else {
|
|
|
|
dwAdsType = 0;
|
|
|
|
}
|
|
|
|
hr = CreatePropEntry(
|
|
bstrPropName,
|
|
dwAdsType,
|
|
dwNumValues,
|
|
dwControlCode,
|
|
varData,
|
|
IID_IDispatch,
|
|
(void **)&pDispatch
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
V_DISPATCH(pVarProp) = pDispatch;
|
|
V_VT(pVarProp) = VT_DISPATCH;
|
|
|
|
error:
|
|
|
|
VariantClear(&varData);
|
|
|
|
if (pAdsValues) {
|
|
AdsTypeFreeAdsObjects(
|
|
pAdsValues,
|
|
dwNumValues
|
|
);
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Needed for dynamic dispid's in the property cache.
|
|
//
|
|
HRESULT
|
|
CLDAPGenObject::GetAttributeSyntax(
|
|
LPWSTR szPropertyName,
|
|
PDWORD pdwSyntaxId
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
hr = LdapGetSyntaxOfAttributeOnServer(
|
|
_pszLDAPServer,
|
|
szPropertyName,
|
|
pdwSyntaxId,
|
|
_Credentials,
|
|
_dwPort
|
|
);
|
|
RRETURN_EXP_IF_ERR(hr);
|
|
}
|
|
|
|
|
|
|
|
HRESULT
|
|
CLDAPGenObject::DeleteObject(
|
|
long lnFlags
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
LDAPControl SeInfoControlRecursDelete =
|
|
{
|
|
LDAP_SERVER_TREE_DELETE_OID_W,
|
|
|
|
{ NULL, NULL},
|
|
|
|
FALSE
|
|
};
|
|
|
|
PLDAPControl ServerControls[2] =
|
|
{
|
|
&SeInfoControlRecursDelete,
|
|
NULL
|
|
};
|
|
|
|
|
|
hr = LdapDeleteExtS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
(PLDAPControl *)&ServerControls,
|
|
NULL
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLDAPGenObject::GetActualHostName(
|
|
LPWSTR * pValue
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD dwLength = MAX_PATH;
|
|
LPWSTR szHostName = NULL;
|
|
int err = 0;
|
|
|
|
LDAPMessage *pMsgResult = NULL;
|
|
LDAPMessage *pMsgEntry = NULL;
|
|
LDAP *pLdapCurrent = NULL;
|
|
LPWSTR Attributes[] = {L"objectClass", NULL};
|
|
|
|
//
|
|
// We need to get at the actual object as we may have a
|
|
// referral
|
|
//
|
|
hr = LdapSearchS(
|
|
_pLdapHandle,
|
|
_pszLDAPDn,
|
|
LDAP_SCOPE_BASE,
|
|
L"(objectClass=*)",
|
|
Attributes,
|
|
0,
|
|
&pMsgResult
|
|
);
|
|
|
|
//
|
|
// Only one entry should be returned
|
|
//
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = LdapFirstEntry(
|
|
_pLdapHandle,
|
|
pMsgResult,
|
|
&pMsgEntry
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
pLdapCurrent = pMsgResult->Connection;
|
|
|
|
err = ldap_get_optionW(
|
|
pLdapCurrent,
|
|
LDAP_OPT_HOST_NAME,
|
|
&szHostName
|
|
);
|
|
|
|
if (err != LDAP_SUCCESS || szHostName == NULL) {
|
|
BAIL_ON_FAILURE(hr = E_FAIL);
|
|
}
|
|
|
|
// If we are here we need to copy the name and return
|
|
|
|
*pValue = AllocADsStr(szHostName);
|
|
if(!(*pValue)) {
|
|
hr = E_OUTOFMEMORY;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
|
|
error:
|
|
|
|
if (pMsgResult) {
|
|
LdapMsgFree(pMsgResult);
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
HRESULT
|
|
GetIntegerFromVariant(
|
|
VARIANT* pvProp,
|
|
DWORD* pdwVal)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
*pdwVal = 0;
|
|
|
|
if (pvProp->vt == VT_I4) {
|
|
|
|
*pdwVal = pvProp->lVal;
|
|
|
|
}
|
|
else if(pvProp->vt == VT_I2) {
|
|
|
|
*pdwVal = pvProp->iVal;
|
|
|
|
} else
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------------
|
|
//
|
|
// Function: TraceTreeForClass
|
|
//
|
|
// Synopsis: Traces the inheritance hierarchy for the class being crerated
|
|
// and returns the list in the arg pppszNameArr. There are
|
|
// *plnNumElements in this array. This can be used later while
|
|
// creating the object so that all the extensions will be available.
|
|
//
|
|
// Arguments:
|
|
// Parent - The ADsPath of the parent,
|
|
// CommonName - RDN of the object being created,
|
|
// pszClassName- Class of the object being created,
|
|
// Credentials - Credentials blob,
|
|
// pppszNameArr- Return value - array of names of parent classes.
|
|
// plnNumItems - Return value - number of elements in above array.
|
|
//
|
|
//-------------------------------------------------------------------------
|
|
HRESULT
|
|
TraceTreeForClass(
|
|
BSTR Parent,
|
|
BSTR CommonName,
|
|
LPWSTR pszClassName,
|
|
CCredentials& Credentials,
|
|
PWCHAR **pppszNameArr,
|
|
PLONG plnNumElements
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IADsClass *pIADsClass = NULL;
|
|
IUnknown *pUnk = NULL;
|
|
|
|
PWCHAR *pszRetVal = NULL;
|
|
WCHAR* pszSchemaPathBase = NULL;
|
|
WCHAR* pszSchemaPath = NULL;
|
|
LPWSTR pszServer = NULL;
|
|
LPWSTR pszLDAPDn = NULL;
|
|
LPWSTR pszCurVal = NULL;
|
|
DWORD dwPort = 0;
|
|
VARIANT vBstrVal;
|
|
CCredentials Creds = Credentials;
|
|
long lnNumItems = 1;
|
|
BOOL fDone = FALSE;
|
|
|
|
PClassesHierarchyList pClassListHead = NULL;
|
|
PClassesHierarchyList pClassListNode = NULL;
|
|
|
|
//
|
|
// We need to make sure that the ADS_AUTH_RESERVED flag is not set
|
|
// in the credentials because that will result in us not getting the
|
|
// IADsClass interface ptr we want.
|
|
//
|
|
Creds.SetAuthFlags(Creds.GetAuthFlags() & ~ADS_AUTH_RESERVED);
|
|
|
|
|
|
VariantInit(&vBstrVal);
|
|
|
|
//
|
|
// Build The schema name
|
|
//
|
|
hr = BuildLDAPPathFromADsPath2(
|
|
Parent,
|
|
&pszServer,
|
|
&pszLDAPDn,
|
|
&dwPort
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (pszServer) {
|
|
|
|
pszSchemaPathBase = new WCHAR[wcslen(L"LDAP://") + wcslen(pszServer) + 1];
|
|
if(!pszSchemaPathBase)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
wsprintf(pszSchemaPathBase, L"LDAP://%s", pszServer);
|
|
|
|
} else {
|
|
pszSchemaPathBase = new WCHAR[wcslen(L"LDAP:/") + 1];
|
|
if(!pszSchemaPathBase)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
|
|
wsprintf(pszSchemaPathBase, L"LDAP:/");
|
|
}
|
|
|
|
pszCurVal = AllocADsStr(pszClassName);
|
|
|
|
if (!pszCurVal) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
while (!fDone && _wcsicmp(L"Top", pszCurVal)) {
|
|
|
|
lnNumItems++;
|
|
//
|
|
// Add a new node to the list
|
|
//
|
|
pClassListNode = (PClassesHierarchyList)
|
|
AllocADsMem(sizeof(ClassesHierarchyList));
|
|
if (!pClassListNode) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
pClassListNode->pszClassName = AllocADsStr(pszCurVal);
|
|
if (!pClassListNode->pszClassName) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
pClassListNode->pNext = pClassListHead;
|
|
pClassListHead = pClassListNode;
|
|
|
|
//
|
|
// Check for the next class on the list
|
|
//
|
|
|
|
if(pszSchemaPath)
|
|
{
|
|
delete [] pszSchemaPath;
|
|
}
|
|
|
|
pszSchemaPath = new WCHAR[wcslen(pszSchemaPathBase) + wcslen(L"/Schema/") + wcslen(pszCurVal) + 1];
|
|
if(!pszSchemaPath)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
BAIL_ON_FAILURE(hr);
|
|
}
|
|
wcscpy(pszSchemaPath, pszSchemaPathBase);
|
|
|
|
wcscat(pszSchemaPath, L"/Schema/");
|
|
wcscat(pszSchemaPath, pszCurVal);
|
|
|
|
FreeADsStr(pszCurVal);
|
|
pszCurVal = NULL;
|
|
|
|
hr = GetObject(
|
|
pszSchemaPath,
|
|
Credentials,
|
|
(LPVOID *) &pUnk
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
hr = pUnk->QueryInterface(
|
|
IID_IADsClass,
|
|
(void **) &pIADsClass
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Release the ref on the pUnk
|
|
//
|
|
pUnk->Release();
|
|
pUnk = NULL;
|
|
|
|
hr = pIADsClass->get_DerivedFrom(&vBstrVal);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Release the ptr as we no longer need it.
|
|
//
|
|
pIADsClass->Release();
|
|
pIADsClass = NULL;
|
|
|
|
|
|
if (vBstrVal.vt == (VT_VARIANT | VT_ARRAY)) {
|
|
//
|
|
// Server has complete list of classes in schema
|
|
//
|
|
hr = AddToClassesList(
|
|
vBstrVal,
|
|
&pszCurVal,
|
|
&pClassListHead,
|
|
&lnNumItems
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// add one to the count for the value passed in
|
|
//
|
|
lnNumItems++;
|
|
fDone = TRUE;
|
|
}
|
|
else if (vBstrVal.vt == VT_BSTR) {
|
|
|
|
pszCurVal = AllocADsStr(vBstrVal.bstrVal);
|
|
VariantClear(&vBstrVal);
|
|
if (!pszCurVal) {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
else {
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
}
|
|
|
|
//
|
|
// We now have the list as well as the number of items
|
|
//
|
|
pszRetVal = (PWCHAR *) AllocADsMem(sizeof(PWCHAR) * (lnNumItems + 1));
|
|
|
|
if (!pszRetVal) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
//
|
|
// Do not move as we may call -- below
|
|
//
|
|
*plnNumElements = lnNumItems;
|
|
|
|
pszRetVal[lnNumItems] = NULL;
|
|
|
|
if (!fDone) {
|
|
//
|
|
// Add top to the list and set last to NULL
|
|
// only if we did not hit addclasses fn.
|
|
//
|
|
pszRetVal[lnNumItems - 1] = AllocADsStr(L"Top");
|
|
if (!pszRetVal[lnNumItems - 1]) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
lnNumItems--;
|
|
}
|
|
|
|
while (pClassListHead && (lnNumItems > -1)) {
|
|
|
|
pszRetVal[--lnNumItems] =
|
|
AllocADsStr(pClassListHead->pszClassName);
|
|
|
|
if (!pszRetVal[lnNumItems]) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
//
|
|
// Free the entry and advance list
|
|
//
|
|
FreeADsStr(pClassListHead->pszClassName);
|
|
pClassListNode = pClassListHead;
|
|
pClassListHead = pClassListHead->pNext;
|
|
FreeADsMem(pClassListNode);
|
|
pClassListNode = NULL;
|
|
}
|
|
|
|
//
|
|
// Put appropriate value in return arg.
|
|
//
|
|
*pppszNameArr = pszRetVal;
|
|
|
|
error:
|
|
|
|
if (pszServer) {
|
|
FreeADsStr(pszServer);
|
|
}
|
|
|
|
if (pszLDAPDn) {
|
|
FreeADsStr(pszLDAPDn);
|
|
}
|
|
|
|
if (pszCurVal) {
|
|
FreeADsStr(pszCurVal);
|
|
}
|
|
|
|
if (pUnk) {
|
|
pUnk->Release();
|
|
}
|
|
|
|
if (pIADsClass) {
|
|
pIADsClass->Release();
|
|
}
|
|
|
|
if(pszSchemaPathBase)
|
|
{
|
|
delete [] pszSchemaPathBase;
|
|
pszSchemaPathBase = NULL;
|
|
}
|
|
|
|
if(pszSchemaPath)
|
|
{
|
|
delete [] pszSchemaPath;
|
|
pszSchemaPath = NULL;
|
|
}
|
|
|
|
//
|
|
// Walk through and free the list if necessary
|
|
//
|
|
while (pClassListHead) {
|
|
|
|
pClassListNode = pClassListHead;
|
|
|
|
if (pClassListHead->pszClassName) {
|
|
FreeADsStr(pClassListHead->pszClassName);
|
|
}
|
|
|
|
pClassListHead = pClassListHead->pNext;
|
|
FreeADsMem(pClassListNode);
|
|
}
|
|
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
//
|
|
// Helper to get the values from the Variant and add to list
|
|
//
|
|
HRESULT
|
|
AddToClassesList(
|
|
VARIANT vProps,
|
|
LPWSTR *ppszCurClass,
|
|
PClassesHierarchyList *ppClassListHead,
|
|
PLONG plnNumItems
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
PClassesHierarchyList pClassNode = NULL;
|
|
VARIANT * pVarArray = NULL;
|
|
VARIANT * pvProp = NULL;
|
|
DWORD dwNumValues = 0, i = 0;
|
|
|
|
pvProp = &vProps;
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
*pvProp,
|
|
&pVarArray,
|
|
&dwNumValues
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// Go through the array adding nodes.
|
|
//
|
|
for (i = 0; i < dwNumValues; i++) {
|
|
|
|
pvProp = pVarArray + i;
|
|
if (pvProp->vt != VT_BSTR) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
//
|
|
// Alloc node to add
|
|
//
|
|
pClassNode = (PClassesHierarchyList)
|
|
AllocADsMem(sizeof(ClassesHierarchyList));
|
|
|
|
if (!pClassNode) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
pClassNode->pszClassName = AllocADsStr(pvProp->bstrVal);
|
|
if (!pClassNode->pszClassName) {
|
|
//
|
|
// Free pClassNode as we will let caller free the list
|
|
// if we run out of memory.
|
|
//
|
|
FreeADsMem(pClassNode);
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
pClassNode->pNext = *ppClassListHead;
|
|
*ppClassListHead = pClassNode;
|
|
} // end for
|
|
|
|
*plnNumItems = dwNumValues;
|
|
error:
|
|
|
|
if (pVarArray) {
|
|
|
|
for (i = 0; i < dwNumValues; i++) {
|
|
VariantClear(pVarArray + i);
|
|
}
|
|
FreeADsMem(pVarArray);
|
|
}
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|
|
|
|
//
|
|
// Helper routine that handles setting the sticky server private
|
|
// option when the input is an array.
|
|
//
|
|
HRESULT
|
|
SetStickyServerWithDomain(
|
|
PVARIANT pvProp
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
VARIANT *pvVarArray = NULL;
|
|
DWORD dwNumVariants = 0;
|
|
DWORD dwCtr = 0;
|
|
LPWSTR *ppszStringArray = NULL;
|
|
|
|
hr = ConvertSafeArrayToVariantArray(
|
|
*pvProp,
|
|
&pvVarArray,
|
|
&dwNumVariants
|
|
);
|
|
|
|
// returns E_FAIL if vProperties is invalid
|
|
if (hr == E_FAIL)
|
|
hr = E_ADS_BAD_PARAMETER;
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
//
|
|
// There have to be precisely 2 entries, one for domain
|
|
// and the second for the serverName.
|
|
//
|
|
if (dwNumVariants != 2) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
hr = ConvertVariantArrayToLDAPStringArray(
|
|
pvVarArray,
|
|
&ppszStringArray,
|
|
dwNumVariants
|
|
);
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
if (!ppszStringArray
|
|
|| !ppszStringArray[0]
|
|
|| !*(ppszStringArray[0])
|
|
|| !ppszStringArray[1]
|
|
|| !*(ppszStringArray[1])
|
|
) {
|
|
BAIL_ON_FAILURE(hr = E_ADS_BAD_PARAMETER);
|
|
}
|
|
|
|
if (gpszStickyServerName) {
|
|
FreeADsStr(gpszStickyServerName);
|
|
gpszStickyServerName = NULL;
|
|
}
|
|
|
|
if (gpszStickyDomainName) {
|
|
FreeADsStr(gpszStickyDomainName);
|
|
gpszStickyDomainName = NULL;
|
|
}
|
|
|
|
gpszStickyServerName = AllocADsStr(ppszStringArray[1]);
|
|
if (!gpszStickyServerName) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
gpszStickyDomainName = AllocADsStr(ppszStringArray[0]);
|
|
if (!gpszStickyDomainName) {
|
|
BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
|
|
}
|
|
|
|
hr = LdapcSetStickyServer(
|
|
gpszStickyDomainName,
|
|
gpszStickyServerName
|
|
);
|
|
|
|
BAIL_ON_FAILURE(hr);
|
|
|
|
error:
|
|
|
|
if (FAILED(hr)) {
|
|
//
|
|
// Clear the global strings on failure.
|
|
//
|
|
if (gpszStickyServerName) {
|
|
FreeADsStr(gpszStickyServerName);
|
|
gpszStickyServerName = NULL;
|
|
}
|
|
|
|
if (gpszStickyDomainName) {
|
|
FreeADsStr(gpszStickyDomainName);
|
|
gpszStickyDomainName = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cleanup variant array and string array.
|
|
//
|
|
if (pvVarArray) {
|
|
for (dwCtr = 0; dwCtr < dwNumVariants; dwCtr++) {
|
|
VariantClear(pvVarArray + dwCtr);
|
|
}
|
|
FreeADsMem(pvVarArray);
|
|
}
|
|
|
|
if (ppszStringArray) {
|
|
for (dwCtr = 0; dwCtr < dwNumVariants; dwCtr++) {
|
|
if (ppszStringArray[dwCtr])
|
|
FreeADsStr(ppszStringArray[dwCtr]);
|
|
}
|
|
FreeADsMem(ppszStringArray);
|
|
}
|
|
|
|
|
|
RRETURN(hr);
|
|
}
|
|
|