|
|
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 1998, Microsoft Corp. All rights reserved.
//
// FILE
//
// ezsam.c
//
// SYNOPSIS
//
// Defines helper functions for SAM API.
//
// MODIFICATION HISTORY
//
// 08/16/1998 Original version.
// 02/18/1999 Connect by DNS name not address.
// 03/23/1999 Tighten up the ezsam API.
// Better failover/retry logic.
// 04/14/1999 Copy SIDs returned by IASSamOpenUser.
//
///////////////////////////////////////////////////////////////////////////////
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntlsa.h>
#include <windows.h>
#include <lm.h>
#include <dsgetdc.h>
#include <statinfo.h>
#include <ezsam.h>
#include <iastrace.h>
//////////
// Private helper functions.
//////////
DWORD WINAPI IASSamOpenDomain( IN PCWSTR DomainName, IN ACCESS_MASK DesiredAccess, IN ULONG Flags, IN BOOL Force, OUT PSID *DomainSid, OUT PSAM_HANDLE DomainHandle );
VOID WINAPI IASSamFreeSid( IN PSID Sid );
VOID WINAPI IASSamCloseDomain( IN SAM_HANDLE SamHandle, IN BOOL Valid );
DWORD WINAPI IASSamLookupUser( IN SAM_HANDLE DomainHandle, IN PCWSTR UserName, IN ACCESS_MASK DesiredAccess, IN OUT OPTIONAL PULONG UserRid, OUT PSAM_HANDLE UserHandle );
//////////
// Handles for the local SAM domains.
//////////
SAM_HANDLE theAccountDomainHandle; SAM_HANDLE theBuiltinDomainHandle;
//////////
// State associated with a cached domain.
//////////
struct CachedDomain { LONG lock; // 1 if the cache is locked, 0 otherwise.
WCHAR domainName[DNLEN + 1]; // Domain name.
ACCESS_MASK access; // Access mask for handle.
ULARGE_INTEGER expiry; // Time when entry expires.
PSID sid; // SID for the domain.
SAM_HANDLE handle; // Handle to domain.
LONG refCount; // Reference count.
};
//////////
// Time in 100 nsec intervals that a cache entry will be retained.
// Set to 900 seconds.
//////////
#define CACHE_LIFETIME (9000000000ui64)
//////////
// The currently cached domain.
//////////
struct CachedDomain theCache;
//////////
// Try to lock the cache.
//////////
#define TRYLOCK_CACHE() \
(InterlockedExchange(&theCache.lock, 1) == 0)
//////////
// Unlock the cache.
//////////
#define UNLOCK_CACHE() \
(InterlockedExchange(&theCache.lock, 0))
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamSidDup
//
// DESCRIPTION
//
// Duplicates the passed in SID. The SID should be freed by calling
// IASSamFreeSid.
//
///////////////////////////////////////////////////////////////////////////////
PSID WINAPI IASSamSidDup( PSID Sid ) { ULONG sidLength; PSID rv;
if (Sid) { sidLength = RtlLengthSid(Sid); rv = RtlAllocateHeap( RtlProcessHeap(), 0, sidLength ); if (rv) { memcpy(rv, Sid, sidLength); } } else { rv = NULL; }
return rv; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamOpenCachedDomain
//
// DESCRIPTION
//
// Attempt to open a domain from the cache.
//
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI IASSamOpenCachedDomain( IN PCWSTR DomainName, IN ACCESS_MASK DesiredAccess, OUT PSID *DomainSid, OUT PSAM_HANDLE DomainHandle ) { BOOL success; ULARGE_INTEGER now;
success = FALSE;
// Can we access the cache ?
if (TRYLOCK_CACHE()) { // Does the domain name match ?
if (_wcsicmp(DomainName, theCache.domainName) == 0) { // Does the cached handle have sufficient access rights ?
if ((DesiredAccess & theCache.access) == DesiredAccess) { GetSystemTimeAsFileTime((LPFILETIME)&now);
// Is the entry still valid ?
if (now.QuadPart < theCache.expiry.QuadPart) { // We got a cache hit, so update the reference count ...
InterlockedIncrement(&theCache.refCount);
// ... and return the data.
*DomainSid = theCache.sid; *DomainHandle = theCache.handle; success = TRUE; } else { // The entry has expired, so NULL out the name to prevent the
// next thread from wasting its time.
theCache.domainName[0] = L'\0'; } } }
UNLOCK_CACHE(); }
return success; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamAddCachedDomain
//
// DESCRIPTION
//
// Attempt to add a domain to the cache.
//
///////////////////////////////////////////////////////////////////////////////
VOID WINAPI IASSamAddCachedDomain( IN PCWSTR DomainName, IN ACCESS_MASK Access, IN PSID DomainSid, IN SAM_HANDLE DomainHandle ) { // Can we access the cache ?
if (TRYLOCK_CACHE()) { // Is the current entry idle ?
if (theCache.refCount == 0) { // Free the current entry.
SamCloseHandle(theCache.handle); SamFreeMemory(theCache.sid);
// Store the cached state.
wcsncpy(theCache.domainName, DomainName, DNLEN); theCache.access = Access; theCache.sid = DomainSid; theCache.handle = DomainHandle;
// Set the expiration time.
GetSystemTimeAsFileTime((LPFILETIME)&theCache.expiry); theCache.expiry.QuadPart += CACHE_LIFETIME;
// The caller already has a reference.
theCache.refCount = 1; }
UNLOCK_CACHE(); } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamInitialize
//
// DESCRIPTION
//
// Initializes the handles for the local SAM domains.
//
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI IASSamInitialize( VOID ) { DWORD status; SAM_HANDLE hLocalServer; UNICODE_STRING uniAccountDomain;
//////////
// Connect to the local SAM.
//////////
status = SamConnect( NULL, &hLocalServer, SAM_SERVER_LOOKUP_DOMAIN, &theObjectAttributes ); if (!NT_SUCCESS(status)) { goto exit; }
//////////
// Open a handle to the account domain.
//////////
status = SamOpenDomain( hLocalServer, DOMAIN_LOOKUP | DOMAIN_GET_ALIAS_MEMBERSHIP | DOMAIN_READ_PASSWORD_PARAMETERS, theAccountDomainSid, &theAccountDomainHandle ); if (!NT_SUCCESS(status)) { goto close_server; }
//////////
// Open a handle to the built-in domain.
//////////
status = SamOpenDomain( hLocalServer, DOMAIN_LOOKUP | DOMAIN_GET_ALIAS_MEMBERSHIP, theBuiltinDomainSid, &theBuiltinDomainHandle ); if (!NT_SUCCESS(status)) { SamCloseHandle(theAccountDomainHandle); theAccountDomainHandle = NULL; }
close_server: SamCloseHandle(hLocalServer);
exit: return RtlNtStatusToDosError(status); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamShutdown
//
// DESCRIPTION
//
// Cleans up global variables.
//
///////////////////////////////////////////////////////////////////////////////
VOID WINAPI IASSamShutdown( VOID ) { // Reset the cache.
SamFreeMemory(theCache.sid); SamCloseHandle(theCache.handle); memset(&theCache, 0, sizeof(theCache));
SamCloseHandle(theAccountDomainHandle); theAccountDomainHandle = NULL;
SamCloseHandle(theBuiltinDomainHandle); theBuiltinDomainHandle = NULL; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamOpenDomain
//
// DESCRIPTION
//
// Opens a connection to a SAM domain. The caller is responsible for
// closing the returned handle and freeing the returned SID.
//
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI IASSamOpenDomain( IN PCWSTR DomainName, IN ACCESS_MASK DesiredAccess, IN ULONG Flags, IN BOOL Force, OUT PSID *DomainSid, OUT PSAM_HANDLE DomainHandle ) { DWORD status; PDOMAIN_CONTROLLER_INFOW dci; UNICODE_STRING uniServerName, uniDomainName; SAM_HANDLE hServer;
//////////
// First check for the local account domain.
//////////
if (_wcsicmp(DomainName, theAccountDomain) == 0) { *DomainSid = theAccountDomainSid; *DomainHandle = theAccountDomainHandle;
IASTraceString("Using cached SAM connection to local account domain.");
return NO_ERROR; }
//////////
// Try for a cache hit.
//////////
if (IASSamOpenCachedDomain( DomainName, DesiredAccess, DomainSid, DomainHandle )) { IASTraceString("Using cached SAM connection.");
return NO_ERROR; }
//////////
// No luck, so get the name of the DC to connect to.
//////////
status = IASGetDcName( DomainName, (Force ? DS_FORCE_REDISCOVERY : 0) | Flags, &dci ); if (status != NO_ERROR) { return status; }
//////////
// Connect to the server.
//////////
IASTracePrintf("Connecting to SAM server on %S.", dci->DomainControllerName);
RtlInitUnicodeString( &uniServerName, dci->DomainControllerName );
status = SamConnect( &uniServerName, &hServer, SAM_SERVER_LOOKUP_DOMAIN, &theObjectAttributes );
// We're through with the server name.
NetApiBufferFree(dci);
if (!NT_SUCCESS(status)) { goto exit; }
//////////
// Get SID for the domain.
//////////
RtlInitUnicodeString( &uniDomainName, DomainName );
status = SamLookupDomainInSamServer( hServer, &uniDomainName, DomainSid ); if (!NT_SUCCESS(status)) { goto close_server; }
//////////
// Open the domain using SID we got above
//////////
status = SamOpenDomain( hServer, DesiredAccess, *DomainSid, DomainHandle );
if (NT_SUCCESS(status)) { // Try to add this to the cache.
IASSamAddCachedDomain( DomainName, DesiredAccess, *DomainSid, *DomainHandle ); } else { // Free the SID. We can use SamFreeMemory since we know this SID isn't
// in the cache.
SamFreeMemory(*DomainSid); *DomainSid = NULL; }
close_server: SamCloseHandle(hServer);
exit: return RtlNtStatusToDosError(status); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamLookupUser
//
// DESCRIPTION
//
// Opens a user in a SAM domain. The caller is responsible for closing
// the returned handle.
//
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI IASSamLookupUser( IN SAM_HANDLE DomainHandle, IN PCWSTR UserName, IN ACCESS_MASK DesiredAccess, IN OUT OPTIONAL PULONG UserRid, OUT PSAM_HANDLE UserHandle ) { DWORD status; UNICODE_STRING uniUserName; ULONG rid, *prid; PSID_NAME_USE nameUse;
if (UserName) { //////////
// Caller supplied a UserName so lookup the RID.
//////////
RtlInitUnicodeString( &uniUserName, UserName );
status = SamLookupNamesInDomain( DomainHandle, 1, &uniUserName, &prid, &nameUse ); if (!NT_SUCCESS(status)) { goto exit; }
// Save the RID ...
rid = *prid;
// ... and free the memory.
SamFreeMemory(prid); SamFreeMemory(nameUse);
// Return the RID to the caller if requested.
if (UserRid) { *UserRid = rid; } } else if (UserRid) { // Caller supplied a RID.
rid = *UserRid; } else { // Caller supplied neither a UserName or a RID.
return ERROR_INVALID_PARAMETER; }
//////////
// Open the user object.
//////////
status = SamOpenUser( DomainHandle, DesiredAccess, rid, UserHandle );
exit: return RtlNtStatusToDosError(status); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamOpenUser
//
// DESCRIPTION
//
// Opens a SAM user. The caller is responsible for closing
// the returned handle.
//
///////////////////////////////////////////////////////////////////////////////
DWORD WINAPI IASSamOpenUser( IN PCWSTR DomainName, IN PCWSTR UserName, IN ACCESS_MASK DesiredAccess, IN ULONG Flags, IN OUT OPTIONAL PULONG UserRid, OUT OPTIONAL PSID *DomainSid, OUT PSAM_HANDLE UserHandle ) { DWORD status; ULONG tries; PSID sid; SAM_HANDLE hDomain; BOOL success;
// Initialize the retry state.
tries = 0; success = FALSE;
do { //////////
// Open a connection to the domain.
//////////
status = IASSamOpenDomain( DomainName, DOMAIN_LOOKUP, Flags, (tries > 0), &sid, &hDomain ); if (status == NO_ERROR) { //////////
// Lookup the user.
//////////
status = IASSamLookupUser( hDomain, UserName, DesiredAccess, UserRid, UserHandle );
switch (status) { case NO_ERROR: // Everything succeeded, so return the domain SID if requested.
if (DomainSid && !(*DomainSid = IASSamSidDup(sid))) { SamCloseHandle(*UserHandle); *UserHandle = NULL; status = STATUS_NO_MEMORY; } // Fall through.
case ERROR_NONE_MAPPED: success = TRUE; break; }
// Free the sid ...
IASSamFreeSid(sid);
// ... and the domain handle.
IASSamCloseDomain(hDomain, success); }
} while (!success && ++tries < 2);
return status; }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamCloseDomain
//
// DESCRIPTION
//
// Closes a handle returned by IASSamOpenDomain.
//
///////////////////////////////////////////////////////////////////////////////
VOID WINAPI IASSamCloseDomain( IN SAM_HANDLE SamHandle, IN BOOL Valid ) { if (SamHandle == theCache.handle) { if (!Valid) { theCache.domainName[0] = L'\0'; }
InterlockedDecrement(&theCache.refCount); } else if (SamHandle != theAccountDomainHandle) { SamCloseHandle(SamHandle); } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASSamFreeSid
//
// DESCRIPTION
//
// Frees a SID returned by IASSamOpenDomain.
//
///////////////////////////////////////////////////////////////////////////////
VOID WINAPI IASSamFreeSid ( IN PSID Sid ) { if (Sid != theAccountDomainSid && Sid != theCache.sid) { SamFreeMemory(Sid); } }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASLengthRequiredChildSid
//
// DESCRIPTION
//
// Returns the number of bytes required for a SID immediately subordinate
// to ParentSid.
//
///////////////////////////////////////////////////////////////////////////////
ULONG WINAPI IASLengthRequiredChildSid( IN PSID ParentSid ) { // Get the parent's SubAuthority count.
ULONG subAuthCount; subAuthCount = (ULONG)*RtlSubAuthorityCountSid(ParentSid);
// And add one for the child RID.
return RtlLengthRequiredSid(1 + subAuthCount); }
///////////////////////////////////////////////////////////////////////////////
//
// FUNCTION
//
// IASInitializeChildSid
//
// DESCRIPTION
//
// Initializes a SID with the concatenation of ParentSid + ChildRid.
//
///////////////////////////////////////////////////////////////////////////////
VOID WINAPI IASInitializeChildSid( IN PSID ChildSid, IN PSID ParentSid, IN ULONG ChildRid ) { PUCHAR pChildCount; ULONG parentCount;
// Start with the parent SID. We assume the child SID is big enough.
RtlCopySid( MAXLONG, ChildSid, ParentSid );
// Get a pointer to the child SubAuthority count.
pChildCount = RtlSubAuthorityCountSid(ChildSid);
// Save the original parent count ...
parentCount = (ULONG)*pChildCount;
// ... then increment the child count.
++*pChildCount;
// Set the last subauthority equal to the RID.
*RtlSubAuthoritySid(ChildSid, parentCount) = ChildRid; }
|