Leaked source code of windows server 2003
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.
 
 
 
 
 
 

2776 lines
76 KiB

/****************************** 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;
}