You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4928 lines
135 KiB
4928 lines
135 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
user.c
|
|
|
|
Abstract:
|
|
|
|
NetUser API functions
|
|
|
|
Author:
|
|
|
|
Cliff Van Dyke (cliffv) 26-Mar-1991
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
Contains NT-specific code.
|
|
Requires ANSI C extensions: slash-slash comments, long external names.
|
|
|
|
Revision History:
|
|
|
|
17-Apr-1991 (cliffv)
|
|
Incorporated review comments.
|
|
|
|
20-Jan-1992 (madana)
|
|
Sundry API changes
|
|
|
|
28-Nov-1992 (chuckc)
|
|
Added stub for NetUserGetLocalGroups
|
|
|
|
1-Dec-1992 (chuckc)
|
|
Added real code for NetUserGetLocalGroups
|
|
|
|
--*/
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#undef DOMAIN_ALL_ACCESS // defined in both ntsam.h and ntwinapi.h
|
|
#include <ntsam.h>
|
|
#include <ntlsa.h>
|
|
|
|
#include <windef.h>
|
|
#include <winbase.h>
|
|
#include <lmcons.h>
|
|
|
|
#include <access.h>
|
|
#include <align.h>
|
|
#include <lmapibuf.h>
|
|
#include <lmaccess.h>
|
|
#include <lmerr.h>
|
|
#include <crypt.h>
|
|
#include <ntmsv1_0.h>
|
|
#include <limits.h>
|
|
#include <netdebug.h>
|
|
#include <netlib.h>
|
|
#include <netlibnt.h>
|
|
#include <rpcutil.h>
|
|
#include <rxuser.h>
|
|
#include <secobj.h>
|
|
#include <stddef.h>
|
|
#include <uasp.h>
|
|
#include <accessp.h>
|
|
|
|
/*lint -e614 */ /* Auto aggregate initializers need not be constant */
|
|
|
|
// Lint complains about casts of one structure type to another.
|
|
// That is done frequently in the code below.
|
|
/*lint -e740 */ /* don't complain about unusual cast */
|
|
|
|
|
|
|
|
//
|
|
// Define the SAM info classes and pseudo-classes used in NetUserModalsSet.
|
|
//
|
|
// The values of these definitions must match the order of the
|
|
// SamInfoClass array in NetUserModalsSet.
|
|
//
|
|
#define SAM_LogoffClass 0
|
|
#define SAM_NameClass 1
|
|
#define SAM_PasswordClass 2
|
|
#define SAM_ReplicationClass 3
|
|
#define SAM_ServerRoleClass 4
|
|
#define SAM_LockoutClass 5
|
|
|
|
//
|
|
// Relate the NetUser API fields to the SAM API fields.
|
|
//
|
|
// This table contains as much information as possible to describe the
|
|
// relationship between fields in the NetUser API and the SAM API.
|
|
//
|
|
|
|
struct _USER_UAS_SAM_TABLE {
|
|
|
|
//
|
|
// Describe the field types for UAS and SAM.
|
|
//
|
|
|
|
enum {
|
|
UMT_STRING, // UAS is LPWSTR. SAM is UNICODE_STRING.
|
|
UMT_USHORT, // UAS is DWORD. SAM is USHORT.
|
|
UMT_ULONG, // UAS is DWORD. SAM is ULONG.
|
|
UMT_ROLE, // UAS is role. SAM is enum.
|
|
UMT_DELTA // UAS is delta seconds. SAM is LARGE_INTEGER.
|
|
} ModalsFieldType;
|
|
|
|
//
|
|
// Define the UAS level and UAS parmnum for this field
|
|
//
|
|
|
|
DWORD UasLevel;
|
|
DWORD UasParmNum;
|
|
|
|
//
|
|
// Describe the byte offset of the field in the appropriate UAS
|
|
// and SAM structures.
|
|
//
|
|
|
|
DWORD UasOffset;
|
|
DWORD SamOffset;
|
|
|
|
//
|
|
// Index to the structure describing the Sam Information class.
|
|
//
|
|
// If multiple fields use the same Sam information class, then
|
|
// this field should have the same index for each such field.
|
|
//
|
|
|
|
DWORD Class;
|
|
|
|
} UserUasSamTable[] = {
|
|
|
|
{ UMT_USHORT, 0, MODALS_MIN_PASSWD_LEN_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_0, usrmod0_min_passwd_len ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MinPasswordLength ),
|
|
SAM_PasswordClass },
|
|
|
|
{ UMT_USHORT, 1001, MODALS_MIN_PASSWD_LEN_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1001, usrmod1001_min_passwd_len ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MinPasswordLength ),
|
|
SAM_PasswordClass },
|
|
|
|
|
|
|
|
{ UMT_DELTA, 0, MODALS_MAX_PASSWD_AGE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_0, usrmod0_max_passwd_age ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MaxPasswordAge ),
|
|
SAM_PasswordClass },
|
|
|
|
{ UMT_DELTA, 1002, MODALS_MAX_PASSWD_AGE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1002, usrmod1002_max_passwd_age ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MaxPasswordAge ),
|
|
SAM_PasswordClass },
|
|
|
|
|
|
{ UMT_DELTA, 0, MODALS_MIN_PASSWD_AGE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_0, usrmod0_min_passwd_age ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MinPasswordAge ),
|
|
SAM_PasswordClass },
|
|
|
|
{ UMT_DELTA, 1003, MODALS_MIN_PASSWD_AGE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1003, usrmod1003_min_passwd_age ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, MinPasswordAge ),
|
|
SAM_PasswordClass },
|
|
|
|
|
|
{ UMT_DELTA, 0, MODALS_FORCE_LOGOFF_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_0, usrmod0_force_logoff ),
|
|
offsetof( DOMAIN_LOGOFF_INFORMATION, ForceLogoff ),
|
|
SAM_LogoffClass },
|
|
|
|
{ UMT_DELTA, 1004, MODALS_FORCE_LOGOFF_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1004, usrmod1004_force_logoff ),
|
|
offsetof( DOMAIN_LOGOFF_INFORMATION, ForceLogoff ),
|
|
SAM_LogoffClass },
|
|
|
|
|
|
{ UMT_USHORT, 0, MODALS_PASSWD_HIST_LEN_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_0, usrmod0_password_hist_len ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, PasswordHistoryLength ),
|
|
SAM_PasswordClass },
|
|
|
|
{ UMT_USHORT, 1005, MODALS_PASSWD_HIST_LEN_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1005, usrmod1005_password_hist_len ),
|
|
offsetof( DOMAIN_PASSWORD_INFORMATION, PasswordHistoryLength ),
|
|
SAM_PasswordClass },
|
|
|
|
|
|
{ UMT_ROLE, 1, MODALS_ROLE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1, usrmod1_role ),
|
|
offsetof( DOMAIN_SERVER_ROLE_INFORMATION, DomainServerRole ),
|
|
SAM_ServerRoleClass },
|
|
|
|
{ UMT_ROLE, 1006, MODALS_ROLE_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1006, usrmod1006_role ),
|
|
offsetof( DOMAIN_SERVER_ROLE_INFORMATION, DomainServerRole ),
|
|
SAM_ServerRoleClass },
|
|
|
|
|
|
{ UMT_STRING, 1, MODALS_PRIMARY_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1, usrmod1_primary ),
|
|
offsetof( DOMAIN_REPLICATION_INFORMATION, ReplicaSourceNodeName ),
|
|
SAM_ReplicationClass },
|
|
|
|
{ UMT_STRING, 1007, MODALS_PRIMARY_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_1007, usrmod1007_primary ),
|
|
offsetof( DOMAIN_REPLICATION_INFORMATION, ReplicaSourceNodeName ),
|
|
SAM_ReplicationClass },
|
|
|
|
|
|
|
|
{ UMT_STRING, 2, MODALS_DOMAIN_NAME_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_2, usrmod2_domain_name ),
|
|
offsetof( DOMAIN_NAME_INFORMATION, DomainName ),
|
|
SAM_NameClass },
|
|
|
|
{ UMT_DELTA, 3, MODALS_LOCKOUT_DURATION_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_3, usrmod3_lockout_duration ),
|
|
offsetof( DOMAIN_LOCKOUT_INFORMATION, LockoutDuration ),
|
|
SAM_LockoutClass },
|
|
|
|
{ UMT_DELTA, 3, MODALS_LOCKOUT_OBSERVATION_WINDOW_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_3, usrmod3_lockout_observation_window ),
|
|
offsetof( DOMAIN_LOCKOUT_INFORMATION, LockoutObservationWindow ),
|
|
SAM_LockoutClass },
|
|
|
|
{ UMT_USHORT, 3, MODALS_LOCKOUT_THRESHOLD_PARMNUM,
|
|
offsetof( USER_MODALS_INFO_3, usrmod3_lockout_threshold ),
|
|
offsetof( DOMAIN_LOCKOUT_INFORMATION, LockoutThreshold ),
|
|
SAM_LockoutClass },
|
|
|
|
};
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserAdd(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN DWORD Level,
|
|
IN LPBYTE Buffer,
|
|
OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a user account in the user accounts database.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
Level - Level of information provided. Must be 1, 2, 3 or 22.
|
|
|
|
Buffer - A pointer to the buffer containing the user information
|
|
structure.
|
|
|
|
ParmError - Optional pointer to a DWORD to return the index of the
|
|
first parameter in error when ERROR_INVALID_PARAMETER is returned.
|
|
If NULL, the parameter is not returned on error.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
UNICODE_STRING UserNameString;
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE UserHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
ULONG RelativeId;
|
|
ULONG GrantedAccess;
|
|
ULONG NewSamAccountType;
|
|
DWORD UasUserFlags;
|
|
ULONG WhichFieldsMask = 0xFFFFFFFF;
|
|
|
|
|
|
//
|
|
// Variables for building the new user's Sid
|
|
//
|
|
|
|
PSID DomainId = NULL; // Domain Id of the primary domain
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: entered \n"));
|
|
}
|
|
|
|
//
|
|
// Initialize
|
|
//
|
|
|
|
NetpSetParmError( PARM_ERROR_NONE );
|
|
|
|
//
|
|
// Validate Level parameter.
|
|
//
|
|
|
|
switch (Level) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
NetpAssert ( offsetof( USER_INFO_1, usri1_flags ) ==
|
|
offsetof( USER_INFO_2, usri2_flags ) );
|
|
NetpAssert ( offsetof( USER_INFO_1, usri1_flags ) ==
|
|
offsetof( USER_INFO_3, usri3_flags ) );
|
|
NetpAssert ( offsetof( USER_INFO_1, usri1_flags ) ==
|
|
offsetof( USER_INFO_4, usri4_flags ) );
|
|
|
|
UasUserFlags = ((PUSER_INFO_1)Buffer)->usri1_flags;
|
|
break;
|
|
|
|
case 22:
|
|
UasUserFlags = ((PUSER_INFO_22)Buffer)->usri22_flags;
|
|
break;
|
|
|
|
default:
|
|
return ERROR_INVALID_LEVEL; // Nothing to cleanup yet
|
|
}
|
|
|
|
|
|
//
|
|
// Determine the account type we're creating.
|
|
//
|
|
|
|
if( UasUserFlags & UF_ACCOUNT_TYPE_MASK ) {
|
|
|
|
//
|
|
// Account Types bits are exclusive, so make sure that
|
|
// precisely one Account Type bit is set.
|
|
//
|
|
|
|
if ( !JUST_ONE_BIT( UasUserFlags & UF_ACCOUNT_TYPE_MASK )) {
|
|
|
|
NetpSetParmError( USER_FLAGS_PARMNUM );
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserAdd: Invalid account control bits (2) \n" ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine what the new account type should be.
|
|
//
|
|
|
|
if ( UasUserFlags & UF_TEMP_DUPLICATE_ACCOUNT ) {
|
|
NewSamAccountType = USER_TEMP_DUPLICATE_ACCOUNT;
|
|
|
|
} else if ( UasUserFlags & UF_NORMAL_ACCOUNT ) {
|
|
NewSamAccountType = USER_NORMAL_ACCOUNT;
|
|
|
|
} else if (UasUserFlags & UF_WORKSTATION_TRUST_ACCOUNT){
|
|
NewSamAccountType = USER_WORKSTATION_TRUST_ACCOUNT;
|
|
|
|
// Because of a bug in NT 3.5x, we have to initially create SERVER
|
|
// and interdomain trust accounts as normal accounts and change them
|
|
// later. Specifically, SAM didn't call I_NetNotifyMachineAccount
|
|
// in SamCreateUser2InDomain. Therefore, netlogon didn't get notified
|
|
// of the change. That bug is fixed in NT 4.0.
|
|
//
|
|
// In NT 5.0, we relaxed that restriction for BDC accounts. An NT 5.0
|
|
// client creating a BDC account on an NT 3.5x DC will have the problem
|
|
// above. However, by making the change, an NT 5.0 BDC creating a BDC
|
|
// account on an NT 5.0 DC will properly create the BDC account as a
|
|
// Computer object.
|
|
//
|
|
|
|
} else if ( UasUserFlags & UF_SERVER_TRUST_ACCOUNT ) {
|
|
NewSamAccountType = USER_SERVER_TRUST_ACCOUNT;
|
|
|
|
} else if (UasUserFlags & UF_INTERDOMAIN_TRUST_ACCOUNT){
|
|
NewSamAccountType = USER_NORMAL_ACCOUNT;
|
|
|
|
} else {
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserAdd: Invalid account type (3)\n"));
|
|
}
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// If SAM has none of its bits set,
|
|
// set USER_NORMAL_ACCOUNT.
|
|
//
|
|
} else {
|
|
NewSamAccountType = USER_NORMAL_ACCOUNT;
|
|
}
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Domain asking for DOMAIN_CREATE_USER access.
|
|
//
|
|
// DOMAIN_LOOKUP is needed to lookup group memberships later.
|
|
//
|
|
// DOMAIN_READ_PASSWORD_PARAMETERS is needed in those cases that we'll
|
|
// set the password on the account.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain(
|
|
SamServerHandle,
|
|
DOMAIN_CREATE_USER | DOMAIN_LOOKUP |
|
|
DOMAIN_READ_PASSWORD_PARAMETERS,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
|
|
if ( NetStatus == ERROR_ACCESS_DENIED &&
|
|
NewSamAccountType == USER_WORKSTATION_TRUST_ACCOUNT ) {
|
|
|
|
// Workstation accounts can be created with either DOMAIN_CREATE_USER access
|
|
// or SE_CREATE_MACHINE_ACCOUNT_PRIVILEGE. So we'll try both.
|
|
// In the later case, we probably will only have access to the account
|
|
// to set the password, so we'll avoid setting any other parameters on the
|
|
// account.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain(
|
|
SamServerHandle,
|
|
DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
WhichFieldsMask = USER_ALL_NTPASSWORDPRESENT;
|
|
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: UaspOpenDomain returns %ld\n",
|
|
NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Create the User with the specified name
|
|
// Create workstation trust accounts(and default security descriptor).
|
|
//
|
|
|
|
RtlInitUnicodeString( &UserNameString, ((PUSER_INFO_1)Buffer)->usri1_name );
|
|
|
|
Status = SamCreateUser2InDomain(
|
|
DomainHandle,
|
|
&UserNameString,
|
|
NewSamAccountType,
|
|
GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE |
|
|
WRITE_DAC | DELETE | USER_FORCE_PASSWORD_CHANGE |
|
|
USER_READ_ACCOUNT | USER_WRITE_ACCOUNT,
|
|
&UserHandle,
|
|
&GrantedAccess,
|
|
&RelativeId );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: SamCreateUserInDomain rets %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Set all the other attributes for this user
|
|
//
|
|
|
|
NetStatus = UserpSetInfo(
|
|
DomainHandle,
|
|
DomainId,
|
|
UserHandle,
|
|
NULL, // BuiltinDomainHandle not needed for create case
|
|
RelativeId,
|
|
((PUSER_INFO_1)Buffer)->usri1_name,
|
|
Level,
|
|
Buffer,
|
|
WhichFieldsMask,
|
|
ParmError );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: UserpSetInfo returns %ld\n",
|
|
NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Done
|
|
//
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
//
|
|
// Clean up
|
|
//
|
|
|
|
Cleanup:
|
|
//
|
|
// Delete the user or close the handle depending on success or failure.
|
|
//
|
|
|
|
if ( UserHandle != NULL ) {
|
|
if ( NetStatus != NERR_Success ) {
|
|
(VOID) SamDeleteUser( UserHandle );
|
|
} else {
|
|
(VOID) SamCloseHandle( UserHandle );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free locally used resources.
|
|
//
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserAdd( (LPWSTR) ServerName, Level, Buffer, ParmError );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserAdd: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserAdd
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserDel(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Delete a User
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - Name of the user to delete.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
SAM_HANDLE BuiltinDomainHandle = NULL;
|
|
SAM_HANDLE UserHandle = NULL;
|
|
PSID DomainId = NULL; // Domain Id of the primary domain
|
|
ULONG UserRelativeId; // RelativeId of the user being deleted
|
|
PSID UserSid = NULL;
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserDel: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the Domain
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Builtin Domain.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP,
|
|
FALSE, // Builtin Domain
|
|
&BuiltinDomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the user asking for delete access.
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
DELETE,
|
|
UserName,
|
|
&UserHandle,
|
|
&UserRelativeId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserDel: UserpOpenUser returns %ld\n",
|
|
NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Determine the SID of the User being deleted.
|
|
//
|
|
|
|
NetStatus = NetpSamRidToSid( UserHandle,
|
|
UserRelativeId,
|
|
&UserSid );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Delete any aliases to this user from the Builtin domain
|
|
//
|
|
|
|
Status = SamRemoveMemberFromForeignDomain( BuiltinDomainHandle,
|
|
UserSid );
|
|
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserDel: SamRemoveMembershipFromForeignDomain returns %lX\n",
|
|
Status ));
|
|
}
|
|
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Delete the user.
|
|
//
|
|
|
|
Status = SamDeleteUser( UserHandle );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserDel: SamDeleteUser returns %lX\n", Status ));
|
|
}
|
|
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = NERR_Success;
|
|
UserHandle = NULL; // Don't touch the handle to a deleted user
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
if ( UserHandle != NULL ) {
|
|
(VOID) SamCloseHandle( UserHandle );
|
|
}
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( BuiltinDomainHandle != NULL ) {
|
|
UaspCloseDomain( BuiltinDomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
if ( UserSid != NULL ) {
|
|
NetpMemoryFree( UserSid );
|
|
}
|
|
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserDel( (LPWSTR)ServerName, (LPWSTR)UserName );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserDel: returning %ld\n", NetStatus ));
|
|
}
|
|
return NetStatus;
|
|
|
|
} // NetUserDel
|
|
|
|
|
|
|
|
|
|
ULONG
|
|
UserpComputeSamPrefMaxLen(
|
|
IN DWORD Level,
|
|
IN DWORD NetUserPrefMaxLen,
|
|
IN DWORD NetUserBytesAlreadyReturned,
|
|
IN DWORD SamBytesAlreadyReturned
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is a helper function for NetUserEnum. NetUserEnum enumerates
|
|
the appropriate users by calling SamEnumerateUsersInDomain. NetUserEnum builds
|
|
the appropriate return structure for each such enumerated user.
|
|
|
|
SamEnumerateUsersInDomain returns a resume handle as does NetUserEnum. If
|
|
NetUserEnum were to return to its caller without having processed all of the
|
|
entries returned from SAM, NetUserEnum would have to "compute" a resume handle
|
|
corresponding to an intermediate entry returned from SAM. That's impossible
|
|
(except in the special cases where no "filter" parameter is passed to SAM).
|
|
|
|
Instead, we choose to pass SamEnumerateUsersInDomain a PrefMaxLen which will
|
|
attempt to enumerate exactly the right number of users that NetUserEnum can
|
|
pack into its PrefMaxLen buffer.
|
|
Since the size of the structure returned from SAM is different than the size of
|
|
the structure returned from it is difficult to determine an optimal PrefMaxLen
|
|
to pass to SamEnumerateUsersInDomain. This routine attempts to do that.
|
|
|
|
We realise that this algorithm may cause NetUserEnum to exceed its PrefMaxLen by
|
|
a significant amount.
|
|
|
|
Arguments:
|
|
|
|
Level - The NetUserEnum info level.
|
|
|
|
NetUserPrefMaxLen - The NetUserEnum prefered maximum length of returned data.
|
|
|
|
NetUserBytesAlreadyReturned - The number of bytes already packed by
|
|
NetUserEnum
|
|
|
|
SamBytesAlreadyReturned - The number of bytes already returned by
|
|
SamEnumerateUserInDomain.
|
|
|
|
Return Value:
|
|
|
|
Value to use as PrefMaxLen on next call to SamEnumerateUsersInDomain.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG RemainingPrefMaxLen;
|
|
ULARGE_INTEGER LargeTemp;
|
|
ULONG SamPrefMaxLen;
|
|
|
|
//
|
|
// If caller simply wants ALL the data,
|
|
// ask SAM for the same thing.
|
|
//
|
|
|
|
if ( NetUserPrefMaxLen == 0xFFFFFFFF ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("SamPrefMaxLen: Net Pref: %ld Net bytes: %ld Sam Bytes: %ld Sam Pref: %ld\n",
|
|
NetUserPrefMaxLen, NetUserBytesAlreadyReturned, SamBytesAlreadyReturned, NetUserPrefMaxLen ));
|
|
}
|
|
return NetUserPrefMaxLen;
|
|
}
|
|
|
|
//
|
|
// If no bytes have been returned yet,
|
|
// use sample data based on a sample domain.
|
|
// Since the information returned by SAM and NetUserEnum is variable
|
|
// length, there is no way to compute a value.
|
|
//
|
|
|
|
if ( NetUserBytesAlreadyReturned == 0 ) {
|
|
|
|
//
|
|
// Use a different constant for each info level.
|
|
//
|
|
|
|
switch ( Level ) {
|
|
case 0:
|
|
SamBytesAlreadyReturned = 1;
|
|
NetUserBytesAlreadyReturned = 1;
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 11:
|
|
SamBytesAlreadyReturned = 1;
|
|
NetUserBytesAlreadyReturned = 10;
|
|
break;
|
|
case 1:
|
|
case 10:
|
|
case 20:
|
|
SamBytesAlreadyReturned = 1;
|
|
NetUserBytesAlreadyReturned = 4;
|
|
break;
|
|
default:
|
|
SamBytesAlreadyReturned = 1;
|
|
NetUserBytesAlreadyReturned = 1;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Use the above computed divisor to compute the desired number of bytes to
|
|
// enumerate from SAM.
|
|
//
|
|
|
|
if ( NetUserBytesAlreadyReturned >= NetUserPrefMaxLen ) {
|
|
RemainingPrefMaxLen = 0;
|
|
} else {
|
|
RemainingPrefMaxLen = NetUserPrefMaxLen - NetUserBytesAlreadyReturned;
|
|
}
|
|
|
|
LargeTemp.QuadPart = UInt32x32To64 ( RemainingPrefMaxLen, SamBytesAlreadyReturned );
|
|
SamPrefMaxLen = (ULONG)(LargeTemp.QuadPart / (ULONGLONG) NetUserBytesAlreadyReturned);
|
|
|
|
//
|
|
// Ensure we always make reasonable progress by returning at least 5
|
|
// entries from SAM (unless the caller is really conservative).
|
|
//
|
|
|
|
#define MIN_SAM_ENUMERATION \
|
|
((sizeof(SAM_RID_ENUMERATION) + LM20_UNLEN * sizeof(WCHAR) + sizeof(WCHAR)))
|
|
#define TYPICAL_SAM_ENUMERATION \
|
|
(MIN_SAM_ENUMERATION * 5)
|
|
|
|
if ( SamPrefMaxLen < TYPICAL_SAM_ENUMERATION && NetUserPrefMaxLen > 1 ) {
|
|
SamPrefMaxLen = TYPICAL_SAM_ENUMERATION;
|
|
} else if ( SamPrefMaxLen < MIN_SAM_ENUMERATION ) {
|
|
SamPrefMaxLen = MIN_SAM_ENUMERATION;
|
|
}
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("SamPrefMaxLen: Net Pref: %ld Net bytes: %ld Sam Bytes: %ld Sam Pref: %ld\n",
|
|
NetUserPrefMaxLen, NetUserBytesAlreadyReturned, SamBytesAlreadyReturned, SamPrefMaxLen ));
|
|
}
|
|
|
|
return SamPrefMaxLen;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserEnum(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN DWORD Level,
|
|
IN DWORD Filter,
|
|
OUT LPBYTE *Buffer,
|
|
IN DWORD PrefMaxLen,
|
|
OUT LPDWORD EntriesRead,
|
|
OUT LPDWORD EntriesLeft,
|
|
IN OUT LPDWORD ResumeHandle OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieve information about each user on a server.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
Level - Level of information required. level 0, 1, 2, 3, 10,
|
|
and 20 are valid
|
|
|
|
Filter - Returns the user accounts of the type specified here. Combination
|
|
of the following types may be specified as filter parameter.
|
|
|
|
#define FILTER_TEMP_DUPLICATE_ACCOUNT (0x0001)
|
|
#define FILTER_NORMAL_ACCOUNT (0x0002)
|
|
#define FILTER_INTERDOMAIN_TRUST_ACCOUNT (0x0008)
|
|
#define FILTER_WORKSTATION_TRUST_ACCOUNT (0x0010)
|
|
#define FILTER_SERVER_TRUST_ACCOUNT (0x0020)
|
|
|
|
Buffer - Returns a pointer to the return information structure.
|
|
Caller must deallocate buffer using NetApiBufferFree.
|
|
|
|
PrefMaxLen - Prefered maximum length of returned data.
|
|
|
|
EntriesRead - Returns the actual enumerated element count.
|
|
|
|
EntriesLeft - Returns the total entries available to be enumerated.
|
|
|
|
ResumeHandle - Used to continue an existing search. The handle should
|
|
be zero on the first call and left unchanged for subsequent calls.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
NTSTATUS CachedStatus;
|
|
|
|
BUFFER_DESCRIPTOR BufferDescriptor;
|
|
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
SAM_HANDLE BuiltinDomainHandle = NULL;
|
|
PSID DomainId = NULL;
|
|
ULONG TotalRemaining = 0;
|
|
|
|
SAM_ENUMERATE_HANDLE EnumHandle;
|
|
PSAM_RID_ENUMERATION EnumBuffer = NULL;
|
|
DWORD CountReturned = 0;
|
|
BOOL AllDone = FALSE;
|
|
|
|
SAM_ENUMERATE_HANDLE LocalEnumHandle;
|
|
DWORD LocalResumeHandle;
|
|
|
|
DWORD SamFilter;
|
|
DWORD SamPrefMaxLen;
|
|
DWORD NetUserBytesAlreadyReturned;
|
|
DWORD SamBytesAlreadyReturned;
|
|
|
|
DWORD Mode = SAM_SID_COMPATIBILITY_ALL;
|
|
|
|
#define USERACCOUNTCONTROL( _f ) ( \
|
|
( ( (_f) & FILTER_TEMP_DUPLICATE_ACCOUNT ) ? \
|
|
USER_TEMP_DUPLICATE_ACCOUNT : 0 ) | \
|
|
( ( (_f) & FILTER_NORMAL_ACCOUNT ) ? \
|
|
USER_NORMAL_ACCOUNT : 0 ) | \
|
|
( ( (_f) & FILTER_INTERDOMAIN_TRUST_ACCOUNT ) ? \
|
|
USER_INTERDOMAIN_TRUST_ACCOUNT : 0 ) | \
|
|
( ( (_f) & FILTER_WORKSTATION_TRUST_ACCOUNT ) ? \
|
|
USER_WORKSTATION_TRUST_ACCOUNT : 0 ) | \
|
|
( ( (_f) & FILTER_SERVER_TRUST_ACCOUNT ) ? \
|
|
USER_SERVER_TRUST_ACCOUNT : 0 ) \
|
|
)
|
|
|
|
|
|
//
|
|
// Pick up the resume handle.
|
|
//
|
|
// Do this early to ensure we don't scrog the ResumeHandle in
|
|
// case we go downlevel.
|
|
//
|
|
|
|
if ( ARGUMENT_PRESENT( ResumeHandle ) ) {
|
|
LocalResumeHandle = *ResumeHandle;
|
|
} else {
|
|
LocalResumeHandle = 0;
|
|
}
|
|
|
|
EnumHandle = (SAM_ENUMERATE_HANDLE) LocalResumeHandle;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*Buffer = NULL;
|
|
*EntriesRead = 0;
|
|
*EntriesLeft = 0;
|
|
RtlZeroMemory(
|
|
&BufferDescriptor,
|
|
sizeof(BUFFER_DESCRIPTOR)
|
|
);
|
|
|
|
SamFilter = USERACCOUNTCONTROL( Filter );
|
|
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserEnum: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = SamGetCompatibilityMode(SamServerHandle,
|
|
&Mode);
|
|
if (NT_SUCCESS(Status)) {
|
|
if ( (Mode == SAM_SID_COMPATIBILITY_STRICT)
|
|
&& ( Level == 3 || Level == 20 ) ) {
|
|
//
|
|
// These info levels return RID's
|
|
//
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Validate Level parameter
|
|
//
|
|
|
|
|
|
switch (Level) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 11:
|
|
|
|
//
|
|
// Open the Builtin Domain.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_GET_ALIAS_MEMBERSHIP,
|
|
FALSE, // Builtin Domain
|
|
&BuiltinDomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
case 0:
|
|
case 10:
|
|
case 20:
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
//
|
|
// Open the Account Domain.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LIST_ACCOUNTS |
|
|
DOMAIN_READ_OTHER_PARAMETERS,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Get the total number of users from SAM
|
|
//
|
|
//
|
|
// the only way to get the total number of specified accounts is
|
|
// enumerate the specified accounts till there is no more accounts
|
|
// and add all CountReturned.
|
|
//
|
|
|
|
TotalRemaining = 0;
|
|
LocalEnumHandle = EnumHandle;
|
|
|
|
SamPrefMaxLen = UserpComputeSamPrefMaxLen(
|
|
Level,
|
|
PrefMaxLen,
|
|
0, // NetUserBytesAlreadyReturned,
|
|
0 );// SamBytesAlreadyReturned
|
|
|
|
SamBytesAlreadyReturned = SamPrefMaxLen;
|
|
|
|
do {
|
|
NTSTATUS LocalStatus;
|
|
PSAM_RID_ENUMERATION LocalEnumBuffer = NULL;
|
|
DWORD LocalCountReturned;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("Calling Enumerate phase 1: PrefLen %ld\n", SamPrefMaxLen ));
|
|
}
|
|
|
|
Status = SamEnumerateUsersInDomain(
|
|
DomainHandle,
|
|
&LocalEnumHandle,
|
|
SamFilter,
|
|
(PVOID *) &LocalEnumBuffer,
|
|
SamPrefMaxLen,
|
|
&LocalCountReturned
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
|
|
if(LocalEnumBuffer != NULL ) {
|
|
|
|
Status = SamFreeMemory( LocalEnumBuffer );
|
|
NetpAssert( NT_SUCCESS( Status ) );
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// aggrigate total count.
|
|
//
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("Enumerate phase 1: Returned %ld entries\n", LocalCountReturned ));
|
|
}
|
|
TotalRemaining += LocalCountReturned;
|
|
|
|
//
|
|
// cache first enum buffer to use it in the loop below.
|
|
//
|
|
|
|
if( EnumBuffer == NULL ) {
|
|
|
|
EnumBuffer = LocalEnumBuffer;
|
|
EnumHandle = LocalEnumHandle;
|
|
CountReturned = LocalCountReturned;
|
|
CachedStatus = Status;
|
|
|
|
// Subsequent calls can use a reasonably large buffer size.
|
|
if ( SamPrefMaxLen < NETP_ENUM_GUESS ) {
|
|
SamPrefMaxLen = NETP_ENUM_GUESS;
|
|
}
|
|
} else {
|
|
|
|
LocalStatus = SamFreeMemory( LocalEnumBuffer );
|
|
NetpAssert( NT_SUCCESS( LocalStatus ) );
|
|
}
|
|
|
|
|
|
} while ( Status == STATUS_MORE_ENTRIES );
|
|
|
|
|
|
//
|
|
// Loop for each user
|
|
//
|
|
//
|
|
|
|
NetUserBytesAlreadyReturned = 0;
|
|
|
|
for ( ;; ) {
|
|
|
|
DWORD i;
|
|
|
|
//
|
|
// use cached enum buffer if one available
|
|
//
|
|
|
|
if( EnumBuffer != NULL ) {
|
|
|
|
Status = CachedStatus;
|
|
} else {
|
|
|
|
SamPrefMaxLen = UserpComputeSamPrefMaxLen(
|
|
Level,
|
|
PrefMaxLen,
|
|
NetUserBytesAlreadyReturned,
|
|
SamBytesAlreadyReturned );
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("Calling Enumerate phase 2: PrefLen %ld\n", SamPrefMaxLen ));
|
|
}
|
|
Status = SamEnumerateUsersInDomain(
|
|
DomainHandle,
|
|
&EnumHandle,
|
|
SamFilter,
|
|
(PVOID *) &EnumBuffer,
|
|
SamPrefMaxLen,
|
|
&CountReturned );
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("Enumerate phase 2: Returned %ld entries\n", CountReturned ));
|
|
}
|
|
|
|
SamBytesAlreadyReturned += SamPrefMaxLen;
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
if( Status != STATUS_MORE_ENTRIES ) {
|
|
|
|
AllDone = TRUE;
|
|
}
|
|
|
|
for( i = 0; i < CountReturned; i++ ) {
|
|
|
|
LPBYTE EndOfVariableData;
|
|
LPBYTE FixedDataEnd;
|
|
|
|
//
|
|
// save return buffer end points.
|
|
//
|
|
|
|
EndOfVariableData = BufferDescriptor.EndOfVariableData;
|
|
FixedDataEnd = BufferDescriptor.FixedDataEnd;
|
|
|
|
//
|
|
// Place another entry into the return buffer.
|
|
//
|
|
// Use 0xFFFFFFFF as PrefMaxLen to prevent this routine from
|
|
// prematurely returning ERROR_MORE_DATA. We'll calculate that
|
|
// ourselves below.
|
|
//
|
|
|
|
NetStatus = UserpGetInfo(
|
|
DomainHandle,
|
|
DomainId,
|
|
BuiltinDomainHandle,
|
|
EnumBuffer[i].Name,
|
|
EnumBuffer[i].RelativeId,
|
|
Level,
|
|
0xFFFFFFFF,
|
|
&BufferDescriptor,
|
|
FALSE, // Not a 'get' operation
|
|
0 );
|
|
|
|
if (NetStatus != NERR_Success) {
|
|
|
|
//
|
|
// We may have access to enumerate objects we don't have access
|
|
// to touch. So, simply ignore those accounts we can't get
|
|
// information for.
|
|
//
|
|
|
|
if ( NetStatus == ERROR_ACCESS_DENIED ) {
|
|
continue;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Only count this entry if it was added to the return buffer.
|
|
//
|
|
|
|
if ( (EndOfVariableData != BufferDescriptor.EndOfVariableData ) ||
|
|
(FixedDataEnd != BufferDescriptor.FixedDataEnd ) ) {
|
|
|
|
(*EntriesRead)++;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// free up current EnumBuffer and get another EnumBuffer.
|
|
//
|
|
|
|
Status = SamFreeMemory( EnumBuffer );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
EnumBuffer = NULL;
|
|
|
|
if( AllDone == TRUE ) {
|
|
NetStatus = NERR_Success;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check here if we've exceeded PrefMaxLen since here we know
|
|
// a valid resume handle.
|
|
//
|
|
|
|
|
|
NetUserBytesAlreadyReturned =
|
|
( BufferDescriptor.AllocSize -
|
|
((DWORD)(BufferDescriptor.EndOfVariableData -
|
|
BufferDescriptor.FixedDataEnd)) );
|
|
|
|
if ( NetUserBytesAlreadyReturned >= PrefMaxLen ) {
|
|
|
|
LocalResumeHandle = EnumHandle;
|
|
|
|
NetStatus = ERROR_MORE_DATA;
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Set EntriesLeft to the number left to return plus those that
|
|
// we returned on this call.
|
|
//
|
|
|
|
if( TotalRemaining >= *EntriesRead ) {
|
|
*EntriesLeft = TotalRemaining;
|
|
}
|
|
else {
|
|
|
|
*EntriesLeft = *EntriesRead;
|
|
}
|
|
|
|
//
|
|
// Free up all resources, we reopen them if the caller calls again.
|
|
//
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( BuiltinDomainHandle != NULL ) {
|
|
UaspCloseDomain( BuiltinDomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
if ( EnumBuffer != NULL ) {
|
|
Status = SamFreeMemory( EnumBuffer );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
//
|
|
// If we're not returning data to the caller,
|
|
// free the return buffer.
|
|
//
|
|
|
|
if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
|
|
|
|
if( NetStatus != NERR_BufTooSmall ) {
|
|
|
|
if ( BufferDescriptor.Buffer != NULL ) {
|
|
MIDL_user_free( BufferDescriptor.Buffer );
|
|
BufferDescriptor.Buffer = NULL;
|
|
}
|
|
*EntriesRead = 0;
|
|
*EntriesLeft = 0;
|
|
}
|
|
else {
|
|
NetpAssert( BufferDescriptor.Buffer == NULL );
|
|
NetpAssert( *EntriesRead == 0 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the output parameters
|
|
//
|
|
|
|
*Buffer = BufferDescriptor.Buffer;
|
|
|
|
if ( ARGUMENT_PRESENT( ResumeHandle ) ) {
|
|
*ResumeHandle = LocalResumeHandle;
|
|
}
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(("NetUserEnum: PrefLen %ld Returned %ld\n", PrefMaxLen,
|
|
( BufferDescriptor.AllocSize -
|
|
((DWORD)(BufferDescriptor.EndOfVariableData -
|
|
BufferDescriptor.FixedDataEnd)) ) ));
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserEnum( (LPWSTR)ServerName,
|
|
Level,
|
|
Buffer,
|
|
PrefMaxLen,
|
|
EntriesRead,
|
|
EntriesLeft,
|
|
ResumeHandle );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserEnum: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserEnum
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserGetInfo(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName,
|
|
IN DWORD Level,
|
|
OUT LPBYTE *Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieve information about a particular user.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - Name of the user to get information about.
|
|
|
|
Level - Level of information required.
|
|
|
|
Buffer - Returns a pointer to the return information structure.
|
|
Caller must deallocate buffer using NetApiBufferFree.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
PSID DomainId = NULL;
|
|
SAM_HANDLE BuiltinDomainHandle = NULL;
|
|
BUFFER_DESCRIPTOR BufferDescriptor;
|
|
|
|
ULONG RelativeId; // Relative Id of the user
|
|
UNICODE_STRING UserNameString;
|
|
|
|
BufferDescriptor.Buffer = NULL;
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetInfo: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Domain
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Builtin Domain.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_GET_ALIAS_MEMBERSHIP,
|
|
FALSE, // Builtin Domain
|
|
&BuiltinDomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Validate the user name and get the relative ID.
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
0, // DesiredAccess
|
|
UserName,
|
|
NULL, // UserHandle
|
|
&RelativeId );
|
|
|
|
if (NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the Information about the user.
|
|
//
|
|
|
|
RtlInitUnicodeString( &UserNameString, UserName );
|
|
NetStatus = UserpGetInfo(
|
|
DomainHandle,
|
|
DomainId,
|
|
BuiltinDomainHandle,
|
|
UserNameString,
|
|
RelativeId,
|
|
Level,
|
|
0, // PrefMaxLen
|
|
&BufferDescriptor,
|
|
TRUE, // Is a 'get' operation
|
|
0 ); // don't filter account
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// If we're returning data to the caller,
|
|
// Don't free the return buffer.
|
|
//
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
*Buffer = BufferDescriptor.Buffer;
|
|
} else {
|
|
if ( BufferDescriptor.Buffer != NULL ) {
|
|
MIDL_user_free( BufferDescriptor.Buffer );
|
|
}
|
|
}
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( BuiltinDomainHandle != NULL ) {
|
|
UaspCloseDomain( BuiltinDomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserGetInfo( (LPWSTR)ServerName, (LPWSTR)UserName, Level, Buffer );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetInfo: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserGetInfo
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserSetInfo(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName,
|
|
IN DWORD Level,
|
|
IN LPBYTE Buffer,
|
|
OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the parameters on a user account in the user accounts database.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - Name of the user to modify.
|
|
|
|
Level - Level of information provided.
|
|
|
|
Buffer - A pointer to the buffer containing the user information
|
|
structure.
|
|
|
|
ParmError - Optional pointer to a DWORD to return the index of the
|
|
first parameter in error when ERROR_INVALID_PARAMETER is returned.
|
|
If NULL, the parameter is not returned on error.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
PSID DomainId = NULL;
|
|
SAM_HANDLE BuiltinDomainHandle = NULL;
|
|
ULONG UserRelativeId;
|
|
|
|
//
|
|
// Initialize
|
|
//
|
|
|
|
NetpSetParmError( PARM_ERROR_NONE );
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserSetInfo: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Account Domain
|
|
// DOMAIN_READ_PASSWORD_PARAMETERS is needed in those cases that we'll
|
|
// set the password on the account.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Builtin Domain.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_GET_ALIAS_MEMBERSHIP,
|
|
FALSE, // Builtin Domain
|
|
&BuiltinDomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the relative ID of the user. Don't open the user yet
|
|
// since we don't know the desired access.
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
0, // DesiredAccess
|
|
UserName,
|
|
NULL, // UserHandle
|
|
&UserRelativeId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Change the user
|
|
//
|
|
|
|
NetStatus = UserpSetInfo(
|
|
DomainHandle,
|
|
DomainId,
|
|
NULL, // UserHandle (let UserpSetInfo open the user)
|
|
BuiltinDomainHandle,
|
|
UserRelativeId,
|
|
UserName,
|
|
Level,
|
|
Buffer,
|
|
0xFFFFFFFF, // set all requested fields
|
|
ParmError );
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( BuiltinDomainHandle != NULL ) {
|
|
UaspCloseDomain( BuiltinDomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserSetInfo( (LPWSTR) ServerName,
|
|
(LPWSTR) UserName,
|
|
Level,
|
|
Buffer,
|
|
ParmError );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserSetInfo: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserSetInfo
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserGetGroups(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName,
|
|
IN DWORD Level,
|
|
OUT LPBYTE *Buffer,
|
|
IN DWORD PrefMaxLen,
|
|
OUT LPDWORD EntriesRead,
|
|
OUT LPDWORD EntriesLeft
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enumerate the groups that this user is a member of.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - The name of the user whose members are to be listed.
|
|
|
|
Level - Level of information required (must be 0 or 1)
|
|
|
|
Buffer - Returns a pointer to the return information structure.
|
|
Caller must deallocate buffer using NetApiBufferFree.
|
|
|
|
PrefMaxLen - Prefered maximum length of returned data.
|
|
|
|
EntriesRead - Returns the actual enumerated element count.
|
|
|
|
EntriesLeft - Returns the total entries available to be enumerated.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
|
|
BUFFER_DESCRIPTOR BufferDescriptor;
|
|
DWORD FixedSize; // The fixed size of each new entry.
|
|
|
|
DWORD i;
|
|
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
SAM_HANDLE UserHandle = NULL;
|
|
|
|
PUNICODE_STRING Names = NULL; // Names corresponding to Ids
|
|
ULONG GroupCount;
|
|
|
|
PGROUP_MEMBERSHIP GroupAttributes = NULL;
|
|
|
|
PULONG MemberIds = NULL; // Sam returned MemberIds
|
|
PULONG MemberGroupAttributes = NULL; // Sam returned MemberAttributes;
|
|
|
|
//
|
|
// Validate Parameters
|
|
//
|
|
|
|
*EntriesRead = 0;
|
|
*EntriesLeft = 0;
|
|
BufferDescriptor.Buffer = NULL;
|
|
|
|
switch (Level) {
|
|
case 0:
|
|
FixedSize = sizeof(GROUP_USERS_INFO_0);
|
|
break;
|
|
|
|
case 1:
|
|
FixedSize = sizeof(GROUP_USERS_INFO_1);
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetGroups: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Domain
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
NULL); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the user asking for USER_LIST_GROUPS access.
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
USER_LIST_GROUPS,
|
|
UserName,
|
|
&UserHandle,
|
|
NULL); // Relative Id
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the membership from SAM
|
|
//
|
|
// This API is an odd one for SAM. It returns all of the membership
|
|
// information in a single call.
|
|
//
|
|
|
|
Status = SamGetGroupsForUser( UserHandle, &GroupAttributes, &GroupCount );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserGetGroups: SamGetGroupsForUser returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Handle the case where there is nothing to return.
|
|
//
|
|
|
|
if ( GroupCount == 0 ) {
|
|
NetStatus = NERR_Success;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Convert the returned relative IDs to user names.
|
|
//
|
|
|
|
//
|
|
// Allocate a buffer for converting relative ids to user names
|
|
//
|
|
|
|
MemberIds = NetpMemoryAllocate( GroupCount * sizeof(ULONG) );
|
|
|
|
if ( MemberIds == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
MemberGroupAttributes = NetpMemoryAllocate( GroupCount * sizeof(ULONG) );
|
|
|
|
if ( MemberGroupAttributes == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate another buffer for store attributes of the groups
|
|
// we returning.
|
|
//
|
|
|
|
//
|
|
// Copy the relative IDs returned from SAM to the allocated buffer.
|
|
//
|
|
|
|
for ( i=0; i < GroupCount; i++ ) {
|
|
MemberIds[*EntriesLeft] = GroupAttributes[i].RelativeId;
|
|
MemberGroupAttributes[*EntriesLeft] = GroupAttributes[i].Attributes;
|
|
(*EntriesLeft)++;
|
|
}
|
|
|
|
//
|
|
// Convert the relative IDs to names
|
|
//
|
|
|
|
Status = SamLookupIdsInDomain( DomainHandle,
|
|
*EntriesLeft,
|
|
MemberIds,
|
|
&Names,
|
|
NULL ); // NameUse
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserGetGroups: SamLookupIdsInDomain returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Determine the number of entries that will fit in the caller's
|
|
// buffer.
|
|
//
|
|
|
|
for ( i=0; i < *EntriesLeft; i++ ) {
|
|
DWORD Size;
|
|
PGROUP_USERS_INFO_0 grui0;
|
|
|
|
//
|
|
// Compute the size of the next entry
|
|
//
|
|
|
|
Size = FixedSize + Names[i].Length + sizeof(WCHAR);
|
|
|
|
//
|
|
// Ensure the return buffer is big enough.
|
|
//
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );
|
|
|
|
NetStatus = NetpAllocateEnumBuffer(
|
|
&BufferDescriptor,
|
|
FALSE, // Is an enumeration routine.
|
|
PrefMaxLen,
|
|
Size,
|
|
GrouppMemberRelocationRoutine,
|
|
Level );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the data into the buffer
|
|
//
|
|
|
|
grui0 = (PGROUP_USERS_INFO_0) BufferDescriptor.FixedDataEnd;
|
|
BufferDescriptor.FixedDataEnd += FixedSize ;
|
|
|
|
NetpAssert( offsetof( GROUP_USERS_INFO_0, grui0_name ) ==
|
|
offsetof( GROUP_USERS_INFO_1, grui1_name ) );
|
|
|
|
if ( !NetpCopyStringToBuffer(
|
|
Names[i].Buffer,
|
|
Names[i].Length/sizeof(WCHAR),
|
|
BufferDescriptor.FixedDataEnd,
|
|
(LPWSTR *)&BufferDescriptor.EndOfVariableData,
|
|
&grui0->grui0_name) ) {
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( Level == 1 ) {
|
|
((PGROUP_USERS_INFO_1)grui0)->grui1_attributes =
|
|
MemberGroupAttributes[i];
|
|
}
|
|
|
|
(*EntriesRead)++;
|
|
|
|
}
|
|
|
|
NetStatus = NERR_Success ;
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Free any resources used locally
|
|
//
|
|
|
|
if( MemberIds != NULL ) {
|
|
NetpMemoryFree( MemberIds );
|
|
}
|
|
|
|
if( MemberGroupAttributes != NULL ) {
|
|
NetpMemoryFree( MemberGroupAttributes );
|
|
}
|
|
|
|
if ( Names != NULL ) {
|
|
Status = SamFreeMemory( Names );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( UserHandle != NULL ) {
|
|
(VOID) SamCloseHandle( UserHandle );
|
|
}
|
|
|
|
if ( GroupAttributes != NULL ) {
|
|
Status = SamFreeMemory( GroupAttributes );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
UaspCloseDomain( DomainHandle );
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
//
|
|
// If we're not returning data to the caller,
|
|
// free the return buffer.
|
|
//
|
|
|
|
if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
|
|
if ( BufferDescriptor.Buffer != NULL ) {
|
|
MIDL_user_free( BufferDescriptor.Buffer );
|
|
BufferDescriptor.Buffer = NULL;
|
|
}
|
|
*EntriesLeft = 0;
|
|
*EntriesRead = 0;
|
|
}
|
|
*Buffer = BufferDescriptor.Buffer;
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserGetGroups( (LPWSTR)ServerName,
|
|
(LPWSTR)UserName,
|
|
Level,
|
|
Buffer,
|
|
PrefMaxLen,
|
|
EntriesRead,
|
|
EntriesLeft );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetGroups: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserGetGroups
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserSetGroups (
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName,
|
|
IN DWORD Level,
|
|
IN LPBYTE Buffer,
|
|
IN DWORD NewGroupCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set the list of groups that is user is a member of.
|
|
|
|
The groups specified by "Buffer" are called new groups. The groups
|
|
that the user is currently a member of are called old groups.
|
|
Groups which are on both the old and new list are called common groups.
|
|
|
|
The SAM API allows only one member to be added or deleted at a time.
|
|
This API allows all of the groups this user is a member of to be
|
|
specified en-masse. This API is careful to always leave the group
|
|
membership in the SAM database in a reasonable state.
|
|
It does by merging the list of
|
|
old and new groups, then only changing those memberships which absolutely
|
|
need changing.
|
|
|
|
Group membership is restored to its previous state (if possible) if
|
|
an error occurs during changing the group membership.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - Name of the user to modify.
|
|
|
|
Level - Level of information provided. Must be 0 or 1.
|
|
|
|
Buffer - A pointer to the buffer containing an array of NewGroupCount
|
|
group membership information structures.
|
|
|
|
NewGroupCount - Number of entries in Buffer.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
SAM_HANDLE UserHandle = NULL;
|
|
ULONG UserRelativeId;
|
|
|
|
DWORD FixedSize;
|
|
|
|
PULONG NewRelativeIds = NULL; // Relative Ids of a list of new groups
|
|
PSID_NAME_USE NewNameUse = NULL; // Name usage of a list of new groups
|
|
PUNICODE_STRING NewNameStrings = NULL;// Names of a list of new groups
|
|
|
|
//
|
|
// Define an internal group membership list structure.
|
|
//
|
|
// This structure defines a list of new group memberships to be added,
|
|
// group memberships whose attributes merely need to be changed,
|
|
// and group memberships which need to be deleted.
|
|
//
|
|
// The list is maintained in relative ID sorted order.
|
|
//
|
|
|
|
struct _GROUP_DESCRIPTION {
|
|
struct _GROUP_DESCRIPTION * Next; // Next entry in linked list;
|
|
|
|
ULONG RelativeId; // Relative ID of this group
|
|
|
|
SAM_HANDLE GroupHandle; // Group Handle of this group
|
|
|
|
enum _Action { // Action taken for this group membership
|
|
AddMember, // Add membership to group
|
|
RemoveMember, // Remove membership from group
|
|
SetAttributesMember, // Change the membership's attributes
|
|
IgnoreMember // Ignore this membership
|
|
} Action;
|
|
|
|
BOOL Done; // True if this action has been taken
|
|
|
|
ULONG NewAttributes; // Attributes to set for the membership
|
|
|
|
ULONG OldAttributes; // Attributes to restore on a recovery
|
|
|
|
} *GroupList = NULL, *CurEntry, **Entry, *TempEntry;
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserSetGroups: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the Domain
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
NULL); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the user
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
USER_LIST_GROUPS,
|
|
UserName,
|
|
&UserHandle,
|
|
&UserRelativeId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Validate the level
|
|
//
|
|
|
|
switch (Level) {
|
|
case 0:
|
|
FixedSize = sizeof( GROUP_USERS_INFO_0 );
|
|
break;
|
|
case 1:
|
|
FixedSize = sizeof( GROUP_USERS_INFO_1 );
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the list of new groups
|
|
//
|
|
|
|
if ( NewGroupCount > 0 ) {
|
|
|
|
DWORD NewIndex; // Index to the current New group
|
|
|
|
//
|
|
// Allocate a buffer big enough to contain all the string variables
|
|
// for the new group names.
|
|
//
|
|
|
|
NewNameStrings = NetpMemoryAllocate( NewGroupCount *
|
|
sizeof(UNICODE_STRING));
|
|
|
|
if ( NewNameStrings == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Fill in the list of group name strings for each new group.
|
|
//
|
|
|
|
NetpAssert( offsetof( GROUP_USERS_INFO_0, grui0_name ) ==
|
|
offsetof( GROUP_USERS_INFO_1, grui1_name ) );
|
|
|
|
for ( NewIndex=0; NewIndex<NewGroupCount; NewIndex++ ) {
|
|
LPWSTR GroupName;
|
|
|
|
GroupName =
|
|
((PGROUP_USERS_INFO_0)(Buffer+FixedSize*NewIndex))->grui0_name;
|
|
|
|
RtlInitUnicodeString( &NewNameStrings[NewIndex], GroupName );
|
|
}
|
|
|
|
//
|
|
// Convert the group names to relative Ids.
|
|
//
|
|
|
|
Status = SamLookupNamesInDomain( DomainHandle,
|
|
NewGroupCount,
|
|
NewNameStrings,
|
|
&NewRelativeIds,
|
|
&NewNameUse );
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserSetGroups: SamLookupNamesInDomain returned %lX\n",
|
|
Status ));
|
|
}
|
|
|
|
if ( Status == STATUS_NONE_MAPPED ) {
|
|
NetStatus = NERR_GroupNotFound;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build a group entry for each of the new groups.
|
|
// The list is maintained in RelativeId sorted order.
|
|
//
|
|
|
|
for ( NewIndex=0; NewIndex<NewGroupCount; NewIndex++ ) {
|
|
|
|
//
|
|
// Ensure the new group name is really a group
|
|
// One cannot become the member of a user!!!
|
|
//
|
|
|
|
if (NewNameUse[NewIndex] != SidTypeGroup) {
|
|
NetStatus = NERR_GroupNotFound;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Find the place to put the new entry
|
|
//
|
|
|
|
Entry = &GroupList;
|
|
while ( *Entry != NULL &&
|
|
(*Entry)->RelativeId < NewRelativeIds[NewIndex] ) {
|
|
|
|
Entry = &( (*Entry)->Next );
|
|
}
|
|
|
|
//
|
|
// If this is not a duplicate entry, allocate a new group structure
|
|
// and fill it in.
|
|
//
|
|
// Just ignore duplicate relative Ids.
|
|
//
|
|
|
|
if ( *Entry == NULL ||
|
|
(*Entry)->RelativeId > NewRelativeIds[NewIndex] ) {
|
|
|
|
CurEntry =
|
|
NetpMemoryAllocate( sizeof(struct _GROUP_DESCRIPTION) );
|
|
|
|
if ( CurEntry == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
CurEntry->Next = *Entry;
|
|
CurEntry->RelativeId = NewRelativeIds[NewIndex];
|
|
CurEntry->Action = AddMember;
|
|
CurEntry->Done = FALSE;
|
|
CurEntry->GroupHandle = NULL;
|
|
|
|
CurEntry->NewAttributes = ( Level == 1 ) ?
|
|
((PGROUP_USERS_INFO_1)Buffer)[NewIndex].grui1_attributes :
|
|
SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
|
|
*Entry = CurEntry;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Merge the old groups into the list.
|
|
//
|
|
|
|
{
|
|
ULONG OldIndex; // Index to current entry
|
|
ULONG OldCount; // Total Number of entries
|
|
PGROUP_MEMBERSHIP GroupAttributes = NULL;
|
|
|
|
//
|
|
// Determine the old group membership
|
|
//
|
|
|
|
Status = SamGetGroupsForUser(
|
|
UserHandle,
|
|
&GroupAttributes,
|
|
&OldCount );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserSetGroups: SamGetGroupsForUser returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Merge each old group into the list
|
|
//
|
|
|
|
for ( OldIndex=0; OldIndex < OldCount; OldIndex++) {
|
|
|
|
//
|
|
// Find the place to put the new entry
|
|
//
|
|
|
|
Entry = &GroupList ;
|
|
while ( *Entry != NULL &&
|
|
(*Entry)->RelativeId < GroupAttributes[OldIndex].RelativeId ) {
|
|
|
|
Entry = &( (*Entry)->Next );
|
|
}
|
|
|
|
//
|
|
// If this entry is not already in the list,
|
|
// this is a group membership which exists now but should
|
|
// be deleted.
|
|
//
|
|
|
|
if( *Entry == NULL ||
|
|
(*Entry)->RelativeId > GroupAttributes[OldIndex].RelativeId){
|
|
|
|
CurEntry =
|
|
NetpMemoryAllocate(sizeof(struct _GROUP_DESCRIPTION));
|
|
|
|
if ( CurEntry == NULL ) {
|
|
Status = SamFreeMemory( GroupAttributes );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
CurEntry->Next = *Entry;
|
|
CurEntry->RelativeId = GroupAttributes[OldIndex].RelativeId;
|
|
CurEntry->Action = RemoveMember;
|
|
CurEntry->Done = FALSE;
|
|
CurEntry->OldAttributes = GroupAttributes[OldIndex].Attributes;
|
|
CurEntry->GroupHandle = NULL;
|
|
|
|
*Entry = CurEntry;
|
|
|
|
//
|
|
// Handle the case where this group is already in the list
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Watch out for SAM returning the same group twice.
|
|
//
|
|
|
|
if ( (*Entry)->Action != AddMember ) {
|
|
Status = SamFreeMemory( GroupAttributes );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If this is info level 1 and the requested attributes are
|
|
// different than the current attributes,
|
|
// Remember to change the attributes.
|
|
//
|
|
|
|
if ( Level == 1 && (*Entry)->NewAttributes !=
|
|
GroupAttributes[OldIndex].Attributes ) {
|
|
|
|
(*Entry)->OldAttributes =
|
|
GroupAttributes[OldIndex].Attributes;
|
|
|
|
(*Entry)->Action = SetAttributesMember;
|
|
|
|
//
|
|
// This is either info level 0 or the level 1 attributes
|
|
// are the same as the existing attributes.
|
|
//
|
|
// In either case, this group membership is already set
|
|
// up properly and we should ignore this entry for the
|
|
// rest of this routine.
|
|
//
|
|
|
|
} else {
|
|
(*Entry)->Action = IgnoreMember;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through the list opening all of the groups
|
|
//
|
|
// Ask for add and remove access for BOTH added and removed memberships.
|
|
// One access is required to do the operation initially. The other access
|
|
// is required to undo the operation during recovery.
|
|
//
|
|
|
|
for ( CurEntry = GroupList; CurEntry != NULL ; CurEntry=CurEntry->Next ) {
|
|
if ( CurEntry->Action == AddMember || CurEntry->Action == RemoveMember){
|
|
Status = SamOpenGroup(
|
|
DomainHandle,
|
|
GROUP_ADD_MEMBER | GROUP_REMOVE_MEMBER,
|
|
CurEntry->RelativeId,
|
|
&CurEntry->GroupHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserSetGroups: SamOpenGroup returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through the list adding membership to all new groups.
|
|
// We do this in a separate loop to minimize the damage that happens
|
|
// should we get an error and not be able to recover.
|
|
//
|
|
|
|
for ( CurEntry = GroupList; CurEntry != NULL ; CurEntry=CurEntry->Next ) {
|
|
if ( CurEntry->Action == AddMember ) {
|
|
Status = SamAddMemberToGroup( CurEntry->GroupHandle,
|
|
UserRelativeId,
|
|
CurEntry->NewAttributes );
|
|
|
|
//
|
|
// For level 0, if the default attributes were incompatible,
|
|
// try these attributes.
|
|
//
|
|
|
|
if ( Level == 0 && Status == STATUS_INVALID_GROUP_ATTRIBUTES ) {
|
|
Status = SamAddMemberToGroup( CurEntry->GroupHandle,
|
|
UserRelativeId,
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED );
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserSetGroups: SamAddMemberToGroup returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
CurEntry->Done = TRUE;
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop through the list deleting membership from all old groups
|
|
// and changing the membership attributes of all common groups.
|
|
//
|
|
|
|
for ( CurEntry = GroupList; CurEntry != NULL ; CurEntry=CurEntry->Next ) {
|
|
|
|
if ( CurEntry->Action == RemoveMember ) {
|
|
Status = SamRemoveMemberFromGroup( CurEntry->GroupHandle,
|
|
UserRelativeId);
|
|
|
|
} else if ( CurEntry->Action == SetAttributesMember ) {
|
|
Status = SamSetMemberAttributesOfGroup( CurEntry->GroupHandle,
|
|
UserRelativeId,
|
|
CurEntry->NewAttributes);
|
|
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserSetGroups: SamRemoveMemberFromGroup (or SetMemberAttributes) returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
CurEntry->Done = TRUE;
|
|
}
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Walk the group list cleaning up any damage we've done
|
|
//
|
|
|
|
for ( CurEntry = GroupList; CurEntry != NULL ; ) {
|
|
|
|
if ( NetStatus != NERR_Success && CurEntry->Done ) {
|
|
switch (CurEntry->Action) {
|
|
case AddMember:
|
|
Status = SamRemoveMemberFromGroup( CurEntry->GroupHandle,
|
|
UserRelativeId );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
break;
|
|
|
|
case RemoveMember:
|
|
Status = SamAddMemberToGroup( CurEntry->GroupHandle,
|
|
UserRelativeId,
|
|
CurEntry->OldAttributes );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
break;
|
|
|
|
case SetAttributesMember:
|
|
Status = SamSetMemberAttributesOfGroup(CurEntry->GroupHandle,
|
|
UserRelativeId,
|
|
CurEntry->OldAttributes);
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CurEntry->GroupHandle != NULL) {
|
|
(VOID) SamCloseHandle( CurEntry->GroupHandle );
|
|
}
|
|
|
|
TempEntry = CurEntry;
|
|
CurEntry = CurEntry->Next;
|
|
NetpMemoryFree( TempEntry );
|
|
}
|
|
|
|
//
|
|
// Free up any locally used resources.
|
|
//
|
|
|
|
if ( NewNameStrings != NULL ) {
|
|
NetpMemoryFree( NewNameStrings );
|
|
}
|
|
|
|
if ( NewRelativeIds != NULL ) {
|
|
Status = SamFreeMemory( NewRelativeIds );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( NewNameUse != NULL ) {
|
|
Status = SamFreeMemory( NewNameUse );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if (UserHandle != NULL) {
|
|
(VOID) SamCloseHandle( UserHandle );
|
|
}
|
|
|
|
UaspCloseDomain( DomainHandle );
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserSetGroups( (LPWSTR)ServerName,
|
|
(LPWSTR)UserName,
|
|
Level,
|
|
Buffer,
|
|
NewGroupCount );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserSetGroups: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserSetGroups
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserGetLocalGroups(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN LPCWSTR UserName,
|
|
IN DWORD Level,
|
|
IN DWORD Flags,
|
|
OUT LPBYTE *Buffer,
|
|
IN DWORD PrefMaxLen,
|
|
OUT LPDWORD EntriesRead,
|
|
OUT LPDWORD EntriesLeft
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enumerate the local groups that this user is a member of.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
UserName - The name of the user whose members are to be listed.
|
|
The UserName can be of the form <UserName> in which case the
|
|
UserName is expected to be found on ServerName. The UserName can also
|
|
be of the form <DomainName>\<UserName> in which case <DomainName> is
|
|
expected to be trusted by ServerName and <UserName> is expected to be to
|
|
be found on that domain.
|
|
|
|
Level - Level of information required (must be 0)
|
|
|
|
Flags - Indicates if indirect local group membership is to be
|
|
included.
|
|
|
|
Buffer - Returns a pointer to the return information structure.
|
|
Caller must deallocate buffer using NetApiBufferFree.
|
|
|
|
PrefMaxLen - Prefered maximum length of returned data.
|
|
|
|
EntriesRead - Returns the actual enumerated element count.
|
|
|
|
EntriesLeft - Returns the total entries available to be enumerated.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
|
|
BUFFER_DESCRIPTOR BufferDescriptor;
|
|
DWORD FixedSize; // The fixed size of each new entry.
|
|
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
SAM_HANDLE UsersDomainHandle = NULL;
|
|
SAM_HANDLE BuiltinDomainHandle = NULL;
|
|
SAM_HANDLE UserHandle = NULL;
|
|
PSID DomainId = NULL ;
|
|
PSID DomainIdToUse;
|
|
PSID UsersDomainId = NULL ;
|
|
PSID *UserSidList = NULL;
|
|
ULONG PartialCount = 0;
|
|
|
|
LPCWSTR OrigUserName = UserName;
|
|
PWCHAR BackSlash;
|
|
|
|
PUNICODE_STRING Names = NULL; // Names corresponding to Ids
|
|
PULONG Aliases = NULL;
|
|
|
|
ULONG GroupCount = 0 ;
|
|
ULONG GroupIndex;
|
|
PGROUP_MEMBERSHIP GroupMembership = NULL;
|
|
|
|
PSID *UserSids = NULL;
|
|
ULONG UserSidCount = 0;
|
|
ULONG UserRelativeId = 0;
|
|
|
|
//
|
|
// Validate Parameters
|
|
//
|
|
|
|
*EntriesRead = 0;
|
|
*EntriesLeft = 0;
|
|
BufferDescriptor.Buffer = NULL;
|
|
if (Flags & ~LG_INCLUDE_INDIRECT) {
|
|
NetStatus = ERROR_INVALID_PARAMETER; // unknown flag
|
|
goto Cleanup;
|
|
}
|
|
|
|
switch (Level) {
|
|
case 0:
|
|
FixedSize = sizeof(LOCALGROUP_USERS_INFO_0);
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetLocalGroups: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Open the Domains (Account & Builtin)
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_LOOKUP | DOMAIN_GET_ALIAS_MEMBERSHIP,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId);
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DOMAIN_GET_ALIAS_MEMBERSHIP,
|
|
FALSE, // Builtin Domain
|
|
&BuiltinDomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Parse the <DomainName>\<UserName>
|
|
//
|
|
|
|
BackSlash = wcschr( UserName, L'\\' );
|
|
|
|
|
|
|
|
//
|
|
// If the global group of the user are to be taken into consideration,
|
|
// get the global groups now.
|
|
//
|
|
if ( Flags & LG_INCLUDE_INDIRECT ) {
|
|
SAM_HANDLE DomainHandleToUse;
|
|
|
|
//
|
|
// Handle the case where no domain is specified
|
|
//
|
|
|
|
if ( BackSlash == NULL ) {
|
|
DomainHandleToUse = DomainHandle;
|
|
DomainIdToUse = DomainId;
|
|
|
|
//
|
|
// Handle the case where a domain name was specified
|
|
//
|
|
|
|
} else {
|
|
|
|
DWORD UsersDomainNameLength;
|
|
WCHAR UsersDomainName[DNLEN+1];
|
|
|
|
//
|
|
// Grab the domain name
|
|
//
|
|
|
|
UsersDomainNameLength = (DWORD)(BackSlash - UserName);
|
|
if ( UsersDomainNameLength == 0 ||
|
|
UsersDomainNameLength > DNLEN ) {
|
|
|
|
NetStatus = NERR_DCNotFound;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory( UsersDomainName, UserName, UsersDomainNameLength*sizeof(WCHAR) );
|
|
UsersDomainName[UsersDomainNameLength] = L'\0';
|
|
UserName = BackSlash+1;
|
|
|
|
//
|
|
// Open a handle to the specified domain's SAM.
|
|
//
|
|
|
|
NetStatus = UaspOpenDomainWithDomainName(
|
|
UsersDomainName,
|
|
DOMAIN_LOOKUP,
|
|
TRUE, // Account Domain
|
|
&UsersDomainHandle,
|
|
&UsersDomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
DomainHandleToUse = UsersDomainHandle;
|
|
DomainIdToUse = UsersDomainId;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Open the user asking for USER_LIST_GROUPS access.
|
|
//
|
|
|
|
NetStatus = UserpOpenUser( DomainHandleToUse,
|
|
USER_LIST_GROUPS,
|
|
UserName,
|
|
&UserHandle,
|
|
&UserRelativeId); // Relative Id
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the group membership from SAM, since we are
|
|
// interested in indirect alias membership via group membership.
|
|
//
|
|
// This API is an odd one for SAM. It returns all of the membership
|
|
// information in a single call.
|
|
//
|
|
|
|
Status = SamGetGroupsForUser( UserHandle, &GroupMembership, &GroupCount );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserGetGroups: SamGetGroupsForUser returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Allocate a buffer to point to the SIDs we're interested in
|
|
// alias membership for.
|
|
//
|
|
|
|
UserSids = (PSID *) NetpMemoryAllocate( (GroupCount+1) * sizeof(PSID) );
|
|
|
|
if ( UserSids == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlZeroMemory( UserSids, (GroupCount+1) * sizeof(PSID) );
|
|
|
|
|
|
//
|
|
// If no domain is specified,
|
|
// just grab the SID of the user account from SAM.
|
|
//
|
|
|
|
if ( BackSlash == NULL ) {
|
|
|
|
//
|
|
// Get the rid of the account
|
|
//
|
|
|
|
if ( UserRelativeId == 0 ) {
|
|
|
|
NetStatus = UserpOpenUser( DomainHandle,
|
|
0,
|
|
UserName,
|
|
NULL,
|
|
&UserRelativeId); // Relative Id
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the User's Sid to the Array of Sids.
|
|
//
|
|
|
|
NetStatus = NetpSamRidToSid( DomainHandle,
|
|
UserRelativeId,
|
|
&UserSids[UserSidCount] );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
UserSidCount ++;
|
|
|
|
//
|
|
// If a domain name is specified,
|
|
// use LookupAccountName to translate the name to a SID.
|
|
//
|
|
// Don't open the user account. We typically don't have access to do that.
|
|
// Newer version of NT don't allow anything over the NULL session.
|
|
//
|
|
|
|
} else {
|
|
|
|
//
|
|
// Translate the name to a SID.
|
|
//
|
|
|
|
NetStatus = AliaspNamesToSids ( ServerName,
|
|
TRUE, // Only allow users
|
|
1,
|
|
(LPWSTR *)&OrigUserName,
|
|
&UserSidList );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
if ( NetStatus == ERROR_NO_SUCH_MEMBER ) {
|
|
NetStatus = NERR_UserNotFound;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Add the User's Sid to the Array of Sids.
|
|
//
|
|
|
|
UserSids[UserSidCount] = UserSidList[0];
|
|
UserSidList[0] = NULL;
|
|
UserSidCount ++;
|
|
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Add each group the user is a member of to the array of Sids.
|
|
// Note that GroupCount would still be zero if LG_INCLUDE_INDIRECT isn't
|
|
// specified.
|
|
//
|
|
|
|
for ( GroupIndex = 0; GroupIndex < GroupCount; GroupIndex ++ ) {
|
|
|
|
NetStatus = NetpSamRidToSid( UserHandle,
|
|
GroupMembership[GroupIndex].RelativeId,
|
|
&UserSids[UserSidCount] );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
UserSidCount ++;
|
|
}
|
|
|
|
|
|
//
|
|
// Find out which aliases in the ACCOUNT domain this user is a member of.
|
|
//
|
|
|
|
Status = SamGetAliasMembership( DomainHandle,
|
|
UserSidCount,
|
|
UserSids,
|
|
&PartialCount,
|
|
&Aliases );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"UserpGetUserPriv: SamGetAliasMembership returns %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (PartialCount > 0)
|
|
{
|
|
//
|
|
// Convert the RIDs to names
|
|
//
|
|
|
|
Status = SamLookupIdsInDomain( DomainHandle,
|
|
PartialCount,
|
|
Aliases,
|
|
&Names,
|
|
NULL ); // NameUse
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserGetGroups: SamLookupIdsInDomain returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = AliaspPackBuf( Level,
|
|
PrefMaxLen,
|
|
PartialCount,
|
|
EntriesRead,
|
|
&BufferDescriptor,
|
|
FixedSize,
|
|
Names) ;
|
|
|
|
if (NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA)
|
|
goto Cleanup;
|
|
|
|
//
|
|
// free up and reset pointers that need to be reused
|
|
//
|
|
|
|
Status = SamFreeMemory( Names );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
Names = NULL ;
|
|
|
|
Status = SamFreeMemory( Aliases );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
Aliases = NULL ;
|
|
|
|
*EntriesLeft = PartialCount ;
|
|
}
|
|
|
|
//
|
|
// Find out which aliases in the BUILTIN domain this user is a member of.
|
|
//
|
|
|
|
Status = SamGetAliasMembership( BuiltinDomainHandle,
|
|
UserSidCount,
|
|
UserSids,
|
|
&PartialCount,
|
|
&Aliases );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"UserpGetUserPriv: SamGetAliasMembership returns %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Convert the RIDs to names
|
|
//
|
|
|
|
Status = SamLookupIdsInDomain( BuiltinDomainHandle,
|
|
PartialCount,
|
|
Aliases,
|
|
&Names,
|
|
NULL ); // NameUse
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserGetGroups: SamLookupIdsInDomain returned %lX\n",
|
|
Status ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = AliaspPackBuf( Level,
|
|
PrefMaxLen,
|
|
PartialCount,
|
|
EntriesRead,
|
|
&BufferDescriptor,
|
|
FixedSize,
|
|
Names) ;
|
|
|
|
*EntriesLeft += PartialCount ;
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Free any resources used locally
|
|
//
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
if ( UsersDomainId != NULL ) {
|
|
NetpMemoryFree( UsersDomainId );
|
|
}
|
|
|
|
if ( Names != NULL ) {
|
|
Status = SamFreeMemory( Names );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( UserHandle != NULL ) {
|
|
(VOID) SamCloseHandle( UserHandle );
|
|
}
|
|
|
|
if ( GroupMembership != NULL ) {
|
|
Status = SamFreeMemory( GroupMembership );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( UserSids != NULL ) {
|
|
|
|
for ( GroupIndex = 0; GroupIndex < UserSidCount; GroupIndex ++ ) {
|
|
NetpMemoryFree( UserSids[GroupIndex] );
|
|
}
|
|
|
|
NetpMemoryFree( UserSids );
|
|
}
|
|
|
|
if ( UserSidList != NULL ) {
|
|
AliaspFreeSidList ( 1, UserSidList );
|
|
}
|
|
|
|
if ( Aliases != NULL ) {
|
|
Status = SamFreeMemory( Aliases );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
|
|
if ( BuiltinDomainHandle != NULL ) {
|
|
UaspCloseDomain( BuiltinDomainHandle );
|
|
}
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( UsersDomainHandle != NULL ) {
|
|
UaspCloseDomain( UsersDomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
|
|
//
|
|
// If we're not returning data to the caller,
|
|
// free the return buffer.
|
|
//
|
|
|
|
if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
|
|
if ( BufferDescriptor.Buffer != NULL ) {
|
|
MIDL_user_free( BufferDescriptor.Buffer );
|
|
BufferDescriptor.Buffer = NULL;
|
|
}
|
|
*EntriesLeft = 0;
|
|
*EntriesRead = 0;
|
|
}
|
|
*Buffer = BufferDescriptor.Buffer;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserGetGroups: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserGetLocalGroups
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
AliaspPackBuf(
|
|
IN DWORD Level,
|
|
IN DWORD PrefMaxLen,
|
|
IN DWORD EntriesCount,
|
|
OUT LPDWORD EntriesRead,
|
|
BUFFER_DESCRIPTOR *BufferDescriptor,
|
|
DWORD FixedSize,
|
|
PUNICODE_STRING Names)
|
|
{
|
|
|
|
NET_API_STATUS NetStatus = NERR_Success ;
|
|
ULONG i ;
|
|
|
|
//
|
|
// Determine the number of entries that will fit in the caller's
|
|
// buffer.
|
|
//
|
|
|
|
for ( i=0; i < EntriesCount; i++ ) {
|
|
DWORD Size;
|
|
PLOCALGROUP_USERS_INFO_0 lgrui0;
|
|
|
|
//
|
|
// Compute the size of the next entry
|
|
//
|
|
|
|
Size = FixedSize + Names[i].Length + sizeof(WCHAR);
|
|
|
|
//
|
|
// Ensure the return buffer is big enough.
|
|
//
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );
|
|
|
|
NetStatus = NetpAllocateEnumBuffer(
|
|
BufferDescriptor,
|
|
FALSE, // Is an enumeration routine.
|
|
PrefMaxLen,
|
|
Size,
|
|
AliaspMemberRelocationRoutine,
|
|
Level );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
break ;
|
|
}
|
|
|
|
//
|
|
// Copy the data into the buffer
|
|
//
|
|
|
|
lgrui0 = (PLOCALGROUP_USERS_INFO_0) BufferDescriptor->FixedDataEnd;
|
|
BufferDescriptor->FixedDataEnd += FixedSize ;
|
|
|
|
if ( !NetpCopyStringToBuffer(
|
|
Names[i].Buffer,
|
|
Names[i].Length/sizeof(WCHAR),
|
|
BufferDescriptor->FixedDataEnd,
|
|
(LPWSTR *)&(BufferDescriptor->EndOfVariableData),
|
|
&lgrui0->lgrui0_name) ) {
|
|
|
|
NetStatus = NERR_InternalError;
|
|
break ;
|
|
}
|
|
|
|
(*EntriesRead)++;
|
|
|
|
}
|
|
|
|
return NetStatus ;
|
|
}
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserModalsGet(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN DWORD Level,
|
|
OUT LPBYTE *Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Retrieve global information for all users and groups in the user
|
|
account database.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
Level - Level of information required. 0, 1, and 2 are valid.
|
|
|
|
Buffer - Returns a pointer to the return information structure.
|
|
Caller must deallocate buffer using NetApiBufferFree.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
NET_API_STATUS NetStatus;
|
|
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
PSID DomainId = NULL;
|
|
|
|
ACCESS_MASK DesiredAccess;
|
|
|
|
DWORD Size; // Size of returned information
|
|
|
|
PDOMAIN_PASSWORD_INFORMATION DomainPassword = NULL;
|
|
PDOMAIN_LOGOFF_INFORMATION DomainLogoff = NULL;
|
|
PDOMAIN_SERVER_ROLE_INFORMATION DomainServerRole = NULL;
|
|
PDOMAIN_REPLICATION_INFORMATION DomainReplication = NULL;
|
|
PDOMAIN_NAME_INFORMATION DomainName = NULL;
|
|
PDOMAIN_LOCKOUT_INFORMATION DomainLockout = NULL;
|
|
|
|
//
|
|
// Validate Level
|
|
//
|
|
|
|
*Buffer = NULL;
|
|
|
|
switch (Level) {
|
|
case 0:
|
|
DesiredAccess =
|
|
DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_READ_PASSWORD_PARAMETERS ;
|
|
break;
|
|
|
|
case 1:
|
|
DesiredAccess = DOMAIN_READ_OTHER_PARAMETERS;
|
|
break;
|
|
|
|
case 2:
|
|
DesiredAccess = DOMAIN_READ_OTHER_PARAMETERS;
|
|
break;
|
|
|
|
case 3:
|
|
DesiredAccess = DOMAIN_READ_PASSWORD_PARAMETERS;
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserModalsGet: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the Domain
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DesiredAccess,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
&DomainId );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the desired information from SAM and determine the size of
|
|
// our return information.
|
|
//
|
|
|
|
switch (Level) {
|
|
case 0:
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainPasswordInformation,
|
|
(PVOID *)&DomainPassword );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainLogoffInformation,
|
|
(PVOID *)&DomainLogoff );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Size = sizeof( USER_MODALS_INFO_0 );
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainServerRoleInformation,
|
|
(PVOID *)&DomainServerRole );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainReplicationInformation,
|
|
(PVOID *)&DomainReplication );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Size = sizeof( USER_MODALS_INFO_1 ) +
|
|
DomainReplication->ReplicaSourceNodeName.Length + sizeof(WCHAR);
|
|
break;
|
|
|
|
case 2:
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainNameInformation,
|
|
(PVOID *)&DomainName );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Size = sizeof( USER_MODALS_INFO_2 ) +
|
|
DomainName->DomainName.Length + sizeof(WCHAR) +
|
|
RtlLengthSid( DomainId );
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
DomainLockoutInformation,
|
|
(PVOID *)&DomainLockout );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
Size = sizeof( USER_MODALS_INFO_3 );
|
|
|
|
break;
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Allocate the return buffer
|
|
//
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );
|
|
|
|
*Buffer = MIDL_user_allocate( Size );
|
|
|
|
if ( *Buffer == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Fill in the return buffer
|
|
//
|
|
|
|
switch (Level) {
|
|
case 0: {
|
|
PUSER_MODALS_INFO_0 usrmod0 = (PUSER_MODALS_INFO_0) *Buffer;
|
|
|
|
usrmod0->usrmod0_min_passwd_len = DomainPassword->MinPasswordLength;
|
|
|
|
usrmod0->usrmod0_max_passwd_age =
|
|
NetpDeltaTimeToSeconds( DomainPassword->MaxPasswordAge );
|
|
|
|
usrmod0->usrmod0_min_passwd_age =
|
|
NetpDeltaTimeToSeconds( DomainPassword->MinPasswordAge );
|
|
|
|
usrmod0->usrmod0_force_logoff =
|
|
NetpDeltaTimeToSeconds( DomainLogoff->ForceLogoff );
|
|
|
|
usrmod0->usrmod0_password_hist_len =
|
|
DomainPassword->PasswordHistoryLength;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
PUSER_MODALS_INFO_1 usrmod1 = (PUSER_MODALS_INFO_1) *Buffer;
|
|
LPWSTR EndOfVariableData = (LPWSTR) (*Buffer + Size);
|
|
|
|
|
|
switch (DomainServerRole->DomainServerRole) {
|
|
|
|
case DomainServerRolePrimary:
|
|
usrmod1->usrmod1_role = UAS_ROLE_PRIMARY;
|
|
break;
|
|
|
|
case DomainServerRoleBackup:
|
|
usrmod1->usrmod1_role = UAS_ROLE_BACKUP;
|
|
break;
|
|
|
|
default:
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( !NetpCopyStringToBuffer(
|
|
DomainReplication->ReplicaSourceNodeName.Buffer,
|
|
DomainReplication->ReplicaSourceNodeName.Length/sizeof(WCHAR),
|
|
*Buffer + sizeof(USER_MODALS_INFO_1),
|
|
&EndOfVariableData,
|
|
&usrmod1->usrmod1_primary) ) {
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2: {
|
|
PUSER_MODALS_INFO_2 usrmod2 = (PUSER_MODALS_INFO_2) *Buffer;
|
|
LPWSTR EndOfVariableData = (LPWSTR) (*Buffer + Size);
|
|
|
|
//
|
|
// Copy text first size it has more stringent alignment requirements
|
|
//
|
|
|
|
if ( !NetpCopyStringToBuffer(
|
|
DomainName->DomainName.Buffer,
|
|
DomainName->DomainName.Length/sizeof(WCHAR),
|
|
*Buffer + sizeof(USER_MODALS_INFO_2),
|
|
&EndOfVariableData,
|
|
&usrmod2->usrmod2_domain_name) ) {
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
if ( !NetpCopyDataToBuffer(
|
|
DomainId,
|
|
RtlLengthSid( DomainId ),
|
|
*Buffer + sizeof(USER_MODALS_INFO_2),
|
|
(LPBYTE *)&EndOfVariableData,
|
|
(LPBYTE *)&usrmod2->usrmod2_domain_id,
|
|
sizeof(BYTE) ) ) {
|
|
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: {
|
|
PUSER_MODALS_INFO_3 usrmod3 = (PUSER_MODALS_INFO_3) *Buffer;
|
|
|
|
usrmod3->usrmod3_lockout_duration =
|
|
NetpDeltaTimeToSeconds( DomainLockout->LockoutDuration );
|
|
|
|
usrmod3->usrmod3_lockout_observation_window =
|
|
NetpDeltaTimeToSeconds( DomainLockout->LockoutObservationWindow );
|
|
|
|
usrmod3->usrmod3_lockout_threshold = DomainLockout->LockoutThreshold;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
if (DomainPassword != NULL) {
|
|
Status = SamFreeMemory( DomainPassword );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if (DomainLogoff != NULL) {
|
|
Status = SamFreeMemory( DomainLogoff );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if (DomainServerRole != NULL) {
|
|
Status = SamFreeMemory( DomainServerRole );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if (DomainReplication != NULL) {
|
|
Status = SamFreeMemory( DomainReplication );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if (DomainName != NULL) {
|
|
Status = SamFreeMemory( DomainName );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( DomainLockout != NULL ) {
|
|
Status = SamFreeMemory( DomainLockout );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
}
|
|
|
|
if ( DomainId != NULL ) {
|
|
NetpMemoryFree( DomainId );
|
|
}
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserModalsGet( (LPWSTR)ServerName, Level, Buffer );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserModalsGet: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserModalsGet
|
|
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserModalsSet(
|
|
IN LPCWSTR ServerName OPTIONAL,
|
|
IN DWORD Level,
|
|
IN LPBYTE Buffer,
|
|
OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets global information for all users and group in the user account.
|
|
|
|
Arguments:
|
|
|
|
ServerName - A pointer to a string containing the name of the remote
|
|
server on which the function is to execute. A NULL pointer
|
|
or string specifies the local machine.
|
|
|
|
Level - Level of information provided.
|
|
|
|
Buffer - A pointer to the buffer containing the user information
|
|
structure.
|
|
|
|
ParmError - Optional pointer to a DWORD to return the index of the
|
|
first parameter in error when ERROR_INVALID_PARAMETER is returned.
|
|
If NULL, the parameter is not returned on error.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
|
|
SAM_HANDLE SamServerHandle = NULL;
|
|
SAM_HANDLE DomainHandle = NULL;
|
|
|
|
ACCESS_MASK DesiredAccess;
|
|
DWORD UasSamIndex;
|
|
|
|
BOOL LSAServerRoleSet = FALSE;
|
|
BOOL BuiltinDomainServerRoleSet = FALSE;
|
|
|
|
//
|
|
// Each SAM Information Class is described here. If multiple fields
|
|
// can be set in the same information class, each field is set in a
|
|
// common copy of the information class strcuture.
|
|
//
|
|
|
|
struct _SAM_INFORMATION_CLASS {
|
|
|
|
//
|
|
// Sam's DomainInformation class for this class.
|
|
//
|
|
|
|
DOMAIN_INFORMATION_CLASS DomainInformationClass;
|
|
|
|
//
|
|
// The size of this information class structure.
|
|
//
|
|
|
|
DWORD SamSize;
|
|
|
|
//
|
|
// The state of this information class. As we decide to use this
|
|
// information class, actually change it, and possibly restore
|
|
// its old value, we change the state so later stages of this routine
|
|
// can handle each entry.
|
|
//
|
|
|
|
enum {
|
|
UTS_NOT_USED, // No fields are being used
|
|
UTS_USED, // At least one field is to be changed
|
|
UTS_READ, // This info class has been read from SAM
|
|
UTS_DONE, // This info class has been changed in SAM
|
|
UTS_RECOVERED // This info class has be reverted to old values.
|
|
} State;
|
|
|
|
//
|
|
// Before this routine changes anything, it gets the old value for
|
|
// each of the information classes. This old value is used for
|
|
// recovery in the event that an error occurs. That is, we will
|
|
// attempt to put the old information back if we aren't successful
|
|
// in changing all the information to the new values.
|
|
//
|
|
// The old information is also used in the case where a single
|
|
// information class contains multiple fields and we're only changing
|
|
// a subset of those fields.
|
|
//
|
|
|
|
PVOID OldInformation;
|
|
|
|
//
|
|
// The new field values are stored in this instance of the information
|
|
// class.
|
|
//
|
|
|
|
PVOID NewInformation;
|
|
|
|
//
|
|
// The DesiredAccess mask includes both the access to read and the
|
|
// access to write the appropriate DomainInformationClass.
|
|
//
|
|
|
|
ACCESS_MASK DesiredAccess;
|
|
|
|
} SamInfoClass[] = {
|
|
|
|
//
|
|
// Define a SAM_INFORMATION_CLASS for each information class possibly
|
|
// used.
|
|
//
|
|
// The order of the entries in this array must match the order of
|
|
// the SAM_* defines above.
|
|
//
|
|
|
|
/* SAM_LogoffClass */ {
|
|
DomainLogoffInformation, sizeof( DOMAIN_LOGOFF_INFORMATION ),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_WRITE_OTHER_PARAMETERS
|
|
},
|
|
|
|
/* SAM_NameClass */ {
|
|
DomainNameInformation, sizeof( DOMAIN_NAME_INFORMATION ),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_WRITE_OTHER_PARAMETERS
|
|
},
|
|
|
|
/* SAM_PasswordClass */ {
|
|
DomainPasswordInformation, sizeof( DOMAIN_PASSWORD_INFORMATION),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_PASSWORD_PARAMETERS | DOMAIN_WRITE_PASSWORD_PARAMS
|
|
},
|
|
|
|
/* SAM_ReplicationClass */ {
|
|
DomainReplicationInformation, sizeof( DOMAIN_REPLICATION_INFORMATION ),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_ADMINISTER_SERVER
|
|
},
|
|
|
|
/* SAM_ServerRoleClass */ {
|
|
DomainServerRoleInformation, sizeof( DOMAIN_SERVER_ROLE_INFORMATION ),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_OTHER_PARAMETERS | DOMAIN_ADMINISTER_SERVER
|
|
},
|
|
|
|
/* Sam_LockoutClass */ {
|
|
DomainLockoutInformation, sizeof( DOMAIN_LOCKOUT_INFORMATION ),
|
|
UTS_NOT_USED, NULL, NULL,
|
|
DOMAIN_READ_PASSWORD_PARAMETERS | DOMAIN_WRITE_PASSWORD_PARAMS
|
|
}
|
|
};
|
|
|
|
//
|
|
// Define several macros for accessing the various fields of the UAS
|
|
// structure. Each macro takes an index into the UserUasSamTable
|
|
// array and returns the value.
|
|
//
|
|
|
|
#define GET_UAS_MODAL_STRING_POINTER( _i ) \
|
|
(*((LPWSTR *)(Buffer + UserUasSamTable[_i].UasOffset)))
|
|
|
|
#define GET_UAS_MODAL_DWORD( _i ) \
|
|
(*((DWORD *)(Buffer + UserUasSamTable[_i].UasOffset)))
|
|
|
|
//
|
|
// Define a macro which returns a pointer the appropriate SamInfoClass
|
|
// structure given an index into the UserUasSamTable.
|
|
//
|
|
|
|
#define SAM_MODAL_CLASS( _i ) \
|
|
SamInfoClass[ UserUasSamTable[_i].Class ]
|
|
|
|
//
|
|
// Define a macro to return a pointer to the appropriate field in the
|
|
// new sam structure.
|
|
//
|
|
// The caller should coerce the pointer as appropriate.
|
|
//
|
|
|
|
#define GET_SAM_MODAL_FIELD_POINTER( _i ) \
|
|
(((LPBYTE)(SAM_MODAL_CLASS(_i).NewInformation)) + \
|
|
UserUasSamTable[_i].SamOffset)
|
|
|
|
//
|
|
// Initialize
|
|
//
|
|
|
|
NetpSetParmError( PARM_ERROR_NONE );
|
|
|
|
//
|
|
// Go through the list of valid fields determining if the info level
|
|
// is valid and computing the desired access to the domain.
|
|
//
|
|
|
|
DesiredAccess = 0;
|
|
for ( UasSamIndex=0 ;
|
|
UasSamIndex<sizeof(UserUasSamTable)/sizeof(UserUasSamTable[0]);
|
|
UasSamIndex++ ){
|
|
|
|
//
|
|
// If this field isn't one we're changing, just skip to the next one
|
|
//
|
|
|
|
if ( Level != UserUasSamTable[UasSamIndex].UasLevel ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Validate the UAS field based on the field type.
|
|
//
|
|
|
|
switch (UserUasSamTable[UasSamIndex].ModalsFieldType ) {
|
|
|
|
//
|
|
// If this is a PARMNUM_ALL and the caller passed in a
|
|
// NULL pointer to a string, he doesn't want to change the string.
|
|
//
|
|
// Testing for this now allows us to completely ignore a
|
|
// particular SAM information level if absolutely no fields
|
|
// change in that information level.
|
|
//
|
|
|
|
case UMT_STRING:
|
|
if ( GET_UAS_MODAL_STRING_POINTER( UasSamIndex ) == NULL ) {
|
|
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Ensure unsigned shorts are really in range.
|
|
//
|
|
|
|
case UMT_USHORT:
|
|
if ( GET_UAS_MODAL_DWORD(UasSamIndex) > USHRT_MAX ) {
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: ushort too big %lx Index:%ld\n",
|
|
GET_UAS_MODAL_DWORD(UasSamIndex),
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Some values are always valid
|
|
//
|
|
|
|
case UMT_ULONG:
|
|
case UMT_DELTA:
|
|
break;
|
|
|
|
//
|
|
// Ensure the role is a recognized one.
|
|
//
|
|
|
|
case UMT_ROLE:
|
|
switch ( GET_UAS_MODAL_DWORD(UasSamIndex) ) {
|
|
case UAS_ROLE_PRIMARY:
|
|
case UAS_ROLE_BACKUP:
|
|
case UAS_ROLE_MEMBER:
|
|
break;
|
|
|
|
default:
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: invalid role %lx Index:%ld\n",
|
|
GET_UAS_MODAL_DWORD(UasSamIndex),
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
break;
|
|
|
|
//
|
|
// All cases are explicitly handled.
|
|
//
|
|
|
|
default:
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
//
|
|
// Flag that this information class is to be set and
|
|
// accumulate the desired access to do all this functionality.
|
|
//
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).State = UTS_USED;
|
|
DesiredAccess |= SAM_MODAL_CLASS(UasSamIndex).DesiredAccess;
|
|
|
|
}
|
|
|
|
//
|
|
// Check to be sure the user specified a valid Level.
|
|
//
|
|
// The search of the UserUasSamTable should have resulted in
|
|
// at least one match if the arguments are valid.
|
|
//
|
|
|
|
if ( DesiredAccess == 0 ) {
|
|
NetStatus = ERROR_INVALID_LEVEL;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Connect to the SAM server
|
|
//
|
|
|
|
NetStatus = UaspOpenSam( ServerName,
|
|
FALSE, // Don't try null session
|
|
&SamServerHandle );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserModalsSet: Cannot UaspOpenSam %ld\n", NetStatus ));
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the domain asking for accumulated desired access
|
|
//
|
|
|
|
NetStatus = UaspOpenDomain( SamServerHandle,
|
|
DesiredAccess,
|
|
TRUE, // Account Domain
|
|
&DomainHandle,
|
|
NULL ); // DomainId
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// For each field we're going to change,
|
|
// Get the current value of the field
|
|
// Determine what the new value will be.
|
|
//
|
|
// The old values will be used later in error recovery and when multiple
|
|
// fields are changed in SAM with one information level.
|
|
//
|
|
|
|
for ( UasSamIndex=0 ;
|
|
UasSamIndex<sizeof(UserUasSamTable)/sizeof(UserUasSamTable[0]);
|
|
UasSamIndex++ ) {
|
|
|
|
//
|
|
// If this field isn't one we're changing, just skip to the next one
|
|
//
|
|
|
|
if ( Level != UserUasSamTable[UasSamIndex].UasLevel ) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Handle field types that have some special attributes.
|
|
//
|
|
|
|
switch (UserUasSamTable[UasSamIndex].ModalsFieldType ) {
|
|
|
|
//
|
|
// If the caller passed in a
|
|
// NULL pointer to a string, he doesn't want to change the string.
|
|
//
|
|
|
|
case UMT_STRING:
|
|
if ( GET_UAS_MODAL_STRING_POINTER( UasSamIndex ) == NULL ) {
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Other field types don't have any special case handling.
|
|
//
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
//
|
|
// ASSERT: This field type is set via a SAM information class
|
|
//
|
|
|
|
//
|
|
// If we've not previously gotten this information class
|
|
// from SAM, do so now.
|
|
//
|
|
// If an information class has multiple fields, then multiple
|
|
// entries in the UserUasSamTable will share the same old
|
|
// information class.
|
|
//
|
|
|
|
if ( SAM_MODAL_CLASS(UasSamIndex).State == UTS_USED ) {
|
|
|
|
//
|
|
// Allocate space for the New information
|
|
//
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).State = UTS_READ;
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).NewInformation = NetpMemoryAllocate(
|
|
SAM_MODAL_CLASS(UasSamIndex).SamSize );
|
|
|
|
if ( SAM_MODAL_CLASS(UasSamIndex).NewInformation == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get this information class from SAM.
|
|
//
|
|
|
|
Status = SamQueryInformationDomain(
|
|
DomainHandle,
|
|
SAM_MODAL_CLASS(UasSamIndex).DomainInformationClass,
|
|
&SAM_MODAL_CLASS(UasSamIndex).OldInformation );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: Error from"
|
|
" SamQueryInformationDomain %lx Index:%ld\n",
|
|
Status,
|
|
UasSamIndex ));
|
|
}
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the new SAM info class structure with the old
|
|
// values.
|
|
//
|
|
|
|
NetpMoveMemory( SAM_MODAL_CLASS(UasSamIndex).NewInformation,
|
|
SAM_MODAL_CLASS(UasSamIndex).OldInformation,
|
|
SAM_MODAL_CLASS(UasSamIndex).SamSize );
|
|
|
|
}
|
|
|
|
//
|
|
// Set the SAM field in the new information class structure to
|
|
// the UAS requested value.
|
|
//
|
|
|
|
switch ( UserUasSamTable[UasSamIndex].ModalsFieldType ) {
|
|
|
|
//
|
|
// Handle values of type string
|
|
//
|
|
|
|
case UMT_STRING:
|
|
RtlInitUnicodeString(
|
|
(PUNICODE_STRING) GET_SAM_MODAL_FIELD_POINTER(UasSamIndex),
|
|
GET_UAS_MODAL_STRING_POINTER(UasSamIndex) );
|
|
break;
|
|
|
|
//
|
|
// Convert delta time to its SAM counterpart
|
|
//
|
|
|
|
case UMT_DELTA:
|
|
|
|
*((PLARGE_INTEGER) GET_SAM_MODAL_FIELD_POINTER(UasSamIndex)) =
|
|
NetpSecondsToDeltaTime( GET_UAS_MODAL_DWORD(UasSamIndex) );
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"UserpsetInfo: Index: %ld Setting DeltaTime %lx %lx %lx\n",
|
|
UasSamIndex,
|
|
((PLARGE_INTEGER) GET_SAM_MODAL_FIELD_POINTER(UasSamIndex))
|
|
->HighPart,
|
|
((PLARGE_INTEGER) GET_SAM_MODAL_FIELD_POINTER(UasSamIndex))
|
|
->LowPart,
|
|
GET_UAS_MODAL_DWORD(UasSamIndex) ));
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
//
|
|
// Copy the unsigned short to the SAM structure
|
|
//
|
|
|
|
case UMT_USHORT:
|
|
*((PUSHORT)GET_SAM_MODAL_FIELD_POINTER(UasSamIndex)) =
|
|
(USHORT)GET_UAS_MODAL_DWORD(UasSamIndex);
|
|
break;
|
|
|
|
//
|
|
// Copy the unsigned long to the SAM structure
|
|
//
|
|
|
|
case UMT_ULONG:
|
|
*((PULONG)GET_SAM_MODAL_FIELD_POINTER(UasSamIndex)) =
|
|
(ULONG)GET_UAS_MODAL_DWORD(UasSamIndex);
|
|
break;
|
|
|
|
//
|
|
// Ensure the role is a recognized one.
|
|
//
|
|
|
|
case UMT_ROLE:
|
|
|
|
|
|
switch ( GET_UAS_MODAL_DWORD(UasSamIndex) ) {
|
|
case UAS_ROLE_PRIMARY:
|
|
*((PDOMAIN_SERVER_ROLE)GET_SAM_MODAL_FIELD_POINTER(UasSamIndex)) =
|
|
DomainServerRolePrimary;
|
|
break;
|
|
case UAS_ROLE_BACKUP:
|
|
*((PDOMAIN_SERVER_ROLE)GET_SAM_MODAL_FIELD_POINTER(UasSamIndex)) =
|
|
DomainServerRoleBackup;
|
|
break;
|
|
|
|
default:
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: invalid role %lx Index:%ld\n",
|
|
GET_UAS_MODAL_DWORD(UasSamIndex),
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
//
|
|
// All types should have been handled above.
|
|
//
|
|
|
|
default:
|
|
NetStatus = NERR_InternalError;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Set the new values of the fields
|
|
//
|
|
// For role change, I should stop/start the SAM server as appropriate.
|
|
// The UI will stop/start the NetLogon service. ??
|
|
//
|
|
|
|
for ( UasSamIndex=0 ;
|
|
UasSamIndex<sizeof(UserUasSamTable)/sizeof(UserUasSamTable[0]);
|
|
UasSamIndex++ ){
|
|
|
|
//
|
|
// Make the changes to the Sam Database now.
|
|
//
|
|
|
|
if ( SAM_MODAL_CLASS(UasSamIndex).State == UTS_READ ) {
|
|
|
|
// if the information class is DomainServerRoleInformation
|
|
// we need to update the ServerRole in LSA first then in SAM.
|
|
|
|
if( SAM_MODAL_CLASS(UasSamIndex).DomainInformationClass ==
|
|
DomainServerRoleInformation ) {
|
|
|
|
NetStatus = UaspLSASetServerRole(
|
|
ServerName,
|
|
SAM_MODAL_CLASS(UasSamIndex).NewInformation );
|
|
|
|
if( NetStatus != NERR_Success ) {
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: Error from"
|
|
" UaspLSASetServerRole %lx Index:%ld\n",
|
|
NetStatus,
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
goto Cleanup;
|
|
}
|
|
|
|
LSAServerRoleSet = TRUE;
|
|
|
|
NetStatus = UaspBuiltinDomainSetServerRole(
|
|
SamServerHandle,
|
|
SAM_MODAL_CLASS(UasSamIndex).NewInformation );
|
|
|
|
if( NetStatus != NERR_Success ) {
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: Error from"
|
|
" UaspBuiltinDomainSetServerRole %lx Index:%ld\n",
|
|
NetStatus,
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
goto Cleanup;
|
|
}
|
|
|
|
BuiltinDomainServerRoleSet = TRUE;
|
|
}
|
|
|
|
Status = SamSetInformationDomain(
|
|
DomainHandle,
|
|
SAM_MODAL_CLASS(UasSamIndex).DomainInformationClass,
|
|
SAM_MODAL_CLASS(UasSamIndex).NewInformation );
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint((
|
|
"NetUserModalsSet: Error from"
|
|
" SamSetInformationDomain %lx Index:%ld\n",
|
|
Status,
|
|
UasSamIndex ));
|
|
}
|
|
NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Mark this Entry as having been done
|
|
//
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).State = UTS_DONE ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
//
|
|
// Clean up.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// need to revert the LSA server role when we are unsuccessful to
|
|
// set the info completely.
|
|
//
|
|
|
|
if( NetStatus != NERR_Success && LSAServerRoleSet ) {
|
|
|
|
#ifdef notdef
|
|
|
|
NetpAssert( !UaspLSASetServerRole(
|
|
ServerName,
|
|
SamInfoClass[SAM_ServerRoleClass].OldInformation ) );
|
|
#endif
|
|
|
|
}
|
|
|
|
//
|
|
// revert server role in builtin domain
|
|
//
|
|
|
|
if( NetStatus != NERR_Success && BuiltinDomainServerRoleSet ) {
|
|
|
|
#ifdef notdef
|
|
|
|
NetpAssert( !UaspBuiltinDomainSetServerRole(
|
|
SamServerHandle,
|
|
SamInfoClass[SAM_ServerRoleClass].OldInformation ) );
|
|
#endif
|
|
|
|
}
|
|
|
|
//
|
|
// Loop through the UserUasSamTable cleaning up anything that
|
|
// needs cleaning.
|
|
//
|
|
|
|
for ( UasSamIndex=0 ;
|
|
UasSamIndex<sizeof(UserUasSamTable)/sizeof(UserUasSamTable[0]);
|
|
UasSamIndex++ ) {
|
|
|
|
//
|
|
// If we've not been able to change everything and
|
|
// this information class has been changed above, change it
|
|
// back to the old value here. Ignore any error codes. We
|
|
// will report the original error to the caller.
|
|
//
|
|
|
|
if ( NetStatus != NERR_Success &&
|
|
SAM_MODAL_CLASS(UasSamIndex).State == UTS_DONE ) {
|
|
|
|
Status = SamSetInformationDomain(
|
|
DomainHandle,
|
|
SAM_MODAL_CLASS(UasSamIndex).DomainInformationClass,
|
|
SAM_MODAL_CLASS(UasSamIndex).OldInformation );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).State = UTS_RECOVERED ;
|
|
}
|
|
|
|
|
|
//
|
|
// Free any allocated Old information class.
|
|
//
|
|
|
|
if ( SAM_MODAL_CLASS(UasSamIndex).OldInformation != NULL ) {
|
|
Status =
|
|
SamFreeMemory( SAM_MODAL_CLASS(UasSamIndex).OldInformation );
|
|
NetpAssert( NT_SUCCESS(Status) );
|
|
|
|
SAM_MODAL_CLASS(UasSamIndex).OldInformation = NULL;
|
|
}
|
|
|
|
//
|
|
// Free any allocated New information class.
|
|
//
|
|
|
|
if ( SAM_MODAL_CLASS(UasSamIndex).NewInformation != NULL ) {
|
|
NetpMemoryFree( SAM_MODAL_CLASS(UasSamIndex).NewInformation );
|
|
SAM_MODAL_CLASS(UasSamIndex).NewInformation = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
if ( DomainHandle != NULL ) {
|
|
UaspCloseDomain( DomainHandle );
|
|
}
|
|
if ( SamServerHandle != NULL ) {
|
|
(VOID) SamCloseHandle( SamServerHandle );
|
|
}
|
|
|
|
//
|
|
// Handle downlevel.
|
|
//
|
|
|
|
UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )
|
|
|
|
NetStatus = RxNetUserModalsSet( (LPWSTR) ServerName, Level, Buffer, ParmError );
|
|
|
|
UASP_DOWNLEVEL_END;
|
|
|
|
IF_DEBUG( UAS_DEBUG_USER ) {
|
|
NetpKdPrint(( "NetUserModalsSet: returning %ld\n", NetStatus ));
|
|
}
|
|
|
|
|
|
return NetStatus;
|
|
|
|
} // NetUserModalsSet
|
|
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS NET_API_FUNCTION
|
|
NetUserChangePassword(
|
|
IN LPCWSTR DomainName,
|
|
IN LPCWSTR UserName,
|
|
IN LPCWSTR OldPassword,
|
|
IN LPCWSTR NewPassword
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Changes a users password.
|
|
|
|
Arguments:
|
|
|
|
DomainName - A pointer to a string containing the name of the domain or
|
|
remote server on which to change the password. The name is assuemd
|
|
to be a domain name unless it begins with "\\". If no domain can be
|
|
located by that name, it is prepended with "\\" and tried as a server
|
|
name.
|
|
If this parameter is not present the domain of the logged on
|
|
user is used.
|
|
|
|
UserName - Name of the user who's password is to be changed.
|
|
|
|
If this parameter is not present, the logged on user is used.
|
|
|
|
OldPassword - NULL terminated string containing the user's old password
|
|
|
|
NewPassword - NULL terminated string containing the user's new password.
|
|
|
|
Return Value:
|
|
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE LsaHandle = NULL;
|
|
NET_API_STATUS NetStatus = 0;
|
|
PMSV1_0_CHANGEPASSWORD_REQUEST ChangeRequest = NULL;
|
|
PMSV1_0_CHANGEPASSWORD_RESPONSE ChangeResponse = NULL;
|
|
STRING PackageName;
|
|
ULONG PackageId;
|
|
ULONG RequestSize;
|
|
ULONG ResponseSize = 0;
|
|
PBYTE Where;
|
|
NTSTATUS ProtocolStatus;
|
|
PSECURITY_SEED_AND_LENGTH SeedAndLength;
|
|
UCHAR Seed;
|
|
PUNICODE_STRING LsaUserName = NULL;
|
|
PUNICODE_STRING LsaDomainName = NULL;
|
|
UNICODE_STRING UserNameU;
|
|
UNICODE_STRING DomainNameU;
|
|
|
|
//
|
|
// If a user name and domain were not supplied, generate them now.
|
|
//
|
|
|
|
if ((DomainName == NULL) || (UserName == NULL)) {
|
|
Status = LsaGetUserName(
|
|
&LsaUserName,
|
|
&LsaDomainName
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if (UserName == NULL) {
|
|
UserNameU = *LsaUserName;
|
|
} else {
|
|
RtlInitUnicodeString(
|
|
&UserNameU,
|
|
UserName
|
|
);
|
|
}
|
|
|
|
if (DomainName == NULL) {
|
|
DomainNameU = *LsaDomainName;
|
|
} else {
|
|
RtlInitUnicodeString(
|
|
&DomainNameU,
|
|
DomainName
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// Calculate the request size
|
|
//
|
|
|
|
RequestSize = sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) +
|
|
UserNameU.Length + sizeof(WCHAR) +
|
|
DomainNameU.Length + sizeof(WCHAR);
|
|
|
|
|
|
if (ARGUMENT_PRESENT(OldPassword)) {
|
|
RequestSize += (wcslen(OldPassword)+1) * sizeof(WCHAR);
|
|
} else {
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(NewPassword)) {
|
|
RequestSize += (wcslen(NewPassword)+1) * sizeof(WCHAR);
|
|
} else {
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Connect to the LSA
|
|
//
|
|
|
|
Status = LsaConnectUntrusted(
|
|
&LsaHandle
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlInitString(
|
|
&PackageName,
|
|
MSV1_0_PACKAGE_NAME
|
|
);
|
|
|
|
Status = LsaLookupAuthenticationPackage(
|
|
LsaHandle,
|
|
&PackageName,
|
|
&PackageId
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate the request buffer
|
|
//
|
|
|
|
ChangeRequest = NetpMemoryAllocate( RequestSize );
|
|
if (ChangeRequest == NULL) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Build up the request message
|
|
//
|
|
|
|
ChangeRequest->MessageType = MsV1_0ChangePassword;
|
|
|
|
ChangeRequest->DomainName = DomainNameU;
|
|
|
|
ChangeRequest->AccountName = UserNameU;
|
|
|
|
RtlInitUnicodeString(
|
|
&ChangeRequest->OldPassword,
|
|
OldPassword
|
|
);
|
|
|
|
//
|
|
// Limit passwords to 127 bytes so we can run-encode them.
|
|
//
|
|
|
|
if (ChangeRequest->OldPassword.Length > 127) {
|
|
NetStatus = ERROR_PASSWORD_RESTRICTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlInitUnicodeString(
|
|
&ChangeRequest->NewPassword,
|
|
NewPassword
|
|
);
|
|
|
|
if (ChangeRequest->NewPassword.Length > 127) {
|
|
NetStatus = ERROR_PASSWORD_RESTRICTION;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Marshall the buffer pointers. We run-encode the passwords so
|
|
// we don't have cleartext password lying around the pagefile.
|
|
//
|
|
|
|
Where = (PBYTE) (ChangeRequest+1);
|
|
|
|
ChangeRequest->DomainName.Buffer = (LPWSTR) Where;
|
|
RtlCopyMemory(
|
|
Where,
|
|
DomainNameU.Buffer,
|
|
ChangeRequest->DomainName.MaximumLength
|
|
);
|
|
Where += ChangeRequest->DomainName.MaximumLength;
|
|
|
|
|
|
ChangeRequest->AccountName.Buffer = (LPWSTR) Where;
|
|
RtlCopyMemory(
|
|
Where,
|
|
UserNameU.Buffer,
|
|
ChangeRequest->AccountName.MaximumLength
|
|
);
|
|
Where += ChangeRequest->AccountName.MaximumLength;
|
|
|
|
|
|
ChangeRequest->OldPassword.Buffer = (LPWSTR) Where;
|
|
RtlCopyMemory(
|
|
Where,
|
|
OldPassword,
|
|
ChangeRequest->OldPassword.MaximumLength
|
|
);
|
|
Where += ChangeRequest->OldPassword.MaximumLength;
|
|
|
|
//
|
|
// Run encode the passwords so they don't lie around the page file.
|
|
//
|
|
|
|
Seed = 0;
|
|
RtlRunEncodeUnicodeString(
|
|
&Seed,
|
|
&ChangeRequest->OldPassword
|
|
);
|
|
SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangeRequest->OldPassword.Length;
|
|
SeedAndLength->Seed = Seed;
|
|
|
|
ChangeRequest->NewPassword.Buffer = (LPWSTR) Where;
|
|
RtlCopyMemory(
|
|
Where,
|
|
NewPassword,
|
|
ChangeRequest->NewPassword.MaximumLength
|
|
);
|
|
Where += ChangeRequest->NewPassword.MaximumLength;
|
|
|
|
Seed = 0;
|
|
RtlRunEncodeUnicodeString(
|
|
&Seed,
|
|
&ChangeRequest->NewPassword
|
|
);
|
|
SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangeRequest->NewPassword.Length;
|
|
SeedAndLength->Seed = Seed;
|
|
|
|
//
|
|
// Since we are running in the caller's process, we most certainly are
|
|
// impersonating him/her.
|
|
//
|
|
|
|
ChangeRequest->Impersonating = TRUE;
|
|
|
|
//
|
|
// Call the MSV1_0 package to change the password.
|
|
//
|
|
|
|
Status = LsaCallAuthenticationPackage(
|
|
LsaHandle,
|
|
PackageId,
|
|
ChangeRequest,
|
|
RequestSize,
|
|
(PVOID *) &ChangeResponse,
|
|
&ResponseSize,
|
|
&ProtocolStatus
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
if (!NT_SUCCESS(ProtocolStatus)) {
|
|
NetStatus = NetpNtStatusToApiStatus( ProtocolStatus );
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = ERROR_SUCCESS;
|
|
|
|
Cleanup:
|
|
if (LsaHandle != NULL) {
|
|
NtClose(LsaHandle);
|
|
}
|
|
if (ChangeRequest != NULL) {
|
|
SecureZeroMemory( ChangeRequest, RequestSize );
|
|
NetpMemoryFree( ChangeRequest );
|
|
}
|
|
if (ChangeResponse != NULL) {
|
|
LsaFreeReturnBuffer( ChangeResponse );
|
|
}
|
|
|
|
if (LsaUserName != NULL) {
|
|
LsaFreeMemory(LsaUserName->Buffer);
|
|
LsaFreeMemory(LsaUserName);
|
|
}
|
|
if (LsaDomainName != NULL) {
|
|
LsaFreeMemory(LsaDomainName->Buffer);
|
|
LsaFreeMemory(LsaDomainName);
|
|
}
|
|
|
|
return(NetStatus);
|
|
|
|
}
|
|
|
|
|
|
/*lint +e614 */
|
|
/*lint +e740 */
|