11285 lines
325 KiB
C
11285 lines
325 KiB
C
/*++
|
||
|
||
Copyright (c) 1990 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
user.c
|
||
|
||
Abstract:
|
||
|
||
This file contains services related to the SAM "user" object.
|
||
|
||
|
||
Author:
|
||
|
||
Jim Kelly (JimK) 4-July-1991
|
||
|
||
Environment:
|
||
|
||
User Mode - Win32
|
||
|
||
Revision History:
|
||
|
||
|
||
--*/
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Includes //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
#include <samsrvp.h>
|
||
#include <lmcons.h>
|
||
#include <nturtl.h>
|
||
#include <ntlsa.h> // need for nlrepl.h
|
||
#include <nlrepl.h> // I_NetNotifyMachineAccount prototype
|
||
#include <msaudite.h>
|
||
#include <rc4.h> // rc4_key(), rc4()
|
||
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// private service prototypes //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
LARGE_INTEGER
|
||
SampGetPasswordMustChange(
|
||
IN ULONG UserAccountControl,
|
||
IN LARGE_INTEGER PasswordLastSet,
|
||
IN LARGE_INTEGER MaxPasswordAge
|
||
);
|
||
|
||
NTSTATUS
|
||
SampComputePasswordExpired(
|
||
IN BOOLEAN PasswordExpired,
|
||
OUT PLARGE_INTEGER PasswordLastSet
|
||
);
|
||
|
||
NTSTATUS
|
||
SampStorePasswordExpired(
|
||
IN PSAMP_OBJECT Context,
|
||
IN BOOLEAN PasswordExpired
|
||
);
|
||
|
||
NTSTATUS
|
||
SampStoreUserPasswords(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PLM_OWF_PASSWORD LmOwfPassword,
|
||
IN BOOLEAN LmPasswordPresent,
|
||
IN PNT_OWF_PASSWORD NtOwfPassword,
|
||
IN BOOLEAN NtPasswordPresent,
|
||
IN BOOLEAN CheckHistory
|
||
);
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserPasswords(
|
||
IN PSAMP_OBJECT Context,
|
||
OUT PLM_OWF_PASSWORD LmOwfPassword,
|
||
OUT PBOOLEAN LmPasswordNonNull,
|
||
OUT PNT_OWF_PASSWORD NtOwfPassword,
|
||
OUT PBOOLEAN NtPasswordPresent,
|
||
OUT PBOOLEAN NtPasswordNonNull
|
||
);
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserMembership(
|
||
IN PSAMP_OBJECT UserContext,
|
||
IN BOOLEAN MakeCopy,
|
||
OUT PULONG MembershipCount,
|
||
OUT PGROUP_MEMBERSHIP *Membership OPTIONAL
|
||
);
|
||
|
||
NTSTATUS
|
||
SampReplaceUserMembership(
|
||
IN PSAMP_OBJECT UserContext,
|
||
IN ULONG MembershipCount,
|
||
IN PGROUP_MEMBERSHIP Membership
|
||
);
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserLogonHours(
|
||
IN PSAMP_OBJECT Context,
|
||
OUT PLOGON_HOURS LogonHours
|
||
);
|
||
|
||
NTSTATUS
|
||
SampReplaceUserLogonHours(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PLOGON_HOURS LogonHours
|
||
);
|
||
|
||
|
||
NTSTATUS
|
||
SampAssignPrimaryGroup(
|
||
IN PSAMP_OBJECT Context,
|
||
IN ULONG GroupRid
|
||
);
|
||
|
||
NTSTATUS
|
||
SampDeleteUserKeys(
|
||
IN PSAMP_OBJECT Context
|
||
);
|
||
|
||
NTSTATUS
|
||
SampCheckPasswordHistory(
|
||
IN PVOID EncryptedPassword,
|
||
IN ULONG EncryptedPasswordLength,
|
||
IN USHORT PasswordHistoryLength,
|
||
IN ULONG HistoryAttributeIndex,
|
||
IN PSAMP_OBJECT Context,
|
||
IN BOOLEAN CheckHistory,
|
||
OUT PUNICODE_STRING OwfHistoryBuffer
|
||
);
|
||
|
||
NTSTATUS
|
||
SampAddPasswordHistory(
|
||
IN PSAMP_OBJECT Context,
|
||
IN ULONG HistoryAttributeIndex,
|
||
IN PUNICODE_STRING NtOwfHistoryBuffer,
|
||
IN PVOID EncryptedPassword,
|
||
IN ULONG EncryptedPasswordLength,
|
||
IN USHORT PasswordHistoryLength
|
||
);
|
||
|
||
NTSTATUS
|
||
SampMatchworkstation(
|
||
IN PUNICODE_STRING LogonWorkStation,
|
||
IN PUNICODE_STRING WorkStations
|
||
);
|
||
|
||
NTSTATUS
|
||
SampChangeUserAccountName(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PUNICODE_STRING NewAccountName,
|
||
OUT PUNICODE_STRING OldAccountName
|
||
);
|
||
|
||
USHORT
|
||
SampQueryBadPasswordCount(
|
||
PSAMP_OBJECT UserContext,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
);
|
||
BOOLEAN
|
||
SampIncrementBadPasswordCount(
|
||
PSAMP_OBJECT UserContext,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
);
|
||
VOID
|
||
SampUpdateAccountLockedOutFlag(
|
||
PSAMP_OBJECT Context,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
|
||
PBOOLEAN IsLocked
|
||
);
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Routines //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrOpenUser(
|
||
IN SAMPR_HANDLE DomainHandle,
|
||
IN ACCESS_MASK DesiredAccess,
|
||
IN ULONG UserId,
|
||
OUT SAMPR_HANDLE *UserHandle
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
This API opens an existing user in the account database. The user
|
||
is specified by a ID value that is relative to the SID of the
|
||
domain. The operations that will be performed on the user must be
|
||
declared at this time.
|
||
|
||
This call returns a handle to the newly opened user that may be
|
||
used for successive operations on the user. This handle may be
|
||
closed with the SamCloseHandle API.
|
||
|
||
|
||
|
||
Parameters:
|
||
|
||
DomainHandle - A domain handle returned from a previous call to
|
||
SamOpenDomain.
|
||
|
||
DesiredAccess - Is an access mask indicating which access types
|
||
are desired to the user. These access types are reconciled
|
||
with the Discretionary Access Control list of the user to
|
||
determine whether the accesses will be granted or denied.
|
||
|
||
UserId - Specifies the relative ID value of the user to be
|
||
opened.
|
||
|
||
UserHandle - Receives a handle referencing the newly opened
|
||
user. This handle will be required in successive calls to
|
||
operate on the user.
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The user was successfully opened.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_NO_SUCH_USER - The specified user does not exist.
|
||
|
||
STATUS_INVALID_HANDLE - The domain handle passed is invalid.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus, IgnoreStatus;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
|
||
SAMTRACE("SamrOpenUser");
|
||
|
||
NtStatus = SampOpenAccount(
|
||
SampUserObjectType,
|
||
DomainHandle,
|
||
DesiredAccess,
|
||
UserId,
|
||
FALSE,
|
||
UserHandle
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
NTSTATUS NtStatus1;
|
||
|
||
//
|
||
// If the domain handle allows reading the password
|
||
// parameters, note that in the context to make life
|
||
// easy for SampGetUserDomainPasswordInformation().
|
||
//
|
||
|
||
SampAcquireReadLock();
|
||
|
||
NtStatus1 = SampLookupContext(
|
||
DomainHandle,
|
||
DOMAIN_READ_PASSWORD_PARAMETERS, // DesiredAccess
|
||
SampDomainObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus1 ) ) {
|
||
|
||
((PSAMP_OBJECT)(*UserHandle))->TypeBody.User.DomainPasswordInformationAccessible = TRUE;
|
||
|
||
//
|
||
// De-reference the object, discarding changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( DomainHandle, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
|
||
} else {
|
||
|
||
((PSAMP_OBJECT)(*UserHandle))->TypeBody.User.DomainPasswordInformationAccessible = FALSE;
|
||
}
|
||
|
||
//
|
||
// Release the lock
|
||
//
|
||
|
||
SampReleaseReadLock();
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SamrDeleteUser(
|
||
IN OUT SAMPR_HANDLE *UserHandle
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This API deletes a user from the account database. If the account
|
||
being deleted is the last account in the database in the ADMIN
|
||
group, then STATUS_LAST_ADMIN is returned, and the Delete fails.
|
||
|
||
Note that following this call, the UserHandle is no longer valid.
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on. The handle must be
|
||
openned for DELETE access.
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
||
|
||
STATUS_LAST_ADMIN - Cannot delete the last enabled administrator account
|
||
|
||
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
||
correct state (disabled or enabled) to perform the requested
|
||
operation. The domain server must be enabled for this
|
||
operation
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
||
incorrect role (primary or backup) to perform the requested
|
||
operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
UNICODE_STRING UserName;
|
||
NTSTATUS NtStatus, IgnoreStatus, TmpStatus;
|
||
PSAMP_OBJECT AccountContext = NULL;
|
||
PSAMP_DEFINED_DOMAINS Domain = NULL;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
PSID AccountSid = NULL;
|
||
PGROUP_MEMBERSHIP Groups = NULL;
|
||
ULONG ObjectRid,
|
||
GroupCount,
|
||
DomainIndex,
|
||
i;
|
||
|
||
|
||
SAMTRACE("SamrDeleteUser");
|
||
|
||
|
||
|
||
//
|
||
// Grab the lock
|
||
//
|
||
|
||
NtStatus = SampAcquireWriteLock();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)(*UserHandle);
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
DELETE,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
|
||
ObjectRid = AccountContext->TypeBody.User.Rid;
|
||
|
||
//
|
||
// Get a pointer to the domain this object is in.
|
||
// This is used for auditing.
|
||
//
|
||
|
||
DomainIndex = AccountContext->DomainIndex;
|
||
Domain = &SampDefinedDomains[ DomainIndex ];
|
||
|
||
//
|
||
// built-in accounts can't be deleted, unless the caller is trusted
|
||
//
|
||
|
||
if ( !AccountContext->TrustedClient ) {
|
||
|
||
NtStatus = SampIsAccountBuiltIn( ObjectRid );
|
||
}
|
||
|
||
//
|
||
// Get the list of groups this user is a member of.
|
||
// Remove the user from each group.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampRetrieveUserMembership(
|
||
AccountContext,
|
||
FALSE, // Make copy
|
||
&GroupCount,
|
||
&Groups
|
||
);
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
ASSERT( GroupCount > 0);
|
||
ASSERT( Groups != NULL );
|
||
|
||
|
||
//
|
||
// Remove the user from each group.
|
||
//
|
||
|
||
for ( i=0; i<GroupCount && NT_SUCCESS(NtStatus); i++) {
|
||
|
||
NtStatus = SampRemoveUserFromGroup(
|
||
Groups[i].RelativeId,
|
||
ObjectRid
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// So far, so good. The user has been removed from all groups.
|
||
// Now remove the user from all aliases
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampCreateAccountSid(AccountContext, &AccountSid);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampRemoveAccountFromAllAliases(
|
||
AccountSid,
|
||
FALSE,
|
||
NULL,
|
||
NULL,
|
||
NULL
|
||
);
|
||
}
|
||
}
|
||
|
||
//
|
||
// Get the AccountControl flags for when we update
|
||
// the display cache, and to let Netlogon know if this
|
||
// is a machine account that is going away.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
//
|
||
// Now we just need to clean up the user keys themselves.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// First get and save the account name for
|
||
// I_NetNotifyLogonOfDelta.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&UserName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// This must be done before we invalidate contexts, because our
|
||
// own handle to the group gets closed as well.
|
||
//
|
||
|
||
NtStatus = SampDeleteUserKeys( AccountContext );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// We must invalidate any open contexts to this user.
|
||
// This will close all handles to the user's keys.
|
||
// THIS IS AN IRREVERSIBLE PROCESS.
|
||
//
|
||
|
||
SampInvalidateUserContexts( ObjectRid );
|
||
|
||
//
|
||
// Commit the whole mess
|
||
//
|
||
|
||
NtStatus = SampCommitAndRetainWriteLock();
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
|
||
|
||
//
|
||
// Update the cached Alias Information
|
||
//
|
||
|
||
IgnoreStatus = SampAlRemoveAccountFromAllAliases(
|
||
AccountSid,
|
||
FALSE,
|
||
NULL,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
//
|
||
// Update the display information
|
||
//
|
||
|
||
AccountInfo.Name = UserName;
|
||
AccountInfo.Rid = ObjectRid;
|
||
AccountInfo.AccountControl = V1aFixed.UserAccountControl;
|
||
RtlInitUnicodeString(&AccountInfo.Comment, NULL);
|
||
RtlInitUnicodeString(&AccountInfo.FullName, NULL);
|
||
|
||
IgnoreStatus = SampUpdateDisplayInformation(&AccountInfo,
|
||
NULL,
|
||
SampUserObjectType);
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
|
||
|
||
|
||
//
|
||
// Audit the deletion before we free the write lock
|
||
// so that we have access to the context block.
|
||
//
|
||
|
||
if (SampDoAccountAuditing(DomainIndex) &&
|
||
NT_SUCCESS(NtStatus) ) {
|
||
|
||
LsaIAuditSamEvent(
|
||
STATUS_SUCCESS,
|
||
SE_AUDITID_USER_DELETED, // AuditId
|
||
Domain->Sid, // Domain SID
|
||
NULL, // Member Rid (not used)
|
||
NULL, // Member Sid (not used)
|
||
&UserName, // Account Name
|
||
&Domain->ExternalName, // Domain
|
||
&ObjectRid, // Account Rid
|
||
NULL // Privileges used
|
||
);
|
||
|
||
}
|
||
|
||
//
|
||
// Notify netlogon of the change
|
||
//
|
||
|
||
SampNotifyNetlogonOfDelta(
|
||
SecurityDbDelete,
|
||
SecurityDbObjectSamUser,
|
||
ObjectRid,
|
||
&UserName,
|
||
(DWORD) FALSE, // Replicate immediately
|
||
NULL // Delta data
|
||
);
|
||
|
||
//
|
||
// Do delete auditing
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
(VOID) NtDeleteObjectAuditAlarm(
|
||
&SampSamSubsystem,
|
||
*UserHandle,
|
||
AccountContext->AuditOnClose
|
||
);
|
||
}
|
||
|
||
|
||
if ( ( V1aFixed.UserAccountControl &
|
||
USER_MACHINE_ACCOUNT_MASK ) != 0 ) {
|
||
|
||
//
|
||
// This was a machine account. Let
|
||
// NetLogon know of the change.
|
||
//
|
||
|
||
IgnoreStatus = I_NetNotifyMachineAccount(
|
||
ObjectRid,
|
||
SampDefinedDomains[SampTransactionDomainIndex].Sid,
|
||
V1aFixed.UserAccountControl,
|
||
0,
|
||
&UserName
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
SampFreeUnicodeString( &UserName );
|
||
}
|
||
}
|
||
|
||
//
|
||
// De-reference the object, discarding changes, and delete the context
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
//
|
||
// If we actually deleted the user, delete the context and
|
||
// let RPC know that the handle is invalid.
|
||
//
|
||
|
||
SampDeleteContext( AccountContext );
|
||
|
||
(*UserHandle) = NULL;
|
||
}
|
||
|
||
} //end_if
|
||
|
||
//
|
||
// Free the lock -
|
||
//
|
||
// Everything has already been committed above, so we must indicate
|
||
// no additional changes have taken place.
|
||
//
|
||
//
|
||
//
|
||
|
||
TmpStatus = SampReleaseWriteLock( FALSE );
|
||
|
||
if (NtStatus == STATUS_SUCCESS) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
|
||
//
|
||
// If necessary, free the AccountSid.
|
||
//
|
||
|
||
if (AccountSid != NULL) {
|
||
|
||
MIDL_user_free(AccountSid);
|
||
AccountSid = NULL;
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SamrQueryInformationUser(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN USER_INFORMATION_CLASS UserInformationClass,
|
||
OUT PSAMPR_USER_INFO_BUFFER *Buffer
|
||
)
|
||
{
|
||
//
|
||
// This is a thin veil to SamrQueryInformationUser2().
|
||
// This is needed so that new-release systems can call
|
||
// this routine without the danger of passing an info
|
||
// level that release 1.0 systems didn't understand.
|
||
//
|
||
|
||
return( SamrQueryInformationUser2(UserHandle, UserInformationClass, Buffer ) );
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SamrQueryInformationUser2(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN USER_INFORMATION_CLASS UserInformationClass,
|
||
OUT PSAMPR_USER_INFO_BUFFER *Buffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
User object QUERY information routine.
|
||
|
||
Arguments:
|
||
|
||
UserHandle - RPC context handle for an open user object.
|
||
|
||
UserInformationClass - Type of information being queried.
|
||
|
||
Buffer - To receive the output (queried) information.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_INVALID_INFO_CLASS - An unknown information class was requested.
|
||
No information has been returned.
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to
|
||
return(the requested information in.
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
NTSTATUS IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
PUSER_ALL_INFORMATION All;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
ACCESS_MASK DesiredAccess;
|
||
ULONG i, WhichFields;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
BOOLEAN NoErrorsYet;
|
||
LM_OWF_PASSWORD LmOwfPassword;
|
||
NT_OWF_PASSWORD NtOwfPassword;
|
||
BOOLEAN NtPasswordNonNull, LmPasswordNonNull;
|
||
BOOLEAN NtPasswordPresent;
|
||
|
||
//
|
||
// Used for tracking allocated blocks of memory - so we can deallocate
|
||
// them in case of error. Don't exceed this number of allocated buffers.
|
||
// ||
|
||
// vv
|
||
PVOID AllocatedBuffer[40];
|
||
ULONG AllocatedBufferCount = 0;
|
||
LARGE_INTEGER TempTime;
|
||
|
||
SAMTRACE("SamrQueryInformationUser2");
|
||
|
||
#define RegisterBuffer(Buffer) \
|
||
{ \
|
||
if ((Buffer) != NULL) { \
|
||
\
|
||
ASSERT(AllocatedBufferCount < \
|
||
sizeof(AllocatedBuffer) / sizeof(*AllocatedBuffer)); \
|
||
\
|
||
AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
|
||
} \
|
||
}
|
||
|
||
#define AllocateBuffer(NewBuffer, Size) \
|
||
{ \
|
||
(NewBuffer) = MIDL_user_allocate(Size); \
|
||
RegisterBuffer(NewBuffer); \
|
||
} \
|
||
|
||
|
||
//
|
||
// Make sure we understand what RPC is doing for (to) us.
|
||
//
|
||
|
||
ASSERT (Buffer != NULL);
|
||
ASSERT ((*Buffer) == NULL);
|
||
|
||
|
||
|
||
//
|
||
// Set the desired access based upon information class.
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserInternal3Information:
|
||
case UserAllInformation:
|
||
|
||
//
|
||
// For trusted clients, we will return everything. For
|
||
// others, we will return everything that they have access to.
|
||
// In either case, we'll have to look at some variables in the
|
||
// context so we'll do the work after the SampLookupContext()
|
||
// below.
|
||
//
|
||
|
||
DesiredAccess = 0;
|
||
break;
|
||
|
||
case UserAccountInformation:
|
||
|
||
DesiredAccess = (USER_READ_GENERAL |
|
||
USER_READ_PREFERENCES |
|
||
USER_READ_LOGON |
|
||
USER_READ_ACCOUNT);
|
||
break;
|
||
|
||
case UserGeneralInformation:
|
||
case UserPrimaryGroupInformation:
|
||
case UserNameInformation:
|
||
case UserAccountNameInformation:
|
||
case UserFullNameInformation:
|
||
case UserAdminCommentInformation:
|
||
|
||
DesiredAccess = USER_READ_GENERAL;
|
||
break;
|
||
|
||
|
||
case UserPreferencesInformation:
|
||
|
||
DesiredAccess = (USER_READ_PREFERENCES |
|
||
USER_READ_GENERAL);
|
||
break;
|
||
|
||
|
||
case UserLogonInformation:
|
||
|
||
DesiredAccess = (USER_READ_GENERAL |
|
||
USER_READ_PREFERENCES |
|
||
USER_READ_LOGON |
|
||
USER_READ_ACCOUNT);
|
||
break;
|
||
|
||
case UserLogonHoursInformation:
|
||
case UserHomeInformation:
|
||
case UserScriptInformation:
|
||
case UserProfileInformation:
|
||
case UserWorkStationsInformation:
|
||
|
||
DesiredAccess = USER_READ_LOGON;
|
||
break;
|
||
|
||
|
||
case UserControlInformation:
|
||
case UserExpiresInformation:
|
||
case UserParametersInformation:
|
||
|
||
DesiredAccess = USER_READ_ACCOUNT;
|
||
break;
|
||
|
||
|
||
case UserInternal1Information:
|
||
case UserInternal2Information:
|
||
|
||
//
|
||
// These levels are only queryable by trusted clients. The code
|
||
// below will check AccountContext->TrustedClient after calling
|
||
// SampLookupContext, and only return the data if it is TRUE.
|
||
//
|
||
|
||
DesiredAccess = (ACCESS_MASK)0; // Trusted client; no need to verify
|
||
break;
|
||
|
||
|
||
case UserSetPasswordInformation: // Can't query password
|
||
default:
|
||
|
||
return(STATUS_INVALID_INFO_CLASS);
|
||
|
||
} // end_switch
|
||
|
||
|
||
|
||
|
||
|
||
//
|
||
// Allocate the info structure
|
||
//
|
||
|
||
AllocateBuffer(*Buffer, sizeof(SAMPR_USER_INFO_BUFFER) );
|
||
if ((*Buffer) == NULL) {
|
||
return(STATUS_INSUFFICIENT_RESOURCES);
|
||
}
|
||
|
||
|
||
|
||
SampAcquireReadLock();
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
DesiredAccess,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If the information level requires, retrieve the V1_FIXED record
|
||
// from the registry.
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserInternal3Information:
|
||
//
|
||
// Only trusted clients may query for this class.
|
||
//
|
||
|
||
if ( !AccountContext->TrustedClient ) {
|
||
NtStatus = STATUS_INVALID_INFO_CLASS;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Drop through to the UserAll case
|
||
//
|
||
|
||
case UserAllInformation: {
|
||
|
||
//
|
||
// We weren't able to check the security stuff above, so do
|
||
// it now.
|
||
//
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
//
|
||
// Give everything to trusted clients, except fields that
|
||
// can't be queried at all.
|
||
//
|
||
|
||
WhichFields = USER_ALL_READ_GENERAL_MASK |
|
||
USER_ALL_READ_LOGON_MASK |
|
||
USER_ALL_READ_ACCOUNT_MASK |
|
||
USER_ALL_READ_PREFERENCES_MASK |
|
||
USER_ALL_READ_TRUSTED_MASK;
|
||
|
||
} else {
|
||
|
||
|
||
//
|
||
// Only return fields that the caller has access to.
|
||
//
|
||
|
||
WhichFields = 0;
|
||
|
||
if ( RtlAreAllAccessesGranted(
|
||
AccountContext->GrantedAccess,
|
||
USER_READ_GENERAL ) ) {
|
||
|
||
WhichFields |= USER_ALL_READ_GENERAL_MASK;
|
||
}
|
||
|
||
if ( RtlAreAllAccessesGranted(
|
||
AccountContext->GrantedAccess,
|
||
USER_READ_LOGON ) ) {
|
||
|
||
WhichFields |= USER_ALL_READ_LOGON_MASK;
|
||
}
|
||
|
||
if ( RtlAreAllAccessesGranted(
|
||
AccountContext->GrantedAccess,
|
||
USER_READ_ACCOUNT ) ) {
|
||
|
||
WhichFields |= USER_ALL_READ_ACCOUNT_MASK;
|
||
}
|
||
|
||
if ( RtlAreAllAccessesGranted(
|
||
AccountContext->GrantedAccess,
|
||
USER_READ_PREFERENCES ) ) {
|
||
|
||
WhichFields |= USER_ALL_READ_PREFERENCES_MASK;
|
||
}
|
||
|
||
if ( WhichFields == 0 ) {
|
||
|
||
//
|
||
// Caller doesn't have access to ANY fields.
|
||
//
|
||
|
||
NtStatus = STATUS_ACCESS_DENIED;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// fall through to pick up the V1aFixed information
|
||
//
|
||
|
||
case UserGeneralInformation:
|
||
case UserPrimaryGroupInformation:
|
||
case UserPreferencesInformation:
|
||
case UserLogonInformation:
|
||
case UserAccountInformation:
|
||
case UserControlInformation:
|
||
case UserExpiresInformation:
|
||
case UserInternal2Information:
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
break;
|
||
|
||
default:
|
||
|
||
NtStatus = STATUS_SUCCESS;
|
||
|
||
} // end_switch
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// case on the type information requested
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserInternal3Information:
|
||
case UserAllInformation:
|
||
|
||
//
|
||
// All and Internal3 are the same except Internal3 has
|
||
// an extra field.
|
||
|
||
All = (PUSER_ALL_INFORMATION)(*Buffer);
|
||
|
||
RtlZeroMemory( (PVOID)All, sizeof(SAMPR_USER_INFO_BUFFER) );
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
if ( WhichFields & ( USER_ALL_PASSWORDMUSTCHANGE |
|
||
USER_ALL_NTPASSWORDPRESENT ) ) {
|
||
|
||
//
|
||
// These fields will need some info from
|
||
// SampRetrieveUserPasswords().
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
AccountContext,
|
||
&LmOwfPassword,
|
||
&LmPasswordNonNull,
|
||
&NtOwfPassword,
|
||
&NtPasswordPresent,
|
||
&NtPasswordNonNull
|
||
);
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_USERNAME ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->All.UserName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->UserName.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_FULLNAME ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->FullName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->FullName.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_USERID ) ) {
|
||
|
||
All->UserId = V1aFixed.UserId;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PRIMARYGROUPID ) ) {
|
||
|
||
All->PrimaryGroupId = V1aFixed.PrimaryGroupId;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_ADMINCOMMENT ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->AdminComment)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->AdminComment.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_USERCOMMENT ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_USER_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->UserComment) // Body
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->UserComment.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_HOMEDIRECTORY ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->HomeDirectory)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->HomeDirectory.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_HOMEDIRECTORYDRIVE ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->HomeDirectoryDrive)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->HomeDirectoryDrive.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_SCRIPTPATH ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->ScriptPath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->ScriptPath.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PROFILEPATH ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->ProfilePath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->ProfilePath.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_WORKSTATIONS ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->WorkStations)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->WorkStations.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_LASTLOGON ) ) {
|
||
|
||
All->LastLogon = V1aFixed.LastLogon;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_LASTLOGOFF ) ) {
|
||
|
||
All->LastLogoff = V1aFixed.LastLogoff;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_LOGONHOURS ) ) {
|
||
|
||
NtStatus = SampRetrieveUserLogonHours(
|
||
AccountContext,
|
||
(PLOGON_HOURS)&(All->LogonHours)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (All->LogonHours.LogonHours != NULL) {
|
||
|
||
RegisterBuffer(All->LogonHours.LogonHours);
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_BADPASSWORDCOUNT ) ) {
|
||
|
||
All->BadPasswordCount = SampQueryBadPasswordCount( AccountContext, &V1aFixed );
|
||
|
||
if (UserInformationClass == UserInternal3Information) {
|
||
(*Buffer)->Internal3.LastBadPasswordTime = V1aFixed.LastBadPasswordTime;
|
||
}
|
||
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_LOGONCOUNT ) ) {
|
||
|
||
All->LogonCount = V1aFixed.LogonCount;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PASSWORDCANCHANGE ) ) {
|
||
|
||
if ( !NtPasswordNonNull && !LmPasswordNonNull ) {
|
||
|
||
//
|
||
// Null passwords can be changed immediately.
|
||
//
|
||
|
||
All->PasswordCanChange = SampHasNeverTime;
|
||
|
||
} else {
|
||
|
||
All->PasswordCanChange = SampAddDeltaTime(
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MinPasswordAge);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields &
|
||
(USER_ALL_PASSWORDMUSTCHANGE|USER_ALL_PASSWORDEXPIRED) ) ) {
|
||
|
||
All->PasswordMustChange = SampGetPasswordMustChange(
|
||
V1aFixed.UserAccountControl,
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MaxPasswordAge);
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PASSWORDEXPIRED ) ) {
|
||
|
||
LARGE_INTEGER TimeNow;
|
||
|
||
NtStatus = NtQuerySystemTime( &TimeNow );
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
if ( TimeNow.QuadPart >= All->PasswordMustChange.QuadPart) {
|
||
|
||
All->PasswordExpired = TRUE;
|
||
|
||
} else {
|
||
|
||
All->PasswordExpired = FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PASSWORDLASTSET ) ) {
|
||
|
||
All->PasswordLastSet = V1aFixed.PasswordLastSet;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_ACCOUNTEXPIRES ) ) {
|
||
|
||
All->AccountExpires = V1aFixed.AccountExpires;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_USERACCOUNTCONTROL ) ) {
|
||
|
||
All->UserAccountControl = V1aFixed.UserAccountControl;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PARAMETERS ) ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PARAMETERS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&(All->Parameters)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->Parameters.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_COUNTRYCODE ) ) {
|
||
|
||
All->CountryCode = V1aFixed.CountryCode;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_CODEPAGE ) ) {
|
||
|
||
All->CodePage = V1aFixed.CodePage;
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_NTPASSWORDPRESENT ) ) {
|
||
|
||
ASSERT( WhichFields & USER_ALL_LMPASSWORDPRESENT);
|
||
|
||
All->LmPasswordPresent = LmPasswordNonNull;
|
||
All->NtPasswordPresent = NtPasswordNonNull;
|
||
|
||
RtlInitUnicodeString(&All->LmPassword, NULL);
|
||
RtlInitUnicodeString(&All->NtPassword, NULL);
|
||
|
||
if ( LmPasswordNonNull ) {
|
||
|
||
All->LmPassword.Buffer =
|
||
MIDL_user_allocate( LM_OWF_PASSWORD_LENGTH );
|
||
|
||
if ( All->LmPassword.Buffer == NULL ) {
|
||
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
} else {
|
||
|
||
RegisterBuffer(All->LmPassword.Buffer);
|
||
|
||
All->LmPassword.Length = LM_OWF_PASSWORD_LENGTH;
|
||
All->LmPassword.MaximumLength =
|
||
LM_OWF_PASSWORD_LENGTH;
|
||
RtlCopyMemory(
|
||
All->LmPassword.Buffer,
|
||
&LmOwfPassword,
|
||
LM_OWF_PASSWORD_LENGTH
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
if ( NtPasswordPresent ) {
|
||
|
||
All->NtPassword.Buffer =
|
||
MIDL_user_allocate( NT_OWF_PASSWORD_LENGTH );
|
||
|
||
if ( All->NtPassword.Buffer == NULL ) {
|
||
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
} else {
|
||
|
||
RegisterBuffer(All->NtPassword.Buffer);
|
||
|
||
All->NtPassword.Length = NT_OWF_PASSWORD_LENGTH;
|
||
All->NtPassword.MaximumLength =
|
||
NT_OWF_PASSWORD_LENGTH;
|
||
RtlCopyMemory(
|
||
All->NtPassword.Buffer,
|
||
&NtOwfPassword,
|
||
NT_OWF_PASSWORD_LENGTH
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_PRIVATEDATA ) ) {
|
||
|
||
All->PrivateDataSensitive = TRUE;
|
||
|
||
NtStatus = SampGetPrivateUserData(
|
||
AccountContext,
|
||
(PULONG)
|
||
(&(All->PrivateData.Length)),
|
||
(PVOID *)
|
||
(&(All->PrivateData.Buffer))
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
All->PrivateData.MaximumLength =
|
||
All->PrivateData.Length;
|
||
|
||
RegisterBuffer(All->PrivateData.Buffer);
|
||
}
|
||
}
|
||
|
||
if ( (NT_SUCCESS( NtStatus )) &&
|
||
( WhichFields & USER_ALL_SECURITYDESCRIPTOR ) ) {
|
||
|
||
NtStatus = SampGetObjectSD(
|
||
AccountContext,
|
||
&(All->SecurityDescriptor.Length),
|
||
(PSECURITY_DESCRIPTOR *)
|
||
&(All->SecurityDescriptor.SecurityDescriptor)
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer(All->SecurityDescriptor.SecurityDescriptor);
|
||
}
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
All->WhichFields = WhichFields;
|
||
}
|
||
|
||
break;
|
||
|
||
case UserAccountInformation:
|
||
|
||
NoErrorsYet = TRUE;
|
||
|
||
|
||
(*Buffer)->Account.UserId = V1aFixed.UserId;
|
||
(*Buffer)->Account.PrimaryGroupId = V1aFixed.PrimaryGroupId;
|
||
|
||
(*Buffer)->Account.LastLogon =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
|
||
|
||
(*Buffer)->Account.LastLogoff =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
|
||
|
||
|
||
(*Buffer)->Account.BadPasswordCount = SampQueryBadPasswordCount( AccountContext, &V1aFixed );
|
||
(*Buffer)->Account.LogonCount = V1aFixed.LogonCount;
|
||
|
||
(*Buffer)->Account.PasswordLastSet =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.PasswordLastSet);
|
||
|
||
(*Buffer)->Account.AccountExpires =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.AccountExpires);
|
||
|
||
(*Buffer)->Account.UserAccountControl = V1aFixed.UserAccountControl;
|
||
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.UserName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.UserName.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.FullName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.FullName.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.HomeDirectory)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.HomeDirectory.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.HomeDirectoryDrive)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.HomeDirectoryDrive.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.ScriptPath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.ScriptPath.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.ProfilePath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.ProfilePath.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.AdminComment) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.AdminComment.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Account.WorkStations)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.WorkStations.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Now get the logon hours
|
||
//
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampRetrieveUserLogonHours(
|
||
AccountContext,
|
||
(PLOGON_HOURS)&((*Buffer)->Account.LogonHours)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ((*Buffer)->Account.LogonHours.LogonHours != NULL) {
|
||
|
||
RegisterBuffer((*Buffer)->Account.LogonHours.LogonHours);
|
||
}
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserGeneralInformation:
|
||
|
||
|
||
(*Buffer)->General.PrimaryGroupId = V1aFixed.PrimaryGroupId;
|
||
|
||
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->General.UserName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->General.UserName.Buffer);
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->General.FullName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->General.FullName.Buffer);
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->General.AdminComment) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->General.AdminComment.Buffer);
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_USER_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->General.UserComment) // Body
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->General.UserComment.Buffer);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserNameInformation:
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Name.UserName) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Name.UserName.Buffer);
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Name.FullName) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Name.FullName.Buffer);
|
||
}
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserAccountNameInformation:
|
||
|
||
//
|
||
// Get copy of the string we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->AccountName.UserName) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->AccountName.UserName.Buffer);
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserFullNameInformation:
|
||
|
||
//
|
||
// Get copy of the string we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->FullName.FullName) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->FullName.FullName.Buffer);
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserAdminCommentInformation:
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->AdminComment.AdminComment) // Body
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->AdminComment.AdminComment.Buffer);
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserPrimaryGroupInformation:
|
||
|
||
|
||
(*Buffer)->PrimaryGroup.PrimaryGroupId = V1aFixed.PrimaryGroupId;
|
||
|
||
break;
|
||
|
||
|
||
case UserPreferencesInformation:
|
||
|
||
|
||
(*Buffer)->Preferences.CountryCode = V1aFixed.CountryCode;
|
||
(*Buffer)->Preferences.CodePage = V1aFixed.CodePage;
|
||
|
||
|
||
|
||
//
|
||
// Read the UserComment field from the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_USER_COMMENT,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Preferences.UserComment) // Body
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Preferences.UserComment.Buffer);
|
||
|
||
//
|
||
// This field isn't used, but make sure RPC doesn't
|
||
// choke on it.
|
||
//
|
||
|
||
(*Buffer)->Preferences.Reserved1.Length = 0;
|
||
(*Buffer)->Preferences.Reserved1.MaximumLength = 0;
|
||
(*Buffer)->Preferences.Reserved1.Buffer = NULL;
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserParametersInformation:
|
||
|
||
|
||
//
|
||
// Read the Parameters field from the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PARAMETERS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Parameters.Parameters)
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Parameters.Parameters.Buffer);
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserLogonInformation:
|
||
|
||
NoErrorsYet = TRUE;
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
(*Buffer)->Logon.UserId = V1aFixed.UserId;
|
||
(*Buffer)->Logon.PrimaryGroupId = V1aFixed.PrimaryGroupId;
|
||
|
||
(*Buffer)->Logon.LastLogon =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
|
||
|
||
(*Buffer)->Logon.LastLogoff =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
|
||
|
||
(*Buffer)->Logon.BadPasswordCount = V1aFixed.BadPasswordCount;
|
||
|
||
(*Buffer)->Logon.PasswordLastSet =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.PasswordLastSet);
|
||
|
||
TempTime = SampAddDeltaTime(
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MinPasswordAge );
|
||
|
||
(*Buffer)->Logon.PasswordCanChange =
|
||
*((POLD_LARGE_INTEGER)&TempTime);
|
||
|
||
|
||
TempTime = SampGetPasswordMustChange(
|
||
V1aFixed.UserAccountControl,
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MaxPasswordAge);
|
||
|
||
(*Buffer)->Logon.PasswordMustChange =
|
||
*((POLD_LARGE_INTEGER)&TempTime);
|
||
|
||
|
||
(*Buffer)->Logon.LogonCount = V1aFixed.LogonCount;
|
||
(*Buffer)->Logon.UserAccountControl = V1aFixed.UserAccountControl;
|
||
|
||
|
||
//
|
||
// If there is no password on the account then
|
||
// modify the password can/must change times
|
||
// so that the password never expires and can
|
||
// be changed immediately.
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
AccountContext,
|
||
&LmOwfPassword,
|
||
&LmPasswordNonNull,
|
||
&NtOwfPassword,
|
||
&NtPasswordPresent,
|
||
&NtPasswordNonNull
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ( !NtPasswordNonNull && !LmPasswordNonNull ) {
|
||
|
||
//
|
||
// The password is NULL.
|
||
// It can be changed immediately.
|
||
//
|
||
|
||
(*Buffer)->Logon.PasswordCanChange =
|
||
*((POLD_LARGE_INTEGER)&SampHasNeverTime);
|
||
|
||
}
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.UserName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.UserName.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.FullName)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.FullName.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.HomeDirectory)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.HomeDirectory.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.HomeDirectoryDrive)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.HomeDirectoryDrive.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.ScriptPath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.ScriptPath.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.ProfilePath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.ProfilePath.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Logon.WorkStations)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.WorkStations.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Now get the logon hours
|
||
//
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampRetrieveUserLogonHours(
|
||
AccountContext,
|
||
(PLOGON_HOURS)&((*Buffer)->Logon.LogonHours)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ((*Buffer)->Logon.LogonHours.LogonHours != NULL) {
|
||
|
||
RegisterBuffer((*Buffer)->Logon.LogonHours.LogonHours);
|
||
}
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserLogonHoursInformation:
|
||
|
||
NtStatus = SampRetrieveUserLogonHours(
|
||
AccountContext,
|
||
(PLOGON_HOURS)&((*Buffer)->LogonHours.LogonHours)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ((*Buffer)->LogonHours.LogonHours.LogonHours != NULL) {
|
||
|
||
RegisterBuffer((*Buffer)->LogonHours.LogonHours.LogonHours);
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserHomeInformation:
|
||
|
||
NoErrorsYet = TRUE;
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Home.HomeDirectory)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Home.HomeDirectory.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
if (NoErrorsYet == TRUE) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Home.HomeDirectoryDrive)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Home.HomeDirectoryDrive.Buffer);
|
||
|
||
} else {
|
||
NoErrorsYet = FALSE;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserScriptInformation:
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Script.ScriptPath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Script.ScriptPath.Buffer);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserProfileInformation:
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->Profile.ProfilePath)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->Profile.ProfilePath.Buffer);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserWorkStationsInformation:
|
||
|
||
//
|
||
// Get copies of the strings we must retrieve from
|
||
// the registry.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
TRUE, // Make copy
|
||
(PUNICODE_STRING)&((*Buffer)->WorkStations.WorkStations)
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
RegisterBuffer((*Buffer)->WorkStations.WorkStations.Buffer);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserControlInformation:
|
||
|
||
(*Buffer)->Control.UserAccountControl = V1aFixed.UserAccountControl;
|
||
break;
|
||
|
||
|
||
case UserExpiresInformation:
|
||
|
||
(*Buffer)->Expires.AccountExpires = V1aFixed.AccountExpires;
|
||
|
||
break;
|
||
|
||
|
||
case UserInternal1Information:
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
//
|
||
// PasswordExpired is a 'write only' flag.
|
||
// We always return FALSE on read.
|
||
//
|
||
|
||
(*Buffer)->Internal1.PasswordExpired = FALSE;
|
||
|
||
//
|
||
// Retrieve the OWF passwords.
|
||
// Since this is a trusted client, we don't need to
|
||
// reencrypt the OWFpasswords we return - so we stuff
|
||
// the OWFs into the structure that holds encryptedOWFs.
|
||
//
|
||
|
||
ASSERT( ENCRYPTED_LM_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH );
|
||
ASSERT( ENCRYPTED_NT_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH );
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
AccountContext,
|
||
(PLM_OWF_PASSWORD)&(*Buffer)->Internal1.
|
||
EncryptedLmOwfPassword,
|
||
&(*Buffer)->Internal1.
|
||
LmPasswordPresent,
|
||
(PNT_OWF_PASSWORD)&(*Buffer)->Internal1.
|
||
EncryptedNtOwfPassword,
|
||
&NtPasswordPresent,
|
||
&(*Buffer)->Internal1.NtPasswordPresent // Return the Non-NULL flag here
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// This information is only queryable by trusted
|
||
// clients.
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_INFO_CLASS;
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserInternal2Information:
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
(*Buffer)->Internal2.LastLogon =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogon);
|
||
|
||
(*Buffer)->Internal2.LastLogoff =
|
||
*((POLD_LARGE_INTEGER)&V1aFixed.LastLogoff);
|
||
|
||
(*Buffer)->Internal2.BadPasswordCount = V1aFixed.BadPasswordCount;
|
||
(*Buffer)->Internal2.LogonCount = V1aFixed.LogonCount;
|
||
|
||
} else {
|
||
|
||
//
|
||
// This information is only queryable by trusted
|
||
// clients.
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_INFO_CLASS;
|
||
}
|
||
|
||
break;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// De-reference the object, discarding changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
|
||
}
|
||
|
||
//
|
||
// Free the read lock
|
||
//
|
||
|
||
SampReleaseReadLock();
|
||
|
||
|
||
|
||
//
|
||
// If we didn't succeed, free any allocated memory
|
||
//
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
for ( i=0; i<AllocatedBufferCount ; i++ ) {
|
||
MIDL_user_free( AllocatedBuffer[i] );
|
||
}
|
||
|
||
(*Buffer) = NULL;
|
||
}
|
||
|
||
return(NtStatus);
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampIsUserAccountControlValid(
|
||
IN PSAMP_OBJECT Context,
|
||
IN ULONG UserAccountControl
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks a UserAccountControl field to make sure that
|
||
the bits set make sense.
|
||
|
||
NOTE: if the set operation is also setting passwords, it must set the
|
||
passwords BEFORE calling this routine!
|
||
|
||
|
||
Parameters:
|
||
|
||
Context - the context of the account being changed.
|
||
|
||
UserAccountControl - the field that is about to be set.
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The UserAccountControl field is valid.
|
||
|
||
STATUS_SPECIAL_ACCOUNT - The administrator account can't be disabled.
|
||
|
||
STATUS_INVALID_PARAMETER - an undefined bit is set, or more than one
|
||
account type bit is set.
|
||
|
||
STATUS_INVALID_PARAMETER_MIX - USER_PASSWORD_NOT_REQUIRED has been
|
||
turned off, but there isn't a bonafide password on the account.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS NtStatus = STATUS_SUCCESS;
|
||
|
||
SAMTRACE("SampIsUserAccountControlValid");
|
||
|
||
#if DBG
|
||
//
|
||
// Make sure that undefined bits aren't set.
|
||
//
|
||
|
||
if ( ( UserAccountControl & 0xfffff800 ) != 0 ) {
|
||
|
||
DbgPrint("SAM: Setting undefined AccountControl flag(s): 0x%lx for user %d\n",
|
||
UserAccountControl, Context->TypeBody.User.Rid);
|
||
}
|
||
#endif //DBG
|
||
|
||
//
|
||
// Make sure that the administrator isn't being disabled.
|
||
//
|
||
|
||
if ( UserAccountControl & USER_ACCOUNT_DISABLED ) {
|
||
|
||
if ( Context->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN ) {
|
||
|
||
return( STATUS_SPECIAL_ACCOUNT );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Make sure that exactly one of the account type bits is set.
|
||
//
|
||
|
||
switch ( UserAccountControl & USER_ACCOUNT_TYPE_MASK ) {
|
||
|
||
case USER_TEMP_DUPLICATE_ACCOUNT:
|
||
case USER_NORMAL_ACCOUNT:
|
||
case USER_SERVER_TRUST_ACCOUNT:
|
||
case USER_WORKSTATION_TRUST_ACCOUNT:
|
||
case USER_INTERDOMAIN_TRUST_ACCOUNT:
|
||
|
||
break;
|
||
|
||
default:
|
||
|
||
return( STATUS_INVALID_PARAMETER );
|
||
}
|
||
|
||
//
|
||
// If USER_PASSWORD_NOT_REQUIRED is turned off, make sure that there
|
||
// already is a password. Note that this requires that the password
|
||
// be set before calling this routine, if both are being done at once.
|
||
//
|
||
|
||
if ( ( UserAccountControl & USER_PASSWORD_NOT_REQUIRED ) == 0 ) {
|
||
|
||
NT_OWF_PASSWORD NtOwfPassword;
|
||
LM_OWF_PASSWORD LmOwfPassword;
|
||
BOOLEAN LmPasswordNonNull, NtPasswordPresent, NtPasswordNonNull;
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
Context,
|
||
&LmOwfPassword,
|
||
&LmPasswordNonNull,
|
||
&NtOwfPassword,
|
||
&NtPasswordPresent,
|
||
&NtPasswordNonNull
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) &&
|
||
( (!LmPasswordNonNull) && (!NtPasswordNonNull) ) ) {
|
||
|
||
}
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampCalculateLmPassword(
|
||
IN PUNICODE_STRING NtPassword,
|
||
OUT PCHAR *LmPasswordBuffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service converts an NT password into a LM password.
|
||
|
||
Parameters:
|
||
|
||
NtPassword - The Nt password to be converted.
|
||
|
||
LmPasswordBuffer - On successful return, points at the LM password
|
||
The buffer should be freed using MIDL_user_free
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - LMPassword contains the LM version of the password.
|
||
|
||
STATUS_NULL_LM_PASSWORD - The password is too complex to be represented
|
||
by a LM password. The LM password returned is a NULL string.
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
#define LM_BUFFER_LENGTH (LM20_PWLEN + 1)
|
||
|
||
NTSTATUS NtStatus;
|
||
ANSI_STRING LmPassword;
|
||
|
||
SAMTRACE("SampCalculateLMPassword");
|
||
|
||
//
|
||
// Prepare for failure
|
||
//
|
||
|
||
*LmPasswordBuffer = NULL;
|
||
|
||
|
||
//
|
||
// Compute the Ansi version to the Unicode password.
|
||
//
|
||
// The Ansi version of the Cleartext password is at most 14 bytes long,
|
||
// exists in a trailing zero filled 15 byte buffer,
|
||
// is uppercased.
|
||
//
|
||
|
||
LmPassword.Buffer = MIDL_user_allocate(LM_BUFFER_LENGTH);
|
||
if (LmPassword.Buffer == NULL) {
|
||
return(STATUS_INSUFFICIENT_RESOURCES);
|
||
}
|
||
|
||
LmPassword.MaximumLength = LmPassword.Length = LM_BUFFER_LENGTH;
|
||
RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
|
||
|
||
NtStatus = RtlUpcaseUnicodeStringToOemString( &LmPassword, NtPassword, FALSE );
|
||
|
||
|
||
if ( !NT_SUCCESS(NtStatus) ) {
|
||
|
||
//
|
||
// The password is longer than the max LM password length
|
||
//
|
||
|
||
NtStatus = STATUS_NULL_LM_PASSWORD; // Informational return code
|
||
RtlZeroMemory( LmPassword.Buffer, LM_BUFFER_LENGTH );
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Return a pointer to the allocated LM password
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
*LmPasswordBuffer = LmPassword.Buffer;
|
||
|
||
} else {
|
||
|
||
MIDL_user_free(LmPassword.Buffer);
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampCalculateLmAndNtOwfPasswords(
|
||
IN PUNICODE_STRING ClearNtPassword,
|
||
OUT PBOOLEAN LmPasswordPresent,
|
||
OUT PLM_OWF_PASSWORD LmOwfPassword,
|
||
OUT PNT_OWF_PASSWORD NtOwfPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine calculates the LM and NT OWF passwordw from the cleartext
|
||
password.
|
||
|
||
Arguments:
|
||
|
||
ClearNtPassword - A Cleartext unicode password
|
||
|
||
LmPasswordPresent - indicates whether an LM OWF password could be
|
||
calculated
|
||
|
||
LmOwfPassword - Gets the LM OWF hash of the cleartext password.
|
||
|
||
NtOwfPassword - Gets the NT OWF hash of the cleartext password.
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
PCHAR LmPassword = NULL;
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampCalculateLmAndNtOwfPassword");
|
||
|
||
//
|
||
// First compute the LM password. If the password is too complex
|
||
// this may not be possible.
|
||
//
|
||
|
||
|
||
NtStatus = SampCalculateLmPassword(
|
||
ClearNtPassword,
|
||
&LmPassword
|
||
);
|
||
|
||
//
|
||
// If it faield because the LM password could not be calculated, that
|
||
// is o.k.
|
||
//
|
||
|
||
if (NtStatus != STATUS_SUCCESS) {
|
||
|
||
if (NtStatus == STATUS_NULL_LM_PASSWORD) {
|
||
*LmPasswordPresent = FALSE;
|
||
NtStatus = STATUS_SUCCESS;
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Now compute the OWF passwords
|
||
//
|
||
|
||
*LmPasswordPresent = TRUE;
|
||
|
||
NtStatus = RtlCalculateLmOwfPassword(
|
||
LmPassword,
|
||
LmOwfPassword
|
||
);
|
||
|
||
}
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlCalculateNtOwfPassword(
|
||
ClearNtPassword,
|
||
NtOwfPassword
|
||
);
|
||
}
|
||
|
||
if (LmPassword != NULL) {
|
||
MIDL_user_free(LmPassword);
|
||
}
|
||
|
||
return(NtStatus);
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampDecryptPasswordWithKey(
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
|
||
IN PBYTE Key,
|
||
IN ULONG KeySize,
|
||
IN BOOLEAN UnicodePasswords,
|
||
OUT PUNICODE_STRING ClearNtPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
struct RC4_KEYSTRUCT Rc4Key;
|
||
NTSTATUS NtStatus;
|
||
OEM_STRING OemPassword;
|
||
PSAMPR_USER_PASSWORD Password = (PSAMPR_USER_PASSWORD) EncryptedPassword;
|
||
|
||
SAMTRACE("SampDecryptPasswordWithKey");
|
||
|
||
//
|
||
// Decrypt the key.
|
||
//
|
||
|
||
rc4_key(
|
||
&Rc4Key,
|
||
KeySize,
|
||
Key
|
||
);
|
||
|
||
rc4(&Rc4Key,
|
||
sizeof(SAMPR_ENCRYPTED_USER_PASSWORD),
|
||
(PUCHAR) Password
|
||
);
|
||
|
||
//
|
||
// Check that the length is valid. If it isn't bail here.
|
||
//
|
||
|
||
if (Password->Length > SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) {
|
||
return(STATUS_WRONG_PASSWORD);
|
||
}
|
||
|
||
|
||
//
|
||
// Convert the password into a unicode string.
|
||
//
|
||
|
||
if (UnicodePasswords) {
|
||
NtStatus = SampInitUnicodeString(
|
||
ClearNtPassword,
|
||
(USHORT) (Password->Length + sizeof(WCHAR))
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
ClearNtPassword->Length = (USHORT) Password->Length;
|
||
|
||
RtlCopyMemory(
|
||
ClearNtPassword->Buffer,
|
||
((PCHAR) Password->Buffer) +
|
||
(SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
|
||
Password->Length,
|
||
Password->Length
|
||
);
|
||
NtStatus = STATUS_SUCCESS;
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// The password is in the OEM character set. Convert it to Unicode
|
||
// and then copy it into the ClearNtPassword structure.
|
||
//
|
||
|
||
OemPassword.Buffer = ((PCHAR)Password->Buffer) +
|
||
(SAM_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
|
||
Password->Length;
|
||
|
||
OemPassword.Length = (USHORT) Password->Length;
|
||
|
||
|
||
NtStatus = RtlOemStringToUnicodeString(
|
||
ClearNtPassword,
|
||
&OemPassword,
|
||
TRUE // allocate destination
|
||
);
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SampDecryptPasswordWithSessionKey(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
|
||
OUT PUNICODE_STRING ClearNtPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
USER_SESSION_KEY UserSessionKey;
|
||
|
||
SAMTRACE("SampDecryptPasswordWithSessionKey");
|
||
|
||
NtStatus = RtlGetUserSessionKeyServer(
|
||
(RPC_BINDING_HANDLE)UserHandle,
|
||
&UserSessionKey
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
return(SampDecryptPasswordWithKey(
|
||
EncryptedPassword,
|
||
(PUCHAR) &UserSessionKey,
|
||
sizeof(USER_SESSION_KEY),
|
||
TRUE,
|
||
ClearNtPassword
|
||
) );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampCheckPasswordRestrictions(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
PUNICODE_STRING NewNtPassword
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service is called to make sure that the password presented meets
|
||
our quality requirements.
|
||
|
||
|
||
Arguments:
|
||
|
||
UserHandle - Handle to a user.
|
||
|
||
NewNtPassword - Pointer to the UNICODE_STRING containing the new
|
||
password.
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - The password is acceptable.
|
||
|
||
STATUS_PASSWORD_RESTRICTION - The password is too short, or is not
|
||
complex enough, etc.
|
||
|
||
STATUS_INVALID_RESOURCES - There was not enough memory to do the
|
||
password checking.
|
||
|
||
|
||
--*/
|
||
{
|
||
USER_DOMAIN_PASSWORD_INFORMATION PasswordInformation;
|
||
NTSTATUS NtStatus;
|
||
PWORD CharInfoBuffer = NULL;
|
||
ULONG i;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
PSAMP_OBJECT AccountContext = (PSAMP_OBJECT) UserHandle;
|
||
|
||
|
||
SAMTRACE("SampCheckPasswordRestrictions");
|
||
|
||
|
||
//
|
||
// Query information domain to get password length and
|
||
// complexity requirements.
|
||
//
|
||
|
||
//
|
||
// BUGBUG: this code was copied from SamrGetUserDomainPasswordInformation
|
||
//
|
||
|
||
//
|
||
// When the user was opened, we checked to see if the domain handle
|
||
// allowed access to the domain password information. Check that here.
|
||
//
|
||
|
||
if ( !( AccountContext->TypeBody.User.DomainPasswordInformationAccessible ) ) {
|
||
|
||
NtStatus = STATUS_ACCESS_DENIED;
|
||
|
||
} else {
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
//
|
||
// If the user account is a machine account,
|
||
// then restrictions are generally not enforced.
|
||
// This is so that simple initial passwords can be
|
||
// established. IT IS EXPECTED THAT COMPLEX PASSWORDS,
|
||
// WHICH MEET THE MOST STRINGENT RESTRICTIONS, WILL BE
|
||
// AUTOMATICALLY ESTABLISHED AND MAINTAINED ONCE THE MACHINE
|
||
// JOINS THE DOMAIN. It is the UI's responsibility to
|
||
// maintain this level of complexity.
|
||
//
|
||
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
if ( (V1aFixed.UserAccountControl &
|
||
(USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
|
||
!= 0 ) {
|
||
|
||
PasswordInformation.MinPasswordLength = 0;
|
||
PasswordInformation.PasswordProperties = 0;
|
||
} else {
|
||
|
||
PasswordInformation.MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
|
||
PasswordInformation.PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
if ( (USHORT)( NewNtPassword->Length / sizeof(WCHAR) ) < PasswordInformation.MinPasswordLength ) {
|
||
|
||
NtStatus = STATUS_PASSWORD_RESTRICTION;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Check password complexity.
|
||
//
|
||
|
||
if ( PasswordInformation.PasswordProperties & DOMAIN_PASSWORD_COMPLEX ) {
|
||
|
||
//
|
||
// Make sure that the password meets our requirements for
|
||
// complexity. If it's got an odd byte count, it's
|
||
// obviously not a hand-entered UNICODE string so we'll
|
||
// consider it complex by default.
|
||
//
|
||
|
||
if ( !( NewNtPassword->Length & 1 ) ) {
|
||
|
||
USHORT NumsInPassword = 0;
|
||
USHORT UppersInPassword = 0;
|
||
USHORT LowersInPassword = 0;
|
||
USHORT OthersInPassword = 0;
|
||
|
||
CharInfoBuffer = MIDL_user_allocate( NewNtPassword->Length );
|
||
|
||
if ( CharInfoBuffer == NULL ) {
|
||
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
|
||
} else {
|
||
|
||
if ( GetStringTypeW(
|
||
CT_CTYPE1,
|
||
NewNtPassword->Buffer,
|
||
NewNtPassword->Length / 2,
|
||
CharInfoBuffer ) ) {
|
||
|
||
for ( i = 0; i < (ULONG)( NewNtPassword->Length / sizeof(WCHAR) ); i++ ) {
|
||
|
||
if ( CharInfoBuffer[i] & C1_DIGIT ) {
|
||
|
||
NumsInPassword = 1;
|
||
}
|
||
|
||
if ( CharInfoBuffer[i] & C1_UPPER ) {
|
||
|
||
UppersInPassword = 1;
|
||
}
|
||
|
||
if ( CharInfoBuffer[i] & C1_LOWER ) {
|
||
|
||
LowersInPassword = 1;
|
||
}
|
||
|
||
if ( !( CharInfoBuffer[i] & ( C1_ALPHA | C1_DIGIT ) ) ) {
|
||
|
||
//
|
||
// Having any "other" characters is
|
||
// sufficient to make the password
|
||
// complex.
|
||
//
|
||
|
||
OthersInPassword = 2;
|
||
}
|
||
}
|
||
|
||
if ( ( NumsInPassword + UppersInPassword +
|
||
LowersInPassword + OthersInPassword ) < 2 ) {
|
||
|
||
//
|
||
// It didn't have at least two of the four
|
||
// types of characters, so it's not complex
|
||
// enough.
|
||
//
|
||
|
||
NtStatus = STATUS_PASSWORD_RESTRICTION;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// GetStringTypeW failed; dunno why. Perhaps the
|
||
// password is binary. Consider it complex by
|
||
// default.
|
||
//
|
||
|
||
NtStatus = STATUS_SUCCESS;
|
||
}
|
||
|
||
MIDL_user_free( CharInfoBuffer );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrSetInformationUser2(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN USER_INFORMATION_CLASS UserInformationClass,
|
||
IN PSAMPR_USER_INFO_BUFFER Buffer
|
||
)
|
||
{
|
||
//
|
||
// This is a thin veil to SamrSetInformationUser().
|
||
// This is needed so that new-release systems can call
|
||
// this routine without the danger of passing an info
|
||
// level that release 1.0 systems didn't understand.
|
||
//
|
||
|
||
|
||
return( SamrSetInformationUser(
|
||
UserHandle,
|
||
UserInformationClass,
|
||
Buffer
|
||
) );
|
||
}
|
||
|
||
NTSTATUS
|
||
SamrSetInformationUser(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN USER_INFORMATION_CLASS UserInformationClass,
|
||
IN PSAMPR_USER_INFO_BUFFER Buffer
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
This API modifies information in a user record. The data modified
|
||
is determined by the UserInformationClass parameter.
|
||
In general, a user may call GetInformation with class
|
||
UserLogonInformation, but may only call SetInformation with class
|
||
UserPreferencesInformation. Access type USER_WRITE_ACCOUNT allows
|
||
changes to be made to all fields.
|
||
|
||
NOTE: If the password is set to a new password then the password-
|
||
set timestamp is reset as well.
|
||
|
||
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
UserInformationClass - Class of information provided. The
|
||
accesses required for each class is shown below:
|
||
|
||
Info Level Required Access Type
|
||
----------------------- ------------------------
|
||
UserGeneralInformation USER_WRITE_ACCOUNT and
|
||
USER_WRITE_PREFERENCES
|
||
|
||
UserPreferencesInformation USER_WRITE_PREFERENCES
|
||
|
||
UserParametersInformation USER_WRITE_ACCOUNT
|
||
|
||
UserLogonInformation (Can't set)
|
||
|
||
UserLogonHoursInformation USER_WRITE_ACCOUNT
|
||
|
||
UserAccountInformation (Can't set)
|
||
|
||
UserNameInformation USER_WRITE_ACCOUNT
|
||
UserAccountNameInformation USER_WRITE_ACCOUNT
|
||
UserFullNameInformation USER_WRITE_ACCOUNT
|
||
UserPrimaryGroupInformation USER_WRITE_ACCOUNT
|
||
UserHomeInformation USER_WRITE_ACCOUNT
|
||
UserScriptInformation USER_WRITE_ACCOUNT
|
||
UserProfileInformation USER_WRITE_ACCOUNT
|
||
UserAdminCommentInformation USER_WRITE_ACCOUNT
|
||
UserWorkStationsInformation USER_WRITE_ACCOUNT
|
||
UserSetPasswordInformation USER_FORCE_PASSWORD_CHANGE
|
||
UserControlInformation USER_WRITE_ACCOUNT
|
||
UserExpiresInformation USER_WRITE_ACCOUNT
|
||
|
||
UserInternal1Information USER_FORCE_PASSWORD_CHANGE
|
||
UserInternal2Information (Trusted client only)
|
||
UserInternal3Information (Trusted client only) -
|
||
UserInternal4Information Similar to All Information
|
||
UserInternal5Information Similar to SetPassword
|
||
UserAllInformation Will set fields that are
|
||
requested by caller. Access
|
||
to fields to be set must be
|
||
held as defined above.
|
||
|
||
|
||
Buffer - Buffer containing a user info struct.
|
||
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
||
|
||
STATUS_INVALID_INFO_CLASS - The class provided was invalid.
|
||
|
||
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
||
correct state (disabled or enabled) to perform the requested
|
||
operation. The domain server must be enabled for this
|
||
operation
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
||
incorrect role (primary or backup) to perform the requested
|
||
operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS NtStatus,
|
||
IgnoreStatus;
|
||
|
||
PSAMP_OBJECT AccountContext = (PSAMP_OBJECT) UserHandle;
|
||
|
||
PUSER_ALL_INFORMATION All;
|
||
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
|
||
ACCESS_MASK DesiredAccess;
|
||
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
|
||
UNICODE_STRING OldAccountName,
|
||
ApiList,
|
||
NewAdminComment,
|
||
NewAccountName,
|
||
NewFullName;
|
||
|
||
NT_OWF_PASSWORD NtOwfPassword;
|
||
|
||
LM_OWF_PASSWORD LmOwfPassword;
|
||
|
||
USER_SESSION_KEY UserSessionKey;
|
||
|
||
BOOLEAN LmPresent;
|
||
BOOLEAN NtPresent;
|
||
BOOLEAN PasswordExpired;
|
||
|
||
ULONG ObjectRid,
|
||
OldUserAccountControl,
|
||
DomainIndex;
|
||
|
||
BOOLEAN UserAccountControlChanged = FALSE,
|
||
MustUpdateAccountDisplay = FALSE,
|
||
MustQueryV1aFixed = FALSE,
|
||
ReplicateImmediately = FALSE,
|
||
TellNetlogon = TRUE,
|
||
AccountLockedOut,
|
||
CurrentlyLocked,
|
||
Unlocking;
|
||
|
||
SECURITY_DB_DELTA_TYPE DeltaType = SecurityDbChange;
|
||
UNICODE_STRING ClearTextPassword;
|
||
UNICODE_STRING AccountName;
|
||
ULONG UserRid = 0;
|
||
|
||
#if DBG
|
||
|
||
TIME_FIELDS
|
||
T1;
|
||
|
||
#endif //DBG
|
||
|
||
SAMTRACE("SamrSetInformationUser");
|
||
|
||
//
|
||
// Initialization.
|
||
//
|
||
|
||
ClearTextPassword.Buffer = NULL;
|
||
ClearTextPassword.Length = 0;
|
||
AccountName.Buffer = NULL;
|
||
|
||
//
|
||
// Make sure we understand what RPC is doing for (to) us.
|
||
//
|
||
|
||
if (Buffer == NULL) {
|
||
return(STATUS_INVALID_PARAMETER);
|
||
}
|
||
|
||
//
|
||
// Reset any strings that we'll be freeing in clean-up code
|
||
//
|
||
|
||
RtlInitUnicodeString(&OldAccountName, NULL);
|
||
RtlInitUnicodeString(&NewAccountName, NULL);
|
||
RtlInitUnicodeString(&NewFullName, NULL);
|
||
RtlInitUnicodeString(&NewAdminComment, NULL);
|
||
|
||
|
||
//
|
||
// Set the desired access based upon the Info class
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserPreferencesInformation:
|
||
|
||
DesiredAccess = USER_WRITE_PREFERENCES;
|
||
break;
|
||
|
||
case UserParametersInformation:
|
||
case UserLogonHoursInformation:
|
||
case UserNameInformation:
|
||
case UserAccountNameInformation:
|
||
case UserFullNameInformation:
|
||
case UserPrimaryGroupInformation:
|
||
case UserHomeInformation:
|
||
case UserScriptInformation:
|
||
case UserProfileInformation:
|
||
case UserAdminCommentInformation:
|
||
case UserWorkStationsInformation:
|
||
case UserControlInformation:
|
||
case UserExpiresInformation:
|
||
|
||
DesiredAccess = USER_WRITE_ACCOUNT;
|
||
break;
|
||
|
||
case UserSetPasswordInformation:
|
||
case UserInternal1Information:
|
||
case UserInternal5Information:
|
||
|
||
DeltaType = SecurityDbChangePassword;
|
||
DesiredAccess = USER_FORCE_PASSWORD_CHANGE;
|
||
break;
|
||
|
||
|
||
|
||
case UserAllInformation:
|
||
case UserInternal3Information:
|
||
case UserInternal4Information:
|
||
|
||
//////////////////////////////////////////////////////////////
|
||
// //
|
||
// !!!! WARNING !!!! //
|
||
// //
|
||
// Be warned that the buffer structure for //
|
||
// UserInternal3/4Information MUST begin with the same //
|
||
// structure as UserAllInformation. //
|
||
// //
|
||
//////////////////////////////////////////////////////////////
|
||
|
||
DesiredAccess = 0;
|
||
|
||
All = (PUSER_ALL_INFORMATION)Buffer;
|
||
|
||
if ( ( All->WhichFields == 0 ) ||
|
||
( All->WhichFields & USER_ALL_WRITE_CANT_MASK ) ) {
|
||
|
||
//
|
||
// Passed in something silly (no fields to set), or is
|
||
// trying to set fields that can't be set.
|
||
//
|
||
|
||
return STATUS_INVALID_PARAMETER;
|
||
}
|
||
|
||
//
|
||
// If the user is the special account Administrator, return an
|
||
// error if trying to set the expiry information, except to the value
|
||
// that means that the account never expires.
|
||
//
|
||
|
||
if ( (All->WhichFields & USER_ALL_ACCOUNTEXPIRES) &&
|
||
(!(AccountContext->TrustedClient)) &&
|
||
( AccountContext->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN )) {
|
||
|
||
LARGE_INTEGER AccountNeverExpires, Temp;
|
||
|
||
AccountNeverExpires = RtlConvertUlongToLargeInteger(
|
||
SAMP_ACCOUNT_NEVER_EXPIRES
|
||
);
|
||
|
||
OLD_TO_NEW_LARGE_INTEGER(All->AccountExpires, Temp);
|
||
|
||
if (!( Temp.QuadPart == AccountNeverExpires.QuadPart)) {
|
||
|
||
return( STATUS_SPECIAL_ACCOUNT );
|
||
}
|
||
}
|
||
|
||
//
|
||
// If the caller is trying to set trusted values, assume the
|
||
// caller is trusted, leave DesiredAccess = 0, and proceed.
|
||
// We'll check to make sure caller is trusted later.
|
||
//
|
||
|
||
if ( !(All->WhichFields & USER_ALL_WRITE_TRUSTED_MASK) ) {
|
||
|
||
//
|
||
// Set desired access based on which fields the caller is
|
||
// trying to change.
|
||
//
|
||
|
||
if ( All->WhichFields & USER_ALL_WRITE_ACCOUNT_MASK ) {
|
||
|
||
DesiredAccess |= USER_WRITE_ACCOUNT;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_WRITE_PREFERENCES_MASK ) {
|
||
|
||
DesiredAccess |= USER_WRITE_PREFERENCES;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_WRITE_FORCE_PASSWORD_CHANGE_MASK ) {
|
||
|
||
DesiredAccess |= USER_FORCE_PASSWORD_CHANGE;
|
||
}
|
||
|
||
ASSERT( DesiredAccess != 0 );
|
||
}
|
||
|
||
break;
|
||
|
||
case UserInternal2Information:
|
||
|
||
//
|
||
// These levels are only setable by trusted clients. The code
|
||
// below will check AccountContext->TrustedClient after calling
|
||
// SampLookupContext, and only set the data if it is TRUE.
|
||
//
|
||
|
||
DesiredAccess = (ACCESS_MASK)0; // trusted client; no need to verify
|
||
break;
|
||
|
||
case UserGeneralInformation:
|
||
case UserAccountInformation:
|
||
case UserLogonInformation:
|
||
default:
|
||
|
||
return(STATUS_INVALID_INFO_CLASS);
|
||
|
||
} // end_switch
|
||
|
||
|
||
|
||
//
|
||
// Grab the lock
|
||
//
|
||
|
||
NtStatus = SampAcquireWriteLock();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
ObjectRid = AccountContext->TypeBody.User.Rid;
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
DesiredAccess,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Get a pointer to the domain this object is in.
|
||
// This is used for auditing.
|
||
//
|
||
|
||
DomainIndex = AccountContext->DomainIndex;
|
||
Domain = &SampDefinedDomains[ DomainIndex ];
|
||
|
||
//
|
||
// Get the user's rid. This is used for notifying other
|
||
// packages of a password change.
|
||
//
|
||
|
||
UserRid = AccountContext->TypeBody.User.Rid;
|
||
|
||
|
||
//
|
||
// If this information level contains reversibly encrypted passwords
|
||
// it is not allowed if the DOMAIN_PASSWORD_NO_CLEAR_CHANGE bit is
|
||
// set. If that happens, return an error indicating that
|
||
// the older information level should be used.
|
||
//
|
||
|
||
if ((UserInformationClass == UserInternal4Information) ||
|
||
(UserInformationClass == UserInternal5Information)) {
|
||
|
||
if (Domain->UnmodifiedFixed.PasswordProperties &
|
||
DOMAIN_PASSWORD_NO_CLEAR_CHANGE) {
|
||
|
||
NtStatus = RPC_NT_INVALID_TAG;
|
||
}
|
||
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If the information level requires, retrieve the V1_FIXED
|
||
// record from the registry. We need to fetch V1_FIXED if we
|
||
// are going to change it or if we need the AccountControl
|
||
// flags for display cache updating.
|
||
//
|
||
// The following information levels change data that is in the cached
|
||
// display list.
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserAllInformation:
|
||
case UserInternal3Information:
|
||
case UserInternal4Information:
|
||
|
||
if ( ( All->WhichFields &
|
||
( USER_ALL_USERNAME | USER_ALL_FULLNAME |
|
||
USER_ALL_ADMINCOMMENT | USER_ALL_USERACCOUNTCONTROL ) )
|
||
== 0 ) {
|
||
|
||
//
|
||
// We're not changing any of the fields in the display
|
||
// info, we don't update the account display.
|
||
//
|
||
|
||
break;
|
||
}
|
||
|
||
case UserControlInformation:
|
||
case UserNameInformation:
|
||
case UserAccountNameInformation:
|
||
case UserFullNameInformation:
|
||
case UserAdminCommentInformation:
|
||
|
||
MustUpdateAccountDisplay = TRUE;
|
||
}
|
||
|
||
//
|
||
// These levels involve updating the V1aFixed structure
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserAllInformation:
|
||
case UserInternal3Information:
|
||
case UserInternal4Information:
|
||
|
||
//
|
||
// Earlier, we might have just trusted that the caller
|
||
// was a trusted client. Check it out here.
|
||
//
|
||
|
||
if ( ( DesiredAccess == 0 ) &&
|
||
( !AccountContext->TrustedClient ) ) {
|
||
|
||
NtStatus = STATUS_ACCESS_DENIED;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Otherwise fall through
|
||
//
|
||
|
||
case UserPreferencesInformation:
|
||
case UserPrimaryGroupInformation:
|
||
case UserControlInformation:
|
||
case UserExpiresInformation:
|
||
case UserSetPasswordInformation:
|
||
case UserInternal1Information:
|
||
case UserInternal2Information:
|
||
case UserInternal5Information:
|
||
|
||
MustQueryV1aFixed = TRUE;
|
||
|
||
break;
|
||
|
||
default:
|
||
|
||
NtStatus = STATUS_SUCCESS;
|
||
|
||
} // end_switch
|
||
|
||
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) &&
|
||
( MustQueryV1aFixed || MustUpdateAccountDisplay ) ) {
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Store away the old account control flags for cache update
|
||
//
|
||
|
||
OldUserAccountControl = V1aFixed.UserAccountControl;
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// case on the type information requested
|
||
//
|
||
|
||
switch (UserInformationClass) {
|
||
|
||
case UserAllInformation:
|
||
case UserInternal3Information:
|
||
case UserInternal4Information:
|
||
|
||
//
|
||
// Set the string data
|
||
//
|
||
|
||
if ( All->WhichFields & USER_ALL_WORKSTATIONS ) {
|
||
|
||
if ( !AccountContext->TrustedClient ) {
|
||
|
||
//
|
||
// Convert the workstation list, which is given
|
||
// to us in UI/Service format, to API list format
|
||
// before storing it. Note that we don't do this
|
||
// for trusted clients, since they're just
|
||
// propogating data that has already been
|
||
// converted.
|
||
//
|
||
|
||
NtStatus = RtlConvertUiListToApiList(
|
||
&(All->WorkStations),
|
||
&ApiList,
|
||
FALSE );
|
||
} else {
|
||
ApiList = All->WorkStations;
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
&ApiList
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_USERNAME ) ) {
|
||
|
||
NtStatus = SampChangeUserAccountName(
|
||
AccountContext,
|
||
&(All->UserName),
|
||
&OldAccountName
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
OldAccountName.Buffer = NULL;
|
||
}
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_FULLNAME ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
&(All->FullName)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_HOMEDIRECTORY ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
&(All->HomeDirectory)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_HOMEDIRECTORYDRIVE ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
&(All->HomeDirectoryDrive)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_SCRIPTPATH ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
&(All->ScriptPath)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_PROFILEPATH ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
&(All->ProfilePath)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_ADMINCOMMENT ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
&(All->AdminComment)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_USERCOMMENT ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_USER_COMMENT,
|
||
&(All->UserComment)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_PARAMETERS ) ) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PARAMETERS,
|
||
&(All->Parameters)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_LOGONHOURS ) ) {
|
||
|
||
//
|
||
// Set the LogonHours
|
||
//
|
||
|
||
NtStatus = SampReplaceUserLogonHours(
|
||
AccountContext,
|
||
&(All->LogonHours)
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) && (
|
||
( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) ||
|
||
( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) ) ) {
|
||
|
||
NT_OWF_PASSWORD NtOwfBuffer;
|
||
LM_OWF_PASSWORD LmOwfBuffer;
|
||
PLM_OWF_PASSWORD TmpLmBuffer;
|
||
PNT_OWF_PASSWORD TmpNtBuffer;
|
||
BOOLEAN TmpLmPresent;
|
||
BOOLEAN TmpNtPresent;
|
||
|
||
|
||
//
|
||
// Get copy of the account name to pass to
|
||
// notification packages.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&AccountName
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
|
||
if (UserInformationClass != UserInternal4Information) {
|
||
|
||
//
|
||
// Hashed passwords were sent.
|
||
//
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
//
|
||
// Set password buffers as trusted client has
|
||
// indicated.
|
||
//
|
||
|
||
if ( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) {
|
||
|
||
TmpLmBuffer = (PLM_OWF_PASSWORD)All->LmPassword.Buffer;
|
||
TmpLmPresent = All->LmPasswordPresent;
|
||
|
||
} else {
|
||
|
||
TmpLmBuffer = (PLM_OWF_PASSWORD)NULL;
|
||
TmpLmPresent = FALSE;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) {
|
||
|
||
TmpNtBuffer = (PNT_OWF_PASSWORD)All->NtPassword.Buffer;
|
||
TmpNtPresent = All->NtPasswordPresent;
|
||
|
||
} else {
|
||
|
||
TmpNtBuffer = (PNT_OWF_PASSWORD)NULL;
|
||
TmpNtPresent = FALSE;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// This call came from the client-side.
|
||
// The OWFs will have been encrypted with the session
|
||
// key across the RPC link.
|
||
//
|
||
// Get the session key and decrypt both OWFs
|
||
//
|
||
|
||
NtStatus = RtlGetUserSessionKeyServer(
|
||
(RPC_BINDING_HANDLE)UserHandle,
|
||
&UserSessionKey
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
|
||
//
|
||
// Decrypt the LM OWF Password with the session key
|
||
//
|
||
|
||
if ( All->WhichFields & USER_ALL_LMPASSWORDPRESENT ) {
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithUserKey(
|
||
(PENCRYPTED_LM_OWF_PASSWORD)
|
||
All->LmPassword.Buffer,
|
||
&UserSessionKey,
|
||
&LmOwfBuffer
|
||
);
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
|
||
TmpLmBuffer = &LmOwfBuffer;
|
||
TmpLmPresent = All->LmPasswordPresent;
|
||
|
||
} else {
|
||
|
||
TmpLmBuffer = (PLM_OWF_PASSWORD)NULL;
|
||
TmpLmPresent = FALSE;
|
||
}
|
||
|
||
//
|
||
// Decrypt the NT OWF Password with the session key
|
||
//
|
||
|
||
if ( All->WhichFields & USER_ALL_NTPASSWORDPRESENT ) {
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithUserKey(
|
||
(PENCRYPTED_NT_OWF_PASSWORD)
|
||
All->NtPassword.Buffer,
|
||
&UserSessionKey,
|
||
&NtOwfBuffer
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
|
||
TmpNtBuffer = &NtOwfBuffer;
|
||
TmpNtPresent = All->NtPasswordPresent;
|
||
|
||
} else {
|
||
|
||
TmpNtBuffer = (PNT_OWF_PASSWORD)NULL;
|
||
TmpNtPresent = FALSE;
|
||
}
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// The clear text password was sent, so use that.
|
||
//
|
||
|
||
NtStatus = SampDecryptPasswordWithSessionKey(
|
||
UserHandle,
|
||
&Buffer->Internal4.UserPassword,
|
||
&ClearTextPassword
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// The caller might be simultaneously setting
|
||
// the password and changing the account to be
|
||
// a machine or trust account. In this case,
|
||
// we don't validate the password (e.g., length).
|
||
//
|
||
|
||
if (!((All->WhichFields & USER_ALL_USERACCOUNTCONTROL) &&
|
||
(All->UserAccountControl &
|
||
(USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
|
||
)) {
|
||
|
||
NtStatus = SampCheckPasswordRestrictions(
|
||
UserHandle,
|
||
&ClearTextPassword
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Compute the hashed passwords.
|
||
//
|
||
|
||
NtStatus = SampCalculateLmAndNtOwfPasswords(
|
||
&ClearTextPassword,
|
||
&TmpLmPresent,
|
||
&LmOwfBuffer,
|
||
&NtOwfBuffer
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
|
||
TmpNtPresent = TRUE;
|
||
TmpLmBuffer = &LmOwfBuffer;
|
||
TmpNtBuffer = &NtOwfBuffer;
|
||
}
|
||
|
||
|
||
//
|
||
// Set the password data
|
||
//
|
||
|
||
NtStatus = SampStoreUserPasswords(
|
||
AccountContext,
|
||
TmpLmBuffer,
|
||
TmpLmPresent,
|
||
TmpNtBuffer,
|
||
TmpNtPresent,
|
||
FALSE
|
||
);
|
||
|
||
//
|
||
// If we set the password,
|
||
// set the PasswordLastSet time to now.
|
||
//
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
NtStatus = SampComputePasswordExpired(
|
||
FALSE, // Password doesn't expire now
|
||
&V1aFixed.PasswordLastSet
|
||
);
|
||
}
|
||
|
||
|
||
//
|
||
// Replicate immediately if this is a machine account
|
||
//
|
||
|
||
if ( (V1aFixed.UserAccountControl & USER_MACHINE_ACCOUNT_MASK) ||
|
||
((All->WhichFields & USER_ALL_USERACCOUNTCONTROL ) &&
|
||
(All->UserAccountControl & USER_MACHINE_ACCOUNT_MASK) )) {
|
||
ReplicateImmediately = TRUE;
|
||
}
|
||
DeltaType = SecurityDbChangePassword;
|
||
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_PASSWORDEXPIRED ) ) {
|
||
|
||
//
|
||
// If the PasswordExpired field is passed in,
|
||
// Only update PasswordLastSet if the password is being
|
||
// forced to expire or if the password is currently forced
|
||
// to expire.
|
||
//
|
||
// Avoid setting the PasswordLastSet field to the current
|
||
// time if it is already non-zero. Otherwise, the field
|
||
// will slowly creep forward each time this function is
|
||
// called and the password will never expire.
|
||
//
|
||
if ( All->PasswordExpired ||
|
||
(SampHasNeverTime.QuadPart == V1aFixed.PasswordLastSet.QuadPart) ) {
|
||
|
||
NtStatus = SampComputePasswordExpired(
|
||
All->PasswordExpired,
|
||
&V1aFixed.PasswordLastSet
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_PRIVATEDATA ) ) {
|
||
|
||
//
|
||
// Set the private data
|
||
//
|
||
|
||
NtStatus = SampSetPrivateUserData(
|
||
AccountContext,
|
||
All->PrivateData.Length,
|
||
All->PrivateData.Buffer
|
||
);
|
||
}
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_SECURITYDESCRIPTOR ) ) {
|
||
|
||
//
|
||
// Set the security descriptor
|
||
//
|
||
|
||
NtStatus = SampSetAccessAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SECURITY_DESCRIPTOR,
|
||
All->SecurityDescriptor.SecurityDescriptor,
|
||
All->SecurityDescriptor.Length
|
||
);
|
||
}
|
||
|
||
//
|
||
// Set the fixed data
|
||
//
|
||
// Note that PasswordCanChange and PasswordMustChange
|
||
// aren't stored; they're calculated when needed.
|
||
//
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) &&
|
||
( All->WhichFields & USER_ALL_USERACCOUNTCONTROL ) ) {
|
||
|
||
//
|
||
// If passwords were passed in, we've already set them,
|
||
// so it's OK to call this now.
|
||
//
|
||
|
||
NtStatus = SampIsUserAccountControlValid(
|
||
AccountContext,
|
||
All->UserAccountControl
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
if ( ( V1aFixed.UserAccountControl &
|
||
USER_MACHINE_ACCOUNT_MASK ) !=
|
||
( All->UserAccountControl &
|
||
USER_MACHINE_ACCOUNT_MASK ) ) {
|
||
|
||
//
|
||
// One or more of the machine account bits has
|
||
// changed; we'll notify netlogon below.
|
||
//
|
||
|
||
UserAccountControlChanged = TRUE;
|
||
|
||
IgnoreStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&OldAccountName
|
||
);
|
||
}
|
||
|
||
//
|
||
// Untrusted clients can:
|
||
//
|
||
// 1) leave the the ACCOUNT_AUTO_LOCK flag set.
|
||
// 2) Clear the ACCOUNT_AUTO_LOCK flag.
|
||
//
|
||
// They can't set it. So, we must AND the user's
|
||
// flag value with the current value and set that
|
||
// in the UserAccountControl field.
|
||
//
|
||
|
||
if (!(AccountContext->TrustedClient)) {
|
||
|
||
//
|
||
// Minimize the passed in AccountControl
|
||
// with the currently set value.
|
||
//
|
||
|
||
All->UserAccountControl |=
|
||
(USER_ACCOUNT_AUTO_LOCKED &
|
||
All->UserAccountControl &
|
||
V1aFixed.UserAccountControl);
|
||
|
||
//
|
||
// If an untrusted client is unlocking the account,
|
||
// then we also need to re-set the BadPasswordCount.
|
||
// Trusted clients are expected to explicitly set
|
||
// the BadPasswordCount.
|
||
//
|
||
|
||
CurrentlyLocked = (V1aFixed.UserAccountControl &
|
||
USER_ACCOUNT_AUTO_LOCKED) != 0;
|
||
Unlocking = (All->UserAccountControl &
|
||
USER_ACCOUNT_AUTO_LOCKED) == 0;
|
||
|
||
if (CurrentlyLocked && Unlocking) {
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: Administrator unlocking account.\n"
|
||
" User Account : 0x%lx\n"
|
||
" Clearing lockout flag.\n"
|
||
" Resetting BadPassowrdCount.\n",
|
||
V1aFixed.UserId) );
|
||
V1aFixed.BadPasswordCount = 0;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Now set the account control flags
|
||
//
|
||
|
||
V1aFixed.UserAccountControl = All->UserAccountControl;
|
||
|
||
}
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
if ( All->WhichFields & USER_ALL_LASTLOGON ) {
|
||
|
||
V1aFixed.LastLogon = All->LastLogon;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_LASTLOGOFF ) {
|
||
|
||
V1aFixed.LastLogoff = All->LastLogoff;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_PASSWORDLASTSET ) {
|
||
|
||
V1aFixed.PasswordLastSet = All->PasswordLastSet;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_ACCOUNTEXPIRES ) {
|
||
|
||
V1aFixed.AccountExpires = All->AccountExpires;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_PRIMARYGROUPID ) {
|
||
|
||
//
|
||
// Make sure the primary group is legitimate
|
||
// (it must be one the user is a member of)
|
||
//
|
||
|
||
NtStatus = SampAssignPrimaryGroup(
|
||
AccountContext,
|
||
All->PrimaryGroupId
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
V1aFixed.PrimaryGroupId = All->PrimaryGroupId;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_COUNTRYCODE ) {
|
||
|
||
V1aFixed.CountryCode = All->CountryCode;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_CODEPAGE ) {
|
||
|
||
V1aFixed.CodePage = All->CodePage;
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_BADPASSWORDCOUNT ) {
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: \n"
|
||
" User Account : 0x%lx\n"
|
||
" Setting BadPasswordCount: %ld\n",
|
||
V1aFixed.UserId,
|
||
All->BadPasswordCount)
|
||
);
|
||
|
||
|
||
|
||
V1aFixed.BadPasswordCount = All->BadPasswordCount;
|
||
|
||
if (UserInformationClass == UserInternal3Information) {
|
||
//
|
||
// Also set LastBadPasswordTime;
|
||
//
|
||
V1aFixed.LastBadPasswordTime =
|
||
Buffer->Internal3.LastBadPasswordTime;
|
||
|
||
#if DBG
|
||
RtlTimeToTimeFields(
|
||
&Buffer->Internal3.LastBadPasswordTime,
|
||
&T1);
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" LastBadPasswordTime : [0x%lx, 0x%lx] %d:%d:%d\n",
|
||
Buffer->Internal3.LastBadPasswordTime.HighPart,
|
||
Buffer->Internal3.LastBadPasswordTime.LowPart,
|
||
T1.Hour, T1.Minute, T1.Second )
|
||
);
|
||
#endif //DBG
|
||
}
|
||
}
|
||
|
||
if ( All->WhichFields & USER_ALL_LOGONCOUNT ) {
|
||
|
||
V1aFixed.LogonCount = All->LogonCount;
|
||
}
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
case UserPreferencesInformation:
|
||
|
||
V1aFixed.CountryCode = Buffer->Preferences.CountryCode;
|
||
V1aFixed.CodePage = Buffer->Preferences.CodePage;
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
|
||
//
|
||
// replace the user comment
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_USER_COMMENT,
|
||
(PUNICODE_STRING)&(Buffer->Preferences.UserComment)
|
||
);
|
||
}
|
||
|
||
|
||
break;
|
||
|
||
|
||
case UserParametersInformation:
|
||
|
||
|
||
//
|
||
// replace the parameters
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PARAMETERS,
|
||
(PUNICODE_STRING)&(Buffer->Parameters.Parameters)
|
||
);
|
||
|
||
break;
|
||
|
||
|
||
case UserLogonHoursInformation:
|
||
|
||
NtStatus = SampReplaceUserLogonHours(
|
||
AccountContext,
|
||
(PLOGON_HOURS)&(Buffer->LogonHours.LogonHours)
|
||
);
|
||
break;
|
||
|
||
|
||
case UserNameInformation:
|
||
|
||
//
|
||
// first change the Full Name, then change the account name...
|
||
//
|
||
|
||
//
|
||
// replace the full name - no value restrictions
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
(PUNICODE_STRING)&(Buffer->Name.FullName)
|
||
);
|
||
|
||
//
|
||
// Change the account name
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampChangeUserAccountName(
|
||
AccountContext,
|
||
(PUNICODE_STRING)&(Buffer->Name.UserName),
|
||
&OldAccountName
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Don't free the OldAccountName yet; we'll need it at the
|
||
// very end.
|
||
//
|
||
|
||
break;
|
||
|
||
|
||
case UserAccountNameInformation:
|
||
|
||
NtStatus = SampChangeUserAccountName(
|
||
AccountContext,
|
||
(PUNICODE_STRING)&(Buffer->AccountName.UserName),
|
||
&OldAccountName
|
||
);
|
||
|
||
//
|
||
// Don't free the OldAccountName; we'll need it at the
|
||
// very end.
|
||
//
|
||
|
||
break;
|
||
|
||
|
||
case UserFullNameInformation:
|
||
|
||
//
|
||
// replace the full name - no value restrictions
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
(PUNICODE_STRING)&(Buffer->FullName.FullName)
|
||
);
|
||
break;
|
||
|
||
|
||
|
||
|
||
case UserPrimaryGroupInformation:
|
||
|
||
//
|
||
// Make sure the primary group is legitimate
|
||
// (it must be one the user is a member of)
|
||
//
|
||
|
||
NtStatus = SampAssignPrimaryGroup(
|
||
AccountContext,
|
||
Buffer->PrimaryGroup.PrimaryGroupId
|
||
);
|
||
|
||
//
|
||
// Update the V1_FIXED info.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
V1aFixed.PrimaryGroupId = Buffer->PrimaryGroup.PrimaryGroupId;
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserHomeInformation:
|
||
|
||
//
|
||
// replace the home directory
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY,
|
||
(PUNICODE_STRING)&(Buffer->Home.HomeDirectory)
|
||
);
|
||
|
||
//
|
||
// replace the home directory drive
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
||
(PUNICODE_STRING)&(Buffer->Home.HomeDirectoryDrive)
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
case UserScriptInformation:
|
||
|
||
//
|
||
// replace the script
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_SCRIPT_PATH,
|
||
(PUNICODE_STRING)&(Buffer->Script.ScriptPath)
|
||
);
|
||
|
||
break;
|
||
|
||
|
||
case UserProfileInformation:
|
||
|
||
//
|
||
// replace the Profile
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_PROFILE_PATH,
|
||
(PUNICODE_STRING)&(Buffer->Profile.ProfilePath)
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserAdminCommentInformation:
|
||
|
||
//
|
||
// replace the admin comment
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
(PUNICODE_STRING)&(Buffer->AdminComment.AdminComment)
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserWorkStationsInformation:
|
||
|
||
//
|
||
// Convert the workstation list, which is given to us in
|
||
// UI/Service format, to API list format before storing
|
||
// it.
|
||
//
|
||
|
||
NtStatus = RtlConvertUiListToApiList(
|
||
(PUNICODE_STRING)&(Buffer->WorkStations.WorkStations),
|
||
&ApiList,
|
||
FALSE );
|
||
|
||
//
|
||
// replace the admin workstations
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_WORKSTATIONS,
|
||
&ApiList
|
||
);
|
||
|
||
RtlFreeHeap( RtlProcessHeap(), 0, ApiList.Buffer );
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserControlInformation:
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
NtStatus = SampIsUserAccountControlValid(
|
||
AccountContext,
|
||
Buffer->Control.UserAccountControl
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ( ( V1aFixed.UserAccountControl &
|
||
USER_MACHINE_ACCOUNT_MASK ) !=
|
||
( Buffer->Control.UserAccountControl &
|
||
USER_MACHINE_ACCOUNT_MASK ) ) {
|
||
|
||
//
|
||
// One of the machine account bits has changed;
|
||
// we'll notify netlogon below.
|
||
//
|
||
|
||
UserAccountControlChanged = TRUE;
|
||
|
||
IgnoreStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&OldAccountName
|
||
);
|
||
}
|
||
|
||
//
|
||
// Untrusted clients can:
|
||
//
|
||
// 1) leave the the ACCOUNT_AUTO_LOCK flag set.
|
||
// 2) Clear the ACCOUNT_AUTO_LOCK flag.
|
||
//
|
||
// They can't set it. So, we must AND the user's
|
||
// flag value with the current value and set that
|
||
// in the UserAccountControl field.
|
||
//
|
||
// One more caveat, when an untrusted client clears the
|
||
// ACCOUNT_AUTO_LOCK flag, then we need to reset
|
||
// the BadPasswordCount flag to zero. We don't
|
||
// do this for trusted clients, because we figure
|
||
// they know what they are doing and will set
|
||
// everthing appropriately.
|
||
//
|
||
|
||
if (!(AccountContext->TrustedClient)) {
|
||
|
||
Buffer->Control.UserAccountControl |=
|
||
(USER_ACCOUNT_AUTO_LOCKED &
|
||
Buffer->Control.UserAccountControl &
|
||
V1aFixed.UserAccountControl);
|
||
|
||
//
|
||
// If an untrusted client is unlocking the account,
|
||
// then we also need to re-set the BadPasswordCount.
|
||
// Trusted clients are expected to explicitly set
|
||
// the BadPasswordCount.
|
||
//
|
||
|
||
CurrentlyLocked = (V1aFixed.UserAccountControl &
|
||
USER_ACCOUNT_AUTO_LOCKED) != 0;
|
||
Unlocking = (Buffer->Control.UserAccountControl &
|
||
USER_ACCOUNT_AUTO_LOCKED) == 0;
|
||
|
||
if (CurrentlyLocked && Unlocking) {
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: Administrator unlocking account.\n"
|
||
" User Account : 0x%lx\n"
|
||
" Clearing lockout flag.\n"
|
||
" Resetting BadPassowrdCount.\n",
|
||
V1aFixed.UserId) );
|
||
V1aFixed.BadPasswordCount = 0;
|
||
}
|
||
}
|
||
|
||
V1aFixed.UserAccountControl = Buffer->Control.UserAccountControl;
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
case UserExpiresInformation:
|
||
|
||
//
|
||
// If the user is the special account Administrator, return an
|
||
// error if trying to set the expiry information, except to the
|
||
// value that means that the account never expires.
|
||
//
|
||
|
||
if ((!AccountContext->TrustedClient) &&
|
||
( AccountContext->TypeBody.User.Rid == DOMAIN_USER_RID_ADMIN )) {
|
||
|
||
LARGE_INTEGER AccountNeverExpires, Temp;
|
||
|
||
AccountNeverExpires = RtlConvertUlongToLargeInteger(
|
||
SAMP_ACCOUNT_NEVER_EXPIRES
|
||
);
|
||
|
||
OLD_TO_NEW_LARGE_INTEGER(All->AccountExpires, Temp);
|
||
|
||
if (!( Temp.QuadPart == AccountNeverExpires.QuadPart)) {
|
||
|
||
NtStatus = STATUS_SPECIAL_ACCOUNT;
|
||
break;
|
||
}
|
||
}
|
||
|
||
V1aFixed.AccountExpires = Buffer->Expires.AccountExpires;
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
break;
|
||
|
||
|
||
case UserSetPasswordInformation:
|
||
|
||
ASSERT(FALSE); // Should have been mapped to INTERNAL1 on client side
|
||
NtStatus = STATUS_INVALID_INFO_CLASS;
|
||
break;
|
||
|
||
|
||
case UserInternal1Information:
|
||
case UserInternal5Information:
|
||
|
||
//
|
||
// Get copy of the account name to pass to
|
||
// notification packages.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&AccountName
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
|
||
|
||
if (UserInformationClass == UserInternal1Information) {
|
||
|
||
LmPresent = Buffer->Internal1.LmPasswordPresent;
|
||
NtPresent = Buffer->Internal1.NtPasswordPresent;
|
||
PasswordExpired = Buffer->Internal1.PasswordExpired;
|
||
|
||
//
|
||
// If our client is trusted, they are on the server side
|
||
// and data from them will not have been encrypted with the
|
||
// user session key - so don't decrypt them
|
||
//
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
//
|
||
// Copy the (not) encrypted owfs into the owf buffers
|
||
//
|
||
|
||
ASSERT(ENCRYPTED_LM_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
|
||
ASSERT(ENCRYPTED_NT_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
|
||
|
||
RtlCopyMemory(&LmOwfPassword,
|
||
&Buffer->Internal1.EncryptedLmOwfPassword,
|
||
LM_OWF_PASSWORD_LENGTH
|
||
);
|
||
|
||
RtlCopyMemory(&NtOwfPassword,
|
||
&Buffer->Internal1.EncryptedNtOwfPassword,
|
||
NT_OWF_PASSWORD_LENGTH
|
||
);
|
||
|
||
} else {
|
||
|
||
|
||
//
|
||
// This call came from the client-side. The
|
||
// The OWFs will have been encrypted with the session
|
||
// key across the RPC link.
|
||
//
|
||
// Get the session key and decrypt both OWFs
|
||
//
|
||
|
||
NtStatus = RtlGetUserSessionKeyServer(
|
||
(RPC_BINDING_HANDLE)UserHandle,
|
||
&UserSessionKey
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
|
||
|
||
//
|
||
// Decrypt the LM OWF Password with the session key
|
||
//
|
||
|
||
if ( Buffer->Internal1.LmPasswordPresent) {
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithUserKey(
|
||
&Buffer->Internal1.EncryptedLmOwfPassword,
|
||
&UserSessionKey,
|
||
&LmOwfPassword
|
||
);
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Decrypt the NT OWF Password with the session key
|
||
//
|
||
|
||
if ( Buffer->Internal1.NtPasswordPresent) {
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithUserKey(
|
||
&Buffer->Internal1.EncryptedNtOwfPassword,
|
||
&UserSessionKey,
|
||
&NtOwfPassword
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
break; // out of switch
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
|
||
//
|
||
// Password was sent cleartext.
|
||
//
|
||
|
||
NtStatus = SampDecryptPasswordWithSessionKey(
|
||
UserHandle,
|
||
&Buffer->Internal5.UserPassword,
|
||
&ClearTextPassword
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
|
||
//
|
||
// Compute the hashed passwords.
|
||
//
|
||
|
||
NtStatus = SampCalculateLmAndNtOwfPasswords(
|
||
&ClearTextPassword,
|
||
&LmPresent,
|
||
&LmOwfPassword,
|
||
&NtOwfPassword
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
break;
|
||
}
|
||
|
||
|
||
|
||
NtPresent = TRUE;
|
||
PasswordExpired = Buffer->Internal5.PasswordExpired;
|
||
|
||
}
|
||
//
|
||
// Store away the new OWF passwords
|
||
//
|
||
|
||
NtStatus = SampStoreUserPasswords(
|
||
AccountContext,
|
||
&LmOwfPassword,
|
||
LmPresent,
|
||
&NtOwfPassword,
|
||
NtPresent,
|
||
FALSE
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
NtStatus = SampStorePasswordExpired(
|
||
AccountContext,
|
||
PasswordExpired
|
||
);
|
||
}
|
||
|
||
//
|
||
// Replicate immediately if this is a machine account
|
||
//
|
||
|
||
if ( V1aFixed.UserAccountControl & USER_MACHINE_ACCOUNT_MASK ) {
|
||
ReplicateImmediately = TRUE;
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
|
||
|
||
case UserInternal2Information:
|
||
|
||
if ( AccountContext->TrustedClient ) {
|
||
|
||
TellNetlogon = FALSE;
|
||
|
||
//
|
||
// There are two ways to set logon/logoff statistics:
|
||
//
|
||
// 1) Directly, specifying each one being set,
|
||
// 2) Implicitly, specifying the action to
|
||
// represent
|
||
//
|
||
// These two forms are mutually exclusive. That is,
|
||
// you can't specify both a direct action and an
|
||
// implicit action. In fact, you can't specify two
|
||
// implicit actions either.
|
||
//
|
||
|
||
if (Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_INTER_SUCCESS_LOGON) {
|
||
|
||
if ( (Buffer->Internal2.StatisticsToApply
|
||
& ~USER_LOGON_INTER_SUCCESS_LOGON) != 0 ) {
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// Set BadPasswordCount = 0
|
||
// Increment LogonCount
|
||
// Set LastLogon = NOW
|
||
//
|
||
//
|
||
//
|
||
|
||
|
||
V1aFixed.BadPasswordCount = 0;
|
||
if (V1aFixed.LogonCount != 0xFFFF) {
|
||
V1aFixed.LogonCount += 1;
|
||
}
|
||
NtQuerySystemTime( &V1aFixed.LastLogon );
|
||
|
||
|
||
#if DBG
|
||
RtlTimeToTimeFields(
|
||
&V1aFixed.LastLogon,
|
||
&T1);
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: Successful interactive logon\n"
|
||
" User Account : 0x%lx\n"
|
||
" ReSetting BadPasswordCount: %ld\n"
|
||
" LastLogon Time : [0x%lx, 0x%lx] %d:%d:%d\n",
|
||
V1aFixed.UserId,
|
||
V1aFixed.BadPasswordCount,
|
||
V1aFixed.LastLogon.HighPart,
|
||
V1aFixed.LastLogon.LowPart,
|
||
T1.Hour, T1.Minute, T1.Second)
|
||
);
|
||
#endif //DGB
|
||
}
|
||
}
|
||
|
||
if (Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_INTER_SUCCESS_LOGOFF) {
|
||
if ( (Buffer->Internal2.StatisticsToApply
|
||
& ~USER_LOGON_INTER_SUCCESS_LOGOFF) != 0 ) {
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// Set LastLogoff time
|
||
// Decrement LogonCount (don't let it become negative)
|
||
//
|
||
|
||
if (V1aFixed.LogonCount != 0) {
|
||
V1aFixed.LogonCount -= 1;
|
||
}
|
||
NtQuerySystemTime( &V1aFixed.LastLogoff );
|
||
}
|
||
}
|
||
|
||
if (Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_NET_SUCCESS_LOGON) {
|
||
|
||
if ( (Buffer->Internal2.StatisticsToApply
|
||
& ~USER_LOGON_NET_SUCCESS_LOGON) != 0 ) {
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// Set BadPasswordCount = 0
|
||
// Set LastLogon = NOW
|
||
//
|
||
//
|
||
//
|
||
|
||
|
||
V1aFixed.BadPasswordCount = 0;
|
||
NtQuerySystemTime( &V1aFixed.LastLogon );
|
||
|
||
|
||
#if DBG
|
||
RtlTimeToTimeFields(
|
||
&V1aFixed.LastLogon,
|
||
&T1);
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: Successful network logon\n"
|
||
" User Account : 0x%lx\n"
|
||
" ReSetting BadPasswordCount: %ld\n"
|
||
" LastLogon Time : [0x%lx, 0x%lx] %d:%d:%d\n",
|
||
V1aFixed.UserId,
|
||
V1aFixed.BadPasswordCount,
|
||
V1aFixed.LastLogon.HighPart,
|
||
V1aFixed.LastLogon.LowPart,
|
||
T1.Hour, T1.Minute, T1.Second )
|
||
);
|
||
#endif //DBG
|
||
}
|
||
}
|
||
|
||
if (Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_NET_SUCCESS_LOGOFF) {
|
||
if ( (Buffer->Internal2.StatisticsToApply
|
||
& ~USER_LOGON_NET_SUCCESS_LOGOFF) != 0 ) {
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// Set LastLogoff time
|
||
//
|
||
|
||
NtQuerySystemTime( &V1aFixed.LastLogoff );
|
||
}
|
||
}
|
||
|
||
if (Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_BAD_PASSWORD) {
|
||
if ( (Buffer->Internal2.StatisticsToApply
|
||
& ~USER_LOGON_BAD_PASSWORD) != 0 ) {
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER;
|
||
break;
|
||
} else {
|
||
|
||
//
|
||
// Increment BadPasswordCount
|
||
// (might lockout account)
|
||
//
|
||
|
||
|
||
AccountLockedOut =
|
||
SampIncrementBadPasswordCount(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
//
|
||
// If the account has been locked out,
|
||
// ensure the BDCs in the domain are told.
|
||
//
|
||
|
||
if ( AccountLockedOut ) {
|
||
TellNetlogon = TRUE;
|
||
ReplicateImmediately = TRUE;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
if ( Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_STAT_LAST_LOGON ) {
|
||
|
||
OLD_TO_NEW_LARGE_INTEGER(
|
||
Buffer->Internal2.LastLogon,
|
||
V1aFixed.LastLogon );
|
||
}
|
||
|
||
if ( Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_STAT_LAST_LOGOFF ) {
|
||
|
||
OLD_TO_NEW_LARGE_INTEGER(
|
||
Buffer->Internal2.LastLogoff,
|
||
V1aFixed.LastLogoff );
|
||
}
|
||
|
||
if ( Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_STAT_BAD_PWD_COUNT ) {
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: SetInformationUser: Bad Password Count\n"
|
||
" User Account : 0x%lx\n"
|
||
" Old BadPasswordCount: %ld\n"
|
||
" New BadPasswordCount: %ld\n",
|
||
V1aFixed.UserId,
|
||
V1aFixed.BadPasswordCount,
|
||
Buffer->Internal2.BadPasswordCount
|
||
)
|
||
);
|
||
V1aFixed.BadPasswordCount =
|
||
Buffer->Internal2.BadPasswordCount;
|
||
}
|
||
|
||
if ( Buffer->Internal2.StatisticsToApply
|
||
& USER_LOGON_STAT_LOGON_COUNT ) {
|
||
|
||
V1aFixed.LogonCount = Buffer->Internal2.LogonCount;
|
||
}
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// This information is only settable by trusted
|
||
// clients.
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_INFO_CLASS;
|
||
}
|
||
|
||
break;
|
||
|
||
|
||
} // end_switch
|
||
|
||
|
||
|
||
} // end_if
|
||
|
||
|
||
|
||
|
||
//
|
||
// Go fetch any data we'll need to update the display cache
|
||
// Do this before we dereference the context
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ( MustUpdateAccountDisplay ) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&NewAccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_FULL_NAME,
|
||
TRUE, // Make copy
|
||
&NewFullName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext,
|
||
SAMP_USER_ADMIN_COMMENT,
|
||
TRUE, // Make copy
|
||
&NewAdminComment
|
||
);
|
||
//
|
||
// If the account name has changed, then OldAccountName
|
||
// is already filled in. If the account name hasn't changed
|
||
// then the OldAccountName is the same as the new!
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) && (OldAccountName.Buffer == NULL)) {
|
||
|
||
NtStatus = SampDuplicateUnicodeString(
|
||
&OldAccountName,
|
||
&NewAccountName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Generate an audit if necessary. We don't account statistic
|
||
// updates, which we also don't notify Netlogon of.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) &&
|
||
SampDoAccountAuditing(DomainIndex) &&
|
||
TellNetlogon) {
|
||
|
||
UNICODE_STRING
|
||
AccountName;
|
||
|
||
IgnoreStatus = SampGetUnicodeStringAttribute(
|
||
AccountContext, // Context
|
||
SAMP_USER_ACCOUNT_NAME, // AttributeIndex
|
||
FALSE, // MakeCopy
|
||
&AccountName // UnicodeAttribute
|
||
);
|
||
if (NT_SUCCESS(IgnoreStatus)) {
|
||
LsaIAuditSamEvent(
|
||
STATUS_SUCCESS,
|
||
SE_AUDITID_USER_CHANGE, // AuditId
|
||
Domain->Sid, // Domain SID
|
||
NULL, // Member Rid (not used)
|
||
NULL, // Member Sid (not used)
|
||
&AccountName, // Account Name
|
||
&Domain->ExternalName, // Domain
|
||
&AccountContext->TypeBody.User.Rid, // Account Rid
|
||
NULL // Privileges used
|
||
);
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Dereference the account context
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// De-reference the object, write out any change to current xaction.
|
||
//
|
||
|
||
NtStatus = SampDeReferenceContext( AccountContext, TRUE );
|
||
|
||
} else {
|
||
|
||
//
|
||
// De-reference the object, ignore changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
} // end_if
|
||
|
||
|
||
|
||
|
||
//
|
||
// Commit the transaction and, if successful,
|
||
// notify netlogon of the changes. Also generate any necessary audits.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ( !TellNetlogon ) {
|
||
|
||
//
|
||
// For logon statistics, we don't notify netlogon about changes
|
||
// to the database. Which means that we don't want the
|
||
// domain's modified count to increase. The commit routine
|
||
// will increase it automatically if this isn't a BDC, so we'll
|
||
// decrement it here.
|
||
//
|
||
|
||
if (SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
|
||
!= DomainServerRoleBackup) {
|
||
|
||
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
|
||
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart -
|
||
1;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
NtStatus = SampCommitAndRetainWriteLock();
|
||
|
||
|
||
if ( NT_SUCCESS(NtStatus) ) {
|
||
|
||
|
||
|
||
//
|
||
// Update the display information if the cache may be affected
|
||
//
|
||
|
||
if ( MustUpdateAccountDisplay ) {
|
||
|
||
SAMP_ACCOUNT_DISPLAY_INFO OldAccountInfo;
|
||
SAMP_ACCOUNT_DISPLAY_INFO NewAccountInfo;
|
||
|
||
OldAccountInfo.Name = OldAccountName;
|
||
OldAccountInfo.Rid = ObjectRid;
|
||
OldAccountInfo.AccountControl = OldUserAccountControl;
|
||
RtlInitUnicodeString(&OldAccountInfo.Comment, NULL);
|
||
RtlInitUnicodeString(&OldAccountInfo.FullName, NULL);
|
||
|
||
NewAccountInfo.Name = NewAccountName;
|
||
NewAccountInfo.Rid = ObjectRid;
|
||
NewAccountInfo.AccountControl = V1aFixed.UserAccountControl;
|
||
NewAccountInfo.Comment = NewAdminComment;
|
||
NewAccountInfo.FullName = NewFullName;
|
||
|
||
IgnoreStatus = SampUpdateDisplayInformation(&OldAccountInfo,
|
||
&NewAccountInfo,
|
||
SampUserObjectType);
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
//
|
||
// Notify netlogon if machine account changes.
|
||
//
|
||
|
||
if ( ( UserAccountControlChanged ) &&
|
||
( OldAccountName.Buffer != NULL ) ) {
|
||
|
||
//
|
||
// The UserAccountControl field changed (more specifically,
|
||
// the bits that netlogon is interested in changed) and
|
||
// we were able to get the name of the account. So notify
|
||
// netlogon of the change.
|
||
//
|
||
|
||
IgnoreStatus = I_NetNotifyMachineAccount(
|
||
ObjectRid,
|
||
SampDefinedDomains[
|
||
SampTransactionDomainIndex].Sid,
|
||
OldUserAccountControl,
|
||
V1aFixed.UserAccountControl,
|
||
&OldAccountName
|
||
);
|
||
}
|
||
|
||
//
|
||
// Notify netlogon of any user account changes
|
||
//
|
||
|
||
if ( ( UserInformationClass == UserNameInformation ) ||
|
||
( UserInformationClass == UserAccountNameInformation ) ||
|
||
( ( UserInformationClass == UserAllInformation ) &&
|
||
( All->WhichFields & USER_ALL_USERNAME ) ) ) {
|
||
|
||
//
|
||
// The account was renamed; let Netlogon know.
|
||
//
|
||
|
||
SampNotifyNetlogonOfDelta(
|
||
SecurityDbRename,
|
||
SecurityDbObjectSamUser,
|
||
ObjectRid,
|
||
&OldAccountName,
|
||
(DWORD) ReplicateImmediately,
|
||
NULL // Delta data
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Something in the account was changed. Notify netlogon about
|
||
// everything except logon statistics changes.
|
||
//
|
||
|
||
if ( TellNetlogon ) {
|
||
SampNotifyNetlogonOfDelta(
|
||
DeltaType,
|
||
SecurityDbObjectSamUser,
|
||
ObjectRid,
|
||
(PUNICODE_STRING) NULL,
|
||
(DWORD) ReplicateImmediately,
|
||
NULL // Delta data
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Release the lock
|
||
//
|
||
|
||
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
|
||
|
||
//
|
||
// Notify any packages that a password was changed.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) && (DeltaType == SecurityDbChangePassword)) {
|
||
|
||
//
|
||
// If the account name was changed, use the new account name.
|
||
//
|
||
|
||
if (NewAccountName.Buffer != NULL) {
|
||
(void) SampPasswordChangeNotify(
|
||
&NewAccountName,
|
||
UserRid,
|
||
&ClearTextPassword
|
||
);
|
||
} else {
|
||
(void) SampPasswordChangeNotify(
|
||
&AccountName,
|
||
UserRid,
|
||
&ClearTextPassword
|
||
);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Clean up strings
|
||
//
|
||
|
||
SampFreeUnicodeString( &OldAccountName );
|
||
SampFreeUnicodeString( &NewAccountName );
|
||
SampFreeUnicodeString( &NewFullName );
|
||
SampFreeUnicodeString( &NewAdminComment );
|
||
SampFreeUnicodeString( &AccountName );
|
||
|
||
if (ClearTextPassword.Buffer != NULL) {
|
||
|
||
RtlZeroMemory(
|
||
ClearTextPassword.Buffer,
|
||
ClearTextPassword.Length
|
||
);
|
||
|
||
RtlFreeUnicodeString( &ClearTextPassword );
|
||
|
||
}
|
||
|
||
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrChangePasswordUser(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
IN BOOLEAN LmPresent,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD OldLmEncryptedWithNewLm,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD NewLmEncryptedWithOldLm,
|
||
IN BOOLEAN NtPresent,
|
||
IN PENCRYPTED_NT_OWF_PASSWORD OldNtEncryptedWithNewNt,
|
||
IN PENCRYPTED_NT_OWF_PASSWORD NewNtEncryptedWithOldNt,
|
||
IN BOOLEAN NtCrossEncryptionPresent,
|
||
IN PENCRYPTED_NT_OWF_PASSWORD NewNtEncryptedWithNewLm,
|
||
IN BOOLEAN LmCrossEncryptionPresent,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD NewLmEncryptedWithNewNt
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service sets the password to NewPassword only if OldPassword
|
||
matches the current user password for this user and the NewPassword
|
||
is not the same as the domain password parameter PasswordHistoryLength
|
||
passwords. This call allows users to change their own password if
|
||
they have access USER_CHANGE_PASSWORD. Password update restrictions
|
||
apply.
|
||
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
LMPresent - TRUE if the LM parameters (below) are valid.
|
||
|
||
LmOldEncryptedWithLmNew - the old LM OWF encrypted with the new LM OWF
|
||
|
||
LmNewEncryptedWithLmOld - the new LM OWF encrypted with the old LM OWF
|
||
|
||
|
||
NtPresent - TRUE if the NT parameters (below) are valid
|
||
|
||
NtOldEncryptedWithNtNew - the old NT OWF encrypted with the new NT OWF
|
||
|
||
NtNewEncryptedWithNtOld - the new NT OWF encrypted with the old NT OWF
|
||
|
||
|
||
NtCrossEncryptionPresent - TRUE if NtNewEncryptedWithLmNew is valid.
|
||
|
||
NtNewEncryptedWithLmNew - the new NT OWF encrypted with the new LM OWF
|
||
|
||
|
||
LmCrossEncryptionPresent - TRUE if LmNewEncryptedWithNtNew is valid.
|
||
|
||
LmNewEncryptedWithNtNew - the new LM OWF encrypted with the new NT OWF
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
||
|
||
STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed,
|
||
e.g. contains characters that can't be entered from the
|
||
keyboard, etc.
|
||
|
||
STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
|
||
from being changed. This may be for a number of reasons,
|
||
including time restrictions on how often a password may be
|
||
changed or length restrictions on the provided password.
|
||
|
||
This error might also be returned if the new password matched
|
||
a password in the recent history log for the account.
|
||
Security administrators indicate how many of the most
|
||
recently used passwords may not be re-used. These are kept
|
||
in the password recent history log.
|
||
|
||
STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
|
||
current password.
|
||
|
||
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
||
correct state (disabled or enabled) to perform the requested
|
||
operation. The domain server must be enabled for this
|
||
operation
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
||
incorrect role (primary or backup) to perform the requested
|
||
operation.
|
||
|
||
STATUS_CROSS_ENCRYPTION_REQUIRED - No NT password is stored, so the caller
|
||
must provide the OldNtEncryptedWithOldLm parameter.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
LARGE_INTEGER TimeNow;
|
||
LM_OWF_PASSWORD StoredLmOwfPassword;
|
||
NT_OWF_PASSWORD StoredNtOwfPassword;
|
||
NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword;
|
||
LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword;
|
||
BOOLEAN StoredLmPasswordNonNull;
|
||
BOOLEAN StoredNtPasswordPresent;
|
||
BOOLEAN StoredNtPasswordNonNull;
|
||
BOOLEAN AccountLockedOut;
|
||
BOOLEAN V1aFixedRetrieved = FALSE;
|
||
BOOLEAN V1aFixedModified = FALSE;
|
||
ULONG ObjectRid;
|
||
UNICODE_STRING AccountName;
|
||
ULONG UserRid;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
|
||
SAMTRACE("SamrChangePasswordUser");
|
||
|
||
|
||
RtlInitUnicodeString(
|
||
&AccountName,
|
||
NULL
|
||
);
|
||
|
||
//
|
||
// Grab the lock
|
||
//
|
||
|
||
NtStatus = SampAcquireWriteLock();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
//
|
||
// Get the current time
|
||
//
|
||
|
||
NtStatus = NtQuerySystemTime( &TimeNow );
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
ObjectRid = AccountContext->TypeBody.User.Rid;
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
USER_CHANGE_PASSWORD,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
// Auditing information
|
||
//
|
||
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute( AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // make a copy
|
||
&AccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Auditing information
|
||
//
|
||
|
||
UserRid = AccountContext->TypeBody.User.Rid;
|
||
|
||
//
|
||
// Get a pointer to the domain object
|
||
//
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
|
||
//
|
||
// Read the old OWF passwords from disk
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
AccountContext,
|
||
&StoredLmOwfPassword,
|
||
&StoredLmPasswordNonNull,
|
||
&StoredNtOwfPassword,
|
||
&StoredNtPasswordPresent,
|
||
&StoredNtPasswordNonNull
|
||
);
|
||
|
||
//
|
||
// Check the password can be changed at this time
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Only do the check if one of the passwords is non-null.
|
||
// A Null password can always be changed.
|
||
//
|
||
|
||
if (StoredNtPasswordNonNull || StoredLmPasswordNonNull) {
|
||
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
//
|
||
// If the min password age is non zero, check it here
|
||
//
|
||
if (Domain->UnmodifiedFixed.MinPasswordAge.QuadPart != SampHasNeverTime.QuadPart) {
|
||
|
||
LARGE_INTEGER PasswordCanChange = SampAddDeltaTime(
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MinPasswordAge);
|
||
|
||
V1aFixedRetrieved = TRUE;
|
||
|
||
if (TimeNow.QuadPart < PasswordCanChange.QuadPart) {
|
||
NtStatus = STATUS_ACCOUNT_RESTRICTION;
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
//
|
||
// Macro that defines whether the old password passed in is complex
|
||
//
|
||
|
||
#define PassedComplex() (NtPresent && !LmPresent)
|
||
|
||
//
|
||
// Macro that defines whether the stored passsword is complex
|
||
//
|
||
|
||
#define StoredComplex() (StoredNtPasswordPresent && \
|
||
StoredNtPasswordNonNull && \
|
||
!StoredLmPasswordNonNull)
|
||
|
||
//
|
||
// Check that the complexity of the old password passed in matches
|
||
// the complexity of the one stored. If it doesn't then the old
|
||
// passwords don't match and we might as well give up now.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ( (StoredComplex() != 0) != (PassedComplex() != 0) ) {
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
}
|
||
}
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (LmPresent) {
|
||
|
||
//
|
||
// Decrypt the doubly-encrypted LM passwords sent to us
|
||
//
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
NewLmEncryptedWithOldLm,
|
||
&StoredLmOwfPassword,
|
||
&NewLmOwfPassword
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
OldLmEncryptedWithNewLm,
|
||
&NewLmOwfPassword,
|
||
&OldLmOwfPassword
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Decrypt the doubly-encrypted NT passwords sent to us
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (NtPresent) {
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
NewNtEncryptedWithOldNt,
|
||
&StoredNtOwfPassword,
|
||
&NewNtOwfPassword
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
OldNtEncryptedWithNewNt,
|
||
&NewNtOwfPassword,
|
||
&OldNtOwfPassword
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Authenticate the password change operation based on what
|
||
// we have stored and what was passed.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (!NtPresent) {
|
||
|
||
//
|
||
// Called from a down-level machine (no NT password passed)
|
||
//
|
||
|
||
if (!LmPresent) {
|
||
|
||
//
|
||
// No NT password passed, no LM password either.
|
||
// They're out of luck
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_PARAMETER_MIX;
|
||
|
||
} else {
|
||
|
||
//
|
||
// LM data only passed. Use LM data for authentication
|
||
//
|
||
|
||
if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
|
||
|
||
//
|
||
// Old LM passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The operation was authenticated based on the LM data
|
||
//
|
||
// We have NtPresent = FALSE, LM Present = TRUE
|
||
//
|
||
// NewLmOwfPassword will be stored.
|
||
// No NT password will be stored.
|
||
//
|
||
}
|
||
}
|
||
|
||
|
||
} else {
|
||
|
||
//
|
||
// NtPresent = TRUE, we were passed an NT password
|
||
// The client is an NT-level machine (or higher !)
|
||
//
|
||
|
||
if (!LmPresent) {
|
||
|
||
//
|
||
// No LM version of old password - the old password is complex
|
||
//
|
||
// Use NT data for authentication
|
||
//
|
||
|
||
if (!RtlEqualNtOwfPassword(&OldNtOwfPassword, &StoredNtOwfPassword)) {
|
||
|
||
//
|
||
// Old NT passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Authentication was successful.
|
||
// We need cross encrypted version of the new LM password
|
||
//
|
||
|
||
if (!LmCrossEncryptionPresent) {
|
||
|
||
NtStatus = STATUS_LM_CROSS_ENCRYPTION_REQUIRED;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Calculate the new LM Owf Password
|
||
//
|
||
|
||
ASSERT(NT_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
NewLmEncryptedWithNewNt,
|
||
(PLM_OWF_PASSWORD)&NewNtOwfPassword,
|
||
&NewLmOwfPassword
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
LmPresent = TRUE;
|
||
|
||
//
|
||
// The operation was authenticated based on NT data
|
||
// The new LM Password was requested and
|
||
// successfully obtained using cross-encryption.
|
||
//
|
||
// We have NtPresent = TRUE, LM Present = TRUE
|
||
//
|
||
// NewLmOwfPassword will be stored.
|
||
// NewNtOwfPassword will be stored.
|
||
//
|
||
}
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// NtPresent == TRUE, LmPresent == TRUE
|
||
//
|
||
// The old password passed is simple (both LM and NT versions)
|
||
//
|
||
// Authenticate using both LM and NT data
|
||
//
|
||
|
||
if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
|
||
|
||
//
|
||
// Old LM passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Old LM passwords matched
|
||
//
|
||
// Do NT authentication if we have a stored NT password
|
||
// or the stored LM password is NULL.
|
||
//
|
||
// (NO stored NT and Stored LM = NULL -> stored pwd=NULL
|
||
// We must compare passed old NT Owf against
|
||
// NULL NT Owf to ensure user didn't specify complex
|
||
// old NT password instead of NULL password)
|
||
//
|
||
// (StoredNtOwfPassword is already initialized to
|
||
// the NullNtOwf if no NT password stored)
|
||
//
|
||
|
||
if (StoredNtPasswordPresent || !StoredLmPasswordNonNull) {
|
||
|
||
if (!RtlEqualNtOwfPassword(&OldNtOwfPassword,
|
||
&StoredNtOwfPassword)) {
|
||
//
|
||
// Old NT passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The operation was authenticated based on
|
||
// both LM and NT data.
|
||
//
|
||
// We have NtPresent = TRUE, LM Present = TRUE
|
||
//
|
||
// NewLmOwfPassword will be stored.
|
||
// NewNtOwfPassword will be stored.
|
||
//
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// The LM authentication was sufficient since
|
||
// we have no stored NT password
|
||
//
|
||
// Go get the new NT password using cross encryption
|
||
//
|
||
|
||
if (!NtCrossEncryptionPresent) {
|
||
|
||
NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Calculate the new NT Owf Password
|
||
//
|
||
|
||
ASSERT(NT_OWF_PASSWORD_LENGTH == LM_OWF_PASSWORD_LENGTH);
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
NewNtEncryptedWithNewLm,
|
||
(PNT_OWF_PASSWORD)&NewLmOwfPassword,
|
||
&NewNtOwfPassword
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// The operation was authenticated based on LM data
|
||
// The new NT Password was requested and
|
||
// successfully obtained using cross-encryption.
|
||
//
|
||
// We have NtPresent = TRUE, LM Present = TRUE
|
||
//
|
||
// NewLmOwfPassword will be stored.
|
||
// NewNtOwfPassword will be stored.
|
||
//
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// We now have a NewLmOwfPassword.
|
||
// If NtPresent = TRUE, we also have a NewNtOwfPassword
|
||
//
|
||
|
||
//
|
||
// Write the new passwords to disk
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// We should always have a LM password to store.
|
||
//
|
||
|
||
ASSERT(LmPresent);
|
||
|
||
NtStatus = SampStoreUserPasswords(
|
||
AccountContext,
|
||
&NewLmOwfPassword,
|
||
TRUE,
|
||
&NewNtOwfPassword,
|
||
NtPresent,
|
||
TRUE
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
//
|
||
// We know the password is not expired.
|
||
//
|
||
|
||
NtStatus = SampStorePasswordExpired(
|
||
AccountContext,
|
||
FALSE
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// if we have a bad password, then increment the bad password
|
||
// count and check to see if the account should be locked.
|
||
//
|
||
|
||
if (NtStatus == STATUS_WRONG_PASSWORD) {
|
||
|
||
//
|
||
// Get the V1aFixed so we can update the bad password count
|
||
//
|
||
|
||
|
||
TmpStatus = STATUS_SUCCESS;
|
||
if (!V1aFixedRetrieved) {
|
||
TmpStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
|
||
//
|
||
// If we can't update the V1aFixed, then return this
|
||
// error so that the user doesn't find out the password
|
||
// was not correct.
|
||
//
|
||
|
||
NtStatus = TmpStatus;
|
||
|
||
} else {
|
||
|
||
|
||
//
|
||
// Increment BadPasswordCount (might lockout account)
|
||
//
|
||
|
||
|
||
AccountLockedOut = SampIncrementBadPasswordCount(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
V1aFixedModified = TRUE;
|
||
|
||
|
||
}
|
||
}
|
||
|
||
if (V1aFixedModified) {
|
||
TmpStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Dereference the account context
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_WRONG_PASSWORD)) {
|
||
|
||
|
||
|
||
//
|
||
// De-reference the object, write out any change to current xaction.
|
||
//
|
||
|
||
TmpStatus = SampDeReferenceContext( AccountContext, TRUE );
|
||
|
||
//
|
||
// retain previous error/success value unless we have
|
||
// an over-riding error from our dereference.
|
||
//
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// De-reference the object, ignore changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Commit changes to disk.
|
||
//
|
||
|
||
if ( NT_SUCCESS(NtStatus) || NtStatus == STATUS_WRONG_PASSWORD) {
|
||
|
||
TmpStatus = SampCommitAndRetainWriteLock();
|
||
|
||
//
|
||
// retain previous error/success value unless we have
|
||
// an over-riding error from our dereference.
|
||
//
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
|
||
if ( NT_SUCCESS(TmpStatus) ) {
|
||
|
||
SampNotifyNetlogonOfDelta(
|
||
SecurityDbChangePassword,
|
||
SecurityDbObjectSamUser,
|
||
ObjectRid,
|
||
(PUNICODE_STRING) NULL,
|
||
(DWORD) FALSE, // Don't Replicate immediately
|
||
NULL // Delta data
|
||
);
|
||
}
|
||
}
|
||
|
||
if (SampDoAccountAuditing(AccountContext->DomainIndex)) {
|
||
|
||
LsaIAuditSamEvent( NtStatus,
|
||
SE_AUDITID_USER_PWD_CHANGED, // AuditId
|
||
Domain->Sid, // Domain SID
|
||
NULL, // Member Rid (not used)
|
||
NULL, // Member Sid (not used)
|
||
&AccountName, // Account Name
|
||
&Domain->ExternalName, // Domain
|
||
&UserRid, // Account Rid
|
||
NULL // Privileges used
|
||
);
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Release the write lock
|
||
//
|
||
|
||
TmpStatus = SampReleaseWriteLock( FALSE );
|
||
ASSERT(NT_SUCCESS(TmpStatus));
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
(void) SampPasswordChangeNotify(
|
||
&AccountName,
|
||
UserRid,
|
||
NULL
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Sleep for three seconds to prevent dictionary attacks.
|
||
//
|
||
|
||
Sleep( 3000 );
|
||
|
||
}
|
||
|
||
SampFreeUnicodeString( &AccountName );
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampDecryptPasswordWithLmOwfPassword(
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
|
||
IN PLM_OWF_PASSWORD StoredPassword,
|
||
IN BOOLEAN UnicodePasswords,
|
||
OUT PUNICODE_STRING ClearNtPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
return( SampDecryptPasswordWithKey(
|
||
EncryptedPassword,
|
||
(PUCHAR) StoredPassword,
|
||
LM_OWF_PASSWORD_LENGTH,
|
||
UnicodePasswords,
|
||
ClearNtPassword
|
||
) );
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SampDecryptPasswordWithNtOwfPassword(
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
|
||
IN PNT_OWF_PASSWORD StoredPassword,
|
||
IN BOOLEAN UnicodePasswords,
|
||
OUT PUNICODE_STRING ClearNtPassword
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// The code is the same as for LM owf password.
|
||
//
|
||
|
||
return(SampDecryptPasswordWithKey(
|
||
EncryptedPassword,
|
||
(PUCHAR) StoredPassword,
|
||
NT_OWF_PASSWORD_LENGTH,
|
||
UnicodePasswords,
|
||
ClearNtPassword
|
||
) );
|
||
}
|
||
|
||
NTSTATUS
|
||
SampOpenUserInServer(
|
||
PUNICODE_STRING UserName,
|
||
BOOLEAN Unicode,
|
||
SAMPR_HANDLE * UserHandle
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Opens a user in the account domain.
|
||
|
||
Arguments:
|
||
|
||
UserName - an OEM or Unicode string of the user's name
|
||
|
||
Unicode - Indicates whether UserName is OEM or Unicode
|
||
|
||
UserHandle - Receives handle to the user, opened with SamOpenUser for
|
||
USER_CHANGE_PASSWORD access
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS NtStatus;
|
||
SAM_HANDLE ServerHandle = NULL;
|
||
SAM_HANDLE DomainHandle = NULL;
|
||
SAMPR_ULONG_ARRAY UserId;
|
||
SAMPR_ULONG_ARRAY SidUse;
|
||
UNICODE_STRING UnicodeUserName;
|
||
ULONG DomainIndex;
|
||
|
||
SAMTRACE("SampOpenUserInServer");
|
||
|
||
|
||
UserId.Element = NULL;
|
||
SidUse.Element = NULL;
|
||
|
||
//
|
||
// Get the unicode user name.
|
||
//
|
||
|
||
if (Unicode) {
|
||
UnicodeUserName = *UserName;
|
||
} else {
|
||
NtStatus = RtlOemStringToUnicodeString(
|
||
&UnicodeUserName,
|
||
(POEM_STRING) UserName,
|
||
TRUE // allocate destination.
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
NtStatus = SamrConnect(
|
||
NULL,
|
||
&ServerHandle,
|
||
SAM_SERVER_LOOKUP_DOMAIN
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
NtStatus = SamrOpenDomain(
|
||
ServerHandle,
|
||
DOMAIN_LOOKUP |
|
||
DOMAIN_LIST_ACCOUNTS |
|
||
DOMAIN_READ_PASSWORD_PARAMETERS,
|
||
SampDefinedDomains[1].Sid,
|
||
&DomainHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If cleartext password change is not allowed, we return the error code
|
||
// indicating that the rpc client should try using the old interfaces.
|
||
//
|
||
|
||
DomainIndex = ((PSAMP_OBJECT) DomainHandle)->DomainIndex;
|
||
if (SampDefinedDomains[DomainIndex].UnmodifiedFixed.PasswordProperties &
|
||
DOMAIN_PASSWORD_NO_CLEAR_CHANGE) {
|
||
|
||
NtStatus = RPC_NT_UNKNOWN_IF;
|
||
goto Cleanup;
|
||
}
|
||
|
||
NtStatus = SamrLookupNamesInDomain(
|
||
DomainHandle,
|
||
1,
|
||
(PRPC_UNICODE_STRING) &UnicodeUserName,
|
||
&UserId,
|
||
&SidUse
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
if (NtStatus == STATUS_NONE_MAPPED) {
|
||
NtStatus = STATUS_NO_SUCH_USER;
|
||
}
|
||
goto Cleanup;
|
||
}
|
||
|
||
NtStatus = SamrOpenUser(
|
||
DomainHandle,
|
||
USER_CHANGE_PASSWORD,
|
||
UserId.Element[0],
|
||
UserHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
goto Cleanup;
|
||
}
|
||
|
||
Cleanup:
|
||
if (DomainHandle != NULL) {
|
||
SamrCloseHandle(&DomainHandle);
|
||
}
|
||
if (ServerHandle != NULL) {
|
||
SamrCloseHandle(&ServerHandle);
|
||
}
|
||
if (UserId.Element != NULL) {
|
||
MIDL_user_free(UserId.Element);
|
||
}
|
||
if (SidUse.Element != NULL) {
|
||
MIDL_user_free(SidUse.Element);
|
||
}
|
||
if (!Unicode && UnicodeUserName.Buffer != NULL) {
|
||
RtlFreeUnicodeString( &UnicodeUserName );
|
||
}
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SampChangePasswordUser2(
|
||
IN PUNICODE_STRING ServerName,
|
||
IN PUNICODE_STRING UserName,
|
||
IN BOOLEAN Unicode,
|
||
IN BOOLEAN NtPresent,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt,
|
||
IN PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt,
|
||
IN BOOLEAN LmPresent,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
|
||
IN BOOLEAN NtKeyUsed,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewLmOrNt
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service sets the password to NewPassword only if OldPassword
|
||
matches the current user password for this user and the NewPassword
|
||
is not the same as the domain password parameter PasswordHistoryLength
|
||
passwords. This call allows users to change their own password if
|
||
they have access USER_CHANGE_PASSWORD. Password update restrictions
|
||
apply.
|
||
|
||
|
||
Parameters:
|
||
|
||
ServerName - Name of the machine this SAM resides on. Ignored by this
|
||
routine, may be UNICODE or OEM string depending on Unicode parameter.
|
||
|
||
UserName - User Name of account to change password on, may be UNICODE or
|
||
OEM depending on Unicode parameter.
|
||
|
||
Unicode - Indicated whether the strings passed in are Unicode or OEM
|
||
strings.
|
||
|
||
NtPresent - Are the Nt encrypted passwords present.
|
||
|
||
NewEncryptedWithOldNt - The new cleartext password encrypted with the old
|
||
NT OWF password. Dependinf on the Unicode parameter, the clear text
|
||
password may be Unicode or OEM.
|
||
|
||
OldNtOwfEncryptedWithNewNt - Old NT OWF password encrypted with the new
|
||
NT OWF password.
|
||
|
||
LmPresent - are the Lm encrypted passwords present.
|
||
|
||
NewEncryptedWithOldLm - Contains new cleartext password (OEM or Unicode)
|
||
encrypted with the old LM OWF password
|
||
|
||
NtKeyUsed - Indicates whether the LM or NT OWF key was used to encrypt
|
||
the OldLmOwfEncryptedWithNewlmOrNt parameter.
|
||
|
||
OldLmOwfEncryptedWithNewlmOrNt - The old LM OWF password encrypted
|
||
with either the new LM OWF password or NT OWF password, depending
|
||
on the NtKeyUsed parameter.
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
||
|
||
STATUS_ILL_FORMED_PASSWORD - The new password is poorly formed,
|
||
e.g. contains characters that can't be entered from the
|
||
keyboard, etc.
|
||
|
||
STATUS_PASSWORD_RESTRICTION - A restriction prevents the password
|
||
from being changed. This may be for a number of reasons,
|
||
including time restrictions on how often a password may be
|
||
changed or length restrictions on the provided password.
|
||
|
||
This error might also be returned if the new password matched
|
||
a password in the recent history log for the account.
|
||
Security administrators indicate how many of the most
|
||
recently used passwords may not be re-used. These are kept
|
||
in the password recent history log.
|
||
|
||
STATUS_WRONG_PASSWORD - OldPassword does not contain the user's
|
||
current password.
|
||
|
||
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
||
correct state (disabled or enabled) to perform the requested
|
||
operation. The domain server must be enabled for this
|
||
operation
|
||
|
||
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
||
incorrect role (primary or backup) to perform the requested
|
||
operation.
|
||
|
||
STATUS_CROSS_ENCRYPTION_REQUIRED - No NT password is stored, so the caller
|
||
must provide the OldNtEncryptedWithOldLm parameter.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
LARGE_INTEGER TimeNow;
|
||
LM_OWF_PASSWORD StoredLmOwfPassword;
|
||
NT_OWF_PASSWORD StoredNtOwfPassword;
|
||
NT_OWF_PASSWORD NewNtOwfPassword, OldNtOwfPassword;
|
||
LM_OWF_PASSWORD NewLmOwfPassword, OldLmOwfPassword;
|
||
UNICODE_STRING NewClearPassword;
|
||
BOOLEAN LmPasswordPresent;
|
||
BOOLEAN StoredLmPasswordNonNull;
|
||
BOOLEAN StoredNtPasswordPresent;
|
||
BOOLEAN StoredNtPasswordNonNull;
|
||
BOOLEAN AccountLockedOut;
|
||
BOOLEAN V1aFixedRetrieved = FALSE;
|
||
BOOLEAN V1aFixedModified = FALSE;
|
||
ULONG ObjectRid;
|
||
UNICODE_STRING AccountName;
|
||
ULONG UserRid;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
SAMPR_HANDLE UserHandle = NULL;
|
||
|
||
SAMTRACE("SampChangePasswordUser2");
|
||
|
||
//
|
||
// Initialize variables
|
||
//
|
||
|
||
NtStatus = STATUS_SUCCESS;
|
||
NewClearPassword.Buffer = NULL;
|
||
AccountName.Buffer = NULL;
|
||
|
||
//
|
||
// Validate some parameters. We require that one of the two passwords
|
||
// be present.
|
||
//
|
||
|
||
if (!NtPresent && !LmPresent) {
|
||
|
||
return(STATUS_INVALID_PARAMETER_MIX);
|
||
}
|
||
|
||
//
|
||
// Open the user
|
||
//
|
||
|
||
NtStatus = SampOpenUserInServer(
|
||
(PUNICODE_STRING) UserName,
|
||
Unicode,
|
||
&UserHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
// Grab the lock
|
||
//
|
||
|
||
NtStatus = SampAcquireWriteLock();
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
SamrCloseHandle(&UserHandle);
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
//
|
||
// Get the current time
|
||
//
|
||
|
||
NtStatus = NtQuerySystemTime( &TimeNow );
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
||
SamrCloseHandle(&UserHandle);
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
ObjectRid = AccountContext->TypeBody.User.Rid;
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
USER_CHANGE_PASSWORD,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
||
SamrCloseHandle(&UserHandle);
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
// Auditing information
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute( AccountContext,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // make a copy
|
||
&AccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Auditing information
|
||
//
|
||
|
||
UserRid = AccountContext->TypeBody.User.Rid;
|
||
|
||
//
|
||
// Get a pointer to the domain object
|
||
//
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
|
||
//
|
||
// Read the old OWF passwords from disk
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserPasswords(
|
||
AccountContext,
|
||
&StoredLmOwfPassword,
|
||
&StoredLmPasswordNonNull,
|
||
&StoredNtOwfPassword,
|
||
&StoredNtPasswordPresent,
|
||
&StoredNtPasswordNonNull
|
||
);
|
||
|
||
//
|
||
// Check the password can be changed at this time
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Only do the check if one of the passwords is non-null.
|
||
// A Null password can always be changed.
|
||
//
|
||
|
||
if (StoredNtPasswordNonNull || StoredLmPasswordNonNull) {
|
||
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If the min password age is non zero, check it here
|
||
//
|
||
|
||
if (Domain->UnmodifiedFixed.MinPasswordAge.QuadPart != SampHasNeverTime.QuadPart) {
|
||
|
||
LARGE_INTEGER PasswordCanChange = SampAddDeltaTime(
|
||
V1aFixed.PasswordLastSet,
|
||
Domain->UnmodifiedFixed.MinPasswordAge);
|
||
|
||
V1aFixedRetrieved = TRUE;
|
||
|
||
if (TimeNow.QuadPart < PasswordCanChange.QuadPart) {
|
||
NtStatus = STATUS_ACCOUNT_RESTRICTION;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// If we have old NtOwf passwords, use them
|
||
// Decrypt the doubly-encrypted NT passwords sent to us
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (StoredNtPasswordPresent && NtPresent) {
|
||
|
||
NtStatus = SampDecryptPasswordWithNtOwfPassword(
|
||
NewEncryptedWithOldNt,
|
||
&StoredNtOwfPassword,
|
||
Unicode,
|
||
&NewClearPassword
|
||
);
|
||
|
||
} else if (LmPresent) {
|
||
|
||
//
|
||
// There was no stored NT password and NT passed, so our only
|
||
// hope now is that the stored LM password works.
|
||
//
|
||
|
||
//
|
||
// Decrypt the new password encrypted with the old LM password
|
||
//
|
||
|
||
NtStatus = SampDecryptPasswordWithLmOwfPassword(
|
||
NewEncryptedWithOldLm,
|
||
&StoredLmOwfPassword,
|
||
Unicode,
|
||
&NewClearPassword
|
||
);
|
||
|
||
|
||
} else {
|
||
|
||
NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
|
||
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// We now have the cleartext new password.
|
||
// Compute the new LmOwf and NtOwf password
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampCalculateLmAndNtOwfPasswords(
|
||
&NewClearPassword,
|
||
&LmPasswordPresent,
|
||
&NewLmOwfPassword,
|
||
&NewNtOwfPassword
|
||
);
|
||
|
||
}
|
||
|
||
//
|
||
// If we have both NT passwords, compute the old NT password,
|
||
// otherwise compute the old LM password
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (StoredNtPasswordPresent && NtPresent) {
|
||
NtStatus = RtlDecryptNtOwfPwdWithNtOwfPwd(
|
||
OldNtOwfEncryptedWithNewNt,
|
||
&NewNtOwfPassword,
|
||
&OldNtOwfPassword
|
||
);
|
||
|
||
}
|
||
|
||
if (LmPresent) {
|
||
|
||
|
||
//
|
||
// If the NT key was used to encrypt this, use the NT key
|
||
// to decrypt it.
|
||
//
|
||
|
||
|
||
if (NtKeyUsed) {
|
||
|
||
ASSERT(LM_OWF_PASSWORD_LENGTH == NT_OWF_PASSWORD_LENGTH);
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
OldLmOwfEncryptedWithNewLmOrNt,
|
||
(PLM_OWF_PASSWORD) &NewNtOwfPassword,
|
||
&OldLmOwfPassword
|
||
);
|
||
|
||
|
||
} else if (LmPasswordPresent) {
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithLmOwfPwd(
|
||
OldLmOwfEncryptedWithNewLmOrNt,
|
||
&NewLmOwfPassword,
|
||
&OldLmOwfPassword
|
||
);
|
||
|
||
|
||
} else {
|
||
NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// Authenticate the password change operation based on what
|
||
// we have stored and what was passed. We authenticate whatever
|
||
// passwords were sent .
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (NtPresent && StoredNtPasswordPresent) {
|
||
|
||
//
|
||
// NtPresent = TRUE, we were passed an NT password
|
||
//
|
||
|
||
if (!RtlEqualNtOwfPassword(&OldNtOwfPassword, &StoredNtOwfPassword)) {
|
||
|
||
//
|
||
// Old NT passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
}
|
||
} else if (LmPresent) {
|
||
|
||
//
|
||
// LM data passed. Use LM data for authentication
|
||
//
|
||
|
||
if (!RtlEqualLmOwfPassword(&OldLmOwfPassword, &StoredLmOwfPassword)) {
|
||
|
||
//
|
||
// Old LM passwords didn't match
|
||
//
|
||
|
||
NtStatus = STATUS_WRONG_PASSWORD;
|
||
|
||
}
|
||
|
||
} else {
|
||
NtStatus = STATUS_NT_CROSS_ENCRYPTION_REQUIRED;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Now we should check password restrictions.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampCheckPasswordRestrictions(
|
||
UserHandle,
|
||
&NewClearPassword
|
||
);
|
||
|
||
}
|
||
|
||
|
||
//
|
||
// We now have a NewLmOwfPassword and a NewNtOwfPassword.
|
||
//
|
||
|
||
//
|
||
// Write the new passwords to disk
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// We should always have an LM and an NT password to store.
|
||
//
|
||
|
||
|
||
NtStatus = SampStoreUserPasswords(
|
||
AccountContext,
|
||
&NewLmOwfPassword,
|
||
LmPasswordPresent,
|
||
&NewNtOwfPassword,
|
||
TRUE,
|
||
TRUE
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
//
|
||
// We know the password is not expired.
|
||
//
|
||
|
||
NtStatus = SampStorePasswordExpired(
|
||
AccountContext,
|
||
FALSE
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// if we have a bad password, then increment the bad password
|
||
// count and check to see if the account should be locked.
|
||
//
|
||
|
||
if (NtStatus == STATUS_WRONG_PASSWORD) {
|
||
|
||
//
|
||
// Get the V1aFixed so we can update the bad password count
|
||
//
|
||
|
||
|
||
TmpStatus = STATUS_SUCCESS;
|
||
if (!V1aFixedRetrieved) {
|
||
TmpStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
|
||
//
|
||
// If we can't update the V1aFixed, then return this
|
||
// error so that the user doesn't find out the password
|
||
// was not correct.
|
||
//
|
||
|
||
NtStatus = TmpStatus;
|
||
|
||
} else {
|
||
|
||
|
||
//
|
||
// Increment BadPasswordCount (might lockout account)
|
||
//
|
||
|
||
|
||
AccountLockedOut = SampIncrementBadPasswordCount(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
V1aFixedModified = TRUE;
|
||
|
||
|
||
}
|
||
}
|
||
|
||
if (V1aFixedModified) {
|
||
TmpStatus = SampReplaceUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Dereference the account context
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) || (NtStatus == STATUS_WRONG_PASSWORD)) {
|
||
|
||
|
||
|
||
//
|
||
// De-reference the object, write out any change to current xaction.
|
||
//
|
||
|
||
TmpStatus = SampDeReferenceContext( AccountContext, TRUE );
|
||
|
||
//
|
||
// retain previous error/success value unless we have
|
||
// an over-riding error from our dereference.
|
||
//
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// De-reference the object, ignore changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Commit changes to disk.
|
||
//
|
||
|
||
if ( NT_SUCCESS(NtStatus) || NtStatus == STATUS_WRONG_PASSWORD) {
|
||
|
||
TmpStatus = SampCommitAndRetainWriteLock();
|
||
|
||
//
|
||
// retain previous error/success value unless we have
|
||
// an over-riding error from our dereference.
|
||
//
|
||
|
||
if (!NT_SUCCESS(TmpStatus)) {
|
||
NtStatus = TmpStatus;
|
||
}
|
||
|
||
if ( NT_SUCCESS(TmpStatus) ) {
|
||
|
||
SampNotifyNetlogonOfDelta(
|
||
SecurityDbChangePassword,
|
||
SecurityDbObjectSamUser,
|
||
ObjectRid,
|
||
(PUNICODE_STRING) NULL,
|
||
(DWORD) FALSE, // Don't Replicate immediately
|
||
NULL // Delta data
|
||
);
|
||
}
|
||
}
|
||
|
||
if (SampDoAccountAuditing(AccountContext->DomainIndex)) {
|
||
|
||
LsaIAuditSamEvent( NtStatus,
|
||
SE_AUDITID_USER_PWD_CHANGED, // AuditId
|
||
Domain->Sid, // Domain SID
|
||
NULL, // Member Rid (not used)
|
||
NULL, // Member Sid (not used)
|
||
&AccountName, // Account Name
|
||
&Domain->ExternalName, // Domain
|
||
&UserRid, // Account Rid
|
||
NULL // Privileges used
|
||
);
|
||
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Release the write lock
|
||
//
|
||
|
||
TmpStatus = SampReleaseWriteLock( FALSE );
|
||
ASSERT(NT_SUCCESS(TmpStatus));
|
||
|
||
SamrCloseHandle(&UserHandle);
|
||
|
||
//
|
||
// Notify any notification packages that a password has changed.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
IgnoreStatus = SampPasswordChangeNotify(
|
||
&AccountName,
|
||
UserRid,
|
||
&NewClearPassword
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Sleep for three seconds to prevent dictionary attacks.
|
||
//
|
||
|
||
Sleep( 3000 );
|
||
}
|
||
|
||
if (NewClearPassword.Buffer != NULL) {
|
||
|
||
RtlZeroMemory(
|
||
NewClearPassword.Buffer,
|
||
NewClearPassword.Length
|
||
);
|
||
|
||
}
|
||
|
||
if ( Unicode ) {
|
||
|
||
SampFreeUnicodeString( &NewClearPassword );
|
||
} else {
|
||
|
||
RtlFreeUnicodeString( &NewClearPassword );
|
||
}
|
||
|
||
SampFreeUnicodeString( &AccountName );
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SamrOemChangePasswordUser2(
|
||
IN handle_t BindingHandle,
|
||
IN PRPC_STRING ServerName,
|
||
IN PRPC_STRING UserName,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewLm
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Server side stub for Unicode password change.
|
||
See SampChangePasswordUser2 for details
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
{
|
||
return(SampChangePasswordUser2(
|
||
(PUNICODE_STRING) ServerName,
|
||
(PUNICODE_STRING) UserName,
|
||
FALSE, // not unicode
|
||
FALSE, // NT not present
|
||
NULL, // new NT password
|
||
NULL, // old NT password
|
||
TRUE, // LM present
|
||
NewEncryptedWithOldLm,
|
||
FALSE, // NT key not used
|
||
OldLmOwfEncryptedWithNewLm
|
||
) );
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrUnicodeChangePasswordUser2(
|
||
IN handle_t BindingHandle,
|
||
IN PRPC_UNICODE_STRING ServerName,
|
||
IN PRPC_UNICODE_STRING UserName,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldNt,
|
||
IN PENCRYPTED_NT_OWF_PASSWORD OldNtOwfEncryptedWithNewNt,
|
||
IN BOOLEAN LmPresent,
|
||
IN PSAMPR_ENCRYPTED_USER_PASSWORD NewEncryptedWithOldLm,
|
||
IN PENCRYPTED_LM_OWF_PASSWORD OldLmOwfEncryptedWithNewNt
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Server side stub for Unicode password change.
|
||
See SampChangePasswordUser2 for details
|
||
|
||
Arguments:
|
||
|
||
|
||
Return Value:
|
||
|
||
--*/
|
||
|
||
{
|
||
return(SampChangePasswordUser2(
|
||
(PUNICODE_STRING) ServerName,
|
||
(PUNICODE_STRING) UserName,
|
||
TRUE, // unicode
|
||
TRUE, // NT present
|
||
NewEncryptedWithOldNt,
|
||
OldNtOwfEncryptedWithNewNt,
|
||
LmPresent,
|
||
NewEncryptedWithOldLm,
|
||
TRUE, // NT key used
|
||
OldLmOwfEncryptedWithNewNt
|
||
) );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrGetGroupsForUser(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
OUT PSAMPR_GET_GROUPS_BUFFER *Groups
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service returns the list of groups that a user is a member of.
|
||
It returns a structure for each group that includes the relative ID
|
||
of the group, and the attributes of the group that are assigned to
|
||
the user.
|
||
|
||
This service requires USER_LIST_GROUPS access to the user account
|
||
object.
|
||
|
||
|
||
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
Groups - Receives a pointer to a buffer containing a count of members
|
||
and a pointer to a second buffer containing an array of
|
||
GROUP_MEMBERSHIPs data structures. When this information is
|
||
no longer needed, these buffers must be freed using
|
||
SamFreeMemory().
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
||
access to complete the operation.
|
||
|
||
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
NTSTATUS IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
|
||
SAMTRACE("SamrGetGroupsForUser");
|
||
|
||
|
||
//
|
||
// Make sure we understand what RPC is doing for (to) us.
|
||
//
|
||
|
||
ASSERT (Groups != NULL);
|
||
|
||
if ((*Groups) != NULL) {
|
||
return(STATUS_INVALID_PARAMETER);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Allocate the first of the return buffers
|
||
//
|
||
|
||
(*Groups) = MIDL_user_allocate( sizeof(SAMPR_GET_GROUPS_BUFFER) );
|
||
|
||
if ( (*Groups) == NULL) {
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
|
||
|
||
|
||
|
||
SampAcquireReadLock();
|
||
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
USER_LIST_GROUPS,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampRetrieveUserMembership(
|
||
AccountContext,
|
||
TRUE, // Make copy
|
||
&(*Groups)->MembershipCount,
|
||
&(*Groups)->Groups
|
||
);
|
||
|
||
//
|
||
// De-reference the object, discarding changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
//
|
||
// Free the read lock
|
||
//
|
||
|
||
SampReleaseReadLock();
|
||
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
(*Groups)->MembershipCount = 0;
|
||
|
||
MIDL_user_free( (*Groups) );
|
||
(*Groups) = NULL;
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrGetUserDomainPasswordInformation(
|
||
IN SAMPR_HANDLE UserHandle,
|
||
OUT PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Takes a user handle, finds the domain for that user, and returns
|
||
password information for the domain. This is so the client\wrappers.c
|
||
can get the information to verify the user's password before it is
|
||
OWF'd.
|
||
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
PasswordInformation - Receives information about password restrictions
|
||
for the user's domain.
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
Other errors may be returned from SampLookupContext() if the handle
|
||
is invalid or does not indicate proper access to the domain's password
|
||
inforamtion.
|
||
|
||
--*/
|
||
{
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
NTSTATUS NtStatus;
|
||
NTSTATUS IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
|
||
SAMTRACE("SamrGetUserDomainPasswordInformation");
|
||
|
||
SampAcquireReadLock();
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
0,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// When the user was opened, we checked to see if the domain handle
|
||
// allowed access to the domain password information. Check that here.
|
||
//
|
||
|
||
if ( !( AccountContext->TypeBody.User.DomainPasswordInformationAccessible ) ) {
|
||
|
||
NtStatus = STATUS_ACCESS_DENIED;
|
||
|
||
} else {
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
//
|
||
// If the user account is a machine account,
|
||
// then restrictions are generally not enforced.
|
||
// This is so that simple initial passwords can be
|
||
// established. IT IS EXPECTED THAT COMPLEX PASSWORDS,
|
||
// WHICH MEET THE MOST STRINGENT RESTRICTIONS, WILL BE
|
||
// AUTOMATICALLY ESTABLISHED AND MAINTAINED ONCE THE MACHINE
|
||
// JOINS THE DOMAIN. It is the UI's responsibility to
|
||
// maintain this level of complexity.
|
||
//
|
||
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
if ( (V1aFixed.UserAccountControl &
|
||
(USER_WORKSTATION_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT))
|
||
!= 0 ) {
|
||
|
||
PasswordInformation->MinPasswordLength = 0;
|
||
PasswordInformation->PasswordProperties = 0;
|
||
} else {
|
||
|
||
PasswordInformation->MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
|
||
PasswordInformation->PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// De-reference the object, discarding changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
SampReleaseReadLock();
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SamrGetDomainPasswordInformation(
|
||
IN handle_t BindingHandle,
|
||
IN OPTIONAL PRPC_UNICODE_STRING ServerName,
|
||
OUT PUSER_DOMAIN_PASSWORD_INFORMATION PasswordInformation
|
||
)
|
||
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Takes a user handle, finds the domain for that user, and returns
|
||
password information for the domain. This is so the client\wrappers.c
|
||
can get the information to verify the user's password before it is
|
||
OWF'd.
|
||
|
||
|
||
Parameters:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
PasswordInformation - Receives information about password restrictions
|
||
for the user's domain.
|
||
|
||
|
||
Return Values:
|
||
|
||
STATUS_SUCCESS - The Service completed successfully.
|
||
|
||
Other errors may be returned from SampLookupContext() if the handle
|
||
is invalid or does not indicate proper access to the domain's password
|
||
inforamtion.
|
||
|
||
--*/
|
||
{
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
NTSTATUS NtStatus;
|
||
NTSTATUS IgnoreStatus;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
SAMPR_HANDLE ServerHandle = NULL;
|
||
SAMPR_HANDLE DomainHandle = NULL;
|
||
|
||
SAMTRACE("SamrGetDomainPasswordInformation");
|
||
|
||
//
|
||
// Connect to the server and open the account domain for
|
||
// DOMAIN_READ_PASSWORD_PARAMETERS access.
|
||
//
|
||
|
||
NtStatus = SamrConnect(
|
||
NULL,
|
||
&ServerHandle,
|
||
SAM_SERVER_LOOKUP_DOMAIN
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
NtStatus = SamrOpenDomain(
|
||
ServerHandle,
|
||
DOMAIN_READ_PASSWORD_PARAMETERS,
|
||
SampDefinedDomains[1].Sid,
|
||
&DomainHandle
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
SamrCloseHandle(&ServerHandle);
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
SampAcquireReadLock();
|
||
|
||
|
||
//
|
||
// We want to look at the account domain, which is domains[1].
|
||
//
|
||
|
||
Domain = &SampDefinedDomains[1];
|
||
|
||
//
|
||
// Copy the password properites into the returned structure.
|
||
//
|
||
|
||
PasswordInformation->MinPasswordLength = Domain->UnmodifiedFixed.MinPasswordLength;
|
||
PasswordInformation->PasswordProperties = Domain->UnmodifiedFixed.PasswordProperties;
|
||
|
||
|
||
SampReleaseReadLock();
|
||
|
||
SamrCloseHandle(&DomainHandle);
|
||
SamrCloseHandle(&ServerHandle);
|
||
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Services Private to this process //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
NTSTATUS
|
||
SamIAccountRestrictions(
|
||
IN SAM_HANDLE UserHandle,
|
||
IN PUNICODE_STRING LogonWorkStation,
|
||
IN PUNICODE_STRING WorkStations,
|
||
IN PLOGON_HOURS LogonHours,
|
||
OUT PLARGE_INTEGER LogoffTime,
|
||
OUT PLARGE_INTEGER KickoffTime
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Validate a user's ability to logon at this time and at the workstation
|
||
being logged onto.
|
||
|
||
|
||
Arguments:
|
||
|
||
UserHandle - The handle of an opened user to operate on.
|
||
|
||
LogonWorkStation - The name of the workstation the logon is being
|
||
attempted at.
|
||
|
||
WorkStations - The list of workstations the user may logon to. This
|
||
information comes from the user's account information. It must
|
||
be in API list format.
|
||
|
||
LogonHours - The times the user may logon. This information comes
|
||
from the user's account information.
|
||
|
||
LogoffTime - Receives the time at which the user should logoff the
|
||
system.
|
||
|
||
KickoffTime - Receives the time at which the user should be kicked
|
||
off the system.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - Logon is permitted.
|
||
|
||
STATUS_INVALID_LOGON_HOURS - The user is not authorized to logon at
|
||
this time.
|
||
|
||
STATUS_INVALID_WORKSTATION - The user is not authorized to logon to
|
||
the specified workstation.
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
#define MILLISECONDS_PER_WEEK 7 * 24 * 60 * 60 * 1000
|
||
|
||
TIME_FIELDS CurrentTimeFields;
|
||
LARGE_INTEGER CurrentTime, CurrentUTCTime;
|
||
LARGE_INTEGER MillisecondsIntoWeekXUnitsPerWeek;
|
||
LARGE_INTEGER LargeUnitsIntoWeek;
|
||
LARGE_INTEGER Delta100Ns;
|
||
PSAMP_OBJECT AccountContext;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
SAMP_OBJECT_TYPE FoundType;
|
||
NTSTATUS NtStatus = STATUS_SUCCESS;
|
||
NTSTATUS IgnoreStatus;
|
||
ULONG CurrentMsIntoWeek;
|
||
ULONG LogoffMsIntoWeek;
|
||
ULONG DeltaMs;
|
||
ULONG MillisecondsPerUnit;
|
||
ULONG CurrentUnitsIntoWeek;
|
||
ULONG LogoffUnitsIntoWeek;
|
||
USHORT i;
|
||
TIME_ZONE_INFORMATION TimeZoneInformation;
|
||
DWORD TimeZoneId;
|
||
LARGE_INTEGER BiasIn100NsUnits;
|
||
LONG BiasInMinutes;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
|
||
SAMTRACE("SamIAccountRestrictions");
|
||
|
||
SampAcquireReadLock();
|
||
|
||
//
|
||
// Validate type of, and access to object.
|
||
//
|
||
|
||
AccountContext = (PSAMP_OBJECT)UserHandle;
|
||
|
||
NtStatus = SampLookupContext(
|
||
AccountContext,
|
||
0L,
|
||
SampUserObjectType, // ExpectedType
|
||
&FoundType
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
AccountContext,
|
||
&V1aFixed
|
||
);
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Only check for users other than the builtin ADMIN
|
||
//
|
||
|
||
if (V1aFixed.UserId != DOMAIN_USER_RID_ADMIN) {
|
||
|
||
//
|
||
// Scan to make sure the workstation being logged into is in the
|
||
// list of valid workstations - or if the list of valid workstations
|
||
// is null, which means that all are valid.
|
||
//
|
||
|
||
NtStatus = SampMatchworkstation( LogonWorkStation, WorkStations );
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
//
|
||
// Check to make sure that the current time is a valid time to logon
|
||
// in the LogonHours.
|
||
//
|
||
// We need to validate the time taking into account whether we are
|
||
// in daylight savings time or standard time. Thus, if the logon
|
||
// hours specify that we are able to logon between 9am and 5pm,
|
||
// this means 9am to 5pm standard time during the standard time
|
||
// period, and 9am to 5pm daylight savings time when in the
|
||
// daylight savings time. Since the logon hours stored by SAM are
|
||
// independent of daylight savings time, we need to add in the
|
||
// difference between standard time and daylight savings time to
|
||
// the current time before checking whether this time is a valid
|
||
// time to logon. Since this difference (or bias as it is called)
|
||
// is actually held in the form
|
||
//
|
||
// Standard time = Daylight savings time + Bias
|
||
//
|
||
// the Bias is a negative number. Thus we actually subtract the
|
||
// signed Bias from the Current Time.
|
||
|
||
//
|
||
// First, get the Time Zone Information.
|
||
//
|
||
|
||
TimeZoneId = GetTimeZoneInformation(
|
||
(LPTIME_ZONE_INFORMATION) &TimeZoneInformation
|
||
);
|
||
|
||
//
|
||
// Next, get the appropriate bias (signed integer in minutes) to subtract from
|
||
// the Universal Time Convention (UTC) time returned by NtQuerySystemTime
|
||
// to get the local time. The bias to be used depends whether we're
|
||
// in Daylight Savings time or Standard Time as indicated by the
|
||
// TimeZoneId parameter.
|
||
//
|
||
// local time = UTC time - bias in 100Ns units
|
||
//
|
||
|
||
switch (TimeZoneId) {
|
||
|
||
case TIME_ZONE_ID_UNKNOWN:
|
||
|
||
//
|
||
// There is no differentiation between standard and
|
||
// daylight savings time. Proceed as for Standard Time
|
||
//
|
||
|
||
BiasInMinutes = TimeZoneInformation.StandardBias;
|
||
break;
|
||
|
||
case TIME_ZONE_ID_STANDARD:
|
||
|
||
BiasInMinutes = TimeZoneInformation.StandardBias;
|
||
break;
|
||
|
||
case TIME_ZONE_ID_DAYLIGHT:
|
||
|
||
BiasInMinutes = TimeZoneInformation.DaylightBias;
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// Something is wrong with the time zone information. Fail
|
||
// the logon request.
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_LOGON_HOURS;
|
||
break;
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Convert the Bias from minutes to 100ns units
|
||
//
|
||
|
||
BiasIn100NsUnits.QuadPart = ((LONGLONG)BiasInMinutes)
|
||
* 60 * 10000000;
|
||
|
||
//
|
||
// Get the UTC time in 100Ns units used by Windows Nt. This
|
||
// time is GMT.
|
||
//
|
||
|
||
NtStatus = NtQuerySystemTime( &CurrentUTCTime );
|
||
}
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
CurrentTime.QuadPart = CurrentUTCTime.QuadPart -
|
||
BiasIn100NsUnits.QuadPart;
|
||
|
||
RtlTimeToTimeFields( &CurrentTime, &CurrentTimeFields );
|
||
|
||
CurrentMsIntoWeek = (((( CurrentTimeFields.Weekday * 24 ) +
|
||
CurrentTimeFields.Hour ) * 60 +
|
||
CurrentTimeFields.Minute ) * 60 +
|
||
CurrentTimeFields.Second ) * 1000 +
|
||
CurrentTimeFields.Milliseconds;
|
||
|
||
MillisecondsIntoWeekXUnitsPerWeek.QuadPart =
|
||
((LONGLONG)CurrentMsIntoWeek) *
|
||
((LONGLONG)LogonHours->UnitsPerWeek);
|
||
|
||
LargeUnitsIntoWeek = RtlExtendedLargeIntegerDivide(
|
||
MillisecondsIntoWeekXUnitsPerWeek,
|
||
MILLISECONDS_PER_WEEK,
|
||
(PULONG)NULL );
|
||
|
||
CurrentUnitsIntoWeek = LargeUnitsIntoWeek.LowPart;
|
||
|
||
if ( !( LogonHours->LogonHours[ CurrentUnitsIntoWeek / 8] &
|
||
( 0x01 << ( CurrentUnitsIntoWeek % 8 ) ) ) ) {
|
||
|
||
NtStatus = STATUS_INVALID_LOGON_HOURS;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Determine the next time that the user is NOT supposed to be logged
|
||
// in, and return that as LogoffTime.
|
||
//
|
||
|
||
i = 0;
|
||
LogoffUnitsIntoWeek = CurrentUnitsIntoWeek;
|
||
|
||
do {
|
||
|
||
i++;
|
||
|
||
LogoffUnitsIntoWeek = ( LogoffUnitsIntoWeek + 1 ) % LogonHours->UnitsPerWeek;
|
||
|
||
} while ( ( i <= LogonHours->UnitsPerWeek ) &&
|
||
( LogonHours->LogonHours[ LogoffUnitsIntoWeek / 8 ] &
|
||
( 0x01 << ( LogoffUnitsIntoWeek % 8 ) ) ) );
|
||
|
||
if ( i > LogonHours->UnitsPerWeek ) {
|
||
|
||
//
|
||
// All times are allowed, so there's no logoff
|
||
// time. Return forever for both logofftime and
|
||
// kickofftime.
|
||
//
|
||
|
||
LogoffTime->HighPart = 0x7FFFFFFF;
|
||
LogoffTime->LowPart = 0xFFFFFFFF;
|
||
|
||
KickoffTime->HighPart = 0x7FFFFFFF;
|
||
KickoffTime->LowPart = 0xFFFFFFFF;
|
||
|
||
} else {
|
||
|
||
//
|
||
// LogoffUnitsIntoWeek points at which time unit the
|
||
// user is to log off. Calculate actual time from
|
||
// the unit, and return it.
|
||
//
|
||
// CurrentTimeFields already holds the current
|
||
// time for some time during this week; just adjust
|
||
// to the logoff time during this week and convert
|
||
// to time format.
|
||
//
|
||
|
||
MillisecondsPerUnit = MILLISECONDS_PER_WEEK / LogonHours->UnitsPerWeek;
|
||
|
||
LogoffMsIntoWeek = MillisecondsPerUnit * LogoffUnitsIntoWeek;
|
||
|
||
if ( LogoffMsIntoWeek < CurrentMsIntoWeek ) {
|
||
|
||
DeltaMs = MILLISECONDS_PER_WEEK - ( CurrentMsIntoWeek - LogoffMsIntoWeek );
|
||
|
||
} else {
|
||
|
||
DeltaMs = LogoffMsIntoWeek - CurrentMsIntoWeek;
|
||
}
|
||
|
||
Delta100Ns = RtlExtendedIntegerMultiply(
|
||
RtlConvertUlongToLargeInteger( DeltaMs ),
|
||
10000
|
||
);
|
||
|
||
LogoffTime->QuadPart = CurrentUTCTime.QuadPart +
|
||
Delta100Ns.QuadPart;
|
||
|
||
//
|
||
// Subtract Domain->ForceLogoff from LogoffTime, and return
|
||
// that as KickoffTime. Note that Domain->ForceLogoff is a
|
||
// negative delta. If its magnitude is sufficiently large
|
||
// (in fact, larger than the difference between LogoffTime
|
||
// and the largest positive large integer), we'll get overflow
|
||
// resulting in a KickOffTime that is negative. In this
|
||
// case, reset the KickOffTime to this largest positive
|
||
// large integer (i.e. "never") value.
|
||
//
|
||
|
||
Domain = &SampDefinedDomains[ AccountContext->DomainIndex ];
|
||
|
||
KickoffTime->QuadPart = LogoffTime->QuadPart -
|
||
Domain->UnmodifiedFixed.ForceLogoff.QuadPart;
|
||
|
||
if (KickoffTime->QuadPart < 0) {
|
||
|
||
KickoffTime->HighPart = 0x7FFFFFFF;
|
||
KickoffTime->LowPart = 0xFFFFFFFF;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Never kick administrators off
|
||
//
|
||
|
||
LogoffTime->HighPart = 0x7FFFFFFF;
|
||
LogoffTime->LowPart = 0xFFFFFFFF;
|
||
KickoffTime->HighPart = 0x7FFFFFFF;
|
||
KickoffTime->LowPart = 0xFFFFFFFF;
|
||
}
|
||
}
|
||
|
||
//
|
||
// De-reference the object, discarding changes
|
||
//
|
||
|
||
IgnoreStatus = SampDeReferenceContext( AccountContext, FALSE );
|
||
ASSERT(NT_SUCCESS(IgnoreStatus));
|
||
}
|
||
|
||
SampReleaseReadLock();
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Services Private to this file //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampReplaceUserV1aFixed(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service replaces the current V1 fixed length information related to
|
||
a specified User.
|
||
|
||
The change is made to the in-memory object data only.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the account context whose V1_FIXED information is
|
||
to be replaced.
|
||
|
||
V1aFixed - Is a buffer containing the new V1_FIXED information.
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been replaced.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
SampSetFixedAttributes()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampReplaceUserV1aFixed");
|
||
|
||
NtStatus = SampSetFixedAttributes(
|
||
Context,
|
||
(PVOID)V1aFixed
|
||
);
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
LARGE_INTEGER
|
||
SampGetPasswordMustChange(
|
||
IN ULONG UserAccountControl,
|
||
IN LARGE_INTEGER PasswordLastSet,
|
||
IN LARGE_INTEGER MaxPasswordAge
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the correct value to set the PasswordMustChange time
|
||
to depending on the time the password was last set, whether the password
|
||
expires on the account, and the maximum password age on the domain.
|
||
|
||
Arguments:
|
||
|
||
UserAccountControl - The UserAccountControl for the user. The
|
||
USER_DONT_EXPIRE_PASSWORD bit is set if the password doesn't expire
|
||
for this user.
|
||
|
||
PasswordLastSet - Time when the password was last set for this user.
|
||
|
||
MaxPasswordAge - Maximum password age for any password in the domain.
|
||
|
||
|
||
Return Value:
|
||
|
||
Returns the time when the password for this user must change.
|
||
|
||
--*/
|
||
{
|
||
LARGE_INTEGER PasswordMustChange;
|
||
|
||
SAMTRACE("SampGetPasswordMustChange");
|
||
|
||
//
|
||
// If the password never expires for this user,
|
||
// return an infinitely large time.
|
||
//
|
||
|
||
if ( UserAccountControl & USER_DONT_EXPIRE_PASSWORD ) {
|
||
|
||
PasswordMustChange = SampWillNeverTime;
|
||
|
||
//
|
||
// If the password for this account is flagged to expire immediately,
|
||
// return a zero time time.
|
||
//
|
||
// Don't return the current time here. The callers clock might be a
|
||
// little off from ours.
|
||
//
|
||
|
||
} else if ( PasswordLastSet.QuadPart == SampHasNeverTime.QuadPart ) {
|
||
|
||
PasswordMustChange = SampHasNeverTime;
|
||
|
||
|
||
//
|
||
// Otherwise compute the expiration time as the time the password was
|
||
// last set plus the maximum age.
|
||
//
|
||
|
||
} else {
|
||
|
||
PasswordMustChange = SampAddDeltaTime(
|
||
PasswordLastSet,
|
||
MaxPasswordAge);
|
||
|
||
}
|
||
|
||
return PasswordMustChange;
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampComputePasswordExpired(
|
||
IN BOOLEAN PasswordExpired,
|
||
OUT PLARGE_INTEGER PasswordLastSet
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the correct value to set the PasswordLastSet time
|
||
to depending on whether the caller has requested the password to expire.
|
||
It does this by setting the PasswordLastSet time to be now (if it's
|
||
not expired) or to SampHasNeverTime (if it is expired).
|
||
|
||
Arguments:
|
||
|
||
PasswordExpired - TRUE if the password should be marked as expired.
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - the PasswordLastSet time has been set to indicate
|
||
whether or not the password is expired.
|
||
|
||
Errors as returned by NtQuerySystemTime.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampComputePasswordExpired");
|
||
|
||
//
|
||
// If immediate expiry is required - set this timestamp to the
|
||
// beginning of time. This will work if the domain enforces a
|
||
// maximum password age. We may have to add a separate flag to
|
||
// the database later if immediate expiry is required on a domain
|
||
// that doesn't enforce a maximum password age.
|
||
//
|
||
|
||
if (PasswordExpired) {
|
||
|
||
//
|
||
// Set password last changed at dawn of time
|
||
//
|
||
|
||
*PasswordLastSet = SampHasNeverTime;
|
||
NtStatus = STATUS_SUCCESS;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Set password last changed 'now'
|
||
//
|
||
|
||
NtStatus = NtQuerySystemTime( PasswordLastSet );
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampStorePasswordExpired(
|
||
IN PSAMP_OBJECT Context,
|
||
IN BOOLEAN PasswordExpired
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine marks the current password as expired, or not expired.
|
||
It does this by setting the PasswordLastSet time to be now (if it's
|
||
not expired) or to SampHasNeverTime (if it is expired).
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context.
|
||
|
||
PasswordExpired - TRUE if the password should be marked as expired.
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS - the PasswordLastSet time has been set to indicate
|
||
whether or not the password is expired.
|
||
|
||
Errors as returned by Samp{Retrieve|Replace}V1Fixed()
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
|
||
SAMTRACE("SampStorePasswordExpired");
|
||
|
||
//
|
||
// Get the V1aFixed info for the user
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
Context,
|
||
&V1aFixed
|
||
);
|
||
|
||
//
|
||
// Update the password-last-changed timestamp for the account
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus ) ) {
|
||
|
||
NtStatus = SampComputePasswordExpired(
|
||
PasswordExpired,
|
||
&V1aFixed.PasswordLastSet );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampReplaceUserV1aFixed(
|
||
Context,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampStoreUserPasswords(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PLM_OWF_PASSWORD LmOwfPassword,
|
||
IN BOOLEAN LmPasswordPresent,
|
||
IN PNT_OWF_PASSWORD NtOwfPassword,
|
||
IN BOOLEAN NtPasswordPresent,
|
||
IN BOOLEAN CheckHistory
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service updates the password for the specified user.
|
||
|
||
This involves encrypting the one-way-functions of both LM and NT
|
||
passwords with a suitable index and writing them into the registry.
|
||
|
||
This service checks the new password for legality including history
|
||
and UAS compatibilty checks - returns STATUS_PASSWORD_RESTRICTION if
|
||
any of these checks fail.
|
||
|
||
The password-last-changed time is updated.
|
||
|
||
THE CHANGE WILL BE ADDED TO THE CURRENT RXACT TRANSACTION.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context.
|
||
|
||
LmOwfPassword - The one-way-function of the LM password.
|
||
|
||
LmPasswordPresent - TRUE if the LmOwfPassword contains valid information.
|
||
|
||
NtOwfPassword - The one-way-function of the NT password.
|
||
|
||
NtPasswordPresent - TRUE if the NtOwfPassword contains valid information.
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The passwords have been updated.
|
||
|
||
STATUS_PASSWORD_RESTRICTION - The new password is not valid for
|
||
for this account at this time.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
ULONG ObjectRid = Context->TypeBody.User.Rid;
|
||
CRYPT_INDEX CryptIndex;
|
||
PSAMP_DEFINED_DOMAINS Domain;
|
||
UNICODE_STRING StringBuffer;
|
||
UNICODE_STRING NtOwfHistoryBuffer;
|
||
UNICODE_STRING LmOwfHistoryBuffer;
|
||
ENCRYPTED_LM_OWF_PASSWORD EncryptedLmOwfPassword;
|
||
ENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
BOOLEAN NtPasswordNull, LmPasswordNull;
|
||
|
||
SAMTRACE("SampStoreUserPasswords");
|
||
|
||
//
|
||
// Get the V1aFixed info for the user
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
Context,
|
||
&V1aFixed
|
||
);
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
return (NtStatus);
|
||
}
|
||
|
||
//
|
||
// Get a pointer to the in-memory domain info
|
||
//
|
||
|
||
Domain = &SampDefinedDomains[ Context->DomainIndex ];
|
||
|
||
|
||
|
||
|
||
//
|
||
// Check for a LM Owf of a NULL password.
|
||
//
|
||
|
||
if (LmPasswordPresent) {
|
||
LmPasswordNull = RtlEqualNtOwfPassword(LmOwfPassword, &SampNullLmOwfPassword);
|
||
}
|
||
|
||
//
|
||
// Check for a NT Owf of a NULL password
|
||
//
|
||
|
||
if (NtPasswordPresent) {
|
||
NtPasswordNull = RtlEqualNtOwfPassword(NtOwfPassword, &SampNullNtOwfPassword);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Check password against restrictions if this isn't a trusted client
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus) && !Context->TrustedClient) {
|
||
|
||
//
|
||
// If we have neither an NT or LM password, check it's allowed
|
||
//
|
||
|
||
if ( ((!LmPasswordPresent) || LmPasswordNull) &&
|
||
((!NtPasswordPresent) || NtPasswordNull) ) {
|
||
|
||
if ( (!(V1aFixed.UserAccountControl & USER_PASSWORD_NOT_REQUIRED))
|
||
&& (Domain->UnmodifiedFixed.MinPasswordLength > 0) ) {
|
||
|
||
NtStatus = STATUS_PASSWORD_RESTRICTION;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// If we have a complex NT password (no LM equivalent), check it's allowed
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if ((!LmPasswordPresent || LmPasswordNull) &&
|
||
(NtPasswordPresent && !NtPasswordNull) ) {
|
||
|
||
if (Domain->UnmodifiedFixed.UasCompatibilityRequired) {
|
||
|
||
NtStatus = STATUS_PASSWORD_RESTRICTION;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Reencrypt both OWFs with the key for this user
|
||
// so they can be stored on disk
|
||
//
|
||
// Note we encrypt the NULL OWF if we do not have a
|
||
// a particular OWF. This is so we always have something
|
||
// to add to the password history.
|
||
//
|
||
|
||
//
|
||
// We'll use the account rid as the encryption index
|
||
//
|
||
|
||
ASSERT(sizeof(ObjectRid) == sizeof(CryptIndex));
|
||
CryptIndex = ObjectRid;
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlEncryptLmOwfPwdWithIndex(
|
||
LmPasswordPresent ? LmOwfPassword :
|
||
&SampNullLmOwfPassword,
|
||
&CryptIndex,
|
||
&EncryptedLmOwfPassword
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlEncryptNtOwfPwdWithIndex(
|
||
NtPasswordPresent ? NtOwfPassword :
|
||
&SampNullNtOwfPassword,
|
||
&CryptIndex,
|
||
&EncryptedNtOwfPassword
|
||
);
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Check password against password history IF client isn't trusted.
|
||
// If client is trusted, it's not the user changing a password but
|
||
// perhaps replication resetting the password from another controller,
|
||
// and the password may well be in the password history but we don't
|
||
// want to return error.
|
||
//
|
||
// Note we don't check NULL passwords against history
|
||
//
|
||
|
||
NtOwfHistoryBuffer.Buffer = NULL;
|
||
NtOwfHistoryBuffer.MaximumLength = NtOwfHistoryBuffer.Length = 0;
|
||
|
||
LmOwfHistoryBuffer.Buffer = NULL;
|
||
LmOwfHistoryBuffer.MaximumLength = LmOwfHistoryBuffer.Length = 0;
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus) && !Context->TrustedClient) {
|
||
|
||
//
|
||
// Always go get the existing password history.
|
||
// We'll use these history buffers when we save the new history
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_LM_PWD_HISTORY,
|
||
TRUE, // Make copy
|
||
&LmOwfHistoryBuffer
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_NT_PWD_HISTORY,
|
||
TRUE, // Make copy
|
||
&NtOwfHistoryBuffer
|
||
);
|
||
}
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus) && LmPasswordPresent && !LmPasswordNull) {
|
||
|
||
NtStatus = SampCheckPasswordHistory(
|
||
&EncryptedLmOwfPassword,
|
||
ENCRYPTED_LM_OWF_PASSWORD_LENGTH,
|
||
Domain->UnmodifiedFixed.PasswordHistoryLength,
|
||
SAMP_USER_LM_PWD_HISTORY,
|
||
Context,
|
||
CheckHistory,
|
||
&LmOwfHistoryBuffer
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus) && NtPasswordPresent && !NtPasswordNull) {
|
||
|
||
NtStatus = SampCheckPasswordHistory(
|
||
&EncryptedNtOwfPassword,
|
||
ENCRYPTED_NT_OWF_PASSWORD_LENGTH,
|
||
Domain->UnmodifiedFixed.PasswordHistoryLength,
|
||
SAMP_USER_NT_PWD_HISTORY,
|
||
Context,
|
||
CheckHistory,
|
||
&NtOwfHistoryBuffer
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// if (NT_SUCCESS(NtStatus ) ) {
|
||
//
|
||
// //
|
||
// // If the filter DLL was loaded, get its blessing of this
|
||
// // password
|
||
// //
|
||
//
|
||
// UNICODE_STRING AccountName, FullName;
|
||
//
|
||
// if (SampPasswordFilterDllRoutine != NULL) {
|
||
//
|
||
//
|
||
// UNICODE_STRING AccountName,
|
||
// FullName;
|
||
//
|
||
// //
|
||
// // Get the account name and full name
|
||
// //
|
||
//
|
||
// NtStatus = SampGetUnicodeStringAttribute(
|
||
// Context,
|
||
// SAMP_USER_ACCOUNT_NAME,
|
||
// TRUE, // Make copy
|
||
// &AccountName
|
||
// );
|
||
//
|
||
// if (NT_SUCCESS(NtStatus)) {
|
||
//
|
||
// NtStatus = SampGetUnicodeStringAttribute(
|
||
// Context,
|
||
// SAMP_USER_FULL_NAME,
|
||
// TRUE, // Make copy
|
||
// &FullName
|
||
// );
|
||
//
|
||
// if (NT_SUCCESS(NtStatus)) {
|
||
//
|
||
// //
|
||
// // now see what the filter dll thinks of this password
|
||
// //
|
||
//
|
||
// NtStatus = (SampPasswordFilterDllRoutine)(
|
||
// &AccountName,
|
||
// &FullName,
|
||
// LmOwfPassword,
|
||
// LmPasswordPresent,
|
||
// NtOwfPassword,
|
||
// NtPasswordPresent
|
||
// );
|
||
//
|
||
// SampFreeUnicodeString(&FullName);
|
||
// }
|
||
//
|
||
// SampFreeUnicodeString(&AccountName);
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus ) ) {
|
||
|
||
//
|
||
// Write the encrypted LM OWF password into the database
|
||
//
|
||
|
||
if (!LmPasswordPresent || LmPasswordNull) {
|
||
StringBuffer.Buffer = NULL;
|
||
StringBuffer.Length = 0;
|
||
} else {
|
||
StringBuffer.Buffer = (PWCHAR)&EncryptedLmOwfPassword;
|
||
StringBuffer.Length = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
|
||
}
|
||
StringBuffer.MaximumLength = StringBuffer.Length;
|
||
|
||
|
||
//
|
||
// Write the encrypted LM OWF password into the registry
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_DBCS_PWD,
|
||
&StringBuffer
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus ) ) {
|
||
|
||
//
|
||
// Write the encrypted NT OWF password into the database
|
||
//
|
||
|
||
if (!NtPasswordPresent) {
|
||
StringBuffer.Buffer = NULL;
|
||
StringBuffer.Length = 0;
|
||
} else {
|
||
StringBuffer.Buffer = (PWCHAR)&EncryptedNtOwfPassword;
|
||
StringBuffer.Length = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
|
||
}
|
||
StringBuffer.MaximumLength = StringBuffer.Length;
|
||
|
||
|
||
//
|
||
// Write the encrypted NT OWF password into the registry
|
||
//
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_UNICODE_PWD,
|
||
&StringBuffer
|
||
);
|
||
}
|
||
|
||
//
|
||
// Update the password history for this account.
|
||
//
|
||
// If both passwords are NULL then don't bother adding
|
||
// them to the history. Note that if either is non-NULL
|
||
// we add both. This is to avoid the weird case where a user
|
||
// changes password many times from a LM machine, then tries
|
||
// to change password from an NT machine and is told they
|
||
// cannot use the password they last set from NT (possibly
|
||
// many years ago.)
|
||
//
|
||
// Also, don't bother with the password history if the client is
|
||
// trusted. Trusted clients will set the history via SetPrivateData().
|
||
// Besides, we didn't get the old history buffer in the trusted
|
||
// client case above.
|
||
//
|
||
|
||
if ( (NT_SUCCESS(NtStatus)) && (!Context->TrustedClient) ) {
|
||
|
||
if ((LmPasswordPresent && !LmPasswordNull) ||
|
||
(NtPasswordPresent && !NtPasswordNull)) {
|
||
|
||
NtStatus = SampAddPasswordHistory(
|
||
Context,
|
||
SAMP_USER_LM_PWD_HISTORY,
|
||
&LmOwfHistoryBuffer,
|
||
&EncryptedLmOwfPassword,
|
||
ENCRYPTED_LM_OWF_PASSWORD_LENGTH,
|
||
Domain->UnmodifiedFixed.PasswordHistoryLength
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampAddPasswordHistory(
|
||
Context,
|
||
SAMP_USER_NT_PWD_HISTORY,
|
||
&NtOwfHistoryBuffer,
|
||
&EncryptedNtOwfPassword,
|
||
ENCRYPTED_NT_OWF_PASSWORD_LENGTH,
|
||
Domain->UnmodifiedFixed.PasswordHistoryLength
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Clean up our history buffers
|
||
//
|
||
|
||
if (NtOwfHistoryBuffer.Buffer != NULL ) {
|
||
MIDL_user_free(NtOwfHistoryBuffer.Buffer );
|
||
}
|
||
if (LmOwfHistoryBuffer.Buffer != NULL ) {
|
||
MIDL_user_free(LmOwfHistoryBuffer.Buffer );
|
||
}
|
||
|
||
return(NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserPasswords(
|
||
IN PSAMP_OBJECT Context,
|
||
OUT PLM_OWF_PASSWORD LmOwfPassword,
|
||
OUT PBOOLEAN LmPasswordNonNull,
|
||
OUT PNT_OWF_PASSWORD NtOwfPassword,
|
||
OUT PBOOLEAN NtPasswordPresent,
|
||
OUT PBOOLEAN NtPasswordNonNull
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service retrieves the stored OWF passwords for a user.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context.
|
||
|
||
LmOwfPassword - The one-way-function of the LM password is returned here.
|
||
|
||
LmPasswordNonNull - TRUE if the LmOwfPassword is not the well-known
|
||
OWF of a NULL password
|
||
|
||
NtOwfPassword - The one-way-function of the NT password is returned here.
|
||
|
||
NtPasswordPresent - TRUE if the NtOwfPassword contains valid information.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The passwords were retrieved successfully.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
ULONG ObjectRid = Context->TypeBody.User.Rid;
|
||
UNICODE_STRING StringBuffer;
|
||
CRYPT_INDEX CryptIndex;
|
||
|
||
SAMTRACE("SampRetrieveUserPasswords");
|
||
|
||
//
|
||
// The OWF passwords are encrypted with the account index in the registry
|
||
// Setup the key we'll use for decryption.
|
||
//
|
||
|
||
ASSERT(sizeof(ObjectRid) == sizeof(CryptIndex));
|
||
CryptIndex = ObjectRid;
|
||
|
||
|
||
|
||
//
|
||
// Read the encrypted LM OWF password from the database
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_DBCS_PWD,
|
||
TRUE, // Make copy
|
||
&StringBuffer
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
return (NtStatus);
|
||
}
|
||
|
||
//
|
||
// Check it is in the expected form
|
||
//
|
||
|
||
ASSERT( (StringBuffer.Length == 0) ||
|
||
(StringBuffer.Length == ENCRYPTED_LM_OWF_PASSWORD_LENGTH));
|
||
|
||
//
|
||
// Determine if there is an LM password.
|
||
//
|
||
|
||
*LmPasswordNonNull = (BOOLEAN)(StringBuffer.Length != 0);
|
||
|
||
//
|
||
// Decrypt the encrypted LM Owf Password
|
||
//
|
||
|
||
if (*LmPasswordNonNull) {
|
||
|
||
NtStatus = RtlDecryptLmOwfPwdWithIndex(
|
||
(PENCRYPTED_LM_OWF_PASSWORD)StringBuffer.Buffer,
|
||
&CryptIndex,
|
||
LmOwfPassword
|
||
);
|
||
} else {
|
||
|
||
//
|
||
// Fill in the NULL password for caller convenience
|
||
//
|
||
|
||
*LmOwfPassword = SampNullLmOwfPassword;
|
||
}
|
||
|
||
|
||
//
|
||
// Free up the returned string buffer
|
||
//
|
||
|
||
SampFreeUnicodeString(&StringBuffer);
|
||
|
||
|
||
//
|
||
// Check if the decryption failed
|
||
//
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
return (NtStatus);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Read the encrypted NT OWF password from the database
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_UNICODE_PWD,
|
||
TRUE, // Make copy
|
||
&StringBuffer
|
||
);
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
return (NtStatus);
|
||
}
|
||
|
||
//
|
||
// Check it is in the expected form
|
||
//
|
||
|
||
ASSERT( (StringBuffer.Length == 0) ||
|
||
(StringBuffer.Length == ENCRYPTED_NT_OWF_PASSWORD_LENGTH));
|
||
|
||
//
|
||
// Determine if there is an Nt password.
|
||
//
|
||
|
||
*NtPasswordPresent = (BOOLEAN)(StringBuffer.Length != 0);
|
||
|
||
//
|
||
// Decrypt the encrypted NT Owf Password
|
||
//
|
||
|
||
if (*NtPasswordPresent) {
|
||
|
||
NtStatus = RtlDecryptNtOwfPwdWithIndex(
|
||
(PENCRYPTED_NT_OWF_PASSWORD)StringBuffer.Buffer,
|
||
&CryptIndex,
|
||
NtOwfPassword
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
*NtPasswordNonNull = (BOOLEAN)!RtlEqualNtOwfPassword(
|
||
NtOwfPassword,
|
||
&SampNullNtOwfPassword
|
||
);
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Fill in the NULL password for caller convenience
|
||
//
|
||
|
||
*NtOwfPassword = SampNullNtOwfPassword;
|
||
*NtPasswordNonNull = FALSE;
|
||
}
|
||
|
||
//
|
||
// Free up the returned string buffer
|
||
//
|
||
|
||
SampFreeUnicodeString(&StringBuffer);
|
||
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserMembership(
|
||
IN PSAMP_OBJECT UserContext,
|
||
IN BOOLEAN MakeCopy,
|
||
OUT PULONG MembershipCount,
|
||
OUT PGROUP_MEMBERSHIP *Membership OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service retrieves the number of groups a user is a member of.
|
||
If desired, it will also retrieve an array of RIDs and attributes
|
||
of the groups the user is a member of.
|
||
|
||
|
||
Arguments:
|
||
|
||
UserContext - User context block
|
||
|
||
MakeCopy - If FALSE, the Membership pointer returned refers to the
|
||
in-memory data for the user. This is only valid as long
|
||
as the user context is valid.
|
||
If TRUE, memory is allocated and the membership list copied
|
||
into it. This buffer should be freed using MIDL_user_free.
|
||
|
||
MembershipCount - Receives the number of groups the user is a member of.
|
||
|
||
Membership - (Otional) Receives a pointer to a buffer containing an array
|
||
of group Relative IDs. If this value is NULL, then this information
|
||
is not returned. The returned buffer is allocated using
|
||
MIDL_user_allocate() and must be freed using MIDL_user_free() when
|
||
no longer needed.
|
||
|
||
If MakeCopy = TRUE, the membership buffer returned has extra space
|
||
allocated at the end of it for one more membership entry.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
|
||
information to be returned in.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
SampGetLargeIntArrayAttribute()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
PGROUP_MEMBERSHIP MemberArray;
|
||
ULONG MemberCount;
|
||
|
||
SAMTRACE("SampRetrieveUserMembership");
|
||
|
||
|
||
NtStatus = SampGetLargeIntArrayAttribute(
|
||
UserContext,
|
||
SAMP_USER_GROUPS,
|
||
FALSE, //Reference data directly.
|
||
(PLARGE_INTEGER *)&MemberArray,
|
||
&MemberCount
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Fill in return info
|
||
//
|
||
|
||
*MembershipCount = MemberCount;
|
||
|
||
if (Membership != NULL) {
|
||
|
||
if (MakeCopy) {
|
||
|
||
//
|
||
// Allocate a buffer large enough to hold the existing
|
||
// membership data and one more and copy data into it.
|
||
//
|
||
|
||
ULONG BytesNow = (*MembershipCount) * sizeof(GROUP_MEMBERSHIP);
|
||
ULONG BytesRequired = BytesNow + sizeof(GROUP_MEMBERSHIP);
|
||
|
||
*Membership = MIDL_user_allocate(BytesRequired);
|
||
|
||
if (*Membership == NULL) {
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
} else {
|
||
RtlCopyMemory(*Membership, MemberArray, BytesNow);
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Reference the data directly
|
||
//
|
||
|
||
*Membership = (PGROUP_MEMBERSHIP)MemberArray;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampReplaceUserMembership(
|
||
IN PSAMP_OBJECT UserContext,
|
||
IN ULONG MembershipCount,
|
||
IN PGROUP_MEMBERSHIP Membership
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service sets the groups a user is a member of.
|
||
|
||
The information is updated in the in-memory copy of the user's data only.
|
||
The data is not written out by this routine.
|
||
|
||
|
||
Arguments:
|
||
|
||
UserContext - User context block
|
||
|
||
MembershipCount - The number of groups the user is a member of.
|
||
|
||
Membership - A pointer to a buffer containing an array of group
|
||
membership structures. May be NULL if membership count is zero.
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been set.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
SampSetUlongArrayAttribute()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampReplaceUserMembership");
|
||
|
||
NtStatus = SampSetLargeIntArrayAttribute(
|
||
UserContext,
|
||
SAMP_USER_GROUPS,
|
||
(PLARGE_INTEGER)Membership,
|
||
MembershipCount
|
||
);
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserLogonHours(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PLOGON_HOURS LogonHours
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service retrieves a user's logon hours from the registry.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context whose logon hours are
|
||
to be retrieved.
|
||
|
||
LogonHours - Receives the logon hours information. If necessary, a buffer
|
||
containing the logon time restriction bitmap will be allocated using
|
||
MIDL_user_allocate().
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
|
||
information to be returned in.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
NtQueryValueKey()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampRetrieveUserLogonHours");
|
||
|
||
NtStatus = SampGetLogonHoursAttribute(
|
||
Context,
|
||
SAMP_USER_LOGON_HOURS,
|
||
TRUE, // Make copy
|
||
LogonHours
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//////////////////////////////// TEMPORARY MIDL WORKAROUND ///////////
|
||
///////////
|
||
if (LogonHours->LogonHours == NULL) { ///////////
|
||
///////////
|
||
LogonHours->UnitsPerWeek = SAM_HOURS_PER_WEEK; ///////////
|
||
LogonHours->LogonHours = MIDL_user_allocate( 21 ); ///////////
|
||
{ ///////////
|
||
ULONG ijk; ///////////
|
||
for ( ijk=0; ijk<21; ijk++ ) { ///////////
|
||
LogonHours->LogonHours[ijk] = 0xff; ///////////
|
||
} ///////////
|
||
} ///////////
|
||
} ///////////
|
||
///////////
|
||
//////////////////////////////// TEMPORARY MIDL WORKAROUND ///////////
|
||
}
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampReplaceUserLogonHours(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PLOGON_HOURS LogonHours
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service replaces a user's logon hours in the registry.
|
||
|
||
THIS IS DONE BY ADDING AN ACTION TO THE CURRENT RXACT TRANSACTION.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context whose logon hours are
|
||
to be replaced.
|
||
|
||
LogonHours - Provides the new logon hours.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
|
||
SAMTRACE("SampReplaceUserLogonHours");
|
||
|
||
if ( LogonHours->UnitsPerWeek > SAM_MINUTES_PER_WEEK ) {
|
||
return(STATUS_INVALID_PARAMETER);
|
||
}
|
||
|
||
|
||
NtStatus = SampSetLogonHoursAttribute(
|
||
Context,
|
||
SAMP_USER_LOGON_HOURS,
|
||
LogonHours
|
||
);
|
||
|
||
return( NtStatus );
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampAssignPrimaryGroup(
|
||
IN PSAMP_OBJECT Context,
|
||
IN ULONG GroupRid
|
||
)
|
||
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service ensures a user is a member of the specified group.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user account context whose primary group is
|
||
being changed.
|
||
|
||
GroupRid - The RID of the group being assigned as primary group.
|
||
The user must be a member of this group.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated to perform
|
||
the operation.
|
||
|
||
STATUS_MEMBER_NOT_IN_GROUP - The user is not a member of the specified
|
||
group.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
SampRetrieveUserMembership()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
ULONG MembershipCount, i;
|
||
PGROUP_MEMBERSHIP Membership;
|
||
BOOLEAN Member = FALSE;
|
||
|
||
SAMTRACE("SampAssignPrimaryGroup");
|
||
|
||
if (IsDsObject(Context))
|
||
{
|
||
NtStatus = SampRetrieveUserMembership(
|
||
Context,
|
||
FALSE, // Make copy
|
||
&MembershipCount,
|
||
&Membership
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
|
||
for ( i=0; i<MembershipCount; i++) {
|
||
if (GroupRid == Membership[i].RelativeId) {
|
||
NtStatus = STATUS_SUCCESS;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Query User Membership using Davestr Call.
|
||
// and do things
|
||
//
|
||
|
||
NtStatus = STATUS_NOT_IMPLEMENTED;
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
// //
|
||
// Services Provided for use by other SAM modules //
|
||
// //
|
||
///////////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserV1aFixed(
|
||
IN PSAMP_OBJECT UserContext,
|
||
OUT PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service retrieves the V1 fixed length information related to
|
||
a specified User.
|
||
|
||
It updates the ACCOUNT_AUTO_LOCKED flag in the AccountControl field
|
||
as appropriate while retrieving the data.
|
||
|
||
|
||
Arguments:
|
||
|
||
UserContext - User context handle
|
||
|
||
V1aFixed - Points to a buffer into which V1_FIXED information is to be
|
||
retrieved.
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
V1aFixed - Is a buffer into which the information is to be returned.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
SampGetFixedAttributes()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
PVOID FixedData;
|
||
BOOLEAN IgnoreState;
|
||
|
||
SAMTRACE("SampRetrieveUserV1aFixed");
|
||
|
||
|
||
NtStatus = SampGetFixedAttributes(
|
||
UserContext,
|
||
FALSE, // Don't copy
|
||
&FixedData
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
|
||
//
|
||
// Copy data into return buffer
|
||
//
|
||
|
||
RtlMoveMemory(
|
||
V1aFixed,
|
||
FixedData,
|
||
sizeof(SAMP_V1_0A_FIXED_LENGTH_USER)
|
||
);
|
||
|
||
//
|
||
// Update the account lockout flag (might need to be turned off)
|
||
//
|
||
|
||
SampUpdateAccountLockedOutFlag( UserContext,
|
||
V1aFixed,
|
||
&IgnoreState );
|
||
|
||
}
|
||
|
||
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SampRetrieveUserGroupAttribute(
|
||
IN ULONG UserRid,
|
||
IN ULONG GroupRid,
|
||
OUT PULONG Attribute
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service retrieves the Attribute of the specified group as assigned
|
||
to the specified user account. This routine is used by group apis that
|
||
don't have a user context available.
|
||
|
||
THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
|
||
|
||
Arguments:
|
||
|
||
UserRid - The relative ID of the user the group is assigned to.
|
||
|
||
GroupRid - The relative ID of the assigned group.
|
||
|
||
Attribute - Receives the Attributes of the group as they are assigned
|
||
to the user.
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
STATUS_INTERNAL_DB_CORRUPTION - The user does not exist or the group
|
||
was not in the user's list of memberships.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
NtQueryValueKey()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus;
|
||
PSAMP_OBJECT UserContext;
|
||
ULONG MembershipCount;
|
||
PGROUP_MEMBERSHIP Membership;
|
||
ULONG i;
|
||
BOOLEAN AttributeFound;
|
||
|
||
SAMTRACE("SampRetrieveUserGroupAttribute");
|
||
|
||
|
||
//
|
||
// Get a context handle for the user
|
||
//
|
||
|
||
NtStatus = SampCreateAccountContext(
|
||
SampUserObjectType,
|
||
UserRid,
|
||
TRUE, // We're trusted
|
||
TRUE, // Account exists
|
||
&UserContext
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Now we have a user context, get the user's group/alias membership
|
||
//
|
||
|
||
if (IsDsObject(UserContext))
|
||
{
|
||
//
|
||
// User is DS Object, then hardwire the attribute to 0
|
||
//
|
||
|
||
*Attribute = 0;
|
||
}
|
||
else
|
||
{
|
||
NtStatus = SampRetrieveUserMembership(
|
||
UserContext,
|
||
FALSE, // Make copy
|
||
&MembershipCount,
|
||
&Membership
|
||
);
|
||
|
||
//
|
||
// Search the list of groups for a match and return
|
||
// the corresponding attribute.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
AttributeFound = FALSE;
|
||
for ( i=0; (i<MembershipCount && !AttributeFound); i++) {
|
||
if (GroupRid == Membership[i].RelativeId) {
|
||
(*Attribute) = Membership[i].Attributes;
|
||
AttributeFound = TRUE;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Clean up the user context
|
||
//
|
||
|
||
SampDeleteContext(UserContext);
|
||
}
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus) && !AttributeFound) {
|
||
NtStatus = STATUS_INTERNAL_DB_CORRUPTION;
|
||
}
|
||
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SampAddGroupToUserMembership(
|
||
IN ULONG GroupRid,
|
||
IN ULONG Attributes,
|
||
IN ULONG UserRid,
|
||
IN SAMP_MEMBERSHIP_DELTA AdminGroup,
|
||
IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
|
||
OUT PBOOLEAN UserActive
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service adds the specified group to the user's membership
|
||
list. It is not assumed that the caller knows anything about
|
||
the target user. In particular, the caller doesn't know whether
|
||
the user exists or not, nor whether the user is already a member
|
||
of the group.
|
||
|
||
If the GroupRid is DOMAIN_GROUP_RID_ADMINS, then this service
|
||
will also indicate whether the user account is currently active.
|
||
|
||
THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
|
||
|
||
Arguments:
|
||
|
||
GroupRid - The relative ID of the group.
|
||
|
||
Attributes - The group attributes as the group is assigned to the
|
||
user.
|
||
|
||
UserRid - The relative ID of the user.
|
||
|
||
AdminGroup - Indicates whether the group the user is being
|
||
added to is an administrator group (that is, directly
|
||
or indirectly a member of the Administrators alias).
|
||
|
||
OperatorGroup - Indicates whether the group the user is being
|
||
added to is an operator group (that is, directly
|
||
or indirectly a member of the Account Operators, Print
|
||
Operators, Backup Operators, or Server Operators aliases)
|
||
|
||
UserActive - is the address of a BOOLEAN to be set to indicate
|
||
whether the user account is currently active. TRUE indicates
|
||
the account is active. This value will only be set if the
|
||
GroupRid is DOMAIN_GROUP_RID_ADMINS.
|
||
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been updated and added to the
|
||
RXACT.
|
||
|
||
STATUS_NO_SUCH_USER - The user does not exist.
|
||
|
||
STATUS_MEMBER_IN_GROUP - The user is already a member of the
|
||
specified group.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
NtQueryValueKey()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
PSAMP_OBJECT UserContext;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
ULONG MembershipCount;
|
||
PGROUP_MEMBERSHIP Membership;
|
||
ULONG i;
|
||
|
||
SAMTRACE("SampAddGroupToUserMembership");
|
||
|
||
//
|
||
// Get a context handle for the user
|
||
//
|
||
|
||
NtStatus = SampCreateAccountContext(
|
||
SampUserObjectType,
|
||
UserRid,
|
||
TRUE, // We're trusted
|
||
TRUE, // Account exists
|
||
&UserContext
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If this group is in the Administrators alias
|
||
// or we are the Domain Administrator group, then
|
||
// get the V1aFixed data.
|
||
//
|
||
|
||
if ((AdminGroup == AddToAdmin) || (OperatorGroup == AddToAdmin)) {
|
||
NtStatus = SampRetrieveUserV1aFixed(
|
||
UserContext,
|
||
&V1aFixed
|
||
);
|
||
}
|
||
|
||
//
|
||
// If necessary, return an indication as to whether this account
|
||
// is enabled or not.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
if (GroupRid == DOMAIN_GROUP_RID_ADMINS) {
|
||
|
||
ASSERT(AdminGroup == AddToAdmin); // Make sure we retrieved the V1aFixed
|
||
|
||
if ((V1aFixed.UserAccountControl & USER_ACCOUNT_DISABLED) == 0) {
|
||
(*UserActive) = TRUE;
|
||
} else {
|
||
(*UserActive) = FALSE;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If the user is being added to an ADMIN group, modify
|
||
// the user's ACLs so that account operators can once again
|
||
// alter the account. This will only occur if the user
|
||
// is no longer a member of any admin groups.
|
||
//
|
||
|
||
if ((AdminGroup == AddToAdmin) || (OperatorGroup == AddToAdmin)) {
|
||
NtStatus = SampChangeOperatorAccessToUser2(
|
||
UserContext,
|
||
&V1aFixed,
|
||
AdminGroup,
|
||
OperatorGroup
|
||
);
|
||
}
|
||
}
|
||
|
||
|
||
if ((NT_SUCCESS(NtStatus)) && (!IsDsObject(UserContext)))
|
||
{
|
||
|
||
//
|
||
// Get the user membership
|
||
// Note the returned buffer already includes space for
|
||
// an extra member. For DS case we do not maintain reverse
|
||
// membership
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserMembership(
|
||
UserContext,
|
||
TRUE, // Make copy
|
||
&MembershipCount,
|
||
&Membership
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// See if the user is already a member ...
|
||
//
|
||
|
||
for (i = 0; i<MembershipCount ; i++ ) {
|
||
if ( Membership[i].RelativeId == GroupRid )
|
||
{
|
||
NtStatus = STATUS_MEMBER_IN_GROUP;
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Add the groups's RID to the end.
|
||
//
|
||
|
||
Membership[MembershipCount].RelativeId = GroupRid;
|
||
Membership[MembershipCount].Attributes = Attributes;
|
||
MembershipCount += 1;
|
||
|
||
//
|
||
// Set the user's new membership
|
||
//
|
||
|
||
NtStatus = SampReplaceUserMembership(
|
||
UserContext,
|
||
MembershipCount,
|
||
Membership
|
||
);
|
||
}
|
||
|
||
//
|
||
// Free up the membership array
|
||
//
|
||
|
||
MIDL_user_free( Membership );
|
||
}
|
||
}
|
||
|
||
//
|
||
// Write out any changes to the user account
|
||
// Don't use the open key handle since we'll be deleting the context.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
|
||
}
|
||
|
||
//
|
||
// Clean up the user context
|
||
//
|
||
|
||
SampDeleteContext(UserContext);
|
||
}
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampRemoveMembershipUser(
|
||
IN ULONG GroupRid,
|
||
IN ULONG UserRid,
|
||
IN SAMP_MEMBERSHIP_DELTA AdminGroup,
|
||
IN SAMP_MEMBERSHIP_DELTA OperatorGroup,
|
||
OUT PBOOLEAN UserActive
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service removes the specified group from the user's membership
|
||
list. It is not assumed that the caller knows anything about
|
||
the target user. In particular, the caller doesn't know whether
|
||
the user exists or not, nor whether the user is really a member
|
||
of the group.
|
||
|
||
If the GroupRid is DOMAIN_GROUP_RID_ADMINS, then this service
|
||
will also indicate whether the user account is currently active.
|
||
|
||
THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
|
||
|
||
Arguments:
|
||
|
||
GroupRid - The relative ID of the group.
|
||
|
||
UserRid - The relative ID of the user.
|
||
|
||
AdminGroup - Indicates whether the group the user is being
|
||
removed from is an administrator group (that is, directly
|
||
or indirectly a member of the Administrators alias).
|
||
|
||
OperatorGroup - Indicates whether the group the user is being
|
||
added to is an operator group (that is, directly
|
||
or indirectly a member of the Account Operators, Print
|
||
Operators, Backup Operators, or Server Operators aliases)
|
||
|
||
UserActive - is the address of a BOOLEAN to be set to indicate
|
||
whether the user account is currently active. TRUE indicates
|
||
the account is active. This value will only be set if the
|
||
GroupRid is DOMAIN_GROUP_RID_ADMINS.
|
||
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been updated and added to the
|
||
RXACT.
|
||
|
||
STATUS_NO_SUCH_USER - The user does not exist.
|
||
|
||
STATUS_MEMBER_NOT_IN_GROUP - The user is not a member of the
|
||
specified group.
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
NtQueryValueKey()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
ULONG MembershipCount, i;
|
||
PGROUP_MEMBERSHIP MembershipArray;
|
||
SAMP_V1_0A_FIXED_LENGTH_USER V1aFixed;
|
||
PSAMP_OBJECT UserContext;
|
||
|
||
SAMTRACE("SampRemoveMembershipUser");
|
||
|
||
//
|
||
// Create a context for the user
|
||
//
|
||
|
||
NtStatus = SampCreateAccountContext(
|
||
SampUserObjectType,
|
||
UserRid,
|
||
TRUE, // Trusted client
|
||
TRUE, // Account exists
|
||
&UserContext
|
||
);
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
return(NtStatus);
|
||
}
|
||
|
||
//
|
||
// Get the v1 fixed information
|
||
// (contains primary group value and control flags)
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserV1aFixed( UserContext, &V1aFixed );
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If the user is being removed from an ADMIN group, modify
|
||
// the user's ACLs so that account operators can once again
|
||
// alter the account. This will only occur if the user
|
||
// is no longer a member of any admin groups.
|
||
//
|
||
|
||
if ((AdminGroup == RemoveFromAdmin) ||
|
||
(OperatorGroup == RemoveFromAdmin)) {
|
||
NtStatus = SampChangeOperatorAccessToUser2(
|
||
UserContext,
|
||
&V1aFixed,
|
||
AdminGroup,
|
||
OperatorGroup
|
||
);
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// If necessary, return an indication as to whether this account
|
||
// is enabled or not.
|
||
//
|
||
|
||
if (GroupRid == DOMAIN_GROUP_RID_ADMINS) {
|
||
|
||
if ((V1aFixed.UserAccountControl & USER_ACCOUNT_DISABLED) == 0) {
|
||
(*UserActive) = TRUE;
|
||
} else {
|
||
(*UserActive) = FALSE;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// See if this is the user's primary group...
|
||
//
|
||
|
||
if (GroupRid == V1aFixed.PrimaryGroupId) {
|
||
NtStatus = STATUS_MEMBERS_PRIMARY_GROUP;
|
||
}
|
||
|
||
|
||
|
||
if ((NT_SUCCESS(NtStatus)) && (!IsDsObject(UserContext)))
|
||
{
|
||
|
||
//
|
||
// Get the user membership, No reverse membership is stored for
|
||
// DS Objects
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserMembership(
|
||
UserContext,
|
||
TRUE, // Make copy
|
||
&MembershipCount,
|
||
&MembershipArray
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// See if the user is a member ...
|
||
//
|
||
|
||
NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
|
||
for (i = 0; i<MembershipCount ; i++ ) {
|
||
if ( MembershipArray[i].RelativeId == GroupRid )
|
||
{
|
||
NtStatus = STATUS_SUCCESS;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Replace the removed group information
|
||
// with the last entry's information.
|
||
//
|
||
|
||
MembershipCount -= 1;
|
||
if (MembershipCount > 0) {
|
||
MembershipArray[i].RelativeId =
|
||
MembershipArray[MembershipCount].RelativeId;
|
||
MembershipArray[i].Attributes =
|
||
MembershipArray[MembershipCount].Attributes;
|
||
}
|
||
|
||
//
|
||
// Update the object with the new information
|
||
//
|
||
|
||
NtStatus = SampReplaceUserMembership(
|
||
UserContext,
|
||
MembershipCount,
|
||
MembershipArray
|
||
);
|
||
}
|
||
|
||
//
|
||
// Free up the membership array
|
||
//
|
||
|
||
MIDL_user_free( MembershipArray );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Write out any changes to the user account
|
||
// Don't use the open key handle since we'll be deleting the context.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
|
||
}
|
||
|
||
|
||
//
|
||
// Clean up the user context
|
||
//
|
||
|
||
SampDeleteContext(UserContext);
|
||
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampSetGroupAttributesOfUser(
|
||
IN ULONG GroupRid,
|
||
IN ULONG Attributes,
|
||
IN ULONG UserRid
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service replaces the attributes of a group assigned to a
|
||
user.
|
||
|
||
The caller does not have to know whether the group is currently
|
||
assigned to the user.
|
||
|
||
THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
|
||
|
||
Arguments:
|
||
|
||
GroupRid - The relative ID of the group.
|
||
|
||
Attributes - The group attributes as the group is assigned to the
|
||
user.
|
||
|
||
UserRid - The relative ID of the user.
|
||
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been updated and added to the
|
||
RXACT.
|
||
|
||
STATUS_NO_SUCH_USER - The user does not exist.
|
||
|
||
STATUS_MEMBER_NOT_IN_GROUP - The user is not in the specified group.
|
||
|
||
|
||
Other status values that may be returned are those returned
|
||
by:
|
||
|
||
NtOpenKey()
|
||
NtQueryValueKey()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
PSAMP_OBJECT UserContext;
|
||
ULONG MembershipCount;
|
||
PGROUP_MEMBERSHIP Membership;
|
||
ULONG i;
|
||
|
||
SAMTRACE("SampSetGroupAttributesOfUser");
|
||
|
||
|
||
//
|
||
// Get a context handle for the user
|
||
//
|
||
|
||
NtStatus = SampCreateAccountContext(
|
||
SampUserObjectType,
|
||
UserRid,
|
||
TRUE, // We're trusted
|
||
TRUE, // Account exists
|
||
&UserContext
|
||
);
|
||
|
||
if ((NT_SUCCESS(NtStatus)) && (!IsDsObject(UserContext))) {
|
||
|
||
//
|
||
// Now we have a user context, get the user's group/alias membership
|
||
// For DS case this is a No Op
|
||
//
|
||
|
||
NtStatus = SampRetrieveUserMembership(
|
||
UserContext,
|
||
TRUE, // Make copy
|
||
&MembershipCount,
|
||
&Membership
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// See if the user is a member ...
|
||
//
|
||
|
||
NtStatus = STATUS_MEMBER_NOT_IN_GROUP;
|
||
for (i = 0; i<MembershipCount; i++ ) {
|
||
if ( Membership[i].RelativeId == GroupRid )
|
||
{
|
||
NtStatus = STATUS_SUCCESS;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Change the groups's attributes.
|
||
//
|
||
|
||
Membership[i].Attributes = Attributes;
|
||
|
||
//
|
||
// Update the user's membership
|
||
//
|
||
|
||
NtStatus = SampReplaceUserMembership(
|
||
UserContext,
|
||
MembershipCount,
|
||
Membership
|
||
);
|
||
}
|
||
|
||
//
|
||
// Free up the membership array
|
||
//
|
||
|
||
MIDL_user_free(Membership);
|
||
}
|
||
|
||
//
|
||
// Write out any changes to the user account
|
||
// Don't use the open key handle since we'll be deleting the context.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
|
||
}
|
||
|
||
//
|
||
// Clean up the user context
|
||
//
|
||
|
||
SampDeleteContext(UserContext);
|
||
}
|
||
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampDeleteUserKeys(
|
||
IN PSAMP_OBJECT Context
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service deletes all registry keys related to a User object.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the User context whose registry keys are
|
||
being deleted.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
|
||
Other status values that may be returned by:
|
||
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
ULONG Rid;
|
||
UNICODE_STRING AccountName, KeyName;
|
||
|
||
SAMTRACE("SampDeleteUserKeys");
|
||
|
||
|
||
Rid = Context->TypeBody.User.Rid;
|
||
|
||
|
||
|
||
|
||
//
|
||
// Decrement the User count
|
||
//
|
||
|
||
NtStatus = SampAdjustAccountCount(SampUserObjectType, FALSE );
|
||
|
||
|
||
|
||
|
||
//
|
||
// Delete the registry key that has the User's name to RID mapping.
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// Get the name
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
&AccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampBuildAccountKeyName(
|
||
SampUserObjectType,
|
||
&KeyName,
|
||
&AccountName
|
||
);
|
||
|
||
SampFreeUnicodeString( &AccountName );
|
||
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlAddActionToRXact(
|
||
SampRXactContext,
|
||
RtlRXactOperationDelete,
|
||
&KeyName,
|
||
0,
|
||
NULL,
|
||
0
|
||
);
|
||
SampFreeUnicodeString( &KeyName );
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
//
|
||
// Delete the attribute keys
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampDeleteAttributeKeys(
|
||
Context
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// Delete the RID key
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampBuildAccountSubKeyName(
|
||
SampUserObjectType,
|
||
&KeyName,
|
||
Rid,
|
||
NULL
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
|
||
NtStatus = RtlAddActionToRXact(
|
||
SampRXactContext,
|
||
RtlRXactOperationDelete,
|
||
&KeyName,
|
||
0,
|
||
NULL,
|
||
0
|
||
);
|
||
|
||
SampFreeUnicodeString( &KeyName );
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
return( NtStatus );
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampAddPasswordHistory(
|
||
IN PSAMP_OBJECT Context,
|
||
IN ULONG HistoryAttributeIndex,
|
||
IN PUNICODE_STRING NtOwfHistoryBuffer,
|
||
IN PVOID EncryptedPassword,
|
||
IN ULONG EncryptedPasswordLength,
|
||
IN USHORT PasswordHistoryLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service adds a password to the given user's password history.
|
||
It will work for either NT or Lanman password histories.
|
||
|
||
This routine should only be called if the password is actually present.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - a pointer to the user context to which changes will be made.
|
||
|
||
HistoryAttributeIndex - the attribue index in the user context which
|
||
contains the password history.
|
||
|
||
NtOwfHistoryBuffer - A pointer to the current password history, as
|
||
it was retrieved from the disk - it's encrypted, and pretending
|
||
to be in the UNICODE_STRING format.
|
||
|
||
EncryptedPasswordLength - ENCRYPTED_NT_OWF_LENGTH or
|
||
ENCRYPTED_LM_OWF_LENGTH, depending on which type of password
|
||
history is being worked on.
|
||
|
||
PasswordHistoryLength - The PasswordHistoryLength for the user's
|
||
domain.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The given password was added to the password history.
|
||
|
||
STATUS_INSUFFICIENT_RESOURCES - The user's password history needs to
|
||
be expanded, but there isn't enough memory to do so.
|
||
|
||
Other errors from building the account subkey name or writing the
|
||
password history out to the registry.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus = STATUS_SUCCESS;
|
||
PCHAR OldBuffer;
|
||
|
||
SAMTRACE("SampAddPasswordHistory");
|
||
|
||
if ( ( NtOwfHistoryBuffer->Length / EncryptedPasswordLength ) <
|
||
( (ULONG)PasswordHistoryLength ) ) {
|
||
|
||
//
|
||
// Password history buffer can be expanded.
|
||
// Allocate a larger buffer, copy the old buffer to the new one
|
||
// while leaving room for the new password, and free the old
|
||
// buffer.
|
||
//
|
||
|
||
OldBuffer = (PCHAR)(NtOwfHistoryBuffer->Buffer);
|
||
|
||
NtOwfHistoryBuffer->Buffer = MIDL_user_allocate(
|
||
NtOwfHistoryBuffer->Length + EncryptedPasswordLength );
|
||
|
||
if ( NtOwfHistoryBuffer->Buffer == NULL ) {
|
||
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
NtOwfHistoryBuffer->Buffer = (PWSTR)OldBuffer;
|
||
|
||
} else {
|
||
|
||
RtlCopyMemory(
|
||
(PVOID)( (PCHAR)(NtOwfHistoryBuffer->Buffer) + EncryptedPasswordLength ),
|
||
(PVOID)OldBuffer,
|
||
NtOwfHistoryBuffer->Length );
|
||
|
||
MIDL_user_free( OldBuffer );
|
||
|
||
NtOwfHistoryBuffer->Length = (USHORT)(NtOwfHistoryBuffer->Length +
|
||
EncryptedPasswordLength);
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Password history buffer is at its maximum size, or larger (for
|
||
// this domain). If it's larger, cut it down to the current maximum.
|
||
//
|
||
|
||
if ( ( NtOwfHistoryBuffer->Length / EncryptedPasswordLength ) >
|
||
( (ULONG)PasswordHistoryLength ) ) {
|
||
|
||
//
|
||
// Password history is too large (the password history length must
|
||
// have been shortened recently).
|
||
// Set length to the proper value,
|
||
//
|
||
|
||
NtOwfHistoryBuffer->Length = (USHORT)(EncryptedPasswordLength *
|
||
PasswordHistoryLength);
|
||
}
|
||
|
||
//
|
||
// Password history buffer is full, at its maximum size.
|
||
// Move buffer contents right 16 bytes, which will lose the oldest
|
||
// password and make room for the new password at the beginning
|
||
// (left).
|
||
// Note that we CAN'T move anything if the password history size
|
||
// is 0. If it's 1, we could but no need since we'll overwrite
|
||
// it below.
|
||
//
|
||
|
||
if ( PasswordHistoryLength > 1 ) {
|
||
|
||
RtlMoveMemory(
|
||
(PVOID)( (PCHAR)(NtOwfHistoryBuffer->Buffer) + EncryptedPasswordLength ),
|
||
(PVOID)NtOwfHistoryBuffer->Buffer,
|
||
NtOwfHistoryBuffer->Length - EncryptedPasswordLength );
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Put the new encrypted OWF at the beginning of the password history
|
||
// buffer (unless, of course, the buffer size is 0), and write the password
|
||
// history to disk.
|
||
//
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
if ( PasswordHistoryLength > 0 ) {
|
||
|
||
RtlCopyMemory(
|
||
(PVOID)NtOwfHistoryBuffer->Buffer,
|
||
(PVOID)EncryptedPassword,
|
||
EncryptedPasswordLength );
|
||
}
|
||
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
Context,
|
||
HistoryAttributeIndex,
|
||
NtOwfHistoryBuffer
|
||
);
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampCheckPasswordHistory(
|
||
IN PVOID EncryptedPassword,
|
||
IN ULONG EncryptedPasswordLength,
|
||
IN USHORT PasswordHistoryLength,
|
||
IN ULONG HistoryAttributeIndex,
|
||
IN PSAMP_OBJECT Context,
|
||
IN BOOLEAN CheckHistory,
|
||
IN PUNICODE_STRING OwfHistoryBuffer
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This service takes the given password, and optionally checks it against the
|
||
password history on the disk. It returns a pointer to the password
|
||
history, which will later be passed to SampAddPasswordHistory().
|
||
|
||
This routine should only be called if the password is actually present.
|
||
|
||
|
||
Arguments:
|
||
|
||
EncryptedPassword - A pointer to the encrypted password that we're
|
||
looking for.
|
||
|
||
EncryptedPasswordLength - ENCRYPTED_NT_OWF_PASSWORD or
|
||
ENCRYPTED_LM_OWF_PASSWORD, depending on the type of password
|
||
history to be searched.
|
||
|
||
PasswordHistoryLength - the length of the password history for this
|
||
domain.
|
||
|
||
SubKeyName - a pointer to a unicode string that describes the name
|
||
of the password history to be read from the disk.
|
||
|
||
Context - a pointer to the user's context.
|
||
|
||
CheckHistory - If TRUE, the password is to be checked against
|
||
the history to see if it is already present and an error returned
|
||
if it is found. If FALSE, the password will not be checked, but a
|
||
pointer to the appropriate history buffer will still be returned
|
||
because the specified password will be added to the history via
|
||
SampAddPasswordHistory.
|
||
|
||
NOTE: The purpose of this flag is to allow Administrator to change
|
||
a user's password regardless of whether it is already in the history.
|
||
|
||
OwfHistoryBuffer - a pointer to a UNICODE_STRING which will be
|
||
used to point to the password history.
|
||
|
||
NOTE: The caller must free OwfHistoryBuffer.Buffer with
|
||
MIDL_user_free().
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The given password was not found in the password
|
||
history.
|
||
|
||
STATUS_PASSWORD_RESTRICTION - The given password was found in the
|
||
password history.
|
||
|
||
Other errors from reading the password history from disk.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS NtStatus = STATUS_SUCCESS;
|
||
PVOID PasswordHistoryEntry;
|
||
ULONG i = 0;
|
||
BOOLEAN OldPasswordFound = FALSE;
|
||
|
||
SAMTRACE("SampCheckPasswordHistory");
|
||
|
||
|
||
if ( ( PasswordHistoryLength > 0 ) && ( OwfHistoryBuffer->Length == 0 ) ) {
|
||
|
||
//
|
||
// Perhaps the domain's PasswordHistoryLength was raised from 0
|
||
// since the last time this user's password was changed. Try to
|
||
// put the current password (if non-null) in the password history.
|
||
//
|
||
|
||
UNICODE_STRING CurrentPassword;
|
||
USHORT PasswordAttributeIndex;
|
||
|
||
//
|
||
// Initialize the CurrentPassword buffer pointer to NULL (and the
|
||
// rest of the structure for consistency. The called routine
|
||
// SampGetUnicodeStringAttribute may perform a MIDL_user_allocate
|
||
// on a zero buffer length and cannot safely be changed as there are
|
||
// many callers. The semantics of a zero-length allocate call are
|
||
// not clear. Currently a pointer to a heap block is returned,
|
||
// but this might be changed to a NULL being returned.
|
||
//
|
||
|
||
CurrentPassword.Length = CurrentPassword.MaximumLength = 0;
|
||
CurrentPassword.Buffer = NULL;
|
||
|
||
|
||
if ( HistoryAttributeIndex == SAMP_USER_LM_PWD_HISTORY ) {
|
||
|
||
PasswordAttributeIndex = SAMP_USER_DBCS_PWD;
|
||
|
||
} else {
|
||
|
||
ASSERT( HistoryAttributeIndex == SAMP_USER_NT_PWD_HISTORY );
|
||
PasswordAttributeIndex = SAMP_USER_UNICODE_PWD;
|
||
}
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
PasswordAttributeIndex,
|
||
TRUE, // Make copy
|
||
&CurrentPassword
|
||
);
|
||
|
||
if ( ( NT_SUCCESS( NtStatus ) ) && ( CurrentPassword.Length != 0 ) ) {
|
||
|
||
ASSERT( (CurrentPassword.Length == ENCRYPTED_NT_OWF_PASSWORD_LENGTH) ||
|
||
(CurrentPassword.Length == ENCRYPTED_LM_OWF_PASSWORD_LENGTH) );
|
||
|
||
NtStatus = SampAddPasswordHistory(
|
||
Context,
|
||
HistoryAttributeIndex,
|
||
OwfHistoryBuffer,
|
||
CurrentPassword.Buffer,
|
||
CurrentPassword.Length,
|
||
PasswordHistoryLength
|
||
);
|
||
|
||
if ( NT_SUCCESS( NtStatus ) ) {
|
||
|
||
//
|
||
// Free the old password history, and re-read the
|
||
// altered password history from the disk.
|
||
//
|
||
|
||
MIDL_user_free( OwfHistoryBuffer->Buffer );
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
HistoryAttributeIndex,
|
||
TRUE, // Make copy
|
||
OwfHistoryBuffer
|
||
);
|
||
}
|
||
}
|
||
|
||
//
|
||
// If memory was allocated, free it.
|
||
//
|
||
|
||
if (CurrentPassword.Buffer != NULL) {
|
||
|
||
SampFreeUnicodeString( &CurrentPassword );
|
||
}
|
||
}
|
||
|
||
if ( !NT_SUCCESS( NtStatus ) ) {
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
//
|
||
// If requested, check the Password History to see if we can use this
|
||
// password. Compare the passed-in password to each of the entries in
|
||
// the password history.
|
||
//
|
||
|
||
if (CheckHistory) {
|
||
|
||
PasswordHistoryEntry = (PVOID)(OwfHistoryBuffer->Buffer);
|
||
|
||
while ( ( i < (ULONG)PasswordHistoryLength ) &&
|
||
( i < ( OwfHistoryBuffer->Length / EncryptedPasswordLength ) ) &&
|
||
( OldPasswordFound == FALSE ) ) {
|
||
|
||
if ( RtlCompareMemory(
|
||
EncryptedPassword,
|
||
PasswordHistoryEntry,
|
||
EncryptedPasswordLength ) == EncryptedPasswordLength ) {
|
||
|
||
OldPasswordFound = TRUE;
|
||
|
||
} else {
|
||
|
||
i++;
|
||
|
||
PasswordHistoryEntry = (PVOID)((PCHAR)(PasswordHistoryEntry) +
|
||
EncryptedPasswordLength );
|
||
}
|
||
}
|
||
|
||
if ( OldPasswordFound ) {
|
||
|
||
//
|
||
// We did find it in the password history, so return an appropriate
|
||
// error.
|
||
//
|
||
|
||
NtStatus = STATUS_PASSWORD_RESTRICTION;
|
||
}
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampMatchworkstation(
|
||
IN PUNICODE_STRING LogonWorkStation,
|
||
IN PUNICODE_STRING WorkStations
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Check if the given workstation is a member of the list of workstations
|
||
given.
|
||
|
||
|
||
Arguments:
|
||
|
||
LogonWorkStations - UNICODE name of the workstation that the user is
|
||
trying to log into.
|
||
|
||
WorkStations - API list of workstations that the user is allowed to
|
||
log into.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The user is allowed to log into the workstation.
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
PWCHAR WorkStationName;
|
||
UNICODE_STRING Unicode;
|
||
NTSTATUS NtStatus;
|
||
WCHAR Buffer[256];
|
||
USHORT LocalBufferLength = 256;
|
||
UNICODE_STRING WorkStationsListCopy;
|
||
BOOLEAN BufferAllocated = FALSE;
|
||
PWCHAR TmpBuffer;
|
||
|
||
SAMTRACE("SampMatchWorkstation");
|
||
|
||
//
|
||
// Local workstation is always allowed
|
||
// If WorkStations field is 0 everybody is allowed
|
||
//
|
||
|
||
if ( ( LogonWorkStation == NULL ) ||
|
||
( LogonWorkStation->Length == 0 ) ||
|
||
( WorkStations->Length == 0 ) ) {
|
||
|
||
return( STATUS_SUCCESS );
|
||
}
|
||
|
||
//
|
||
// Assume failure; change status only if we find the string.
|
||
//
|
||
|
||
NtStatus = STATUS_INVALID_WORKSTATION;
|
||
|
||
//
|
||
// WorkStationApiList points to our current location in the list of
|
||
// WorkStations.
|
||
//
|
||
|
||
if ( WorkStations->Length > LocalBufferLength ) {
|
||
|
||
WorkStationsListCopy.Buffer = RtlAllocateHeap( RtlProcessHeap(), 0, WorkStations->Length );
|
||
BufferAllocated = TRUE;
|
||
|
||
if ( WorkStationsListCopy.Buffer == NULL ) {
|
||
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
||
return( NtStatus );
|
||
}
|
||
|
||
WorkStationsListCopy.MaximumLength = WorkStations->Length;
|
||
|
||
} else {
|
||
|
||
WorkStationsListCopy.Buffer = Buffer;
|
||
WorkStationsListCopy.MaximumLength = LocalBufferLength;
|
||
}
|
||
|
||
RtlCopyUnicodeString( &WorkStationsListCopy, WorkStations );
|
||
ASSERT( WorkStationsListCopy.Length == WorkStations->Length );
|
||
|
||
//
|
||
// wcstok requires a string the first time it's called, and NULL
|
||
// for all subsequent calls. Use a temporary variable so we
|
||
// can do this.
|
||
//
|
||
|
||
TmpBuffer = WorkStationsListCopy.Buffer;
|
||
|
||
while( WorkStationName = wcstok(TmpBuffer, L",") ) {
|
||
|
||
TmpBuffer = NULL;
|
||
RtlInitUnicodeString( &Unicode, WorkStationName );
|
||
if (RtlEqualComputerName( &Unicode, LogonWorkStation )) {
|
||
NtStatus = STATUS_SUCCESS;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( BufferAllocated ) {
|
||
RtlFreeHeap( RtlProcessHeap(), 0, WorkStationsListCopy.Buffer );
|
||
}
|
||
|
||
return( NtStatus );
|
||
}
|
||
|
||
|
||
LARGE_INTEGER
|
||
SampAddDeltaTime(
|
||
IN LARGE_INTEGER Time,
|
||
IN LARGE_INTEGER DeltaTime
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This service adds a delta time to a time and limits the result to
|
||
the maximum legal absolute time value
|
||
|
||
Arguments:
|
||
|
||
Time - An absolute time
|
||
|
||
DeltaTime - A delta time
|
||
|
||
Return Value:
|
||
|
||
The time modified by delta time.
|
||
|
||
--*/
|
||
{
|
||
//
|
||
// Check the time and delta time aren't switched
|
||
//
|
||
|
||
SAMTRACE("SampAddDeleteTime");
|
||
|
||
ASSERT(!(Time.QuadPart < 0));
|
||
ASSERT(!(DeltaTime.QuadPart > 0));
|
||
|
||
try {
|
||
|
||
Time.QuadPart = (Time.QuadPart - DeltaTime.QuadPart);
|
||
|
||
} except(EXCEPTION_EXECUTE_HANDLER) {
|
||
|
||
return( SampWillNeverTime );
|
||
}
|
||
|
||
//
|
||
// Limit the resultant time to the maximum valid absolute time
|
||
//
|
||
|
||
if (Time.QuadPart < 0) {
|
||
Time = SampWillNeverTime;
|
||
}
|
||
|
||
return(Time);
|
||
}
|
||
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SampChangeUserAccountName(
|
||
IN PSAMP_OBJECT Context,
|
||
IN PUNICODE_STRING NewAccountName,
|
||
OUT PUNICODE_STRING OldAccountName
|
||
)
|
||
|
||
/*++
|
||
Routine Description:
|
||
|
||
This routine changes the account name of a user account.
|
||
|
||
THIS SERVICE MUST BE CALLED WITH THE TRANSACTION DOMAIN SET.
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the User context whose name is to be changed.
|
||
|
||
NewAccountName - New name to give this account
|
||
|
||
OldAccountName - old name is returned here. The buffer should be freed
|
||
by calling MIDL_user_free.
|
||
|
||
Return Value:
|
||
|
||
|
||
STATUS_SUCCESS - The information has been retrieved.
|
||
|
||
|
||
Other status values that may be returned by:
|
||
|
||
SampGetUnicodeStringAttribute()
|
||
SampSetUnicodeStringAttribute()
|
||
SampValidateAccountNameChange()
|
||
RtlAddActionToRXact()
|
||
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS NtStatus;
|
||
UNICODE_STRING KeyName;
|
||
|
||
SAMTRACE("SampChangeUserAccountName");
|
||
|
||
|
||
/////////////////////////////////////////////////////////////
|
||
// There are two copies of the name of each account. //
|
||
// one is under the DOMAIN\(domainName)\USER\NAMES key, //
|
||
// one is the value of the //
|
||
// DOMAIN\(DomainName)\USER\(rid)\NAME key //
|
||
/////////////////////////////////////////////////////////////
|
||
|
||
|
||
//
|
||
// Get the current name so we can delete the old Name->Rid
|
||
// mapping key.
|
||
//
|
||
|
||
NtStatus = SampGetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
TRUE, // Make copy
|
||
OldAccountName
|
||
);
|
||
|
||
//
|
||
// Make sure the name is valid and not already in use
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampValidateAccountNameChange(
|
||
NewAccountName,
|
||
OldAccountName
|
||
);
|
||
|
||
//
|
||
// Delete the old name key
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampBuildAccountKeyName(
|
||
SampUserObjectType,
|
||
&KeyName,
|
||
OldAccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = RtlAddActionToRXact(
|
||
SampRXactContext,
|
||
RtlRXactOperationDelete,
|
||
&KeyName,
|
||
0,
|
||
NULL,
|
||
0
|
||
);
|
||
SampFreeUnicodeString( &KeyName );
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
//
|
||
// Create the new Name->Rid mapping key
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampBuildAccountKeyName(
|
||
SampUserObjectType,
|
||
&KeyName,
|
||
NewAccountName
|
||
);
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
ULONG ObjectRid = Context->TypeBody.User.Rid;
|
||
|
||
NtStatus = RtlAddActionToRXact(
|
||
SampRXactContext,
|
||
RtlRXactOperationSetValue,
|
||
&KeyName,
|
||
ObjectRid,
|
||
(PVOID)NULL,
|
||
0
|
||
);
|
||
|
||
SampFreeUnicodeString( &KeyName );
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
//
|
||
// replace the account's name
|
||
//
|
||
|
||
if (NT_SUCCESS(NtStatus)) {
|
||
|
||
NtStatus = SampSetUnicodeStringAttribute(
|
||
Context,
|
||
SAMP_USER_ACCOUNT_NAME,
|
||
NewAccountName
|
||
);
|
||
}
|
||
|
||
//
|
||
// Free up the old account name if we failed
|
||
//
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
SampFreeUnicodeString( OldAccountName );
|
||
OldAccountName->Buffer = NULL;
|
||
}
|
||
|
||
}
|
||
|
||
|
||
return(NtStatus);
|
||
}
|
||
|
||
|
||
USHORT
|
||
SampQueryBadPasswordCount(
|
||
PSAMP_OBJECT UserContext,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is used to retrieve the effective BadPasswordCount
|
||
value of a user.
|
||
|
||
When querying BadPasswordCount, some quick
|
||
analysis has to be done. If the last bad password
|
||
was set more than LockoutObservationWindow time ago,
|
||
then we re-set the BadPasswordCount. Otherwise, we
|
||
return the current value.
|
||
|
||
|
||
NOTE: The V1aFixed data for the user object MUST be valid.
|
||
This routine does not retrieve the data from disk.
|
||
|
||
Arguments:
|
||
|
||
UserContext - Points to the object context block of the user whose
|
||
bad password count is to be returned.
|
||
|
||
V1aFixed - Points to a local copy of the user's V1aFixed data.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
The effective bad password count.
|
||
|
||
|
||
--*/
|
||
{
|
||
|
||
SAMTRACE("SampQueryBadPasswordCount");
|
||
|
||
if (SampStillInLockoutObservationWindow( UserContext, V1aFixed ) ) {
|
||
return(V1aFixed->BadPasswordCount);
|
||
}
|
||
|
||
return(0);
|
||
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
SampStillInLockoutObservationWindow(
|
||
PSAMP_OBJECT UserContext,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns a boolean indicating whether the provided user
|
||
account context is within an account lockout window or not.
|
||
|
||
An account lockout window is the time window starting at the
|
||
last time a bad password was provided in a logon attempt
|
||
(since the last valid logon) and extending for the duration of
|
||
time specified in the LockoutObservationWindow field of the
|
||
corresponding domain object.
|
||
|
||
BY DEFINITION, a user account that has zero bad passwords, is
|
||
NOT in an observation window.
|
||
|
||
NOTE: The V1aFixed data for the both the user and corresponding
|
||
domain objects MUST be valid. This routine does NOT retrieve
|
||
data from disk.
|
||
|
||
Arguments:
|
||
|
||
UserContext - Points to the user object context block.
|
||
|
||
V1aFixed - Points to a local copy of the user's V1aFixed data.
|
||
|
||
|
||
Return Value:
|
||
|
||
|
||
TRUE - the user is in a lockout observation window.
|
||
|
||
FALSE - the user is not in a lockout observation window.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
LARGE_INTEGER
|
||
WindowLength,
|
||
LastBadPassword,
|
||
CurrentTime,
|
||
EndOfWindow;
|
||
|
||
SAMTRACE("SampStillInLockoutObservationWindow");
|
||
|
||
|
||
if (V1aFixed->BadPasswordCount == 0) {
|
||
return(FALSE);
|
||
}
|
||
|
||
//
|
||
// At least one bad password.
|
||
// See if we are still in its observation window.
|
||
//
|
||
|
||
LastBadPassword = V1aFixed->LastBadPasswordTime;
|
||
ASSERT( LastBadPassword.HighPart >= 0 );
|
||
|
||
WindowLength =
|
||
SampDefinedDomains[UserContext->DomainIndex].CurrentFixed.LockoutObservationWindow;
|
||
ASSERT( WindowLength.HighPart <= 0 ); // Must be a delta time
|
||
|
||
|
||
NtStatus = NtQuerySystemTime( &CurrentTime );
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
|
||
//
|
||
// See if current time is outside the observation window.
|
||
// * you must subtract a delta time from an absolute time*
|
||
// * to end up with a time in the future. *
|
||
//
|
||
|
||
EndOfWindow = SampAddDeltaTime( LastBadPassword, WindowLength );
|
||
|
||
return(CurrentTime.QuadPart <= EndOfWindow.QuadPart);
|
||
|
||
}
|
||
|
||
|
||
BOOLEAN
|
||
SampIncrementBadPasswordCount(
|
||
PSAMP_OBJECT UserContext,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine increments a user's bad password count.
|
||
This may result in the account becoming locked out.
|
||
It may also result in the BadPasswordCount being
|
||
reduced (because we left one LockoutObservationWindow
|
||
and had to start another).
|
||
|
||
If (and only if) this call results in the user account
|
||
transitioning from not locked out to locked out, a value
|
||
of TRUE will be returned. Otherwise, a value of FALSE is
|
||
returned.
|
||
|
||
|
||
NOTE: The V1aFixed data for the both the user and corresponding
|
||
domain objects MUST be valid. This routine does NOT retrieve
|
||
data from disk.
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user object context block.
|
||
|
||
V1aFixed - Points to a local copy of the user's V1aFixed data.
|
||
|
||
Return Value:
|
||
|
||
|
||
TRUE - the user became locked-out due to this call.
|
||
|
||
FALSE - the user was either already locked-out, or did
|
||
not become locked out due to this call.
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS
|
||
NtStatus;
|
||
|
||
BOOLEAN
|
||
IsLocked,
|
||
WasLocked;
|
||
|
||
#if DBG
|
||
|
||
TIME_FIELDS
|
||
T1;
|
||
|
||
#endif //DBG
|
||
|
||
SAMTRACE("SampIncrementBadPasswordCount");
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: IncrementBadPasswordCount: \n"
|
||
" User Account: 0x%lx\n",
|
||
V1aFixed->UserId));
|
||
|
||
//
|
||
// Reset the locked out flag if necessary.
|
||
// We might turn right around and set it again below,
|
||
// but we need to know when we transition into a locked-out
|
||
// state. This is necessary to give us information we
|
||
// need to do lockout auditing at some time. Note that
|
||
// the lockout flag itself is updated in a very lazy fashion,
|
||
// and so its state may or may not be accurate at any point
|
||
// in time. You must call SampUpdateAccountLockoutFlag to
|
||
// ensure it is up to date.
|
||
//
|
||
|
||
SampUpdateAccountLockedOutFlag( UserContext,
|
||
V1aFixed,
|
||
&WasLocked );
|
||
|
||
//
|
||
// If we are not in a lockout observation window, then
|
||
// reset the bad password count.
|
||
//
|
||
|
||
if (!SampStillInLockoutObservationWindow( UserContext, V1aFixed )) {
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: IncrementBadPasswordCount: \n"
|
||
" Starting new observation window.\n"
|
||
" Resetting bad password count before increment.\n"));
|
||
V1aFixed->BadPasswordCount = 0; // Dirty flag will be set later
|
||
}
|
||
|
||
V1aFixed->BadPasswordCount++;
|
||
|
||
NtStatus = NtQuerySystemTime( &V1aFixed->LastBadPasswordTime );
|
||
ASSERT(NT_SUCCESS(NtStatus));
|
||
|
||
#if DBG
|
||
RtlTimeToTimeFields(
|
||
&V1aFixed->LastBadPasswordTime,
|
||
&T1);
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" LastBadPasswordTime: [0x%lx, 0x%lx] %d:%d:%d\n",
|
||
V1aFixed->LastBadPasswordTime.HighPart,
|
||
V1aFixed->LastBadPasswordTime.LowPart,
|
||
T1.Hour, T1.Minute, T1.Second )
|
||
);
|
||
#endif //DBG
|
||
|
||
|
||
//
|
||
// Update the state of the flag to reflect its new situation
|
||
//
|
||
|
||
SampUpdateAccountLockedOutFlag( UserContext,
|
||
V1aFixed,
|
||
&IsLocked );
|
||
|
||
|
||
//
|
||
// Now to return our completion value.
|
||
// If the user was originally not locked, but now is locked
|
||
// then we need to return TRUE to indicate a transition into
|
||
// LOCKED occured. Otherwise, return false to indicate we
|
||
// did not transition into LOCKED (although we might have
|
||
// transitioned out of LOCKED).
|
||
//
|
||
|
||
if (!WasLocked) {
|
||
if (IsLocked) {
|
||
return(TRUE);
|
||
}
|
||
}
|
||
|
||
return(FALSE);
|
||
}
|
||
|
||
|
||
|
||
VOID
|
||
SampUpdateAccountLockedOutFlag(
|
||
PSAMP_OBJECT Context,
|
||
PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
|
||
PBOOLEAN IsLocked
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks to see if a user's account should
|
||
currently be locked out. If it should, it turns on
|
||
the AccountLockedOut flag. If not, it turns the flag
|
||
off.
|
||
|
||
|
||
Arguments:
|
||
|
||
Context - Points to the user object context block.
|
||
|
||
V1aFixed - Points to a local copy of the user's V1aFixed data.
|
||
|
||
V1aFixedDirty - If any changes are made to V1aFixed, then
|
||
V1aFixedDirty will be set to TRUE, otherwise V1aFixedDirty
|
||
WILL NOT BE MODIFIED.
|
||
|
||
IsState - Indicates whether the account is currently locked
|
||
or unlocked. A value of TRUE indicates the account is
|
||
locked. A value of false indicates the account is not
|
||
locked.
|
||
|
||
Return Value:
|
||
|
||
|
||
TRUE - the user's lockout status changed.
|
||
|
||
FALSE - the user's lockout status did not change.
|
||
|
||
|
||
--*/
|
||
{
|
||
USHORT
|
||
Threshold;
|
||
|
||
LARGE_INTEGER
|
||
CurrentTime,
|
||
LastBadPassword,
|
||
LockoutDuration,
|
||
EndOfLockout;
|
||
|
||
BOOLEAN
|
||
BeyondLockoutDuration;
|
||
|
||
#if DBG
|
||
|
||
LARGE_INTEGER
|
||
TmpTime;
|
||
|
||
TIME_FIELDS
|
||
AT1, AT2, AT3, DT1;
|
||
#endif //DBG
|
||
|
||
|
||
|
||
SAMTRACE("SampUpdateAccountLockedOutFlag");
|
||
|
||
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
("SAM: UpdateAccountLockedOutFlag: \n"
|
||
" User account 0x%lx\n",
|
||
V1aFixed->UserId));
|
||
|
||
//
|
||
// One of two situations exist:
|
||
//
|
||
// 1) The account was left in a locked out state. In this
|
||
// case we need to see if it should still be locked
|
||
// out.
|
||
//
|
||
// 2) The account was left in a not locked state. In this
|
||
// case we need to see if we should lock it.
|
||
//
|
||
|
||
if ((V1aFixed->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) !=0) {
|
||
|
||
//
|
||
// Left locked out - do we need to unlock it?
|
||
//
|
||
|
||
LastBadPassword = V1aFixed->LastBadPasswordTime;
|
||
LockoutDuration =
|
||
SampDefinedDomains[Context->DomainIndex].CurrentFixed.LockoutDuration;
|
||
|
||
EndOfLockout =
|
||
SampAddDeltaTime( LastBadPassword, LockoutDuration );
|
||
|
||
NtQuerySystemTime( &CurrentTime );
|
||
|
||
BeyondLockoutDuration = CurrentTime.QuadPart > EndOfLockout.QuadPart;
|
||
|
||
#if DBG
|
||
|
||
RtlTimeToTimeFields( &LastBadPassword, &AT1);
|
||
RtlTimeToTimeFields( &CurrentTime, &AT2);
|
||
RtlTimeToTimeFields( &EndOfLockout, &AT3 );
|
||
|
||
TmpTime.QuadPart = -LockoutDuration.QuadPart;
|
||
RtlTimeToElapsedTimeFields( &TmpTime, &DT1 );
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" Account previously locked.\n"
|
||
" Current Time : [0x%lx, 0x%lx] %d:%d:%d\n"
|
||
" End of Lockout : [0x%lx, 0x%lx] %d:%d:%d\n"
|
||
" Lockout Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
|
||
" LastBadPasswordTime: [0x%lx, 0x%lx] %d:%d:%d\n",
|
||
CurrentTime.HighPart, CurrentTime.LowPart, AT2.Hour, AT2.Minute, AT2.Second,
|
||
EndOfLockout.HighPart, EndOfLockout.LowPart, AT3.Hour, AT3.Minute, AT3.Second,
|
||
LockoutDuration.HighPart, LockoutDuration.LowPart, DT1.Hour, DT1.Minute, DT1.Second,
|
||
V1aFixed->LastBadPasswordTime.HighPart, V1aFixed->LastBadPasswordTime.LowPart,
|
||
AT1.Hour, AT1.Minute, AT1.Second)
|
||
);
|
||
#endif //DBG
|
||
|
||
if (BeyondLockoutDuration) {
|
||
|
||
//
|
||
// Unlock account
|
||
//
|
||
|
||
V1aFixed->UserAccountControl &= ~USER_ACCOUNT_AUTO_LOCKED;
|
||
V1aFixed->BadPasswordCount = 0;
|
||
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" ** unlocking account **\n") );
|
||
#if DBG
|
||
} else {
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" leaving account locked\n") );
|
||
#endif //DBG
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" Account previously not locked.\n"
|
||
" BadPasswordCount: %ld\n",
|
||
V1aFixed->BadPasswordCount) );
|
||
|
||
//
|
||
// Left in a not locked state. Do we need to lock it?
|
||
//
|
||
|
||
Threshold =
|
||
SampDefinedDomains[Context->DomainIndex].CurrentFixed.LockoutThreshold;
|
||
|
||
if (V1aFixed->BadPasswordCount >= Threshold &&
|
||
Threshold != 0) { // Zero is a special case threshold
|
||
|
||
//
|
||
// account must be locked.
|
||
//
|
||
|
||
V1aFixed->UserAccountControl |= USER_ACCOUNT_AUTO_LOCKED;
|
||
|
||
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" ** locking account **\n") );
|
||
#if DBG
|
||
} else {
|
||
SampDiagPrint( DISPLAY_LOCKOUT,
|
||
(" leaving account unlocked\n") );
|
||
#endif //DBG
|
||
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Now return the state of the flag.
|
||
//
|
||
|
||
if ((V1aFixed->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) !=0) {
|
||
(*IsLocked) = TRUE;
|
||
} else {
|
||
(*IsLocked) = FALSE;
|
||
}
|
||
|
||
return;
|
||
}
|