/////////////////////////////////////////////////////////////////////////////// // // 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_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); } }