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.
1746 lines
47 KiB
1746 lines
47 KiB
#include "StdAfx.h"
|
|
#include "ADMTScript.h"
|
|
#include "MigrationBase.h"
|
|
|
|
#include <LM.h>
|
|
#include <DsGetDc.h>
|
|
#include <NtSecApi.h>
|
|
#include <Sddl.h>
|
|
#include <dsrole.h>
|
|
#include "SidHistoryFlags.h"
|
|
#include "VerifyConfiguration.h"
|
|
|
|
#include "Error.h"
|
|
#include "VarSetAccounts.h"
|
|
#include "VarSetServers.h"
|
|
#include "FixHierarchy.h"
|
|
#include "GetDcName.h"
|
|
|
|
using namespace _com_util;
|
|
|
|
#ifndef STATUS_SUCCESS
|
|
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
|
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
|
|
#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L)
|
|
#endif
|
|
|
|
namespace MigrationBase
|
|
{
|
|
|
|
bool __stdcall IsInboundTrustDefined(PCWSTR pszDomain);
|
|
bool __stdcall IsOutboundTrustDefined(PCWSTR pszDomainController, PCWSTR pszDomainSid);
|
|
DWORD __stdcall GetOutboundTrustStatus(PCWSTR pszDomainController, PCWSTR pszDomain);
|
|
|
|
void GetNamesFromData(VARIANT& vntData, StringSet& setNames);
|
|
void GetNamesFromVariant(VARIANT* pvnt, StringSet& setNames);
|
|
void GetNamesFromString(BSTR bstr, StringSet& setNames);
|
|
void GetNamesFromStringArray(SAFEARRAY* psa, StringSet& setNames);
|
|
void GetNamesFromVariantArray(SAFEARRAY* psa, StringSet& setNames);
|
|
|
|
void GetNamesFromFile(VARIANT& vntData, StringSet& setNames);
|
|
void GetNamesFromFile(LPCTSTR pszFileName, StringSet& setNames);
|
|
void GetNamesFromStringA(LPCSTR pchString, DWORD cchString, StringSet& setNames);
|
|
void GetNamesFromStringW(LPCWSTR pchString, DWORD cchString, StringSet& setNames);
|
|
|
|
_bstr_t RemoveTrailingDollarSign(LPCTSTR pszName);
|
|
|
|
|
|
void __stdcall AdmtCheckError(HRESULT hr)
|
|
{
|
|
if (FAILED(hr))
|
|
{
|
|
IErrorInfo* pErrorInfo = NULL;
|
|
|
|
if (GetErrorInfo(0, &pErrorInfo) == S_OK)
|
|
{
|
|
_com_raise_error(hr, pErrorInfo);
|
|
}
|
|
else
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
using namespace MigrationBase;
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// MigrationBase Class
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
// Constructor
|
|
|
|
CMigrationBase::CMigrationBase() :
|
|
m_nRecurseMaintain(0),
|
|
m_Mutex(ADMT_MUTEX)
|
|
{
|
|
}
|
|
|
|
|
|
// Destructor
|
|
|
|
CMigrationBase::~CMigrationBase()
|
|
{
|
|
}
|
|
|
|
|
|
// InitSourceDomainAndContainer Method
|
|
|
|
void CMigrationBase::InitSourceDomainAndContainer(bool bMustExist)
|
|
{
|
|
try
|
|
{
|
|
m_SourceDomain.Initialize(m_spInternal->SourceDomain);
|
|
m_SourceContainer = m_SourceDomain.GetContainer(m_spInternal->SourceOu);
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
//
|
|
// if the domain must exist then throw error
|
|
// otherwise at least the domain name must be specified
|
|
//
|
|
|
|
if (bMustExist || (ce.Error() == E_INVALIDARG))
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// InitTargetDomainAndContainer Method
|
|
|
|
void CMigrationBase::InitTargetDomainAndContainer()
|
|
{
|
|
m_TargetDomain.Initialize(m_spInternal->TargetDomain);
|
|
m_TargetContainer = m_TargetDomain.GetContainer(m_spInternal->TargetOu);
|
|
|
|
// verify target domain is in native mode
|
|
|
|
if (m_TargetDomain.NativeMode() == false)
|
|
{
|
|
AdmtThrowError(
|
|
GUID_NULL, GUID_NULL,
|
|
E_INVALIDARG, IDS_E_TARGET_DOMAIN_NOT_NATIVE_MODE,
|
|
(LPCTSTR)m_TargetDomain.Name()
|
|
);
|
|
}
|
|
|
|
VerifyTargetContainerPathLength();
|
|
}
|
|
|
|
|
|
// VerifyInterIntraForest Method
|
|
|
|
void CMigrationBase::VerifyInterIntraForest()
|
|
{
|
|
// if the source and target domains have the same forest name then they are intra-forest
|
|
|
|
bool bIntraForest = m_spInternal->IntraForest ? true : false;
|
|
|
|
if (m_SourceDomain.ForestName() == m_TargetDomain.ForestName())
|
|
{
|
|
// intra-forest must be set to true to match the domains
|
|
|
|
if (!bIntraForest)
|
|
{
|
|
AdmtThrowError(
|
|
GUID_NULL, GUID_NULL,
|
|
E_INVALIDARG, IDS_E_NOT_INTER_FOREST,
|
|
(LPCTSTR)m_SourceDomain.Name(), (LPCTSTR)m_TargetDomain.Name()
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// intra-forest must be set to false to match the domains
|
|
|
|
if (bIntraForest)
|
|
{
|
|
AdmtThrowError(
|
|
GUID_NULL, GUID_NULL,
|
|
E_INVALIDARG, IDS_E_NOT_INTRA_FOREST,
|
|
(LPCTSTR)m_SourceDomain.Name(), (LPCTSTR)m_TargetDomain.Name()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// VerifyCallerDelegated Method
|
|
//
|
|
// Synopsis
|
|
// If an intra-forest move operation is being performed then verify that the
|
|
// calling user's account has not been marked as sensitive and therefore
|
|
// cannot be delegated. As the move operation is performed on the domain
|
|
// controller which has the RID master role in the source domain it is
|
|
// necessary to delegate the user's security context.
|
|
//
|
|
// Note that a failure to verify whether the caller's account is marked
|
|
// sensitive or whether we are running on the source domain controller
|
|
// holding the RID master role will not generate an error.
|
|
//
|
|
// Arguments
|
|
// None
|
|
//
|
|
// Return Value
|
|
// None. An exception with rich error information is thrown if the caller's
|
|
// account is marked as sensitive.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CMigrationBase::VerifyCallerDelegated()
|
|
{
|
|
//
|
|
// It is only necessary to check this for intra-forest.
|
|
//
|
|
|
|
bool bIntraForest = m_spInternal->IntraForest ? true : false;
|
|
|
|
if (bIntraForest)
|
|
{
|
|
bool bDelegatable = false;
|
|
|
|
HRESULT hr = IsCallerDelegatable(bDelegatable);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (bDelegatable == false)
|
|
{
|
|
//
|
|
// Caller's account is not delegatable. Retrieve name of domain controller
|
|
// in the source domain that holds the RID master role and the name of this
|
|
// computer.
|
|
//
|
|
|
|
_bstr_t strDnsName;
|
|
_bstr_t strFlatName;
|
|
|
|
hr = GetRidPoolAllocator4(m_SourceDomain.Name(), strDnsName, strFlatName);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_TCHAR szComputerName[MAX_PATH];
|
|
DWORD cchComputerName = sizeof(szComputerName) / sizeof(szComputerName[0]);
|
|
|
|
if (GetComputerNameEx(ComputerNameDnsFullyQualified, szComputerName, &cchComputerName))
|
|
{
|
|
//
|
|
// If this computer is not the domain controller holding the
|
|
// RID master role in the source domain then generate error.
|
|
//
|
|
|
|
if (_tcsicmp(szComputerName, strDnsName) != 0)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_FAIL, IDS_E_CALLER_NOT_DELEGATED);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(dwError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
_Module.Log(ErrW, IDS_E_UNABLE_VERIFY_CALLER_NOT_DELEGATED, _com_error(hr));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// SetDefaultExcludedSystemProperties
|
|
//
|
|
// Synopsis
|
|
// Sets the default system property exclusion list if the list has not already
|
|
// been set. Note that the default system property exclusion list consists of
|
|
// the mail, proxyAddresses and all attributes not marked as being part of the
|
|
// base schema.
|
|
//
|
|
// Arguments
|
|
// None
|
|
//
|
|
// Return Value
|
|
// None - generate warning message in log if a failure occurs.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CMigrationBase::SetDefaultExcludedSystemProperties()
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// Only perform if inter-forest migration and
|
|
// system properties exclusion set value is zero.
|
|
//
|
|
|
|
if (m_spInternal->IntraForest == VARIANT_FALSE)
|
|
{
|
|
IIManageDBPtr spIManageDB(__uuidof(IManageDB));
|
|
|
|
IVarSetPtr spSettings(__uuidof(VarSet));
|
|
IUnknownPtr spUnknown(spSettings);
|
|
IUnknown* punk = spUnknown;
|
|
|
|
spIManageDB->GetSettings(&punk);
|
|
|
|
long lSet = spSettings->get(GET_BSTR(DCTVS_AccountOptions_ExcludedSystemPropsSet));
|
|
|
|
if (lSet == 0)
|
|
{
|
|
IObjPropBuilderPtr spObjPropBuilder(__uuidof(ObjPropBuilder));
|
|
|
|
_bstr_t strNonBaseProperties = spObjPropBuilder->GetNonBaseProperties(m_TargetDomain.Name());
|
|
_bstr_t strProperties = _T("mail,proxyAddresses,") + strNonBaseProperties;
|
|
|
|
spSettings->put(GET_BSTR(DCTVS_AccountOptions_ExcludedSystemProps), strProperties);
|
|
|
|
spIManageDB->SaveSettings(punk);
|
|
}
|
|
}
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
_Module.Log(ErrW, IDS_E_UNABLE_SET_EXCLUDED_SYSTEM_PROPERTIES, ce);
|
|
}
|
|
}
|
|
|
|
|
|
// DoOption Method
|
|
|
|
void CMigrationBase::DoOption(long lOptions, VARIANT& vntInclude, VARIANT& vntExclude)
|
|
{
|
|
m_setIncludeNames.clear();
|
|
m_setExcludeNames.clear();
|
|
|
|
InitRecurseMaintainOption(lOptions);
|
|
|
|
GetExcludeNames(vntExclude, m_setExcludeNames);
|
|
|
|
switch (lOptions & 0xFF)
|
|
{
|
|
case admtNone:
|
|
{
|
|
DoNone();
|
|
break;
|
|
}
|
|
case admtData:
|
|
{
|
|
GetNamesFromData(vntInclude, m_setIncludeNames);
|
|
DoNames();
|
|
break;
|
|
}
|
|
case admtFile:
|
|
{
|
|
GetNamesFromFile(vntInclude, m_setIncludeNames);
|
|
DoNames();
|
|
break;
|
|
}
|
|
case admtDomain:
|
|
{
|
|
m_setIncludeNames.clear();
|
|
DoDomain();
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_INVALID_OPTION);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// DoNone Method
|
|
|
|
void CMigrationBase::DoNone()
|
|
{
|
|
}
|
|
|
|
|
|
// DoNames Method
|
|
|
|
void CMigrationBase::DoNames()
|
|
{
|
|
}
|
|
|
|
|
|
// DoDomain Method
|
|
|
|
void CMigrationBase::DoDomain()
|
|
{
|
|
}
|
|
|
|
|
|
// InitRecurseMaintainOption Method
|
|
|
|
void CMigrationBase::InitRecurseMaintainOption(long lOptions)
|
|
{
|
|
switch (lOptions & 0xFF)
|
|
{
|
|
case admtData:
|
|
case admtFile:
|
|
{
|
|
if (lOptions & 0xFF00)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_DATA_OPTION_FLAGS_NOT_ALLOWED);
|
|
}
|
|
|
|
m_nRecurseMaintain = 0;
|
|
break;
|
|
}
|
|
case admtDomain:
|
|
{
|
|
m_nRecurseMaintain = 0;
|
|
|
|
if (lOptions & admtRecurse)
|
|
{
|
|
++m_nRecurseMaintain;
|
|
|
|
if (lOptions & admtMaintainHierarchy)
|
|
{
|
|
++m_nRecurseMaintain;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
m_nRecurseMaintain = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GetExcludeNames Method
|
|
|
|
void CMigrationBase::GetExcludeNames(VARIANT& vntExclude, StringSet& setExcludeNames)
|
|
{
|
|
try
|
|
{
|
|
switch (V_VT(&vntExclude))
|
|
{
|
|
case VT_EMPTY:
|
|
case VT_ERROR:
|
|
{
|
|
setExcludeNames.clear();
|
|
break;
|
|
}
|
|
case VT_BSTR:
|
|
{
|
|
GetNamesFromFile(V_BSTR(&vntExclude), setExcludeNames);
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_BYREF:
|
|
{
|
|
BSTR* pbstr = V_BSTRREF(&vntExclude);
|
|
|
|
if (pbstr)
|
|
{
|
|
GetNamesFromFile(*pbstr, setExcludeNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_ARRAY:
|
|
{
|
|
GetNamesFromStringArray(V_ARRAY(&vntExclude), setExcludeNames);
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_ARRAY|VT_BYREF:
|
|
{
|
|
SAFEARRAY** ppsa = V_ARRAYREF(&vntExclude);
|
|
|
|
if (ppsa)
|
|
{
|
|
GetNamesFromStringArray(*ppsa, setExcludeNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_BYREF:
|
|
{
|
|
VARIANT* pvnt = V_VARIANTREF(&vntExclude);
|
|
|
|
if (pvnt)
|
|
{
|
|
GetExcludeNames(*pvnt, setExcludeNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_ARRAY:
|
|
{
|
|
GetNamesFromVariantArray(V_ARRAY(&vntExclude), setExcludeNames);
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_ARRAY|VT_BYREF:
|
|
{
|
|
SAFEARRAY** ppsa = V_ARRAYREF(&vntExclude);
|
|
|
|
if (ppsa)
|
|
{
|
|
GetNamesFromVariantArray(*ppsa, setExcludeNames);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
_com_issue_error(E_INVALIDARG);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, ce.Error(), IDS_E_INVALID_EXCLUDE_DATA_TYPE);
|
|
}
|
|
catch (...)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_FAIL, IDS_E_INVALID_EXCLUDE_DATA_TYPE);
|
|
}
|
|
}
|
|
|
|
|
|
// FillInVarSetForUsers Method
|
|
|
|
void CMigrationBase::FillInVarSetForUsers(CDomainAccounts& rUsers, CVarSet& rVarSet)
|
|
{
|
|
CVarSetAccounts aAccounts(rVarSet);
|
|
|
|
for (CDomainAccounts::iterator it = rUsers.begin(); it != rUsers.end(); it++)
|
|
{
|
|
aAccounts.AddAccount(_T("User"), it->GetADsPath(), it->GetName(), it->GetUserPrincipalName());
|
|
}
|
|
}
|
|
|
|
|
|
// FillInVarSetForGroups Method
|
|
|
|
void CMigrationBase::FillInVarSetForGroups(CDomainAccounts& rGroups, CVarSet& rVarSet)
|
|
{
|
|
CVarSetAccounts aAccounts(rVarSet);
|
|
|
|
for (CDomainAccounts::iterator it = rGroups.begin(); it != rGroups.end(); it++)
|
|
{
|
|
aAccounts.AddAccount(_T("Group"), it->GetADsPath(), it->GetName());
|
|
}
|
|
}
|
|
|
|
|
|
// FillInVarSetForComputers Method
|
|
|
|
void CMigrationBase::FillInVarSetForComputers(CDomainAccounts& rComputers, bool bMigrateOnly, bool bMoveToTarget, bool bReboot, long lRebootDelay, CVarSet& rVarSet)
|
|
{
|
|
CVarSetAccounts aAccounts(rVarSet);
|
|
CVarSetServers aServers(rVarSet);
|
|
|
|
for (CDomainAccounts::iterator it = rComputers.begin(); it != rComputers.end(); it++)
|
|
{
|
|
// remove trailing '$'
|
|
// ADMT doesn't accept true SAM account name
|
|
|
|
_bstr_t strName = RemoveTrailingDollarSign(it->GetSamAccountName());
|
|
|
|
aAccounts.AddAccount(_T("Computer"), strName);
|
|
aServers.AddServer(strName, it->GetDnsHostName(), bMigrateOnly, bMoveToTarget, bReboot, lRebootDelay);
|
|
}
|
|
}
|
|
|
|
|
|
// VerifyRenameConflictPrefixSuffixValid Method
|
|
|
|
void CMigrationBase::VerifyRenameConflictPrefixSuffixValid()
|
|
{
|
|
int nTotalPrefixSuffixLength = 0;
|
|
|
|
long lRenameOption = m_spInternal->RenameOption;
|
|
|
|
if ((lRenameOption == admtRenameWithPrefix) || (lRenameOption == admtRenameWithSuffix))
|
|
{
|
|
_bstr_t strPrefixSuffix = m_spInternal->RenamePrefixOrSuffix;
|
|
|
|
nTotalPrefixSuffixLength += strPrefixSuffix.length();
|
|
}
|
|
|
|
long lConflictOption = m_spInternal->ConflictOptions & 0x0F;
|
|
|
|
if ((lConflictOption == admtRenameConflictingWithSuffix) || (lConflictOption == admtRenameConflictingWithPrefix))
|
|
{
|
|
_bstr_t strPrefixSuffix = m_spInternal->ConflictPrefixOrSuffix;
|
|
|
|
nTotalPrefixSuffixLength += strPrefixSuffix.length();
|
|
}
|
|
|
|
if (nTotalPrefixSuffixLength > MAXIMUM_PREFIX_SUFFIX_LENGTH)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_PREFIX_SUFFIX_TOO_LONG, MAXIMUM_PREFIX_SUFFIX_LENGTH);
|
|
}
|
|
}
|
|
|
|
|
|
// VerifyCanAddSidHistory Method
|
|
|
|
void CMigrationBase::VerifyCanAddSidHistory()
|
|
{
|
|
bool bMessageDefined = false;
|
|
|
|
try
|
|
{
|
|
long lErrorFlags = 0;
|
|
|
|
IAccessCheckerPtr spAccessChecker(__uuidof(AccessChecker));
|
|
|
|
spAccessChecker->CanUseAddSidHistory(
|
|
m_SourceDomain.Name(),
|
|
m_TargetDomain.Name(),
|
|
m_TargetDomain.DomainControllerName(),
|
|
&lErrorFlags
|
|
);
|
|
|
|
if (lErrorFlags != 0)
|
|
{
|
|
_bstr_t strError;
|
|
|
|
CComBSTR str;
|
|
|
|
if (lErrorFlags & F_NO_AUDITING_SOURCE)
|
|
{
|
|
str.LoadString(IDS_E_NO_AUDITING_SOURCE);
|
|
strError += str.operator BSTR();
|
|
}
|
|
|
|
if (lErrorFlags & F_NO_AUDITING_TARGET)
|
|
{
|
|
str.LoadString(IDS_E_NO_AUDITING_TARGET);
|
|
strError += str.operator BSTR();
|
|
}
|
|
|
|
if (lErrorFlags & F_NO_LOCAL_GROUP)
|
|
{
|
|
str.LoadString(IDS_E_NO_SID_HISTORY_LOCAL_GROUP);
|
|
strError += str.operator BSTR();
|
|
}
|
|
|
|
if (lErrorFlags & F_NO_REG_KEY)
|
|
{
|
|
str.LoadString(IDS_E_NO_SID_HISTORY_REGISTRY_ENTRY);
|
|
strError += str.operator BSTR();
|
|
}
|
|
|
|
if (lErrorFlags & F_NOT_DOMAIN_ADMIN)
|
|
{
|
|
str.LoadString(IDS_E_NO_SID_HISTORY_DOMAIN_ADMIN);
|
|
strError += str.operator BSTR();
|
|
}
|
|
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_FAIL, IDS_E_SID_HISTORY_CONFIGURATION, (LPCTSTR)strError);
|
|
}
|
|
|
|
//
|
|
// If adding SID history from a downlevel (Windows NT 4 or earlier) domain and not using
|
|
// explicit credentials then DsAddSidHistory requires that the call be made on a domain
|
|
// controller in the target domain and that the source domain trusts the target domain.
|
|
//
|
|
// No credentials are supplied only when using scripting or the command-line therefore
|
|
// this check is only performed here.
|
|
//
|
|
|
|
if (m_SourceDomain.UpLevel() == false)
|
|
{
|
|
//
|
|
// The source domain is downlevel.
|
|
//
|
|
|
|
//
|
|
// Verify that this computer is in the target domain.
|
|
//
|
|
|
|
CADsADSystemInfo siSystemInfo;
|
|
_bstr_t strThisDomain = siSystemInfo.GetDomainDNSName();
|
|
_bstr_t strTargetDomain = m_TargetDomain.NameDns();
|
|
|
|
if (!strThisDomain || !strTargetDomain)
|
|
{
|
|
_com_issue_error(E_OUTOFMEMORY);
|
|
}
|
|
|
|
if (_wcsicmp((PCWSTR)strThisDomain, (PCWSTR)strTargetDomain) != 0)
|
|
{
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(ERROR_DS_MUST_BE_RUN_ON_DST_DC), IDS_E_SID_HISTORY_MUST_RUN_ON_DOMAIN_CONTROLLER);
|
|
}
|
|
|
|
//
|
|
// Verify that this computer is a domain controller.
|
|
//
|
|
|
|
PSERVER_INFO_101 psiInfo = NULL;
|
|
NET_API_STATUS nasStatus = NetServerGetInfo(NULL, 101, (LPBYTE*)&psiInfo);
|
|
|
|
if (nasStatus != ERROR_SUCCESS)
|
|
{
|
|
_com_issue_error(HRESULT_FROM_WIN32(nasStatus));
|
|
}
|
|
|
|
bool bIsDC = (psiInfo->sv101_type & (SV_TYPE_DOMAIN_CTRL|SV_TYPE_DOMAIN_BAKCTRL)) != 0;
|
|
NetApiBufferFree(psiInfo);
|
|
|
|
if (!bIsDC)
|
|
{
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(ERROR_DS_MUST_BE_RUN_ON_DST_DC), IDS_E_SID_HISTORY_MUST_RUN_ON_DOMAIN_CONTROLLER);
|
|
}
|
|
|
|
//
|
|
// Verify trusted domain object exists in target domain
|
|
// for source domain and that an inbound trust is defined.
|
|
//
|
|
|
|
if (IsInboundTrustDefined(m_SourceDomain.NameFlat()) == false)
|
|
{
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), IDS_E_SID_HISTORY_SOURCE_MUST_TRUST_TARGET);
|
|
}
|
|
|
|
//
|
|
// Verify trusted domain object exists in source domain for
|
|
// target domain which specifies an outbound trust.
|
|
//
|
|
|
|
if (IsOutboundTrustDefined(m_SourceDomain.DomainControllerName(), m_TargetDomain.Sid()) == false)
|
|
{
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), IDS_E_SID_HISTORY_SOURCE_MUST_TRUST_TARGET);
|
|
}
|
|
|
|
//
|
|
// Check outbound trust status on source domain controller.
|
|
//
|
|
|
|
DWORD dwError = GetOutboundTrustStatus(m_SourceDomain.DomainControllerName(), m_TargetDomain.NameFlat());
|
|
|
|
if (dwError != ERROR_SUCCESS)
|
|
{
|
|
bMessageDefined = true;
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(dwError), IDS_E_SID_HISTORY_SOURCE_MUST_TRUST_TARGET);
|
|
}
|
|
}
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
if (bMessageDefined)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, ce, IDS_E_CAN_ADD_SID_HISTORY);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_FAIL, IDS_E_CAN_ADD_SID_HISTORY);
|
|
}
|
|
}
|
|
|
|
|
|
// VerifyTargetContainerPathLength Method
|
|
|
|
void CMigrationBase::VerifyTargetContainerPathLength()
|
|
{
|
|
_bstr_t strPath = GetTargetContainer().GetPath();
|
|
|
|
if (strPath.length() > 999)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_TARGET_CONTAINER_PATH_TOO_LONG);
|
|
}
|
|
}
|
|
|
|
|
|
// VerifyPasswordServer Method
|
|
|
|
void CMigrationBase::VerifyPasswordOption()
|
|
{
|
|
if (m_spInternal->PasswordOption == admtCopyPassword)
|
|
{
|
|
_bstr_t strServer = m_spInternal->PasswordServer;
|
|
|
|
// a password server must be specified for copy password option
|
|
|
|
if (strServer.length() == 0)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_PASSWORD_DC_NOT_SPECIFIED);
|
|
}
|
|
|
|
//
|
|
// verify that password server exists and is a domain controller
|
|
//
|
|
|
|
_bstr_t strPrefixedServer;
|
|
_bstr_t strUnprefixedServer;
|
|
|
|
|
|
if (_tcsncmp(strServer, _T("\\\\"), 2) == 0)
|
|
{
|
|
strPrefixedServer = strServer;
|
|
strUnprefixedServer = &(((const wchar_t*)strServer)[2]);
|
|
}
|
|
else
|
|
{
|
|
strPrefixedServer = _T("\\\\") + strServer;
|
|
strUnprefixedServer = strServer;
|
|
}
|
|
|
|
PSERVER_INFO_101 psiInfo;
|
|
|
|
NET_API_STATUS nasStatus = NetServerGetInfo(strPrefixedServer, 101, (LPBYTE*)&psiInfo);
|
|
|
|
if (nasStatus != NERR_Success)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(nasStatus), IDS_E_PASSWORD_DC_NOT_FOUND, (LPCTSTR)strServer);
|
|
}
|
|
|
|
UINT uMsgId = 0;
|
|
|
|
if (psiInfo->sv101_platform_id != PLATFORM_ID_NT)
|
|
{
|
|
uMsgId = IDS_E_PASSWORD_DC_NOT_NT;
|
|
}
|
|
else if (!(psiInfo->sv101_type & SV_TYPE_DOMAIN_CTRL) && !(psiInfo->sv101_type & SV_TYPE_DOMAIN_BAKCTRL))
|
|
{
|
|
uMsgId = IDS_E_PASSWORD_DC_NOT_DC;
|
|
}
|
|
|
|
NetApiBufferFree(psiInfo);
|
|
|
|
if (uMsgId)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, uMsgId, (LPCTSTR)strServer);
|
|
}
|
|
|
|
|
|
//
|
|
// Verify that the password server is in fact a domain controller for
|
|
// the source domain.
|
|
//
|
|
DSROLE_PRIMARY_DOMAIN_INFO_BASIC * pDomInfo = NULL;
|
|
|
|
|
|
DWORD err = DsRoleGetPrimaryDomainInformation(strUnprefixedServer,
|
|
DsRolePrimaryDomainInfoBasic,
|
|
(PBYTE*)&pDomInfo);
|
|
|
|
if (err != NO_ERROR)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, HRESULT_FROM_WIN32(err), IDS_E_PASSWORD_DC_NOT_FOUND, (LPCTSTR)strServer);
|
|
}
|
|
|
|
|
|
|
|
// compare them
|
|
if ( ( (pDomInfo->DomainNameFlat != NULL) &&
|
|
((const wchar_t*)m_SourceDomain.NameFlat() != NULL) &&
|
|
(_wcsicmp(pDomInfo->DomainNameFlat, (const wchar_t*)m_SourceDomain.NameFlat())==0) ) ||
|
|
|
|
( (pDomInfo->DomainNameDns != NULL) &&
|
|
((const wchar_t*)m_SourceDomain.NameDns() != NULL) &&
|
|
(_wcsicmp(pDomInfo->DomainNameDns, (const wchar_t*)m_SourceDomain.NameDns())==0) ) )
|
|
|
|
{
|
|
// at least one of them matches
|
|
uMsgId = 0;
|
|
}
|
|
else
|
|
{
|
|
// no match
|
|
uMsgId = IDS_E_PASSWORD_DC_WRONG_DOMAIN;
|
|
}
|
|
|
|
|
|
DsRoleFreeMemory(pDomInfo);
|
|
|
|
|
|
if (uMsgId)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, uMsgId, (LPCTSTR)strServer);
|
|
}
|
|
|
|
//
|
|
// verify that password server is configured properly
|
|
//
|
|
|
|
IPasswordMigrationPtr spPasswordMigration(__uuidof(PasswordMigration));
|
|
|
|
spPasswordMigration->EstablishSession(strPrefixedServer, m_TargetDomain.DomainControllerName());
|
|
}
|
|
}
|
|
|
|
|
|
// PerformMigration Method
|
|
|
|
void CMigrationBase::PerformMigration(CVarSet& rVarSet)
|
|
{
|
|
IPerformMigrationTaskPtr spMigrator(__uuidof(Migrator));
|
|
|
|
try
|
|
{
|
|
AdmtCheckError(spMigrator->raw_PerformMigrationTask(IUnknownPtr(rVarSet.GetInterface()), 0));
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
if (ce.Error() == MIGRATOR_E_PROCESSES_STILL_RUNNING)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, ce.Error(), IDS_E_ADMT_PROCESS_RUNNING);
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// FixObjectsInHierarchy Method
|
|
|
|
void CMigrationBase::FixObjectsInHierarchy(LPCTSTR pszType)
|
|
{
|
|
CFixObjectsInHierarchy fix;
|
|
|
|
fix.SetObjectType(pszType);
|
|
fix.SetIntraForest(m_spInternal->IntraForest ? true : false);
|
|
|
|
long lOptions = m_spInternal->ConflictOptions;
|
|
long lOption = lOptions & 0x0F;
|
|
long lFlags = lOptions & 0xF0;
|
|
|
|
fix.SetFixReplaced((lOption == admtReplaceConflicting) && (lFlags & admtMoveReplacedAccounts));
|
|
|
|
fix.SetSourceContainerPath(m_SourceContainer.GetPath());
|
|
fix.SetTargetContainerPath(m_TargetContainer.GetPath());
|
|
|
|
fix.FixObjects();
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
namespace MigrationBase
|
|
{
|
|
|
|
|
|
// GetNamesFromData Method
|
|
|
|
void GetNamesFromData(VARIANT& vntData, StringSet& setNames)
|
|
{
|
|
try
|
|
{
|
|
GetNamesFromVariant(&vntData, setNames);
|
|
}
|
|
catch (_com_error& ce)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, ce.Error(), IDS_E_INVALID_DATA_OPTION_DATA_TYPE);
|
|
}
|
|
catch (...)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_FAIL, IDS_E_INVALID_DATA_OPTION_DATA_TYPE);
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromVariant Method
|
|
|
|
void GetNamesFromVariant(VARIANT* pvntData, StringSet& setNames)
|
|
{
|
|
switch (V_VT(pvntData))
|
|
{
|
|
case VT_BSTR:
|
|
{
|
|
GetNamesFromString(V_BSTR(pvntData), setNames);
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_BYREF:
|
|
{
|
|
BSTR* pbstr = V_BSTRREF(pvntData);
|
|
|
|
if (pbstr)
|
|
{
|
|
GetNamesFromString(*pbstr, setNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_ARRAY:
|
|
{
|
|
GetNamesFromStringArray(V_ARRAY(pvntData), setNames);
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_ARRAY|VT_BYREF:
|
|
{
|
|
SAFEARRAY** ppsa = V_ARRAYREF(pvntData);
|
|
|
|
if (ppsa)
|
|
{
|
|
GetNamesFromStringArray(*ppsa, setNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_BYREF:
|
|
{
|
|
VARIANT* pvnt = V_VARIANTREF(pvntData);
|
|
|
|
if (pvnt)
|
|
{
|
|
GetNamesFromVariant(pvnt, setNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_ARRAY:
|
|
{
|
|
GetNamesFromVariantArray(V_ARRAY(pvntData), setNames);
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_ARRAY|VT_BYREF:
|
|
{
|
|
SAFEARRAY** ppsa = V_ARRAYREF(pvntData);
|
|
|
|
if (ppsa)
|
|
{
|
|
GetNamesFromVariantArray(*ppsa, setNames);
|
|
}
|
|
break;
|
|
}
|
|
case VT_EMPTY:
|
|
{
|
|
// ignore empty variants
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
_com_issue_error(E_INVALIDARG);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromString Method
|
|
|
|
void GetNamesFromString(BSTR bstr, StringSet& setNames)
|
|
{
|
|
if (bstr)
|
|
{
|
|
UINT cch = SysStringLen(bstr);
|
|
|
|
if (cch > 0)
|
|
{
|
|
GetNamesFromStringW(bstr, cch, setNames);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromStringArray Method
|
|
|
|
void GetNamesFromStringArray(SAFEARRAY* psa, StringSet& setNames)
|
|
{
|
|
BSTR* pbstr;
|
|
|
|
HRESULT hr = SafeArrayAccessData(psa, (void**)&pbstr);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
try
|
|
{
|
|
UINT uDimensionCount = psa->cDims;
|
|
|
|
for (UINT uDimension = 0; uDimension < uDimensionCount; uDimension++)
|
|
{
|
|
UINT uElementCount = psa->rgsabound[uDimension].cElements;
|
|
|
|
for (UINT uElement = 0; uElement < uElementCount; uElement++)
|
|
{
|
|
setNames.insert(_bstr_t(*pbstr++));
|
|
}
|
|
}
|
|
|
|
SafeArrayUnaccessData(psa);
|
|
}
|
|
catch (...)
|
|
{
|
|
SafeArrayUnaccessData(psa);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromVariantArray Method
|
|
|
|
void GetNamesFromVariantArray(SAFEARRAY* psa, StringSet& setNames)
|
|
{
|
|
VARIANT* pvnt;
|
|
|
|
HRESULT hr = SafeArrayAccessData(psa, (void**)&pvnt);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
try
|
|
{
|
|
UINT uDimensionCount = psa->cDims;
|
|
|
|
for (UINT uDimension = 0; uDimension < uDimensionCount; uDimension++)
|
|
{
|
|
UINT uElementCount = psa->rgsabound[uDimension].cElements;
|
|
|
|
for (UINT uElement = 0; uElement < uElementCount; uElement++)
|
|
{
|
|
GetNamesFromVariant(pvnt++, setNames);
|
|
}
|
|
}
|
|
|
|
SafeArrayUnaccessData(psa);
|
|
}
|
|
catch (...)
|
|
{
|
|
SafeArrayUnaccessData(psa);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromFile Method
|
|
//
|
|
// - the maximum file size this implementation can handle is 4,294,967,295 bytes
|
|
|
|
void GetNamesFromFile(VARIANT& vntData, StringSet& setNames)
|
|
{
|
|
bool bInvalidArg = false;
|
|
|
|
switch (V_VT(&vntData))
|
|
{
|
|
case VT_BSTR:
|
|
{
|
|
BSTR bstr = V_BSTR(&vntData);
|
|
|
|
if (bstr)
|
|
{
|
|
GetNamesFromFile(bstr, setNames);
|
|
}
|
|
else
|
|
{
|
|
bInvalidArg = true;
|
|
}
|
|
break;
|
|
}
|
|
case VT_BSTR|VT_BYREF:
|
|
{
|
|
BSTR* pbstr = V_BSTRREF(&vntData);
|
|
|
|
if (pbstr && *pbstr)
|
|
{
|
|
GetNamesFromFile(*pbstr, setNames);
|
|
}
|
|
else
|
|
{
|
|
bInvalidArg = true;
|
|
}
|
|
break;
|
|
}
|
|
case VT_VARIANT|VT_BYREF:
|
|
{
|
|
VARIANT* pvnt = V_VARIANTREF(&vntData);
|
|
|
|
if (pvnt)
|
|
{
|
|
GetNamesFromFile(*pvnt, setNames);
|
|
}
|
|
else
|
|
{
|
|
bInvalidArg = true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
bInvalidArg = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bInvalidArg)
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, E_INVALIDARG, IDS_E_INVALID_FILE_OPTION_DATA_TYPE);
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromFile Method
|
|
//
|
|
// - the maximum file size this implementation can handle is 4,294,967,295 bytes
|
|
|
|
void GetNamesFromFile(LPCTSTR pszFileName, StringSet& setNames)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (pszFileName)
|
|
{
|
|
HANDLE hFile = CreateFile(pszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD dwFileSize = GetFileSize(hFile, NULL);
|
|
|
|
if (dwFileSize > 0)
|
|
{
|
|
HANDLE hFileMappingObject = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
|
|
|
if (hFileMappingObject != NULL)
|
|
{
|
|
LPVOID pvBase = MapViewOfFile(hFileMappingObject, FILE_MAP_READ, 0, 0, 0);
|
|
|
|
if (pvBase != NULL)
|
|
{
|
|
// if Unicode signature assume Unicode file
|
|
// otherwise it must be an ANSI file
|
|
|
|
LPCWSTR pwcs = (LPCWSTR)pvBase;
|
|
|
|
if ((dwFileSize >= 2) && (*pwcs == L'\xFEFF'))
|
|
{
|
|
GetNamesFromStringW(pwcs + 1, dwFileSize / sizeof(WCHAR) - 1, setNames);
|
|
}
|
|
else
|
|
{
|
|
GetNamesFromStringA((LPCSTR)pvBase, dwFileSize, setNames);
|
|
}
|
|
|
|
UnmapViewOfFile(pvBase);
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
CloseHandle(hFileMappingObject);
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
AdmtThrowError(GUID_NULL, GUID_NULL, hr, IDS_E_INCLUDE_NAMES_FILE, pszFileName);
|
|
}
|
|
}
|
|
|
|
|
|
// GetNamesFromStringA Method
|
|
|
|
void GetNamesFromStringA(LPCSTR pchString, DWORD cchString, StringSet& setNames)
|
|
{
|
|
static const CHAR chSeparators[] = "\t\n\r";
|
|
|
|
LPSTR pszName = NULL;
|
|
size_t cchName = 0;
|
|
|
|
try
|
|
{
|
|
LPCSTR pchStringEnd = &pchString[cchString];
|
|
|
|
for (LPCSTR pch = pchString; pch < pchStringEnd; pch++)
|
|
{
|
|
// skip space characters
|
|
|
|
while ((pch < pchStringEnd) && (*pch == ' '))
|
|
{
|
|
++pch;
|
|
}
|
|
|
|
// beginning of name
|
|
|
|
LPCSTR pchBeg = pch;
|
|
|
|
// scan for separator saving pointer to last non-whitespace character
|
|
|
|
LPCSTR pchEnd = pch;
|
|
|
|
while ((pch < pchStringEnd) && (strchr(chSeparators, *pch) == NULL))
|
|
{
|
|
if (*pch++ != ' ')
|
|
{
|
|
pchEnd = pch;
|
|
}
|
|
}
|
|
|
|
// insert name which doesn't contain any leading or trailing whitespace characters
|
|
|
|
if (pchEnd > pchBeg)
|
|
{
|
|
size_t cch = pchEnd - pchBeg;
|
|
|
|
//
|
|
// If potential size of buffer does not exceed maximum value of size_t.
|
|
//
|
|
|
|
if (cch < (cch + 256))
|
|
{
|
|
//
|
|
// If buffer size is less than or equal to the number
|
|
// of characters in the name then reallocate the buffer.
|
|
// Note that this accounts for the final null character.
|
|
//
|
|
|
|
if (cchName <= cch)
|
|
{
|
|
//
|
|
// Delete current buffer. Increase buffer size to a multiple
|
|
// of 256 characters greater than the length of the current
|
|
// name. Note that this allows for the final null character.
|
|
// Allocate a new buffer.
|
|
//
|
|
|
|
delete [] pszName;
|
|
|
|
while (cchName <= cch)
|
|
{
|
|
cchName += 256;
|
|
}
|
|
|
|
pszName = new CHAR[cchName];
|
|
|
|
if (pszName == NULL)
|
|
{
|
|
_com_issue_error(E_OUTOFMEMORY);
|
|
}
|
|
}
|
|
|
|
strncpy(pszName, pchBeg, cch);
|
|
pszName[cch] = '\0';
|
|
|
|
setNames.insert(_bstr_t(pszName));
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Should never get here as this means the pointer
|
|
// difference is within 256 characters of the maximum
|
|
// value of the size_t data type.
|
|
//
|
|
|
|
_com_issue_error(E_FAIL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
delete [] pszName;
|
|
throw;
|
|
}
|
|
|
|
delete [] pszName;
|
|
}
|
|
|
|
|
|
// GetNamesFromStringW Method
|
|
|
|
void GetNamesFromStringW(LPCWSTR pchString, DWORD cchString, StringSet& setNames)
|
|
{
|
|
static const WCHAR chSeparators[] = L"\t\n\r";
|
|
|
|
LPCWSTR pchStringEnd = &pchString[cchString];
|
|
|
|
for (LPCWSTR pch = pchString; pch < pchStringEnd; pch++)
|
|
{
|
|
// skip space characters
|
|
|
|
while ((pch < pchStringEnd) && (*pch == L' '))
|
|
{
|
|
++pch;
|
|
}
|
|
|
|
// beginning of name
|
|
|
|
LPCWSTR pchBeg = pch;
|
|
|
|
// scan for separator saving pointer to last non-whitespace character
|
|
|
|
LPCWSTR pchEnd = pch;
|
|
|
|
while ((pch < pchStringEnd) && (wcschr(chSeparators, *pch) == NULL))
|
|
{
|
|
if (*pch++ != L' ')
|
|
{
|
|
pchEnd = pch;
|
|
}
|
|
}
|
|
|
|
// insert name which doesn't contain any leading or trailing whitespace characters
|
|
|
|
if (pchEnd > pchBeg)
|
|
{
|
|
_bstr_t strName(SysAllocStringLen(pchBeg, pchEnd - pchBeg), false);
|
|
|
|
setNames.insert(strName);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// RemoveTrailingDollarSign Method
|
|
|
|
_bstr_t RemoveTrailingDollarSign(LPCTSTR pszName)
|
|
{
|
|
LPTSTR psz = _T("");
|
|
|
|
if (pszName)
|
|
{
|
|
size_t cch = _tcslen(pszName);
|
|
|
|
if (cch > 0)
|
|
{
|
|
psz = reinterpret_cast<LPTSTR>(_alloca((cch + 1) * sizeof(_TCHAR)));
|
|
|
|
_tcscpy(psz, pszName);
|
|
|
|
LPTSTR p = &psz[cch - 1];
|
|
|
|
if (*p == _T('$'))
|
|
{
|
|
*p = _T('\0');
|
|
}
|
|
}
|
|
}
|
|
|
|
return psz;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// IsInboundTrustDefined Function
|
|
//
|
|
// Synopsis
|
|
// Verifies that a trusted domain object exists for the specified domain and
|
|
// that an inbound trust is defined (i.e. the specified domain trusts this
|
|
// domain).
|
|
//
|
|
// Arguments
|
|
// IN pszDomain - the name of the trusting domain
|
|
//
|
|
// Return
|
|
// True - trusted domain object exists and an inbound trust is defined
|
|
// False - either trusted domain object does not exist or an inbound trust
|
|
// is not defined
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool __stdcall IsInboundTrustDefined(PCWSTR pszDomain)
|
|
{
|
|
bool bTrust = false;
|
|
|
|
LSA_HANDLE lsahPolicy = NULL;
|
|
PTRUSTED_DOMAIN_INFORMATION_EX ptdieInfo = NULL;
|
|
|
|
try
|
|
{
|
|
//
|
|
// Open local policy object with view local information access.
|
|
//
|
|
|
|
LSA_OBJECT_ATTRIBUTES lsaoa = { sizeof(LSA_OBJECT_ATTRIBUTES), NULL, NULL, 0, NULL, NULL };
|
|
|
|
NTSTATUS ntsStatus = LsaOpenPolicy(NULL, &lsaoa, POLICY_VIEW_LOCAL_INFORMATION, &lsahPolicy);
|
|
|
|
if (ntsStatus != STATUS_SUCCESS)
|
|
{
|
|
_com_issue_error(HRESULT_FROM_WIN32(LsaNtStatusToWinError(ntsStatus)));
|
|
}
|
|
|
|
//
|
|
// Query for trusted domain object for specified domain.
|
|
//
|
|
|
|
PWSTR pwsDomain = const_cast<PWSTR>(pszDomain);
|
|
USHORT cbDomain = wcslen(pszDomain) * sizeof(WCHAR);
|
|
|
|
LSA_UNICODE_STRING lsausDomain = { cbDomain, cbDomain, pwsDomain };
|
|
|
|
ntsStatus = LsaQueryTrustedDomainInfoByName(
|
|
lsahPolicy,
|
|
&lsausDomain,
|
|
TrustedDomainInformationEx,
|
|
(PVOID*)&ptdieInfo
|
|
);
|
|
|
|
if (ntsStatus == STATUS_SUCCESS)
|
|
{
|
|
//
|
|
// Trusted domain object exists. Verify
|
|
// that an inbound trust is defined.
|
|
//
|
|
|
|
ULONG ulDirection = ptdieInfo->TrustDirection;
|
|
|
|
if ((ulDirection == TRUST_DIRECTION_INBOUND) || (ulDirection == TRUST_DIRECTION_BIDIRECTIONAL))
|
|
{
|
|
bTrust = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// If error is not that trusted domain object
|
|
// does not exist then generate exception.
|
|
//
|
|
|
|
if (ntsStatus != STATUS_OBJECT_NAME_NOT_FOUND)
|
|
{
|
|
_com_issue_error(HRESULT_FROM_WIN32(LsaNtStatusToWinError(ntsStatus)));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
if (ptdieInfo)
|
|
{
|
|
LsaFreeMemory(ptdieInfo);
|
|
}
|
|
|
|
if (lsahPolicy)
|
|
{
|
|
LsaClose(lsahPolicy);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
if (ptdieInfo)
|
|
{
|
|
LsaFreeMemory(ptdieInfo);
|
|
}
|
|
|
|
if (lsahPolicy)
|
|
{
|
|
LsaClose(lsahPolicy);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
return bTrust;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// IsOutboundTrustDefined Function
|
|
//
|
|
// Synopsis
|
|
// Verifies that a trusted domain object exists for the specified domain on
|
|
// the specified domain controller (i.e. the domain of the specified domain
|
|
// controller trusts the specified domain).
|
|
//
|
|
// Note that this function should only be used for downlevel (NT4 or earlier)
|
|
// domains and that simply the presence of a trusted domain object is
|
|
// sufficient in this case to indicate an outbound trust.
|
|
//
|
|
// Arguments
|
|
// IN pszDomainController - the name of a domain controller in the trusting
|
|
// domain
|
|
// IN pszDomainSid - the SID of the trusted domain in string format
|
|
//
|
|
// Return
|
|
// True - trusted domain object exists
|
|
// False - trusted domain object does not exist
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool __stdcall IsOutboundTrustDefined(PCWSTR pszDomainController, PCWSTR pszDomainSid)
|
|
{
|
|
bool bTrust = false;
|
|
|
|
LSA_HANDLE lsahPolicy = NULL;
|
|
PSID psidDomain = NULL;
|
|
PTRUSTED_DOMAIN_NAME_INFO ptdniDomainNameInfo = NULL;
|
|
|
|
try
|
|
{
|
|
//
|
|
// Open policy object on specified domain controller
|
|
// with view local information access.
|
|
//
|
|
|
|
PWSTR pwsDomainController = const_cast<PWSTR>(pszDomainController);
|
|
USHORT cbDomainController = wcslen(pszDomainController) * sizeof(WCHAR);
|
|
|
|
LSA_UNICODE_STRING lsausDomainController = { cbDomainController, cbDomainController, pwsDomainController };
|
|
LSA_OBJECT_ATTRIBUTES lsaoa = { sizeof(LSA_OBJECT_ATTRIBUTES), NULL, NULL, 0, NULL, NULL };
|
|
|
|
NTSTATUS ntsStatus = LsaOpenPolicy(&lsausDomainController, &lsaoa, POLICY_VIEW_LOCAL_INFORMATION, &lsahPolicy);
|
|
|
|
if (ntsStatus != STATUS_SUCCESS)
|
|
{
|
|
_com_issue_error(HRESULT_FROM_WIN32(LsaNtStatusToWinError(ntsStatus)));
|
|
}
|
|
|
|
//
|
|
// Convert SID from string format to binary format.
|
|
//
|
|
|
|
if (!ConvertStringSidToSid(pszDomainSid, &psidDomain))
|
|
{
|
|
DWORD dwError = GetLastError();
|
|
_com_issue_error(HRESULT_FROM_WIN32(dwError));
|
|
}
|
|
|
|
//
|
|
// Query for trusted domain object. Note that LsaQueryTrustedDomainInfo is
|
|
// used because LsaQueryTrustedDomainInfoByName is only supported on
|
|
// Windows 2000 or later.
|
|
//
|
|
|
|
ntsStatus = LsaQueryTrustedDomainInfo(
|
|
lsahPolicy,
|
|
psidDomain,
|
|
TrustedDomainNameInformation,
|
|
(PVOID*)&ptdniDomainNameInfo
|
|
);
|
|
|
|
switch (ntsStatus)
|
|
{
|
|
case STATUS_SUCCESS:
|
|
{
|
|
//
|
|
// The trusted domain object exists.
|
|
//
|
|
bTrust = true;
|
|
break;
|
|
}
|
|
case STATUS_OBJECT_NAME_NOT_FOUND:
|
|
{
|
|
//
|
|
// The trusted domain object does not exist.
|
|
//
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
//
|
|
// Another error has occurred therefore generate an exception.
|
|
//
|
|
_com_issue_error(HRESULT_FROM_WIN32(LsaNtStatusToWinError(ntsStatus)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
if (ptdniDomainNameInfo)
|
|
{
|
|
LsaFreeMemory(ptdniDomainNameInfo);
|
|
}
|
|
|
|
if (psidDomain)
|
|
{
|
|
LocalFree(psidDomain);
|
|
}
|
|
|
|
if (lsahPolicy)
|
|
{
|
|
LsaClose(lsahPolicy);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
if (ptdniDomainNameInfo)
|
|
{
|
|
LsaFreeMemory(ptdniDomainNameInfo);
|
|
}
|
|
|
|
if (psidDomain)
|
|
{
|
|
LocalFree(psidDomain);
|
|
}
|
|
|
|
if (lsahPolicy)
|
|
{
|
|
LsaClose(lsahPolicy);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
return bTrust;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// GetOutboundTrustStatus Function
|
|
//
|
|
// Synopsis
|
|
// Retrieves the trust connection status for the specified domain on the
|
|
// specified domain controller. The status represents the last connection
|
|
// status of the secure channel but does not quarantee that a future request
|
|
// will succeed. The only way to really verify the secure channel is to
|
|
// reset the secure channel which should not be done arbitrarily.
|
|
//
|
|
// Arguments
|
|
// IN pszDomainController - the name of a domain controller in the trusting
|
|
// domain
|
|
// IN pszDomain - the name of the trusted domain
|
|
//
|
|
// Return
|
|
// ERROR_SUCCESS - the last connection status is okay otherwise the last
|
|
// connection status error
|
|
//---------------------------------------------------------------------------
|
|
|
|
DWORD __stdcall GetOutboundTrustStatus(PCWSTR pszDomainController, PCWSTR pszDomain)
|
|
{
|
|
PNETLOGON_INFO_2 pni2Info = NULL;
|
|
|
|
NET_API_STATUS nasStatus = I_NetLogonControl2(
|
|
pszDomainController,
|
|
NETLOGON_CONTROL_TC_QUERY,
|
|
2,
|
|
(LPBYTE)&pszDomain,
|
|
(LPBYTE*)&pni2Info
|
|
);
|
|
|
|
if (nasStatus == ERROR_SUCCESS)
|
|
{
|
|
nasStatus = pni2Info->netlog2_tc_connection_status;
|
|
}
|
|
|
|
if (pni2Info)
|
|
{
|
|
NetApiBufferFree(pni2Info);
|
|
}
|
|
|
|
return nasStatus;
|
|
}
|
|
|
|
|
|
} // namespace
|