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.
305 lines
8.6 KiB
305 lines
8.6 KiB
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// namemapper.cpp
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// This file defines the class NameMapper.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#include "ias.h"
|
|
#include "iaslsa.h"
|
|
#include "samutil.h"
|
|
#include "namemapper.h"
|
|
#include "cracker.h"
|
|
#include "identityhelper.h"
|
|
#include "iastlutl.h"
|
|
#include "ntdsapip.h"
|
|
|
|
NameCracker NameMapper::cracker;
|
|
IdentityHelper NameMapper::identityHelper;
|
|
|
|
|
|
STDMETHODIMP NameMapper::Initialize() const throw()
|
|
{
|
|
DWORD error = IASLsaInitialize();
|
|
if (error) { return HRESULT_FROM_WIN32(error); }
|
|
|
|
return identityHelper.initialize();
|
|
}
|
|
|
|
|
|
STDMETHODIMP NameMapper::Shutdown() const throw()
|
|
{
|
|
IASLsaUninitialize();
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
IASREQUESTSTATUS NameMapper::onSyncRequest(IRequest* pRequest) throw ()
|
|
{
|
|
// This is function scope, so it can be freed in the catch block.
|
|
PDS_NAME_RESULTW result = NULL;
|
|
wchar_t identity[254];
|
|
wchar_t* pIdentity = identity;
|
|
HRESULT hr = S_OK;
|
|
|
|
try
|
|
{
|
|
size_t identitySize = sizeof(identity);
|
|
IASRequest request(pRequest);
|
|
bool identityRetrieved = identityHelper.getIdentity(request,
|
|
pIdentity,
|
|
identitySize);
|
|
|
|
if (!identityRetrieved)
|
|
{
|
|
// allocate a big enough buffer and use it
|
|
pIdentity = new wchar_t[identitySize];
|
|
identityRetrieved = identityHelper.getIdentity(request,
|
|
pIdentity,
|
|
identitySize);
|
|
if (!identityRetrieved)
|
|
{
|
|
IASTraceString("Error: no user identity found");
|
|
_com_issue_error(IAS_PROXY_MALFORMED_RESPONSE);
|
|
}
|
|
}
|
|
|
|
// Allocate an attribute to hold the NT4 Account Name.
|
|
IASAttribute nt4Name(true);
|
|
nt4Name->dwId = IAS_ATTRIBUTE_NT4_ACCOUNT_NAME;
|
|
|
|
DS_NAME_FORMAT formatOffered = DS_UNKNOWN_NAME;
|
|
|
|
// If it already contains a backslash
|
|
// then use as is.
|
|
PWCHAR delim = wcschr(identity, L'\\');
|
|
if (delim)
|
|
{
|
|
if (IASGetRole() == IAS_ROLE_STANDALONE ||
|
|
IASGetProductType() == IAS_PRODUCT_WORKSTATION)
|
|
{
|
|
// Strip out the domain.
|
|
*delim = L'\0';
|
|
|
|
// Make sure this is a local user.
|
|
if (!IASIsDomainLocal(identity))
|
|
{
|
|
IASTraceString("Non-local users are not allowed -- rejecting.");
|
|
_com_issue_error(IAS_LOCAL_USERS_ONLY);
|
|
}
|
|
|
|
// Restore the delimiter.
|
|
*delim = L'\\';
|
|
}
|
|
|
|
IASTraceString("Username is already an NT4 account name.");
|
|
|
|
nt4Name.setString(identity);
|
|
}
|
|
else if (isCrackable(identity, formatOffered) &&
|
|
(IASGetRole() != IAS_ROLE_STANDALONE))
|
|
{
|
|
// identity seems to be crackable and IAS is not a standalone machine.
|
|
// (either a domain member or a domain controller)
|
|
mapName(identity, nt4Name, formatOffered, 0);
|
|
}
|
|
else
|
|
{
|
|
// Assume no domain was specified and use the default domain
|
|
IASTraceString("Prepending default domain.");
|
|
nt4Name->Value.String.pszWide = prependDefaultDomain(identity);
|
|
nt4Name->Value.String.pszAnsi = NULL;
|
|
nt4Name->Value.itType = IASTYPE_STRING;
|
|
}
|
|
|
|
// Convert the domain name to uppercase.
|
|
delim = wcschr(nt4Name->Value.String.pszWide, L'\\');
|
|
*delim = L'\0';
|
|
_wcsupr(nt4Name->Value.String.pszWide);
|
|
*delim = L'\\';
|
|
|
|
nt4Name.store(request);
|
|
|
|
// For now, we'll use this as the FQDN as well.
|
|
IASStoreFQUserName(
|
|
request,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
nt4Name->Value.String.pszWide
|
|
);
|
|
|
|
IASTracePrintf("SAM-Account-Name is \"%S\".",
|
|
nt4Name->Value.String.pszWide);
|
|
}
|
|
catch (const _com_error& ce)
|
|
{
|
|
IASTraceExcept();
|
|
hr = ce.Error();
|
|
}
|
|
|
|
if (pIdentity != identity)
|
|
{
|
|
delete[] pIdentity;
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
cracker.freeNameResult(result);
|
|
}
|
|
|
|
if ( FAILED(hr) || ((hr != S_OK) && (hr < 0x0000ffff)) )
|
|
{
|
|
// IAS reason code: the reason code will be used
|
|
// or error code: will map to internal error
|
|
return IASProcessFailure(pRequest, hr);
|
|
}
|
|
else
|
|
{
|
|
// S_OK
|
|
return IAS_REQUEST_STATUS_HANDLED;
|
|
}
|
|
}
|
|
|
|
|
|
PWSTR NameMapper::prependDefaultDomain(PCWSTR username)
|
|
{
|
|
IASTraceString("NameMapper::prependDefaultDomain");
|
|
|
|
_ASSERT(username != NULL);
|
|
|
|
// Figure out how long everything is.
|
|
PCWSTR domain = IASGetDefaultDomain();
|
|
ULONG domainLen = wcslen(domain);
|
|
ULONG usernameLen = wcslen(username) + 1;
|
|
|
|
// Allocate the needed memory.
|
|
ULONG needed = domainLen + usernameLen + 1;
|
|
PWSTR retval = (PWSTR)CoTaskMemAlloc(needed * sizeof(WCHAR));
|
|
if (!retval) { _com_issue_error(E_OUTOFMEMORY); }
|
|
|
|
// Set up the cursor used for packing the strings.
|
|
PWSTR dst = retval;
|
|
|
|
// Copy in the domain name.
|
|
memcpy(dst, domain, domainLen * sizeof(WCHAR));
|
|
dst += domainLen;
|
|
|
|
// Add the delimiter.
|
|
*dst++ = L'\\';
|
|
|
|
// Copy in the username.
|
|
// Note: usernameLen includes the null-terminator.
|
|
memcpy(dst, username, usernameLen * sizeof(WCHAR));
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
//////////
|
|
// Determines whether an identity can be cracked through DsCrackNames and which
|
|
// name format should be offered if it is crackable.
|
|
//////////
|
|
bool NameMapper::isCrackable(
|
|
const wchar_t* szIdentity,
|
|
DS_NAME_FORMAT& format
|
|
) const throw ()
|
|
{
|
|
format = DS_UNKNOWN_NAME;
|
|
|
|
if (wcschr(szIdentity, L'@') != 0)
|
|
{
|
|
if (allowAltSecId)
|
|
{
|
|
format = static_cast<DS_NAME_FORMAT>(
|
|
DS_USER_PRINCIPAL_NAME_AND_ALTSECID
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return (wcschr(szIdentity, L'=') != 0) || // DS_FQDN_1779_NAME
|
|
(wcschr(szIdentity, L'/') != 0); // DS_CANONICAL_NAME
|
|
}
|
|
|
|
|
|
|
|
void NameMapper::mapName(
|
|
const wchar_t* identity,
|
|
IASAttribute& nt4Name,
|
|
DS_NAME_FORMAT formatOffered,
|
|
const wchar_t* suffix
|
|
)
|
|
{
|
|
_ASSERT(identity != NULL);
|
|
_ASSERT(nt4Name != NULL);
|
|
_ASSERT(*nt4Name != NULL);
|
|
|
|
PDS_NAME_RESULTW result = NULL;
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
do
|
|
{
|
|
// call cracker
|
|
DWORD dwErr = cracker.crackNames(
|
|
DS_NAME_FLAG_EVAL_AT_DC,
|
|
formatOffered,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
identity,
|
|
suffix,
|
|
&result
|
|
);
|
|
|
|
if (dwErr != NO_ERROR)
|
|
{
|
|
IASTraceFailure("DsCrackNames", dwErr);
|
|
hr = IAS_GLOBAL_CATALOG_UNAVAILABLE;
|
|
break;
|
|
}
|
|
|
|
if (result->rItems->status == DS_NAME_NO_ERROR)
|
|
{
|
|
IASTraceString("Successfully cracked username.");
|
|
|
|
// DsCrackNames returned an NT4 Account Name, so use it.
|
|
nt4Name.setString(result->rItems->pName);
|
|
}
|
|
else
|
|
{
|
|
// GC could not crack the name
|
|
if (formatOffered != DS_SID_OR_SID_HISTORY_NAME)
|
|
{
|
|
// Not using SID: try to append the default domain to the identity
|
|
IASTraceString("Global Catalog could not crack username; "
|
|
"prepending default domain.");
|
|
// If it can't be cracked we'll assume that it's a flat
|
|
// username with some weird characters.
|
|
nt4Name->Value.String.pszWide = prependDefaultDomain(identity);
|
|
nt4Name->Value.String.pszAnsi = NULL;
|
|
nt4Name->Value.itType = IASTYPE_STRING;
|
|
|
|
}
|
|
else
|
|
{
|
|
// using SID. nothing else can be done.
|
|
IASTracePrintf("Global Catalog could not crack username. Error %x",
|
|
result->rItems->status);
|
|
hr = IAS_NO_SUCH_USER;
|
|
}
|
|
}
|
|
}
|
|
while(false);
|
|
|
|
cracker.freeNameResult(result);
|
|
|
|
if ( FAILED(hr) || ((hr != S_OK) && (hr < 0x0000ffff)))
|
|
{
|
|
_com_issue_error(hr);
|
|
}
|
|
}
|