/*++

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 <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>

/*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.
//

DBGSTATIC 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 LPWSTR 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 UserHandle = NULL;
    SAM_HANDLE DomainHandle = NULL;
    ULONG RelativeId;
    ULONG DesiredAccess;
    BOOLEAN SpecialCaseWorkstationAccount;
    ULONG WhichFieldsMask;


    //
    // Variables for building the new user's Sid
    //

    PSID DomainId = NULL;            // Domain Id of the primary domain

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserAdd: entered \n");
    }

    //
    // Initialize
    //

    NetpSetParmError( PARM_ERROR_NONE );

    //
    // Validate Level parameter.
    //

    switch (Level) {
    case 1:
    case 2:
    case 3:
    case 22:
        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 ) );
        break;

    default:
        return ERROR_INVALID_LEVEL;  // Nothing to cleanup yet
    }

    //
    // 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.
    //

    SpecialCaseWorkstationAccount = FALSE;
    NetStatus = UaspOpenDomain(
                    ServerName,
                    DOMAIN_CREATE_USER | DOMAIN_LOOKUP |
                    DOMAIN_READ_PASSWORD_PARAMETERS,
                    TRUE,   // Account Domain
                    &DomainHandle,
                    &DomainId );


    if ( NetStatus == ERROR_ACCESS_DENIED &&
        (( ( Level == 22 )
          ? ((PUSER_INFO_22)Buffer)->usri22_flags
          : ((PUSER_INFO_1)Buffer)->usri1_flags ) & UF_WORKSTATION_TRUST_ACCOUNT )) {

        NetStatus = UaspOpenDomain(
                        ServerName,
                        DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
                        TRUE,   // Account Domain
                        &DomainHandle,
                        &DomainId );

        SpecialCaseWorkstationAccount = TRUE;

    }

    if ( NetStatus != NERR_Success ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "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 );
    DesiredAccess = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE |
                        WRITE_DAC | DELETE | USER_FORCE_PASSWORD_CHANGE |
                        USER_READ_ACCOUNT | USER_WRITE_ACCOUNT;
    if ( SpecialCaseWorkstationAccount ) {
        ULONG GrantedAccess;

        Status = SamCreateUser2InDomain(
                    DomainHandle,
                    &UserNameString,
                    USER_WORKSTATION_TRUST_ACCOUNT,
                    DesiredAccess,
                    &UserHandle,
                    &GrantedAccess,
                    &RelativeId );
        //
        // Since we are creating a workstation, and Sam creates these already
        // enabled, all we need to do is set the password.
        //
        WhichFieldsMask = USER_ALL_NTPASSWORDPRESENT;

    } else {

        Status = SamCreateUserInDomain(
                    DomainHandle,
                    &UserNameString,
                    DesiredAccess,
                    &UserHandle,
                    &RelativeId );
        WhichFieldsMask = 0xFFFFFFFF;
    }

    if ( !NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "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 ) {
            NetpDbgPrint( "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 ( DomainId != NULL ) {
        NetpMemoryFree( DomainId );
    }


    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserAdd( ServerName, Level, Buffer, ParmError );

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserAdd: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserAdd


NET_API_STATUS NET_API_FUNCTION
NetUserDel(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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 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;


    //
    // Open the Domain
    //

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LOOKUP,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                &DomainId );

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    //
    // Open the Builtin Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ) {
            NetpDbgPrint( "NetUserDel: UserpOpenUser returns %ld\n",
                       NetStatus );
        }
        goto Cleanup;
    }


    //
    // Determine the SID of the User being deleted.
    //

    NetStatus = NetpDomainIdToSid( DomainId,
                                   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 ) {
            NetpDbgPrint(
               "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 ) {
            NetpDbgPrint( "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 ( DomainId != NULL ) {
        NetpMemoryFree( DomainId );
    }

    if ( UserSid != NULL ) {
        NetpMemoryFree( UserSid );
    }


    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserDel( ServerName, UserName );

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserDel: returning %ld\n", NetStatus );
    }
    return NetStatus;

} // NetUserDel


NET_API_STATUS NET_API_FUNCTION
NetUserEnum(
    IN LPWSTR 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  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;

#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;


    //
    // Validate Level parameter
    //

    *Buffer = NULL;
    *EntriesRead = 0;
    *EntriesLeft = 0;
    BufferDescriptor.Buffer = NULL;

    SamFilter = USERACCOUNTCONTROL( Filter );

    switch (Level) {
    case 1:
    case 2:
    case 3:
    case 11:

        //
        // Open the Builtin Domain.
        //

        NetStatus = UaspOpenDomain( ServerName,
                                    DOMAIN_GET_ALIAS_MEMBERSHIP,
                                    FALSE,  // Builtin Domain
                                    &BuiltinDomainHandle,
                                    NULL ); // DomainId

        if ( NetStatus != NERR_Success ) {
            goto Cleanup;
        }

    case 0:
    case 10:
    case 20:
        break;

    default:
        return ERROR_INVALID_LEVEL;

    }

    //
    // Open the Account Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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;

    do {
        NTSTATUS LocalStatus;
        PSAM_RID_ENUMERATION LocalEnumBuffer = NULL;
        DWORD LocalCountReturned;

        Status = SamEnumerateUsersInDomain(
                        DomainHandle,
                        &LocalEnumHandle,
                        SamFilter,
                        (PVOID *) &LocalEnumBuffer,
                        PrefMaxLen,
                        &LocalCountReturned
                    );

        if ( !NT_SUCCESS(Status) ) {
            NetStatus = NetpNtStatusToApiStatus( Status );

            if(LocalEnumBuffer != NULL ) {

                Status =  SamFreeMemory( LocalEnumBuffer );
                NetpAssert( NT_SUCCESS( Status ) );
            }

            goto Cleanup;
        }

        //
        // aggrigate total count.
        //

        TotalRemaining += LocalCountReturned;

        //
        // cache first enum buffer if the SamFilter is 0
        //

        if( (SamFilter == 0) && (EnumBuffer == NULL ) ) {

            EnumBuffer = LocalEnumBuffer;
            EnumHandle = LocalEnumHandle;
            CountReturned = LocalCountReturned;
            CachedStatus = Status;
        }
        else {

            LocalStatus =  SamFreeMemory( LocalEnumBuffer );
            NetpAssert( NT_SUCCESS( LocalStatus ) );
        }

    } while ( Status == STATUS_MORE_ENTRIES );

    //
    // Loop for each user
    //
    //

    for ( ;; ) {

        DWORD i;

        //
        // use cached enum buffer if one available
        //

        if( EnumBuffer != NULL ) {

            Status = CachedStatus;
        }
        else {

            Status = SamEnumerateUsersInDomain(
                            DomainHandle,
                            &EnumHandle,
                            0, // enum all accounts and filter req.
                               // accts in UserpGetInfo
                            (PVOID *) &EnumBuffer,
                            PrefMaxLen,
                            &CountReturned );
        }

        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.
            //

            NetStatus = UserpGetInfo(
                            DomainHandle,
                            DomainId,
                            BuiltinDomainHandle,
                            EnumBuffer[i].Name,
                            EnumBuffer[i].RelativeId,
                            Level,
                            PrefMaxLen,
                            &BufferDescriptor,
                            FALSE, // Not a 'get' operation
                            SamFilter );

            if (NetStatus != NERR_Success) {
                goto Cleanup;
            }

            //
            // if nothing added to the return buffer indicates that this
            // user account is skipped.
            //

            if ( (EndOfVariableData != BufferDescriptor.EndOfVariableData ) ||
                 (FixedDataEnd != BufferDescriptor.FixedDataEnd ) ) {

                (*EntriesRead)++;
            }

            //
            // increament the resume handle anyway.
            //

            LocalResumeHandle++;

        }

        //
        // free up current EnumBuffer and get another EnumBuffer.
        //

        Status = SamFreeMemory( EnumBuffer );
        NetpAssert( NT_SUCCESS(Status) );
        EnumBuffer = NULL;

        if( AllDone == TRUE ) {
            break;
        }

    }

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

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserEnum( ServerName,
                                   Level,
                                   Buffer,
                                   PrefMaxLen,
                                   EntriesRead,
                                   EntriesLeft,
                                   ResumeHandle );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserEnum: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserEnum


NET_API_STATUS NET_API_FUNCTION
NetUserGetInfo(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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 DomainHandle = NULL;
    PSID DomainId = NULL;
    SAM_HANDLE BuiltinDomainHandle = NULL;
    BUFFER_DESCRIPTOR BufferDescriptor;

    ULONG RelativeId;           // Relative Id of the user
    UNICODE_STRING UserNameString;

    //
    // Open the Domain
    //

    BufferDescriptor.Buffer = NULL;

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LOOKUP,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                &DomainId );

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    //
    // Open the Builtin Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ( DomainId != NULL ) {
        NetpMemoryFree( DomainId );
    }

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserGetInfo( ServerName, UserName, Level, Buffer );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserGetInfo: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserGetInfo


NET_API_STATUS NET_API_FUNCTION
NetUserSetInfo(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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 DomainHandle = NULL;
    PSID DomainId = NULL;
    SAM_HANDLE BuiltinDomainHandle = NULL;
    ULONG UserRelativeId;

    //
    // Initialize
    //

    NetpSetParmError( PARM_ERROR_NONE );

    //
    // Open the Account Domain
    //  DOMAIN_READ_PASSWORD_PARAMETERS is needed in those cases that we'll
    //  set the password on the account.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LOOKUP | DOMAIN_READ_PASSWORD_PARAMETERS,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                &DomainId );

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    //
    // Open the Builtin Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ( DomainId != NULL ) {
        NetpMemoryFree( DomainId );
    }

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserSetInfo( ServerName,
                                      UserName,
                                      Level,
                                      Buffer,
                                      ParmError );

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserSetInfo: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserSetInfo


NET_API_STATUS NET_API_FUNCTION
NetUserGetGroups(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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  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;
    }

    //
    // Open the Domain
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ) {
            NetpDbgPrint(
                "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 ) {
            NetpDbgPrint(
                "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 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( ServerName,
                                        UserName,
                                        Level,
                                        Buffer,
                                        PrefMaxLen,
                                        EntriesRead,
                                        EntriesLeft );

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserGetGroups: returning %ld\n", NetStatus );
    }


    return NetStatus;

} // NetUserGetGroups


NET_API_STATUS NET_API_FUNCTION
NetUserSetGroups (
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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 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;


    //
    // Open the Domain
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ) {
                NetpDbgPrint(
                    "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 ) {
                NetpDbgPrint(
                    "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 ) {
                    NetpDbgPrint(
                        "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 ) {
                    NetpDbgPrint(
                        "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 ) {
                NetpDbgPrint(
                    "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 );

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserSetGroups( ServerName,
                                        UserName,
                                        Level,
                                        Buffer,
                                        NewGroupCount );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserSetGroups: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserSetGroups


NET_API_STATUS NET_API_FUNCTION
NetUserGetLocalGroups(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR 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.

    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.

    BOOL   EnumIndirectMembership ;

    SAM_HANDLE  DomainHandle = NULL;
    SAM_HANDLE  BuiltinDomainHandle = NULL;
    SAM_HANDLE  UserHandle = NULL;
    PSID DomainId = NULL ;
    ULONG PartialCount = 0;

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

    EnumIndirectMembership = Flags & LG_INCLUDE_INDIRECT;

    //
    // Open the Domains (Account & Builtin)
    //

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LOOKUP | DOMAIN_GET_ALIAS_MEMBERSHIP,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                &DomainId);

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_GET_ALIAS_MEMBERSHIP,
                                FALSE,  // Builtin Domain
                                &BuiltinDomainHandle,
                                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,
                               &UserRelativeId);  // Relative Id

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    if (EnumIndirectMembership) {

        //
        // 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 ) {
                NetpDbgPrint(
                    "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;
    }

    //
    // Add the User's Sid to the Array of Sids.
    //

    NetStatus = NetpDomainIdToSid( DomainId,
                                   UserRelativeId,
                                   &UserSids[0] );

    if ( NetStatus != NERR_Success ) {
        goto Cleanup;
    }

    UserSidCount ++;



    //
    // Add each group the user is a member of to the array of Sids.
    // Note that GroupCount would still be zero if we didnt have
    // the EnumIndirectMembership, and this will do nothing.
    //

    for ( GroupIndex = 0; GroupIndex < GroupCount; GroupIndex ++ ) {

        NetStatus = NetpDomainIdToSid( DomainId,
                                       GroupMembership[GroupIndex].RelativeId,
                                       &UserSids[GroupIndex+1] );

        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 ) {
            NetpDbgPrint(
                "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 ) {
                NetpDbgPrint(
                    "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 ) {
            NetpDbgPrint(
                "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 ) {
            NetpDbgPrint(
                "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 ( 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 ( Aliases != NULL ) {
        Status = SamFreeMemory( Aliases );
        NetpAssert( NT_SUCCESS(Status) );
    }


    if ( BuiltinDomainHandle != NULL ) {
        UaspCloseDomain( BuiltinDomainHandle );
    }

    if ( DomainHandle != NULL ) {
        UaspCloseDomain( DomainHandle );
    }


    //
    // 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 ) {
        NetpDbgPrint( "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 LPWSTR 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 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;
    }

    //
    // Open the Domain
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 );
    }

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserModalsGet( ServerName, Level, Buffer );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserModalsGet: returning %ld\n", NetStatus );
    }

    return NetStatus;

} // NetUserModalsGet



NET_API_STATUS NET_API_FUNCTION
NetUserModalsSet(
    IN LPWSTR 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 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 ) {
                    NetpDbgPrint(
                        "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 ) {
                    NetpDbgPrint(
                        "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;
    }

    //
    // Open the domain asking for accumulated desired access
    //

    NetStatus = UaspOpenDomain( ServerName,
                                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 ) {
                    NetpDbgPrint(
                        "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 ) {
                NetpDbgPrint(
                    "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 ) {
                    NetpDbgPrint(
                        "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 ) {
                        NetpDbgPrint(
                            "NetUserModalsSet: Error from"
                            " UaspLSASetServerRole %lx Index:%ld\n",
                            NetStatus,
                            UasSamIndex );
                    }
                    NetpSetParmError( UserUasSamTable[UasSamIndex].UasParmNum );
                    goto Cleanup;
                }

                LSAServerRoleSet = TRUE;

                NetStatus = UaspBuiltinDomainSetServerRole(
                                ServerName,
                                SAM_MODAL_CLASS(UasSamIndex).NewInformation );

                if( NetStatus != NERR_Success ) {

                    IF_DEBUG( UAS_DEBUG_USER ) {
                        NetpDbgPrint(
                            "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 ) {
                    NetpDbgPrint(
                        "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(
                        ServerName,
                        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 );
    }

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetUserModalsSet( ServerName, Level, Buffer, ParmError );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "NetUserModalsSet: returning %ld\n", NetStatus );
    }


    return NetStatus;

} // NetUserModalsSet
/*lint +e614 */
/*lint +e740 */
