|
|
/****************************** Module Header ******************************\
* Module Name: security.c * * Copyright (c) 1991, Microsoft Corporation * * Handles security aspects of winlogon operation. * * History: * 12-05-91 Davidc Created - mostly taken from old winlogon.c \***************************************************************************/
#include "msgina.h"
#include "authmon.h"
#pragma hdrstop
#define SECURITY_WIN32
#define SECURITY_KERBEROS
#include <security.h>
#include <secint.h>
#include <wincrypt.h>
#include <sclogon.h>
#include <md5.h>
#include <align.h>
#include <ginacomn.h>
#include <Winsock2.h>
extern BOOL g_IsTerminalServer; //
// 'Constants' used in this module only.
//
SID_IDENTIFIER_AUTHORITY gSystemSidAuthority = SECURITY_NT_AUTHORITY; SID_IDENTIFIER_AUTHORITY gLocalSidAuthority = SECURITY_LOCAL_SID_AUTHORITY; PSID gLocalSid; // Initialized in 'InitializeSecurityGlobals'
PSID gAdminSid; // Initialized in 'InitializeSecurityGlobals'
PSID pWinlogonSid; // Initialized in 'InitializeSecurityGlobals'
//
// This structure wraps parameters passed to the background logon thread.
// The background logon is queued to the thread pool after a fast-cached
// logon to update the cached credentials.
//
typedef struct _BACKGROUND_LOGON_PARAMETERS { ULONG AuthenticationPackage; ULONG AuthenticationInformationLength; PVOID AuthenticationInformation; PWCHAR UserSidString; HANDLE LsaHandle; } BACKGROUND_LOGON_PARAMETERS, *PBACKGROUND_LOGON_PARAMETERS;
//
// Routines to check for & perform fast cached logon if policy allows it.
//
NTSTATUS AttemptCachedLogon( HANDLE LsaHandle, PLSA_STRING OriginName, SECURITY_LOGON_TYPE LogonType, ULONG AuthenticationPackage, PVOID AuthenticationInformation, ULONG AuthenticationInformationLength, PTOKEN_GROUPS LocalGroups, PTOKEN_SOURCE SourceContext, PVOID *ProfileBuffer, PULONG ProfileBufferLength, PLUID LogonId, PHANDLE UserToken, PQUOTA_LIMITS Quotas, PNTSTATUS SubStatus, POPTIMIZED_LOGON_STATUS OptimizedLogonStatus );
DWORD BackgroundLogonWorker( PBACKGROUND_LOGON_PARAMETERS LogonParameters );
#define PASSWORD_HASH_STRING TEXT("Long string used by msgina inside of winlogon to hash out the password")
typedef LONG ACEINDEX; typedef ACEINDEX *PACEINDEX;
typedef struct _MYACE { PSID Sid; ACCESS_MASK AccessMask; UCHAR InheritFlags; } MYACE; typedef MYACE *PMYACE;
BOOL InitializeWindowsSecurity( PGLOBALS pGlobals );
BOOL InitializeAuthentication( IN PGLOBALS pGlobals );
/***************************************************************************\
* SetMyAce * * Helper routine that fills in a MYACE structure. * * History: * 02-06-92 Davidc Created \***************************************************************************/ VOID SetMyAce( PMYACE MyAce, PSID Sid, ACCESS_MASK Mask, UCHAR InheritFlags ) { MyAce->Sid = Sid; MyAce->AccessMask= Mask; MyAce->InheritFlags = InheritFlags; }
/***************************************************************************\
* CreateAccessAllowedAce * * Allocates memory for an ACCESS_ALLOWED_ACE and fills it in. * The memory should be freed by calling DestroyACE. * * Returns pointer to ACE on success, NULL on failure * * History: * 12-05-91 Davidc Created \***************************************************************************/ PVOID CreateAccessAllowedAce( PSID Sid, ACCESS_MASK AccessMask, UCHAR AceFlags, UCHAR InheritFlags ) { ULONG LengthSid = RtlLengthSid(Sid); ULONG LengthACE = sizeof(ACE_HEADER) + sizeof(ACCESS_MASK) + LengthSid; PACCESS_ALLOWED_ACE Ace;
Ace = (PACCESS_ALLOWED_ACE)Alloc(LengthACE); if (Ace == NULL) { DebugLog((DEB_ERROR, "CreateAccessAllowedAce : Failed to allocate ace\n")); return NULL; }
Ace->Header.AceType = ACCESS_ALLOWED_ACE_TYPE; Ace->Header.AceSize = (UCHAR)LengthACE; Ace->Header.AceFlags = AceFlags | InheritFlags; Ace->Mask = AccessMask; RtlCopySid(LengthSid, (PSID)(&(Ace->SidStart)), Sid );
return(Ace); }
/***************************************************************************\
* DestroyAce * * Frees the memory allocate for an ACE * * History: * 12-05-91 Davidc Created \***************************************************************************/ VOID DestroyAce( PVOID Ace ) { Free(Ace); }
/***************************************************************************\
* CreateSecurityDescriptor * * Creates a security descriptor containing an ACL containing the specified ACEs * * A SD created with this routine should be destroyed using * DeleteSecurityDescriptor * * Returns a pointer to the security descriptor or NULL on failure. * * 02-06-92 Davidc Created. \***************************************************************************/
PSECURITY_DESCRIPTOR CreateSecurityDescriptor( PMYACE MyAce, ACEINDEX AceCount ) { NTSTATUS Status; ACEINDEX AceIndex; PACCESS_ALLOWED_ACE *Ace; PACL Acl = NULL; PSECURITY_DESCRIPTOR SecurityDescriptor = NULL; ULONG LengthAces; ULONG LengthAcl; ULONG LengthSd;
//
// Allocate space for the ACE pointer array
//
Ace = (PACCESS_ALLOWED_ACE *)Alloc(sizeof(PACCESS_ALLOWED_ACE) * AceCount); if (Ace == NULL) { DebugLog((DEB_ERROR, "Failed to allocated ACE array\n")); return(NULL); }
//
// Create the ACEs and calculate total ACE size
//
LengthAces = 0; for (AceIndex=0; AceIndex < AceCount; AceIndex ++) { Ace[AceIndex] = CreateAccessAllowedAce(MyAce[AceIndex].Sid, MyAce[AceIndex].AccessMask, 0, MyAce[AceIndex].InheritFlags); if (Ace[AceIndex] == NULL) { DebugLog((DEB_ERROR, "Failed to allocate ace\n")); } else { LengthAces += Ace[AceIndex]->Header.AceSize; } }
//
// Calculate ACL and SD sizes
//
LengthAcl = sizeof(ACL) + LengthAces; LengthSd = SECURITY_DESCRIPTOR_MIN_LENGTH;
//
// Create the ACL
//
Acl = Alloc(LengthAcl);
if (Acl != NULL) {
Status = RtlCreateAcl(Acl, LengthAcl, ACL_REVISION); ASSERT(NT_SUCCESS(Status));
//
// Add the ACES to the ACL and destroy the ACEs
//
for (AceIndex = 0; AceIndex < AceCount; AceIndex ++) {
if (Ace[AceIndex] != NULL) {
Status = RtlAddAce(Acl, ACL_REVISION, 0, Ace[AceIndex], Ace[AceIndex]->Header.AceSize);
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "AddAce failed, status = 0x%lx", Status)); }
DestroyAce( Ace[AceIndex] ); } }
} else { DebugLog((DEB_ERROR, "Failed to allocate ACL\n"));
for ( AceIndex = 0 ; AceIndex < AceCount ; AceIndex++ ) { if ( Ace[AceIndex] ) { DestroyAce( Ace[ AceIndex ] ); } } }
//
// Free the ACE pointer array
//
Free(Ace);
//
// Create the security descriptor
//
SecurityDescriptor = Alloc(LengthSd);
if (SecurityDescriptor != NULL) {
Status = RtlCreateSecurityDescriptor(SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION); ASSERT(NT_SUCCESS(Status));
//
// Set the DACL on the security descriptor
//
Status = RtlSetDaclSecurityDescriptor(SecurityDescriptor, TRUE, Acl, FALSE); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "SetDACLSD failed, status = 0x%lx", Status)); } } else {
DebugLog((DEB_ERROR, "Failed to allocate security descriptor\n"));
Free( Acl ); }
//
// Return with our spoils
//
return(SecurityDescriptor); }
//+---------------------------------------------------------------------------
//
// Function: FreeSecurityDescriptor
//
// Synopsis: Frees security descriptors created by CreateSecurityDescriptor
//
// Arguments: [SecurityDescriptor] --
//
// History: 5-09-96 RichardW Created
//
// Notes:
//
//----------------------------------------------------------------------------
VOID FreeSecurityDescriptor( PSECURITY_DESCRIPTOR SecurityDescriptor ) { PACL Acl; BOOL Present; BOOL Defaulted;
Acl = NULL;
GetSecurityDescriptorDacl( SecurityDescriptor, &Present, &Acl, &Defaulted );
if ( Acl ) { Free( Acl ); }
Free( SecurityDescriptor );
} /***************************************************************************\
* CreateUserThreadTokenSD * * Creates a security descriptor to protect tokens on user threads * * History: * 12-05-91 Davidc Created \***************************************************************************/ PSECURITY_DESCRIPTOR CreateUserThreadTokenSD( PSID UserSid, PSID WinlogonSid ) { MYACE Ace[2]; ACEINDEX AceCount = 0; PSECURITY_DESCRIPTOR SecurityDescriptor;
ASSERT(UserSid != NULL); // should always have a non-null user sid
//
// Define the User ACEs
//
SetMyAce(&(Ace[AceCount++]), UserSid, TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | READ_CONTROL, 0 );
//
// Define the Winlogon ACEs
//
SetMyAce(&(Ace[AceCount++]), WinlogonSid, TOKEN_ALL_ACCESS, 0 );
// Check we didn't goof
ASSERT((sizeof(Ace) / sizeof(MYACE)) >= AceCount);
//
// Create the security descriptor
//
SecurityDescriptor = CreateSecurityDescriptor(Ace, AceCount); if (SecurityDescriptor == NULL) { DebugLog((DEB_ERROR, "failed to create user process token security descriptor\n")); }
return(SecurityDescriptor);
}
/****************************************************************************\
* * FUNCTION: DuplicateSID * * PURPOSE: Duplicates a given SID * * PARAMS: PSID, the SID to duplicate * * RETURNS: the duplicated SID or * NULL. * * HISTORY: 10/08/2001 - crisilac created * * \****************************************************************************/ PSID DuplicateSID(PSID pSrcSID) { ULONG uSidLength = 0; PSID pOutSID = NULL;
if ( pSrcSID && RtlValidSid(pSrcSID) ) { uSidLength = RtlLengthSid(pSrcSID); pOutSID = Alloc(uSidLength);
if( NULL != pOutSID ) { if( !NT_SUCCESS(RtlCopySid(uSidLength, pOutSID, pSrcSID)) ) { Free(pOutSID); pOutSID = NULL; } } }
return pOutSID; }
/***************************************************************************\
* InitializeSecurityGlobals * * Initializes the various global constants (mainly Sids used in this module. * * History: * 12-05-91 Davidc Created \***************************************************************************/ VOID InitializeSecurityGlobals( VOID ) { NTSTATUS Status; SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; ULONG SidLength;
//
// Get our sid so it can be put on object ACLs
//
SidLength = RtlLengthRequiredSid(1); pWinlogonSid = (PSID)Alloc(SidLength); if (!pWinlogonSid) { //
// We're dead. Couldn't even allocate memory for a measly SID...
//
return; }
RtlInitializeSid(pWinlogonSid, &SystemSidAuthority, 1); *(RtlSubAuthoritySid(pWinlogonSid, 0)) = SECURITY_LOCAL_SYSTEM_RID;
//
// Initialize the local sid for later
//
Status = RtlAllocateAndInitializeSid( &gLocalSidAuthority, 1, SECURITY_LOCAL_RID, 0, 0, 0, 0, 0, 0, 0, &gLocalSid );
if (!NT_SUCCESS(Status)) { WLPrint(("Failed to initialize local sid, status = 0x%lx", Status)); }
//
// Initialize the admin sid for later
//
Status = RtlAllocateAndInitializeSid( &gSystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &gAdminSid ); if (!NT_SUCCESS(Status)) { WLPrint(("Failed to initialize admin alias sid, status = 0x%lx", Status)); }
}
VOID FreeSecurityGlobals( VOID )
{ RtlFreeSid(gAdminSid); RtlFreeSid(gLocalSid); LocalFree(pWinlogonSid); }
/***************************************************************************\
* InitializeAuthentication * * Initializes the authentication service. i.e. connects to the authentication * package using the Lsa. * * On successful return, the following fields of our global structure are * filled in : * LsaHandle * SecurityMode * AuthenticationPackage * * Returns TRUE on success, FALSE on failure * * History: * 12-05-91 Davidc Created \***************************************************************************/ BOOL InitializeAuthentication( IN PGLOBALS pGlobals ) { NTSTATUS Status; STRING LogonProcessName;
if (!EnablePrivilege(SE_TCB_PRIVILEGE, TRUE)) { DebugLog((DEB_ERROR, "Failed to enable SeTcbPrivilege!\n")); return(FALSE); }
//
// Hookup to the LSA and locate our authentication package.
//
RtlInitString(&LogonProcessName, "Winlogon\\MSGina"); Status = LsaRegisterLogonProcess( &LogonProcessName, &pGlobals->LsaHandle, &pGlobals->SecurityMode );
if (!NT_SUCCESS(Status)) {
DebugLog((DEB_ERROR, "LsaRegisterLogonProcess failed: %#x\n", Status)); return(FALSE); }
return TRUE; }
PVOID FormatPasswordCredentials( IN PUNICODE_STRING UserName, IN PUNICODE_STRING Domain, IN PUNICODE_STRING Password, IN BOOLEAN Unlock, IN OPTIONAL PLUID LogonId, OUT PULONG Size ) { PKERB_INTERACTIVE_LOGON KerbAuthInfo; ULONG AuthInfoSize; PSECURITY_SEED_AND_LENGTH SeedAndLength; UCHAR Seed; PBYTE Where; PWCHAR BackSlash; UNICODE_STRING UserNameBackup = {0}; UNICODE_STRING DomainBackup = {0};
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)(&Password->Length); Seed = SeedAndLength->Seed;
if (NULL != (BackSlash = wcschr(UserName->Buffer, L'\\'))) { // we're going to massage UserName Domain
// Let's save the current param
memcpy(&UserNameBackup, UserName, sizeof(UNICODE_STRING)); memcpy(&DomainBackup, Domain, sizeof(UNICODE_STRING));
*BackSlash = 0; // turn \ in 0
RtlInitUnicodeString(UserName, BackSlash+1); RtlInitUnicodeString(Domain, UserNameBackup.Buffer); } // else BackSlash = NULL will be our trigger
//
// Build the authentication information buffer
//
if (Seed != 0) { RevealPassword( Password ); }
AuthInfoSize = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) + UserName->Length + 2 + Domain->Length + 2 + Password->Length + 2 ;
KerbAuthInfo = Alloc(AuthInfoSize); if (KerbAuthInfo == NULL) { DebugLog((DEB_ERROR, "failed to allocate memory for authentication buffer\n"));
if ( Seed != 0 ) { HidePassword( &Seed, Password); }
return( NULL ); }
//
// This authentication buffer will be used for a logon attempt
//
if (Unlock) { ASSERT(ARGUMENT_PRESENT(LogonId)); KerbAuthInfo->MessageType = KerbWorkstationUnlockLogon ; ((PKERB_INTERACTIVE_UNLOCK_LOGON) KerbAuthInfo)->LogonId = *LogonId; Where = (PBYTE) (KerbAuthInfo) + sizeof(KERB_INTERACTIVE_UNLOCK_LOGON); } else { KerbAuthInfo->MessageType = KerbInteractiveLogon ; Where = (PBYTE) (KerbAuthInfo + 1); }
//
// Copy the user name into the authentication buffer
//
KerbAuthInfo->UserName.Length = (USHORT) sizeof(TCHAR) * (USHORT) lstrlen(UserName->Buffer);
KerbAuthInfo->UserName.MaximumLength = KerbAuthInfo->UserName.Length + sizeof(TCHAR);
KerbAuthInfo->UserName.Buffer = (PWSTR)Where; lstrcpy(KerbAuthInfo->UserName.Buffer, UserName->Buffer);
//
// Copy the domain name into the authentication buffer
//
KerbAuthInfo->LogonDomainName.Length = (USHORT) sizeof(TCHAR) * (USHORT) lstrlen(Domain->Buffer);
KerbAuthInfo->LogonDomainName.MaximumLength = KerbAuthInfo->LogonDomainName.Length + sizeof(TCHAR);
KerbAuthInfo->LogonDomainName.Buffer = (PWSTR) ((PBYTE)(KerbAuthInfo->UserName.Buffer) + KerbAuthInfo->UserName.MaximumLength);
lstrcpy(KerbAuthInfo->LogonDomainName.Buffer, Domain->Buffer);
//
// Copy the password into the authentication buffer
// Hide it once we have copied it. Use the same seed value
// that we used for the original password in pGlobals.
//
KerbAuthInfo->Password.Length = (USHORT) sizeof(TCHAR) * (USHORT) lstrlen(Password->Buffer);
KerbAuthInfo->Password.MaximumLength = KerbAuthInfo->Password.Length + sizeof(TCHAR);
KerbAuthInfo->Password.Buffer = (PWSTR) ((PBYTE)(KerbAuthInfo->LogonDomainName.Buffer) + KerbAuthInfo->LogonDomainName.MaximumLength); lstrcpy(KerbAuthInfo->Password.Buffer, Password->Buffer);
if ( Seed != 0 ) { HidePassword( &Seed, Password); }
HidePassword( &Seed, (PUNICODE_STRING) &KerbAuthInfo->Password);
*Size = AuthInfoSize ;
if (NULL != BackSlash) // We need to restore our parameters
{ *BackSlash = L'\\'; memcpy(UserName, &UserNameBackup, sizeof(UNICODE_STRING)); memcpy(Domain, &DomainBackup, sizeof(UNICODE_STRING)); }
return KerbAuthInfo ; }
PVOID FormatSmartCardCredentials( PUNICODE_STRING Pin, PVOID SmartCardInfo, IN BOOLEAN Unlock, IN OPTIONAL PLUID LogonId, OUT PULONG Size ) { PKERB_SMART_CARD_LOGON KerbAuthInfo; ULONG AuthInfoSize; PSECURITY_SEED_AND_LENGTH SeedAndLength; UCHAR Seed; PULONG ScInfo ; PUCHAR Where ;
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)(&Pin->Length); Seed = SeedAndLength->Seed;
//
// Build the authentication information buffer
//
ScInfo = (PULONG) SmartCardInfo ;
if (Seed != 0) { RevealPassword( Pin ); }
AuthInfoSize = sizeof( KERB_SMART_CARD_UNLOCK_LOGON ) + ROUND_UP_COUNT(Pin->Length + 2, 8) + *ScInfo ;
KerbAuthInfo = (PKERB_SMART_CARD_LOGON) LocalAlloc( LMEM_FIXED, AuthInfoSize );
if ( !KerbAuthInfo ) { if ( Seed != 0 ) { HidePassword( &Seed, Pin ); }
return NULL ; }
if (Unlock) { ASSERT(ARGUMENT_PRESENT(LogonId)); KerbAuthInfo->MessageType = KerbSmartCardUnlockLogon ; ((PKERB_SMART_CARD_UNLOCK_LOGON) KerbAuthInfo)->LogonId = *LogonId; Where = (PUCHAR) (KerbAuthInfo) + sizeof(KERB_SMART_CARD_UNLOCK_LOGON) ; } else { KerbAuthInfo->MessageType = KerbSmartCardLogon ; Where = (PUCHAR) (KerbAuthInfo + 1) ; }
KerbAuthInfo->Pin.Buffer = (PWSTR) Where ; KerbAuthInfo->Pin.Length = Pin->Length ; KerbAuthInfo->Pin.MaximumLength = Pin->Length + 2 ;
RtlCopyMemory( Where, Pin->Buffer, Pin->Length + 2 );
Where += ROUND_UP_COUNT(Pin->Length + 2, 8) ;
if ( Seed != 0 ) { HidePassword( &Seed, Pin ); }
KerbAuthInfo->CspDataLength = *ScInfo ; KerbAuthInfo->CspData = Where ;
RtlCopyMemory( Where, SmartCardInfo, *ScInfo );
*Size = AuthInfoSize ;
return KerbAuthInfo ;
}
/***************************************************************************\
* LogonUser * * Calls the Lsa to logon the specified user. * * The LogonSid and a LocalSid is added to the user's groups on successful logon * * For this release, password lengths are restricted to 255 bytes in length. * This allows us to use the upper byte of the String.Length field to * carry a seed needed to decode the run-encoded password. If the password * is not run-encoded, the upper byte of the String.Length field should * be zero. * * NOTE: This function will LocalFree the passed in AuthInfo buffer. * * On successful return, LogonToken is a handle to the user's token, * the profile buffer contains user profile information. * * History: * 12-05-91 Davidc Created \***************************************************************************/ NTSTATUS WinLogonUser( IN HANDLE LsaHandle, IN ULONG AuthenticationPackage, IN SECURITY_LOGON_TYPE LogonType, IN PVOID AuthInfo, IN ULONG AuthInfoSize, IN PSID LogonSid, OUT PLUID LogonId, OUT PHANDLE LogonToken, OUT PQUOTA_LIMITS Quotas, OUT PVOID *pProfileBuffer, OUT PULONG pProfileBufferLength, OUT PNTSTATUS pSubStatus, OUT POPTIMIZED_LOGON_STATUS OptimizedLogonStatus ) { NTSTATUS Status; STRING OriginName; TOKEN_SOURCE SourceContext; PTOKEN_GROUPS TokenGroups = NULL; PMSV1_0_INTERACTIVE_PROFILE UserProfile; PWCHAR UserSidString; DWORD ErrorCode; DWORD LogonCacheable; DWORD DaysToCheck; DWORD DaysToExpiry; LARGE_INTEGER CurrentTime; BOOLEAN UserLoggedOnUsingCache;
DebugLog((DEB_TRACE, " LsaHandle = %x\n", LsaHandle)); DebugLog((DEB_TRACE, " AuthenticationPackage = %d\n", AuthenticationPackage)); #if DBG
if (!RtlValidSid(LogonSid)) { DebugLog((DEB_ERROR, "LogonSid is invalid!\n")); Status = STATUS_INVALID_PARAMETER; goto cleanup; } #endif
//
// Initialize source context structure
//
strncpy(SourceContext.SourceName, "User32 ", sizeof(SourceContext.SourceName)); // LATER from res file
Status = NtAllocateLocallyUniqueId(&SourceContext.SourceIdentifier); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "failed to allocate locally unique id, status = 0x%lx", Status)); goto cleanup; }
//
// Get any run-encoding information out of the way
// and decode the password. This creates a window
// where the cleartext password will be in memory.
// Keep it short.
//
// Save the seed so we can use the same one again.
//
//
// Set logon origin
//
RtlInitString(&OriginName, "Winlogon");
//
// Create logon token groups
//
#define TOKEN_GROUP_COUNT 2 // We'll add the local SID and the logon SID
TokenGroups = (PTOKEN_GROUPS)Alloc(sizeof(TOKEN_GROUPS) + (TOKEN_GROUP_COUNT - ANYSIZE_ARRAY) * sizeof(SID_AND_ATTRIBUTES)); if (TokenGroups == NULL) { DebugLog((DEB_ERROR, "failed to allocate memory for token groups")); Status = STATUS_NO_MEMORY; goto cleanup; }
//
// Fill in the logon token group list
//
TokenGroups->GroupCount = TOKEN_GROUP_COUNT; TokenGroups->Groups[0].Sid = LogonSid; TokenGroups->Groups[0].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_LOGON_ID; TokenGroups->Groups[1].Sid = gLocalSid; TokenGroups->Groups[1].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT;
//
// If logging on Interactive to the console, try cached logon first.
//
UserLoggedOnUsingCache = FALSE;
if (LogonType == Interactive) {
//
// Optimized logon that does not hit the network does not
// make sense for local logins.
//
if (IsMachineDomainMember()) {
Status = AttemptCachedLogon(LsaHandle, &OriginName, CachedInteractive, AuthenticationPackage, AuthInfo, AuthInfoSize, TokenGroups, &SourceContext, pProfileBuffer, pProfileBufferLength, LogonId, LogonToken, Quotas, pSubStatus, OptimizedLogonStatus);
if (NT_SUCCESS(Status)) {
UserLoggedOnUsingCache = TRUE;
//
// AttemptCachedLogon will take care of freeing AuthInfo.
//
AuthInfo = NULL; }
} else {
*OptimizedLogonStatus = OLS_MachineIsNotDomainMember; } } else {
*OptimizedLogonStatus = OLS_NonCachedLogonType; } //
// If we have not been able to log the user on using cached credentials,
// fall back to real network logon.
//
if (!UserLoggedOnUsingCache) {
SOCKADDR_IN sa; sa.sin_family = AF_INET; // initial values in case this is not a TS session
sa.sin_addr.S_un.S_un_b.s_b1 = 127; // local host 127.0.0.1:0
sa.sin_addr.S_un.S_un_b.s_b2 = 0; sa.sin_addr.S_un.S_un_b.s_b3 = 0; sa.sin_addr.S_un.S_un_b.s_b4 = 1; sa.sin_port = 0; if ( g_IsTerminalServer ) { if ( !IsActiveConsoleSession()) { WINSTATIONREMOTEADDRESS WinStationRemoteAddress_Info; WINSTATIONINFOCLASS WinStationInformationClass; ULONG Length = 0; BOOL fResult = FALSE; WinStationInformationClass = WinStationRemoteAddress; memset(&WinStationRemoteAddress_Info, 0, sizeof(WINSTATIONREMOTEADDRESS)); fResult = WinStationQueryInformation( SERVERNAME_CURRENT, LOGONID_CURRENT, WinStationInformationClass, (PVOID)&WinStationRemoteAddress_Info, sizeof(WINSTATIONREMOTEADDRESS), &Length);
//init for error case
sa.sin_addr.S_un.S_un_b.s_b1 = 0; sa.sin_addr.S_un.S_un_b.s_b2 = 0; sa.sin_addr.S_un.S_un_b.s_b3 = 0; sa.sin_addr.S_un.S_un_b.s_b4 = 0;
if(fResult) { sa.sin_family = WinStationRemoteAddress_Info.sin_family;
switch( sa.sin_family ) { case AF_INET: sa.sin_port = WinStationRemoteAddress_Info.ipv4.sin_port; sa.sin_addr.S_un.S_un_b.s_b1 = ( (PUCHAR)&WinStationRemoteAddress_Info.ipv4.in_addr ) [0] ; sa.sin_addr.S_un.S_un_b.s_b2 = ( (PUCHAR)&WinStationRemoteAddress_Info.ipv4.in_addr ) [1] ; sa.sin_addr.S_un.S_un_b.s_b3 = ( (PUCHAR)&WinStationRemoteAddress_Info.ipv4.in_addr ) [2] ; sa.sin_addr.S_un.S_un_b.s_b4 = ( (PUCHAR)&WinStationRemoteAddress_Info.ipv4.in_addr ) [3] ; break; } } } } // Either we will use the local host ip, or in TS case, the ip of the remote client.
SecpSetIPAddress ( (PUCHAR ) &sa, sizeof( sa ));
Status = LsaLogonUser ( LsaHandle, &OriginName, LogonType, AuthenticationPackage, AuthInfo, AuthInfoSize, TokenGroups, &SourceContext, pProfileBuffer, pProfileBufferLength, LogonId, LogonToken, Quotas, pSubStatus );
if (NT_SUCCESS(Status)) { ASSERT(*pProfileBuffer != NULL); if (*pProfileBuffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; CloseHandle(*LogonToken); } } }
#if 0
// If this failed it may be because we are doing a UPN logon. Try again
// with no domain
if (!NT_SUCCESS(Status)) { *pfUpnLogon = TRUE;
PKERB_INTERACTIVE_LOGON pinfo = (PKERB_INTERACTIVE_LOGON) AuthInfo;
// Null out domain string
pinfo->LogonDomainName.Length = 0; pinfo->LogonDomainName.Buffer[0] = 0;
Status = LsaLogonUser ( LsaHandle, &OriginName, LogonType, AuthenticationPackage, AuthInfo, AuthInfoSize, TokenGroups, &SourceContext, pProfileBuffer, pProfileBufferLength, LogonId, LogonToken, Quotas, pSubStatus ); } #endif
//
// If this was a successful login perform tasks for optimized logon
// maintenance.
//
if (NT_SUCCESS(Status)) {
UserProfile = *pProfileBuffer;
//
// Get user's SID in string form.
//
UserSidString = GcGetSidString(*LogonToken);
if (UserSidString) {
//
// Save whether we did an optimized logon or the reason why
// we did not.
//
GcSetOptimizedLogonStatus(UserSidString, *OptimizedLogonStatus);
//
// Check if this was a cached logon.
//
if (!(UserProfile->UserFlags & LOGON_CACHED_ACCOUNT)) { FgPolicyRefreshInfo UserPolicyRefreshInfo;
//
// If this is not a cached logon because user's profile
// does not allow it, we have to force group policy to
// apply synchronously.
//
ErrorCode = GcCheckIfProfileAllowsCachedLogon(&UserProfile->HomeDirectory, &UserProfile->ProfilePath, UserSidString, &LogonCacheable);
if (ErrorCode != ERROR_SUCCESS || !LogonCacheable) {
//
// If policy is already sync, leave it alone.
//
GetNextFgPolicyRefreshInfo( UserSidString, &UserPolicyRefreshInfo ); if ( UserPolicyRefreshInfo.mode == GP_ModeAsyncForeground ) { UserPolicyRefreshInfo.reason = GP_ReasonNonCachedCredentials; UserPolicyRefreshInfo.mode = GP_ModeSyncForeground; SetNextFgPolicyRefreshInfo( UserSidString, UserPolicyRefreshInfo ); } }
//
// Determine if we should allow next logon to be optimized.
// We may have disallowed optimizing next logon via this
// mechanism because
// - Our background logon attempt failed e.g. password has
// changed, account has been disabled etc.
// - We are entering the password expiry warning period.
// When we do an optimized logon warning dialogs don't show
// because cached logon invents password expiry time field.
//
// We will allow optimized logon again if this was a non
// cached logon and user got authenticated by the DC, unless
// we are entering the password expiry warning period.
//
if (LogonType == Interactive) {
//
// Are we entering password expiry warning period?
//
GetSystemTimeAsFileTime((FILETIME*) &CurrentTime);
DaysToCheck = GetPasswordExpiryWarningPeriod();
if (GetDaysToExpiry(&CurrentTime, &UserProfile->PasswordMustChange, &DaysToExpiry)) {
if (DaysToExpiry > DaysToCheck) {
//
// We passed this check too. We can allow optimized
// logon next time. Note that, even if we allow it,
// policy, profile, logon scripts etc. might still
// disallow it!
//
GcSetNextLogonCacheable(UserSidString, TRUE); } } } }
GcDeleteSidString(UserSidString); } }
cleanup:
if (AuthInfo) { // Zeroize this for security reason, even though the password is run encoded
ZeroMemory(AuthInfo, AuthInfoSize); LocalFree(AuthInfo); }
if (TokenGroups) { Free(TokenGroups); }
return(Status); }
/***************************************************************************\
* EnablePrivilege * * Enables/disables the specified well-known privilege in the current thread * token if there is one, otherwise the current process token. * * Returns TRUE on success, FALSE on failure * * History: * 12-05-91 Davidc Created \***************************************************************************/ BOOL EnablePrivilege( ULONG Privilege, BOOL Enable ) { NTSTATUS Status; BOOLEAN WasEnabled;
//
// Try the thread token first
//
Status = RtlAdjustPrivilege(Privilege, (BOOLEAN)Enable, TRUE, &WasEnabled);
if (Status == STATUS_NO_TOKEN) {
//
// No thread token, use the process token
//
Status = RtlAdjustPrivilege(Privilege, (BOOLEAN)Enable, FALSE, &WasEnabled); }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failed to %ws privilege : 0x%lx, status = 0x%lx", Enable ? TEXT("enable") : TEXT("disable"), Privilege, Status)); return(FALSE); }
return(TRUE); }
/***************************************************************************\
* TestTokenForAdmin * * Returns TRUE if the token passed represents an admin user, otherwise FALSE * * The token handle passed must have TOKEN_QUERY access. * * History: * 05-06-92 Davidc Created \***************************************************************************/ BOOL TestTokenForAdmin( HANDLE Token ) { BOOL FoundAdmin ; TOKEN_TYPE Type ; NTSTATUS Status ; ULONG Actual ; HANDLE ImpToken ;
Status = NtQueryInformationToken( Token, TokenType, (PVOID) &Type, sizeof( Type ), &Actual );
if ( !NT_SUCCESS( Status ) ) { return FALSE ; }
if ( Type == TokenPrimary ) { //
// Need an impersonation token for this:
//
if ( DuplicateTokenEx( Token, TOKEN_IMPERSONATE | TOKEN_READ, NULL, SecurityImpersonation, TokenImpersonation, &ImpToken ) ) { if ( !CheckTokenMembership( ImpToken, gAdminSid, &FoundAdmin ) ) { FoundAdmin = FALSE ; }
CloseHandle( ImpToken ); } else { FoundAdmin = FALSE ; }
} else { if ( !CheckTokenMembership( Token, gAdminSid, &FoundAdmin ) ) { FoundAdmin = FALSE ; }
}
return FoundAdmin ; }
/***************************************************************************\
* TestUserForAdmin * * Returns TRUE if the named user is an admin. This is done by attempting to * log the user on and examining their token. * * NOTE: The password will be erased upon return to prevent it from being * visually identifiable in a pagefile. * * History: * 03-16-92 Davidc Created \***************************************************************************/ BOOL TestUserForAdmin( PGLOBALS pGlobals, IN PWCHAR UserName, IN PWCHAR Domain, IN PUNICODE_STRING PasswordString ) { NTSTATUS Status, SubStatus, IgnoreStatus; UNICODE_STRING UserNameString; UNICODE_STRING DomainString; PVOID ProfileBuffer; ULONG ProfileBufferLength; QUOTA_LIMITS Quotas; HANDLE Token; BOOL UserIsAdmin; LUID LogonId; PVOID AuthInfo ; ULONG AuthInfoSize ;
RtlInitUnicodeString(&UserNameString, UserName); RtlInitUnicodeString(&DomainString, Domain);
//
// Temporarily log this new subject on and see if their groups
// contain the appropriate admin group
//
AuthInfo = FormatPasswordCredentials( &UserNameString, &DomainString, PasswordString, FALSE, // no unlock
NULL, // no logon id
&AuthInfoSize );
if ( !AuthInfo ) { return FALSE ; }
Status = WinLogonUser( pGlobals->LsaHandle, pGlobals->AuthenticationPackage, Interactive, AuthInfo, AuthInfoSize, pGlobals->LogonSid, // any sid will do
&LogonId, &Token, &Quotas, &ProfileBuffer, &ProfileBufferLength, &SubStatus, &pGlobals->OptimizedLogonStatus);
RtlEraseUnicodeString( PasswordString );
//
// If we couldn't log them on, they're not an admin
//
if (!NT_SUCCESS(Status)) { return(FALSE); }
//
// Free up the profile buffer
//
IgnoreStatus = LsaFreeReturnBuffer(ProfileBuffer); ASSERT(NT_SUCCESS(IgnoreStatus));
//
// See if the token represents an admin user
//
UserIsAdmin = TestTokenForAdmin(Token);
//
// We're finished with the token
//
IgnoreStatus = NtClose(Token); ASSERT(NT_SUCCESS(IgnoreStatus));
return(UserIsAdmin); }
BOOL UnlockLogon( PGLOBALS pGlobals, IN BOOL SmartCardUnlock, IN PWCHAR UserName, IN PWCHAR Domain, IN PUNICODE_STRING PasswordString, OUT PNTSTATUS pStatus, OUT PBOOL IsAdmin, OUT PBOOL IsLoggedOnUser, OUT PVOID *pProfileBuffer, OUT ULONG *pProfileBufferLength ) { NTSTATUS Status, SubStatus, IgnoreStatus; UNICODE_STRING UserNameString; UNICODE_STRING DomainString; QUOTA_LIMITS Quotas; HANDLE Token; HANDLE ImpToken ; LUID LogonId; PVOID AuthInfo ; ULONG AuthInfoSize ; ULONG SidSize ; UCHAR Buffer[ sizeof( TOKEN_USER ) + 8 + SID_MAX_SUB_AUTHORITIES * sizeof(DWORD) ]; PTOKEN_USER User ; PUCHAR SmartCardInfo ; PWLX_SC_NOTIFICATION_INFO ScInfo = NULL; PVOID LocalProfileBuffer = NULL; ULONG LocalProfileBufferLength;
#ifdef SMARTCARD_DOGFOOD
DWORD StartTime, EndTime; #endif
//
// Assume no admin
//
*IsAdmin = FALSE ; *IsLoggedOnUser = FALSE ;
//
// Bundle up the credentials for passing down to the auth pkgs:
//
if ( !SmartCardUnlock ) {
RtlInitUnicodeString(&UserNameString, UserName); RtlInitUnicodeString(&DomainString, Domain);
AuthInfo = FormatPasswordCredentials( &UserNameString, &DomainString, PasswordString, TRUE, // unlock
&pGlobals->LogonId, &AuthInfoSize );
} else { if ( !pWlxFuncs->WlxGetOption( pGlobals->hGlobalWlx, WLX_OPTION_SMART_CARD_INFO, (PULONG_PTR) &ScInfo ) ) { return FALSE ; }
if ( ScInfo == NULL ) { return FALSE ; }
SmartCardInfo = ScBuildLogonInfo( ScInfo->pszCard, ScInfo->pszReader, ScInfo->pszContainer, ScInfo->pszCryptoProvider );
#ifndef SMARTCARD_DOGFOOD
LocalFree(ScInfo); #endif
if ( SmartCardInfo == NULL ) { #ifdef SMARTCARD_DOGFOOD
LocalFree(ScInfo); #endif
return FALSE ; }
AuthInfo = FormatSmartCardCredentials( PasswordString, SmartCardInfo, TRUE, // unlock
&pGlobals->LogonId, &AuthInfoSize);
LocalFree( SmartCardInfo );
}
//
// Make sure that worked:
//
if ( !AuthInfo ) { #ifdef SMARTCARD_DOGFOOD
if (ScInfo) LocalFree(ScInfo); #endif
return FALSE ; }
#ifdef SMARTCARD_DOGFOOD
StartTime = GetTickCount(); #endif
//
// Initialize profile buffer
//
if ( !pProfileBuffer ) { pProfileBuffer = &LocalProfileBuffer; pProfileBufferLength = &LocalProfileBufferLength; }
SubStatus = 0;
Status = WinLogonUser( pGlobals->LsaHandle, ( SmartCardUnlock ? pGlobals->SmartCardLogonPackage : pGlobals->PasswordLogonPackage ), Unlock, AuthInfo, AuthInfoSize, pGlobals->LogonSid, // any sid will do
&LogonId, &Token, &Quotas, pProfileBuffer, pProfileBufferLength, &SubStatus, &pGlobals->OptimizedLogonStatus);
if (SmartCardUnlock) { switch (SubStatus) { case STATUS_SMARTCARD_WRONG_PIN: case STATUS_SMARTCARD_CARD_BLOCKED: case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED: case STATUS_SMARTCARD_NO_CARD: case STATUS_SMARTCARD_NO_KEY_CONTAINER: case STATUS_SMARTCARD_NO_CERTIFICATE: case STATUS_SMARTCARD_NO_KEYSET: case STATUS_SMARTCARD_IO_ERROR: case STATUS_SMARTCARD_CERT_EXPIRED: case STATUS_SMARTCARD_CERT_REVOKED: case STATUS_ISSUING_CA_UNTRUSTED: case STATUS_REVOCATION_OFFLINE_C: case STATUS_PKINIT_CLIENT_FAILURE: Status = SubStatus; break; } }
#ifdef SMARTCARD_DOGFOOD
EndTime = GetTickCount();
if (SmartCardUnlock) { AuthMonitor( AuthOperUnlock, g_Console, &pGlobals->UserNameString, &pGlobals->DomainString, (ScInfo ? ScInfo->pszCard : NULL), (ScInfo ? ScInfo->pszReader : NULL), (PKERB_SMART_CARD_PROFILE) pGlobals->Profile, EndTime - StartTime, Status ); }
if (ScInfo) LocalFree(ScInfo); #endif
//
// Do *NOT* erase the password string.
//
// RtlEraseUnicodeString( PasswordString );
if ( !NT_SUCCESS( Status ) ) { if ( Status == STATUS_ACCOUNT_RESTRICTION ) { Status = SubStatus ; } }
*pStatus = Status ;
//
// If we couldn't log them on, forget it.
//
if (!NT_SUCCESS(Status)) { return(FALSE); }
//
// No error check - if we can't tell if the user is an admin, then
// as far as we're concerned, he isn't.
//
*IsAdmin = TestTokenForAdmin( Token );
//
// Determine if this really is the logged on user:
//
User = (PTOKEN_USER) Buffer ;
Status = NtQueryInformationToken( Token, TokenUser, User, sizeof( Buffer ), &SidSize );
if ( NT_SUCCESS( Status ) ) { if ( pGlobals->UserProcessData.UserSid ) { if ( DuplicateToken( Token, SecurityImpersonation, &ImpToken ) ) { if ( !CheckTokenMembership(ImpToken, pGlobals->UserProcessData.UserSid, IsLoggedOnUser ) ) { *IsLoggedOnUser = FALSE ; }
NtClose( ImpToken ); } else { if ( RtlEqualSid( User->User.Sid, pGlobals->UserProcessData.UserSid ) ) { *IsLoggedOnUser = TRUE ; } else { *IsLoggedOnUser = FALSE ; } } } else { *IsLoggedOnUser = FALSE ; } }
//
// If we're using our local buffer pointer, free up the profile buffer
//
if ( LocalProfileBuffer ) { IgnoreStatus = LsaFreeReturnBuffer(LocalProfileBuffer);
ASSERT(NT_SUCCESS(IgnoreStatus)); }
//
// We're finished with the token
//
IgnoreStatus = NtClose(Token); ASSERT(NT_SUCCESS(IgnoreStatus));
return( TRUE ); }
/***************************************************************************\
* FUNCTION: ImpersonateUser * * PURPOSE: Impersonates the user by setting the users token * on the specified thread. If no thread is specified the token * is set on the current thread. * * RETURNS: Handle to be used on call to StopImpersonating() or NULL on failure * If a non-null thread handle was passed in, the handle returned will * be the one passed in. (See note) * * NOTES: Take care when passing in a thread handle and then calling * StopImpersonating() with the handle returned by this routine. * StopImpersonating() will close any thread handle passed to it - * even yours ! * * HISTORY: * * 04-21-92 Davidc Created. * \***************************************************************************/
HANDLE ImpersonateUser( PUSER_PROCESS_DATA UserProcessData, HANDLE ThreadHandle ) { NTSTATUS Status, IgnoreStatus; HANDLE UserToken = UserProcessData->UserToken; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; OBJECT_ATTRIBUTES ObjectAttributes; HANDLE ImpersonationToken; BOOL ThreadHandleOpened = FALSE;
if (ThreadHandle == NULL) {
//
// Get a handle to the current thread.
// Once we have this handle, we can set the user's impersonation
// token into the thread and remove it later even though we ARE
// the user for the removal operation. This is because the handle
// contains the access rights - the access is not re-evaluated
// at token removal time.
//
Status = NtDuplicateObject( NtCurrentProcess(), // Source process
NtCurrentThread(), // Source handle
NtCurrentProcess(), // Target process
&ThreadHandle, // Target handle
THREAD_SET_THREAD_TOKEN,// Access
0L, // Attributes
DUPLICATE_SAME_ATTRIBUTES ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "ImpersonateUser : Failed to duplicate thread handle, status = 0x%lx", Status)); return(NULL); }
ThreadHandleOpened = TRUE; }
//
// If the usertoken is NULL, there's nothing to do
//
if (UserToken != NULL) {
//
// UserToken is a primary token - create an impersonation token version
// of it so we can set it on our thread
//
InitializeObjectAttributes( &ObjectAttributes, NULL, 0L, NULL, UserProcessData->NewThreadTokenSD);
SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE;
ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService;
Status = NtDuplicateToken( UserToken, TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &ObjectAttributes, FALSE, TokenImpersonation, &ImpersonationToken ); if (!NT_SUCCESS(Status)) {
DebugLog((DEB_ERROR, "Failed to duplicate users token to create impersonation thread, status = 0x%lx", Status));
if (ThreadHandleOpened) { IgnoreStatus = NtClose(ThreadHandle); ASSERT(NT_SUCCESS(IgnoreStatus)); }
return(NULL); }
//
// Set the impersonation token on this thread so we 'are' the user
//
Status = NtSetInformationThread( ThreadHandle, ThreadImpersonationToken, (PVOID)&ImpersonationToken, sizeof(ImpersonationToken) ); //
// We're finished with our handle to the impersonation token
//
IgnoreStatus = NtClose(ImpersonationToken); ASSERT(NT_SUCCESS(IgnoreStatus));
//
// Check we set the token on our thread ok
//
if (!NT_SUCCESS(Status)) {
DebugLog((DEB_ERROR, "Failed to set user impersonation token on winlogon thread, status = 0x%lx", Status));
if (ThreadHandleOpened) { IgnoreStatus = NtClose(ThreadHandle); ASSERT(NT_SUCCESS(IgnoreStatus)); }
return(NULL); } }
return(ThreadHandle);
}
/***************************************************************************\
* FUNCTION: StopImpersonating * * PURPOSE: Stops impersonating the client by removing the token on the * current thread. * * PARAMETERS: ThreadHandle - handle returned by ImpersonateUser() call. * * RETURNS: TRUE on success, FALSE on failure * * NOTES: If a thread handle was passed in to ImpersonateUser() then the * handle returned was one and the same. If this is passed to * StopImpersonating() the handle will be closed. Take care ! * * HISTORY: * * 04-21-92 Davidc Created. * \***************************************************************************/
BOOL StopImpersonating( HANDLE ThreadHandle ) { NTSTATUS Status, IgnoreStatus; HANDLE ImpersonationToken;
//
// Remove the user's token from our thread so we are 'ourself' again
//
ImpersonationToken = NULL;
Status = NtSetInformationThread( ThreadHandle, ThreadImpersonationToken, (PVOID)&ImpersonationToken, sizeof(ImpersonationToken) ); //
// We're finished with the thread handle
//
IgnoreStatus = NtClose(ThreadHandle); ASSERT(NT_SUCCESS(IgnoreStatus));
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failed to remove user impersonation token from winlogon thread, status = 0x%lx", Status)); }
return(NT_SUCCESS(Status)); }
/***************************************************************************\
* TestUserPrivilege * * Looks at the user token to determine if they have the specified privilege * * Returns TRUE if the user has the privilege, otherwise FALSE * * History: * 04-21-92 Davidc Created \***************************************************************************/ BOOL TestUserPrivilege( HANDLE UserToken, ULONG Privilege ) { NTSTATUS Status; NTSTATUS IgnoreStatus; BOOL TokenOpened; LUID LuidPrivilege; LUID TokenPrivilege; PTOKEN_PRIVILEGES Privileges; ULONG BytesRequired; ULONG i; BOOL Found;
TokenOpened = FALSE;
//
// If the token is NULL, get a token for the current process since
// this is the token that will be inherited by new processes.
//
if (UserToken == NULL) {
Status = NtOpenProcessToken( NtCurrentProcess(), TOKEN_QUERY, &UserToken ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Can't open own process token for token_query access")); return(FALSE); }
TokenOpened = TRUE; }
//
// Find out how much memory we need to allocate
//
Status = NtQueryInformationToken( UserToken, // Handle
TokenPrivileges, // TokenInformationClass
NULL, // TokenInformation
0, // TokenInformationLength
&BytesRequired // ReturnLength
);
if (Status != STATUS_BUFFER_TOO_SMALL) {
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failed to query privileges from user token, status = 0x%lx", Status)); }
if (TokenOpened) { IgnoreStatus = NtClose(UserToken); ASSERT(NT_SUCCESS(IgnoreStatus)); }
return(FALSE); }
//
// Allocate space for the privilege array
//
Privileges = Alloc(BytesRequired); if (Privileges == NULL) {
DebugLog((DEB_ERROR, "Failed to allocate memory for user privileges"));
if (TokenOpened) { IgnoreStatus = NtClose(UserToken); ASSERT(NT_SUCCESS(IgnoreStatus)); }
return(FALSE); }
//
// Read in the user privileges
//
Status = NtQueryInformationToken( UserToken, // Handle
TokenPrivileges, // TokenInformationClass
Privileges, // TokenInformation
BytesRequired, // TokenInformationLength
&BytesRequired // ReturnLength
);
//
// We're finished with the token handle
//
if (TokenOpened) { IgnoreStatus = NtClose(UserToken); ASSERT(NT_SUCCESS(IgnoreStatus)); }
//
// See if we got the privileges
//
if (!NT_SUCCESS(Status)) {
DebugLog((DEB_ERROR, "Failed to query privileges from user token"));
Free(Privileges);
return(FALSE); }
//
// See if the user has the privilege we're looking for.
//
LuidPrivilege = RtlConvertLongToLuid(Privilege); Found = FALSE;
for (i=0; i<Privileges->PrivilegeCount; i++) {
TokenPrivilege = *((LUID UNALIGNED *) &Privileges->Privileges[i].Luid); if (RtlEqualLuid(&TokenPrivilege, &LuidPrivilege)) { Found = TRUE; break; }
}
Free(Privileges);
return(Found); }
/***************************************************************************\
* FUNCTION: HidePassword * * PURPOSE: Run-encodes the password so that it is not very visually * distinguishable. This is so that if it makes it to a * paging file, it wont be obvious. * * if pGlobals->Seed is zero, then we will allocate and assign * a seed value. Otherwise, the existing seed value is used. * * WARNING - This routine will use the upper portion of the * password's length field to store the seed used in encoding * password. Be careful you don't pass such a string to * a routine that looks at the length (like and RPC routine). * * * RETURNS: (None) * * NOTES: * * HISTORY: * * 04-27-93 JimK Created. * \***************************************************************************/ VOID HidePassword( PUCHAR Seed OPTIONAL, PUNICODE_STRING Password ) { PSECURITY_SEED_AND_LENGTH SeedAndLength;
UCHAR LocalSeed;
//
// If no seed address passed, use our own local seed buffer
//
if (Seed == NULL) { Seed = &LocalSeed; LocalSeed = 0; }
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)&Password->Length; //ASSERT(*((LPWCH)SeedAndLength+Password->Length) == 0);
ASSERT((SeedAndLength->Seed) == 0);
RtlRunEncodeUnicodeString( Seed, Password );
SeedAndLength->Seed = (*Seed); return; }
/***************************************************************************\
* FUNCTION: RevealPassword * * PURPOSE: Reveals a previously hidden password so that it * is plain text once again. * * RETURNS: (None) * * NOTES: * * HISTORY: * * 04-27-93 JimK Created. * \***************************************************************************/ VOID RevealPassword( PUNICODE_STRING HiddenPassword ) { PSECURITY_SEED_AND_LENGTH SeedAndLength;
UCHAR Seed;
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)&HiddenPassword->Length; Seed = SeedAndLength->Seed; SeedAndLength->Seed = 0;
RtlRunDecodeUnicodeString( Seed, HiddenPassword );
return; }
/***************************************************************************\
* FUNCTION: ErasePassword * * PURPOSE: zeros a password that is no longer needed. * * RETURNS: (None) * * NOTES: * * HISTORY: * * 04-27-93 JimK Created. * \***************************************************************************/ VOID ErasePassword( PUNICODE_STRING Password ) { PSECURITY_SEED_AND_LENGTH SeedAndLength;
SeedAndLength = (PSECURITY_SEED_AND_LENGTH)&Password->Length; SeedAndLength->Seed = 0;
RtlEraseUnicodeString( Password );
return;
}
VOID HashPassword( PUNICODE_STRING Password, PUCHAR HashBuffer ) { MD5_CTX Context ;
MD5Init( &Context ); MD5Update( &Context, (PUCHAR) Password->Buffer, Password->Length ); MD5Update( &Context, (PUCHAR) PASSWORD_HASH_STRING, sizeof( PASSWORD_HASH_STRING ) ); MD5Final( &Context );
RtlCopyMemory( HashBuffer, Context.digest, MD5DIGESTLEN );
}
/***************************************************************************\
* AttemptCachedLogon * * Checks to see if we are allowed to use cached credentials to log the user * in fast, and does so. * * Parameters are the same list of parameters passed to LsaLogonUser. * * On successful return, LogonToken is a handle to the user's token, * the profile buffer contains user profile information. * * History: * 03-23-01 Cenke Created \***************************************************************************/ NTSTATUS AttemptCachedLogon( HANDLE LsaHandle, PLSA_STRING OriginName, SECURITY_LOGON_TYPE LogonType, ULONG AuthenticationPackage, PVOID AuthenticationInformation, ULONG AuthenticationInformationLength, PTOKEN_GROUPS LocalGroups, PTOKEN_SOURCE SourceContext, PVOID *ProfileBuffer, PULONG ProfileBufferLength, PLUID LogonId, PHANDLE UserToken, PQUOTA_LIMITS Quotas, PNTSTATUS SubStatus, POPTIMIZED_LOGON_STATUS OptimizedLogonStatus ) { PWCHAR UserSidString; PMSV1_0_INTERACTIVE_PROFILE UserProfile; FgPolicyRefreshInfo UserPolicyRefreshInfo; PBACKGROUND_LOGON_PARAMETERS LogonParameters; OSVERSIONINFOEXW OsVersion; NTSTATUS Status; DWORD ErrorCode; BOOL Success; DWORD NextLogonCacheable; BOOLEAN UserLoggedOn; BOOL RunSynchronous;
//
// Initialize locals.
//
UserSidString = NULL; UserLoggedOn = FALSE; LogonParameters = NULL; *OptimizedLogonStatus = OLS_Unspecified; //
// Verify parameters.
//
ASSERT(LogonType == CachedInteractive);
//
// Check if SKU allows cached interactive logons.
//
ZeroMemory(&OsVersion, sizeof(OsVersion)); OsVersion.dwOSVersionInfoSize = sizeof(OsVersion); Status = RtlGetVersion((POSVERSIONINFOW)&OsVersion);
if (!NT_SUCCESS(Status)) { *OptimizedLogonStatus = OLS_UnsupportedSKU; goto cleanup; }
if (OsVersion.wProductType != VER_NT_WORKSTATION) { Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_UnsupportedSKU; goto cleanup; }
//
// Attempt a cached logon.
//
Status = LsaLogonUser(LsaHandle, OriginName, LogonType, AuthenticationPackage, AuthenticationInformation, AuthenticationInformationLength, LocalGroups, SourceContext, ProfileBuffer, ProfileBufferLength, LogonId, UserToken, Quotas, SubStatus);
//
// If cached logon was not successful we cannot continue.
//
if (!NT_SUCCESS(Status)) { *OptimizedLogonStatus = OLS_LogonFailed; goto cleanup; }
UserLoggedOn = TRUE;
ASSERT(*ProfileBuffer != NULL); if (*ProfileBuffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; *OptimizedLogonStatus = OLS_InsufficientResources; goto cleanup; }
//
// Get user's SID.
//
UserSidString = GcGetSidString(*UserToken);
if (!UserSidString) { Status = STATUS_INSUFFICIENT_RESOURCES; *OptimizedLogonStatus = OLS_InsufficientResources; goto cleanup; }
//
// Check if in last logon of this user we determined we cannot do
// a cached logon this time.
//
ErrorCode = GcGetNextLogonCacheable(UserSidString, &NextLogonCacheable);
if (ErrorCode == ERROR_SUCCESS && !NextLogonCacheable) { Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_NextLogonNotCacheable; goto cleanup; }
//
// Does policy allow cached logons on this machine for the user?
//
if (IsSyncForegroundPolicyRefresh(FALSE, *UserToken)) { Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_SyncMachinePolicy; goto cleanup; }
//
// Check if policy allows cached logon for this user.
//
ErrorCode = GetNextFgPolicyRefreshInfo(UserSidString, &UserPolicyRefreshInfo);
if (ErrorCode != ERROR_SUCCESS || UserPolicyRefreshInfo.mode != GP_ModeAsyncForeground) {
Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_SyncUserPolicy; goto cleanup; }
//
// Check if user profile does not support default cached logons.
// e.g if the user has a remote home directory or a roaming profile etc.
//
UserProfile = *ProfileBuffer;
ErrorCode = GcCheckIfProfileAllowsCachedLogon(&UserProfile->HomeDirectory, &UserProfile->ProfilePath, UserSidString, &NextLogonCacheable);
if (ErrorCode != ERROR_SUCCESS || !NextLogonCacheable) { Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_ProfileDisallows; goto cleanup; }
//
// Check if logon scripts are set to run synchronously.
//
RunSynchronous = GcCheckIfLogonScriptsRunSync(UserSidString);
if (RunSynchronous) { Status = STATUS_NOT_SUPPORTED; *OptimizedLogonStatus = OLS_SyncLogonScripts; goto cleanup; }
//
// We are fine to run with cached logon. We still need to launch a work
// item to do a real interactive logon that will update the cache.
//
LogonParameters = Alloc(sizeof(*LogonParameters));
if (!LogonParameters) { Status = STATUS_INSUFFICIENT_RESOURCES; *OptimizedLogonStatus = OLS_InsufficientResources; goto cleanup; }
//
// Initialize the structure so we know what to cleanup.
//
ZeroMemory(LogonParameters, sizeof(*LogonParameters));
LogonParameters->LsaHandle = LsaHandle;
//
// Hand over the allocated UserSidString to background logon to cleanup.
//
LogonParameters->UserSidString = UserSidString; UserSidString = NULL;
//
// Hand over AuthenticationInfo structure to background logon.
// The password is already hidden.
//
LogonParameters->AuthenticationPackage = AuthenticationPackage; LogonParameters->AuthenticationInformationLength = AuthenticationInformationLength;
//
// Background logon will use the authentication information and free it.
//
LogonParameters->AuthenticationInformation = AuthenticationInformation;
//
// Queue a work item to perform the background logon to update the cache.
// This background "logon" has nothing to do with the current user
// successfully logging on, logging off etc. So we don't have to monitor
// those. All it does is to update the cache.
//
Success = QueueUserWorkItem(BackgroundLogonWorker, LogonParameters, WT_EXECUTELONGFUNCTION);
if (!Success) {
//
// We want to back out from the cached logon if we could not queue
// an actual logon to update the cache for the next time.
//
Status = STATUS_INSUFFICIENT_RESOURCES; *OptimizedLogonStatus = OLS_InsufficientResources; goto cleanup; }
//
// We are done.
//
Status = STATUS_SUCCESS; *OptimizedLogonStatus = OLS_LogonIsCached;
cleanup:
if (!NT_SUCCESS(Status)) { //
// If we failed after logging on the user using cached credentials,
// we have to cleanup.
//
if (UserLoggedOn) {
//
// Close the user's token.
//
CloseHandle(*UserToken); //
// Free the profile buffer.
//
if (*ProfileBuffer) { LsaFreeReturnBuffer(*ProfileBuffer); } }
if (LogonParameters) {
if (LogonParameters->UserSidString) { GcDeleteSidString(LogonParameters->UserSidString); }
Free(LogonParameters); } }
if (UserSidString) { GcDeleteSidString(UserSidString); }
return Status; }
/***************************************************************************\
* BackgroundLogonWorker * * If the actual interactive logon was performed using cached credentials * because of policy, this workitem is queued to perform an actual network * logon to update the cached information in the security packages. * * Authentication information to perform the logon is passed in as the * parameter and must be freed when the thread is done. * * History: * 03-23-01 Cenke Created \***************************************************************************/ DWORD BackgroundLogonWorker( PBACKGROUND_LOGON_PARAMETERS LogonParameters ) { PMSV1_0_INTERACTIVE_PROFILE Profile; HANDLE UserToken; LSA_STRING OriginName; TOKEN_SOURCE SourceContext; QUOTA_LIMITS Quotas; PSECURITY_LOGON_SESSION_DATA LogonSessionData; LUID LogonId; NTSTATUS SubStatus; NTSTATUS Status; DWORD ErrorCode; ULONG ProfileBufferLength; ULONG NameBufferNumChars; static LONG LogonServicesStarted = 0; DWORD MaxWaitTime; BOOLEAN UserLoggedOn; BOOLEAN ImpersonatingUser; WCHAR NameBuffer[UNLEN + 1]; DWORD DaysToCheck; DWORD DaysToExpiry; LARGE_INTEGER CurrentTime;
//
// Initialize locals.
//
Profile = NULL; RtlInitString(&OriginName, "Winlogon-Background"); LogonSessionData = NULL; ZeroMemory(&SourceContext, sizeof(SourceContext)); strncpy(SourceContext.SourceName, "GinaBkg", TOKEN_SOURCE_LENGTH); UserLoggedOn = FALSE; ImpersonatingUser = FALSE; NameBufferNumChars = sizeof(NameBuffer) / sizeof(NameBuffer[0]);
//
// Verify parameters.
//
ASSERT(LogonParameters); ASSERT(LogonParameters->AuthenticationInformation); ASSERT(LogonParameters->UserSidString); //
// Make sure workstation and netlogon services have started.
//
if (!LogonServicesStarted) {
MaxWaitTime = 120000; // 2 minutes.
GcWaitForServiceToStart(SERVICE_WORKSTATION, MaxWaitTime); GcWaitForServiceToStart(SERVICE_NETLOGON, MaxWaitTime);
LogonServicesStarted = 1; }
//
// Try to log the user in to initiate update of cached credentials.
//
Status = LsaLogonUser(LogonParameters->LsaHandle, &OriginName, Interactive, LogonParameters->AuthenticationPackage, LogonParameters->AuthenticationInformation, LogonParameters->AuthenticationInformationLength, NULL, &SourceContext, &(PVOID)Profile, &ProfileBufferLength, &LogonId, &UserToken, &Quotas, &SubStatus);
if (!NT_SUCCESS(Status)) {
//
// If the error is real we will force non-cached logon next time.
//
if (Status != STATUS_NO_LOGON_SERVERS) { GcSetNextLogonCacheable(LogonParameters->UserSidString, FALSE); }
ErrorCode = LsaNtStatusToWinError(Status); goto cleanup; }
UserLoggedOn = TRUE;
ASSERT(Profile != NULL); if (Profile == NULL) { ErrorCode = ERROR_NO_SYSTEM_RESOURCES; // Generic but enough in this case
goto cleanup; }
//
// Did we actually end up doing a cached logon?
//
if (Profile->UserFlags & LOGON_CACHED_ACCOUNT) {
//
// We are done, just cleanup.
//
ErrorCode = ERROR_SUCCESS; goto cleanup; }
//
// If we are entering the password expiry warning period, disable optimized
// logon for next time so warning dialogs get shown. Otherwise for cached
// logons password expiry date gets invented to be forever in future.
//
if (Profile) {
GetSystemTimeAsFileTime((FILETIME*) &CurrentTime);
DaysToCheck = GetPasswordExpiryWarningPeriod();
if (GetDaysToExpiry(&CurrentTime, &Profile->PasswordMustChange, &DaysToExpiry)) {
if (DaysToCheck >= DaysToExpiry) { GcSetNextLogonCacheable(LogonParameters->UserSidString, FALSE); } } }
//
// Make a GetUserName call to update the user name cache. Ignore errors.
//
if (!ImpersonateLoggedOnUser(UserToken)) { ErrorCode = GetLastError(); goto cleanup; }
ImpersonatingUser = TRUE;
GetUserNameEx(NameSamCompatible, NameBuffer, &NameBufferNumChars);
//
// We are done.
//
ErrorCode = ERROR_SUCCESS;
cleanup:
//
// Stop impersonation.
//
if (ImpersonatingUser) { RevertToSelf(); }
//
// Cleanup passed in LogonParameters.
//
// Zeroize this for security reason, even though the password is run encoded
ZeroMemory(LogonParameters->AuthenticationInformation, LogonParameters->AuthenticationInformationLength); LocalFree(LogonParameters->AuthenticationInformation); Free(LogonParameters->UserSidString); Free(LogonParameters);
//
// If the user logged on, cleanup.
//
if (UserLoggedOn) { CloseHandle(UserToken); if (Profile) { LsaFreeReturnBuffer(Profile); } }
if (LogonSessionData) { LsaFreeReturnBuffer(LogonSessionData); }
return ErrorCode; }
|