/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    userp.c

Abstract:

    Internal routines for supporting the 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.

    17-Jan-1992 (madana)
        Added a new entry in the UserpUasSamTable to support account
        rename.

    20-Jan-1992 (madana)
        Sundry API changes.

--*/

#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 <ntsamp.h>
#include <ntlsa.h>

#include <windef.h>
#include <winbase.h>
#include <lmcons.h>

#include <access.h>
#include <accessp.h>
#include <align.h>
#include <limits.h>
#include <lmapibuf.h>
#include <lmaccess.h>
#include <lmerr.h>
#include <netdebug.h>
#include <netlib.h>
#include <netlibnt.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 */



DBGSTATIC ULONG
UserpSizeOfLogonHours(
    IN DWORD UnitsPerWeek
    )

/*++

Routine Description:

    This routine calculates the size in bytes of a logon hours string
    given the number of Units per Week.

Parameters:

    UnitsPerWeek - The number of bits in the logon hours string.

Return Values:

    None.

--*/

{

    //
    // Calculate the number of bytes in the array, rounding up to the
    // nearest number of UCHARs needed to store that many bits.
    //

    return((UnitsPerWeek + 8 * sizeof(UCHAR) - 1) / (8 * sizeof(UCHAR)));
} // UserpSizeOfLogonHours



DBGSTATIC NET_API_STATUS
UserpGetUserPriv(
    IN SAM_HANDLE BuiltinDomainHandle,
    IN SAM_HANDLE UserHandle,
    IN ULONG UserRelativeId,
    IN PSID DomainId,
    OUT LPDWORD Priv,
    OUT LPDWORD AuthFlags
    )

/*++

Routine Description:

    Determines the Priv and AuthFlags for the specified user.

Arguments:

    BuiltinDomainHandle - A Handle to the Builtin Domain.  This handle
        must grant DOMAIN_GET_ALIAS_MEMBERSHIP access.

    UserHandle - A handle to the user.  This handle must grant
        USER_LIST_GROUPS access.

    UserRelativeId - Relative ID of the user to query.

    DomainId - Domain Sid of the Domain this user belongs to

    Priv - Returns the Lanman 2.0 Privilege level for the specified user.

    AuthFlags - Returns the Lanman 2.0 Authflags for the specified user.


Return Value:

    Status of the operation.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;

    PGROUP_MEMBERSHIP GroupMembership = NULL;
    ULONG GroupCount;
    ULONG GroupIndex;
    PSID *UserSids = NULL;
    ULONG UserSidCount = 0;
    ULONG AliasCount;
    PULONG Aliases = NULL;


    //
    // Determine all the groups this user is a member of
    //

    Status = SamGetGroupsForUser( UserHandle,
                                  &GroupMembership,
                                  &GroupCount);

    if ( !NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpGetUserPriv: SamGetGroupsForUser returns %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.
    //

    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 builtin domain this user is a member of.
    //

    Status = SamGetAliasMembership( BuiltinDomainHandle,
                                    UserSidCount,
                                    UserSids,
                                    &AliasCount,
                                    &Aliases );

    if ( !NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpGetUserPriv: SamGetAliasMembership returns %lX\n",
                Status );
        }
        NetStatus = NetpNtStatusToApiStatus( Status );
        goto Cleanup;
    }

    //
    // Convert the alias membership to priv and auth flags
    //

    NetpAliasMemberToPriv(
                 AliasCount,
                 Aliases,
                 Priv,
                 AuthFlags );

    NetStatus = NERR_Success;

    //
    // Free Locally used resources.
    //
Cleanup:
    if ( Aliases != NULL ) {
        Status = SamFreeMemory( Aliases );
        NetpAssert( NT_SUCCESS(Status) );
    }

    if ( GroupMembership != NULL ) {
        Status = SamFreeMemory( GroupMembership );
        NetpAssert( NT_SUCCESS(Status) );
    }

    if ( UserSids != NULL ) {

        for ( GroupIndex = 0; GroupIndex < UserSidCount; GroupIndex ++ ) {
            NetpMemoryFree( UserSids[GroupIndex] );
        }

        NetpMemoryFree( UserSids );
    }

    return NetStatus;
}


DBGSTATIC NET_API_STATUS
UserpGetDacl(
    IN SAM_HANDLE UserHandle,
    OUT PACL *UserDacl,
    OUT LPDWORD UserDaclSize OPTIONAL
    )
/*++

Routine Description:

    Get the DACL for a particular user record in SAM.

Arguments:

    UserHandle - A Handle to the particular user.

    UserDacl - Returns a pointer to the DACL for the user.  The caller
        should free this buffer using NetpMemoryFree.
        Will return NULL if there is no DACL for this user.

    UserDaclSize - Returns the size (in bytes) of the UserDacl.

Return Value:

    Status of the operation.

--*/
{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;

    PSECURITY_DESCRIPTOR SecurityDescriptor = NULL;
    BOOLEAN DaclPresent;
    PACL Dacl;
    BOOLEAN DaclDefaulted;
    ACL_SIZE_INFORMATION AclSize;


    //
    // Get the Discretionary ACL (DACL) for the user
    //

    Status = SamQuerySecurityObject(
                UserHandle,
                DACL_SECURITY_INFORMATION,
                &SecurityDescriptor );

    if ( ! NT_SUCCESS( Status ) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpGetDacl: SamQuerySecurityObject returns %lX\n",
                Status );
        }
        NetStatus = NetpNtStatusToApiStatus( Status );
        goto Cleanup;
    }

    Status = RtlGetDaclSecurityDescriptor(
                    SecurityDescriptor,
                    &DaclPresent,
                    &Dacl,
                    &DaclDefaulted );


    if ( ! NT_SUCCESS( Status ) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpGetDacl: RtlGetDaclSecurityObject returns %lX\n",
                Status );
        }
        NetStatus = NERR_InternalError;
        goto Cleanup;
    }

    //
    // If there is no DACL, simply tell the caller
    //

    if ( !DaclPresent ) {
        NetStatus = NERR_Success;
        *UserDacl = NULL;
        if ( UserDaclSize != NULL ) {
            *UserDaclSize = 0;
        }
        goto Cleanup;
    }


    //
    // Determine the size of the DACL so we can copy it
    //

    Status = RtlQueryInformationAcl(
                        Dacl,
                        &AclSize,
                        sizeof(AclSize),
                        AclSizeInformation );

    if ( ! NT_SUCCESS( Status ) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpGetDacl: RtlQueryInformationAcl returns %lX\n",
                Status );
        }
        NetStatus = NERR_InternalError;
        goto Cleanup;
    }

    //
    // Copy the DACL to an allocated buffer.
    //

    *UserDacl = NetpMemoryAllocate( AclSize.AclBytesInUse );

    if ( *UserDacl == NULL ) {
        NetStatus = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    NetpMoveMemory( *UserDacl, Dacl, AclSize.AclBytesInUse );

    if ( UserDaclSize != NULL ) {
        *UserDaclSize = AclSize.AclBytesInUse;
    }
    NetStatus = NERR_Success;


    //
    // Cleanup
    //

Cleanup:
    if ( SecurityDescriptor != NULL ) {
        Status = SamFreeMemory( SecurityDescriptor );
        NetpAssert( NT_SUCCESS(Status) );
    }

    return NetStatus;

}



DBGSTATIC NET_API_STATUS
UserpSetDacl(
    IN SAM_HANDLE UserHandle,
    IN PACL Dacl
    )
/*++

Routine Description:

    Set the specified Dacl on the specified SAM user record.

Arguments:

    UserHandle - A handle to the user to modify.

    Dacl - The DACL to set on the user.

Return Value:

    Status code.

--*/
{
    NTSTATUS Status;
    PUCHAR SecurityDescriptor[SECURITY_DESCRIPTOR_MIN_LENGTH];

    //
    // Initialize a security descriptor to contain a pointer to the
    // DACL.
    //

    Status = RtlCreateSecurityDescriptor(
                SecurityDescriptor,
                SECURITY_DESCRIPTOR_REVISION );

    if (!NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpSetDacl: RtlCreateSecurityDescriptor rets %lX\n",
                Status );
        }
        return NetpNtStatusToApiStatus( Status );
    }

    Status = RtlSetDaclSecurityDescriptor(
                    (PSECURITY_DESCRIPTOR) SecurityDescriptor,
                    (BOOLEAN) TRUE,       // Dacl is present
                    Dacl,
                    (BOOLEAN) FALSE );    // Dacl wasn't defaulted

    if (!NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpSetDacl: RtlSetDaclSecurityDescriptor rets %lX\n",
                Status );
        }
        return NetpNtStatusToApiStatus( Status );
    }

    //
    // Set this new security descriptor on the user
    //

    Status = SamSetSecurityObject(
                UserHandle,
                DACL_SECURITY_INFORMATION,
                SecurityDescriptor );


    if ( !NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "NetUserAdd: SamSetSecurityObject rets %lX\n",
                      Status );
        }
        return NetpNtStatusToApiStatus( Status );
    }

    return NERR_Success;


}



NET_API_STATUS
UserpOpenUser(
    IN SAM_HANDLE DomainHandle,
    IN ACCESS_MASK DesiredAccess,
    IN LPWSTR UserName,
    OUT PSAM_HANDLE UserHandle OPTIONAL,
    OUT PULONG RelativeId OPTIONAL
    )

/*++

Routine Description:

    Open a Sam User by Name

Arguments:

    DomainHandle - Supplies the Domain Handle.

    DesiredAccess - Supplies access mask indicating desired access to user.

    UserName - User name of the user.

    UserHandle - Returns a handle to the user.  If NULL, user is not
        actually opened (merely the relative ID is returned).

    RelativeId - Returns the relative ID of the user.  If NULL the relative
        Id is not returned.

Return Value:

    Error code for the operation.

--*/

{
    NTSTATUS Status;
    NET_API_STATUS NetStatus;

    //
    // Variables for converting names to relative IDs
    //

    UNICODE_STRING NameString;
    PSID_NAME_USE NameUse;
    PULONG LocalRelativeId;

    //
    // Convert user name to relative ID.
    //

    RtlInitUnicodeString( &NameString, UserName );
    Status = SamLookupNamesInDomain( DomainHandle,
                                     1,
                                     &NameString,
                                     &LocalRelativeId,
                                     &NameUse );

    if ( !NT_SUCCESS(Status) ) {
        if ( Status == STATUS_NONE_MAPPED ) {
            NetStatus = NERR_UserNotFound;
        } else {
            NetStatus = NetpNtStatusToApiStatus( Status );
        }
        goto Cleanup;
    }

    if ( *NameUse != SidTypeUser ) {
        NetStatus = NERR_UserNotFound;
        goto Cleanup;
    }

    //
    // Open the user
    //

    if ( UserHandle != NULL ) {
        Status = SamOpenUser( DomainHandle,
                              DesiredAccess,
                              *LocalRelativeId,
                              UserHandle);

        if ( !NT_SUCCESS(Status) ) {
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }
    }

    //
    // Return the relative Id if it's wanted.
    //

    if ( RelativeId != NULL ) {
        *RelativeId = *LocalRelativeId;
    }
    NetStatus = NERR_Success;


    //
    // Cleanup
    //

Cleanup:
    if ( LocalRelativeId != NULL ) {
        Status = SamFreeMemory( LocalRelativeId );
        NetpAssert( NT_SUCCESS(Status) );
    }

    if ( NameUse != NULL ) {
        Status = SamFreeMemory( NameUse );
        NetpAssert( NT_SUCCESS(Status) );
    }
    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "UserpOpenUser: %wZ: returns %ld\n",
                  &NameString, NetStatus );
    }

    return NetStatus;

} // UserpOpenUser


DBGSTATIC VOID
UserpRelocationRoutine(
    IN DWORD Level,
    IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
    IN PTRDIFF_T Offset
    )

/*++

Routine Description:

   Routine to relocate the pointers from the fixed portion of an enumeration
   buffer to the string portion of an enumeration buffer.  It is called
   as a callback routine from NetpAllocateEnumBuffer when it re-allocates
   such a buffer.  NetpAllocateEnumBuffer copied the fixed portion and
   string portion into the new buffer before calling this routine.

Arguments:

    Level - Level of information in the  buffer.

    BufferDescriptor - Description of the new buffer.

    Offset - Offset to add to each pointer in the fixed portion.

Return Value:

    Returns the error code for the operation.

--*/

{
    DWORD EntryCount;
    DWORD EntryNumber;
    DWORD FixedSize;
    IF_DEBUG( UAS_DEBUG_USER ) {
        NetpDbgPrint( "UserpRelocationRoutine: entering\n" );
    }

    //
    // Compute the number of fixed size entries
    //

    switch (Level) {
    case 0:
        FixedSize = sizeof(USER_INFO_0);
        break;

    case 1:
        FixedSize = sizeof(USER_INFO_1);
        break;

    case 2:
        FixedSize = sizeof(USER_INFO_2);
        break;

    case 3:
        FixedSize = sizeof(USER_INFO_3);
        break;

    case 10:
        FixedSize = sizeof(USER_INFO_10);
        break;

    case 11:
        FixedSize = sizeof(USER_INFO_11);
        break;

    case 20:
        FixedSize = sizeof(USER_INFO_20);
        break;

    default:
        NetpAssert( FALSE );
        return;

    }

    EntryCount =
        (DWORD)((BufferDescriptor->FixedDataEnd - BufferDescriptor->Buffer)) /
        FixedSize;

    //
    // Loop relocating each field in each fixed size structure
    //

#define DO_ONE( _type, _fieldname ) \
    RELOCATE_ONE( ((_type)TheStruct)->_fieldname, Offset)

    for ( EntryNumber=0; EntryNumber<EntryCount; EntryNumber++ ) {

        LPBYTE TheStruct = BufferDescriptor->Buffer + FixedSize * EntryNumber;

        switch ( Level ) {
        case 3:
            DO_ONE( PUSER_INFO_3, usri3_profile );
            DO_ONE( PUSER_INFO_3, usri3_home_dir_drive );

            /* Drop through to case 2 */

        case 2:
            DO_ONE( PUSER_INFO_2, usri2_full_name );
            DO_ONE( PUSER_INFO_2, usri2_usr_comment );
            DO_ONE( PUSER_INFO_2, usri2_parms );
            DO_ONE( PUSER_INFO_2, usri2_workstations );
            DO_ONE( PUSER_INFO_2, usri2_logon_hours );
            DO_ONE( PUSER_INFO_2, usri2_logon_server );

            /* Drop through to case 1 */

        case 1:
            DO_ONE( PUSER_INFO_1, usri1_home_dir );
            DO_ONE( PUSER_INFO_1, usri1_comment );
            DO_ONE( PUSER_INFO_1, usri1_script_path );
            /* Drop through to case 0 */

        case 0:
            DO_ONE( PUSER_INFO_0, usri0_name );
            break;

        case 11:
            DO_ONE( PUSER_INFO_11, usri11_home_dir );
            DO_ONE( PUSER_INFO_11, usri11_parms );
            DO_ONE( PUSER_INFO_11, usri11_logon_server );
            DO_ONE( PUSER_INFO_11, usri11_workstations );
            DO_ONE( PUSER_INFO_11, usri11_home_dir );
            DO_ONE( PUSER_INFO_11, usri11_logon_hours );

            /* Drop through to case 10 */

        case 10:
            DO_ONE( PUSER_INFO_10, usri10_name );
            DO_ONE( PUSER_INFO_10, usri10_comment );
            DO_ONE( PUSER_INFO_10, usri10_usr_comment );
            DO_ONE( PUSER_INFO_10, usri10_full_name );
            break;

        case 20:
            DO_ONE( PUSER_INFO_20, usri20_name );
            DO_ONE( PUSER_INFO_20, usri20_full_name );
            DO_ONE( PUSER_INFO_20, usri20_comment );
            break;

        default:
            NetpAssert( FALSE );
            return;


        }

    }

    return;

} // UserpRelocationRoutine


NET_API_STATUS
UserpGetInfo(
    IN SAM_HANDLE DomainHandle,
    IN PSID DomainId,
    IN SAM_HANDLE BuiltinDomainHandle OPTIONAL,
    IN UNICODE_STRING UserName,
    IN ULONG UserRelativeId,
    IN DWORD Level,
    IN DWORD PrefMaxLen,
    IN OUT PBUFFER_DESCRIPTOR BufferDescriptor,
    IN BOOL IsGet,
    IN DWORD SamFilter
    )

/*++

Routine Description:

   Get the information on one user and fill that information into an
   allocated buffer.

Arguments:

    DomainHandle - Domain Handle for the Account domain.

    DomainId - Domain Id corresponding to DomainHandle

    BuiltinDomainHandle - Domain Handle for the builtin domain.  Need only be
        specified for info level 1, 2, 3, and 11.

    UserName - User name of the user to query.

    UserRelativeId - Relative ID of the user to query.

    Level - Level of information required. level 0, 1, 2, 3, 10, 11 and 20
        are valid.

    PrefMaxLen - Callers prefered maximum length

    BufferDescriptor - Points to a structure which describes the allocated
        buffer.  On the first call, pass in BufferDescriptor->Buffer set
        to NULL.  On subsequent calls (in the 'enum' case), pass in the
        structure just as it was passed back on a previous call.

        The caller must deallocate the BufferDescriptor->Buffer using
        MIDL_user_free if it is non-null.

    IsGet - True iff this is a 'get' call and not an 'enum' call.

Return Value:

    Error code for the operation.

    If this is an Enum call, the status can be ERROR_MORE_DATA implying that
    the Buffer has grown to PrefMaxLen and that this much data should
    be returned to the caller.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;

    SAM_HANDLE UserHandle = NULL;
    USER_ALL_INFORMATION *UserAll = NULL;
    UNICODE_STRING LogonServer;

    ACCESS_MASK  DesiredAccess;
    ULONG RequiredFields;
    PACL UserDacl = NULL;

    DWORD Size;                 // The size of the info returned for this user
    DWORD FixedSize;            // The size of the info returned for this user
    LPBYTE NewStruct;           // Pointer to fixed portion of new structure

    //
    // Variables describes membership in the special groups.
    //

    DWORD Priv;
    DWORD AuthFlags;

    //
    // Validate Level parameter and remember the fixed size of each returned
    //  array entry.
    //
    RtlInitUnicodeString( &LogonServer, L"\\\\*" );

    switch (Level) {

    case 0:
        FixedSize = sizeof(USER_INFO_0);
        DesiredAccess = 0;

        RequiredFields = 0;
        break;

    case 1:
        FixedSize = sizeof(USER_INFO_1);
        DesiredAccess = USER_LIST_GROUPS | USER_READ_GENERAL |
            USER_READ_LOGON | USER_READ_ACCOUNT | READ_CONTROL;

        RequiredFields = USER_ALL_USERNAME |
                         USER_ALL_PASSWORDLASTSET |
                         USER_ALL_HOMEDIRECTORY |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERACCOUNTCONTROL |
                         USER_ALL_SCRIPTPATH ;

        break;

    case 2:
        FixedSize = sizeof(USER_INFO_2);
        DesiredAccess = USER_LIST_GROUPS | USER_READ_GENERAL |
            USER_READ_LOGON | USER_READ_ACCOUNT | READ_CONTROL |
            USER_READ_PREFERENCES;

        RequiredFields = USER_ALL_FULLNAME |
                         USER_ALL_USERCOMMENT |
                         USER_ALL_PARAMETERS |
                         USER_ALL_WORKSTATIONS |
                         USER_ALL_LASTLOGON |
                         USER_ALL_LASTLOGOFF |
                         USER_ALL_ACCOUNTEXPIRES |
                         USER_ALL_LOGONHOURS |
                         USER_ALL_BADPASSWORDCOUNT |
                         USER_ALL_LOGONCOUNT |
                         USER_ALL_COUNTRYCODE |
                         USER_ALL_CODEPAGE |
                         USER_ALL_USERNAME |
                         USER_ALL_PASSWORDLASTSET |
                         USER_ALL_HOMEDIRECTORY |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERACCOUNTCONTROL |
                         USER_ALL_SCRIPTPATH ;

        break;

    case 3:
        FixedSize = sizeof(USER_INFO_3);
        DesiredAccess = USER_LIST_GROUPS | USER_READ_GENERAL |
            USER_READ_LOGON | USER_READ_ACCOUNT | READ_CONTROL |
            USER_READ_PREFERENCES;

        RequiredFields = USER_ALL_USERID |
                         USER_ALL_PRIMARYGROUPID |
                         USER_ALL_PROFILEPATH |
                         USER_ALL_HOMEDIRECTORYDRIVE |
                         USER_ALL_PASSWORDMUSTCHANGE |
                         USER_ALL_FULLNAME |
                         USER_ALL_USERCOMMENT |
                         USER_ALL_PARAMETERS |
                         USER_ALL_WORKSTATIONS |
                         USER_ALL_LASTLOGON |
                         USER_ALL_LASTLOGOFF |
                         USER_ALL_ACCOUNTEXPIRES |
                         USER_ALL_LOGONHOURS |
                         USER_ALL_BADPASSWORDCOUNT |
                         USER_ALL_LOGONCOUNT |
                         USER_ALL_COUNTRYCODE |
                         USER_ALL_CODEPAGE |
                         USER_ALL_USERNAME |
                         USER_ALL_PASSWORDLASTSET |
                         USER_ALL_HOMEDIRECTORY |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERACCOUNTCONTROL |
                         USER_ALL_SCRIPTPATH ;

        break;

    case 10:
        FixedSize = sizeof(USER_INFO_10);
        DesiredAccess = USER_READ_GENERAL;

        RequiredFields = USER_ALL_USERNAME |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERCOMMENT |
                         USER_ALL_FULLNAME ;
        break;

    case 11:
        FixedSize = sizeof(USER_INFO_11);
        DesiredAccess = USER_LIST_GROUPS | USER_READ_GENERAL | USER_READ_LOGON |
            USER_READ_ACCOUNT | USER_READ_PREFERENCES;

        RequiredFields = USER_ALL_USERNAME |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERCOMMENT |
                         USER_ALL_FULLNAME |
                         USER_ALL_PASSWORDLASTSET |
                         USER_ALL_HOMEDIRECTORY |
                         USER_ALL_PARAMETERS |
                         USER_ALL_LASTLOGON |
                         USER_ALL_LASTLOGOFF |
                         USER_ALL_BADPASSWORDCOUNT |
                         USER_ALL_LOGONCOUNT |
                         USER_ALL_COUNTRYCODE |
                         USER_ALL_WORKSTATIONS |
                         USER_ALL_LOGONHOURS |
                         USER_ALL_CODEPAGE ;
        break;

    case 20:
        FixedSize = sizeof(USER_INFO_20);
        DesiredAccess =  USER_READ_GENERAL | USER_READ_ACCOUNT | READ_CONTROL;

        RequiredFields = USER_ALL_USERNAME |
                         USER_ALL_FULLNAME |
                         USER_ALL_ADMINCOMMENT |
                         USER_ALL_USERACCOUNTCONTROL;
        break;

    default:
        NetStatus = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    //
    // if we need to filter this account then query
    // USER_ALL_USERACCOUNTCONTROL also.
    //

    if( SamFilter ) {

        DesiredAccess |= USER_READ_ACCOUNT;
        RequiredFields |= USER_ALL_USERACCOUNTCONTROL;
    }

    //
    // Open the User account if need be
    //

    if ( DesiredAccess != 0 ) {

        Status = SamOpenUser( DomainHandle,
                              DesiredAccess,
                              UserRelativeId,
                              &UserHandle);

        if ( !NT_SUCCESS(Status) ) {
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint( "UserpGetInfo: SamOpenUser returns %lX\n",
                          Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }

    }

    //
    // Get all the information we need about the user
    //

    if ( RequiredFields != 0 ) {

        Status = SamQueryInformationUser( UserHandle,
                                          UserAllInformation,
                                          (PVOID *)&UserAll );

        if ( ! NT_SUCCESS( Status ) ) {
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint(
                    "UserpGetInfo: SamQueryInformationUser returns %lX\n",
                    Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }

        if ( (UserAll->WhichFields & RequiredFields) != RequiredFields ) {
#if DBG
            NetpDbgPrint( "UserpGetInfo: WhichFields: %lX RequireFields: %lX\n",
                          UserAll->WhichFields,
                          RequiredFields );
#endif // DBG
            NetStatus = ERROR_ACCESS_DENIED;
            goto Cleanup;

        }

        //
        // check the account type to filter this account.
        //

        if( (SamFilter != 0) &&
                ((UserAll->UserAccountControl & SamFilter) == 0)) {

            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint( "UserpGetInfo: %wZ is skipped \n", &UserName );
            }

            NetStatus = NERR_Success ;
            goto Cleanup;
        }
    }

    //
    // Level 1, 2 and 3 use the User's DACL to figure out the usriX_flags field.
    //

    if ((Level == 1) || (Level == 2) || (Level == 3) || (Level == 20) ) {


        //
        // Get the DACL for this user.
        //

        NetStatus = UserpGetDacl( UserHandle, &UserDacl, NULL );

        if ( NetStatus != NERR_Success ) {
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint(
                    "UserpGetInfo: UserpGetDacl returns %ld\n",
                    NetStatus );
            }
            goto Cleanup;
        }

    }

    //
    // Determine the Priv and AuthFlags
    //

    if (Level == 1 || Level == 2 || Level == 3 || Level == 11 ) {

        //
        //

        NetStatus = UserpGetUserPriv(
                     BuiltinDomainHandle,
                     UserHandle,
                     UserRelativeId,
                     DomainId,
                     &Priv,
                     &AuthFlags );

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

    }


    //
    // Determine the total size of the return information.
    //

    Size = FixedSize;
    switch (Level) {
    case 0:
        Size += UserName.Length + sizeof(WCHAR);
        break;

    case 3:
        Size += UserAll->ProfilePath.Length + sizeof(WCHAR) +
                UserAll->HomeDirectoryDrive.Length + sizeof(WCHAR);

        /* Drop through to case 2 */

    case 2:
        Size += UserAll->FullName.Length + sizeof(WCHAR) +
                UserAll->UserComment.Length + sizeof(WCHAR) +
                UserAll->Parameters.Length + sizeof(WCHAR) +
                UserAll->WorkStations.Length + sizeof(WCHAR) +
                LogonServer.Length + sizeof(WCHAR) +
                UserpSizeOfLogonHours( UserAll->LogonHours.UnitsPerWeek );

        /* Drop through to case 1 */

    case 1:
        Size += UserAll->UserName.Length + sizeof(WCHAR) +
                UserAll->HomeDirectory.Length + sizeof(WCHAR) +
                UserAll->AdminComment.Length + sizeof(WCHAR) +
                UserAll->ScriptPath.Length + sizeof(WCHAR);

        break;

    case 10:
        Size += UserAll->UserName.Length + sizeof(WCHAR) +
                UserAll->AdminComment.Length + sizeof(WCHAR) +
                UserAll->UserComment.Length + sizeof(WCHAR) +
                UserAll->FullName.Length + sizeof(WCHAR);

        break;

    case 11:
        Size += UserAll->UserName.Length + sizeof(WCHAR) +
                UserAll->AdminComment.Length + sizeof(WCHAR) +
                UserAll->UserComment.Length + sizeof(WCHAR) +
                UserAll->FullName.Length + sizeof(WCHAR) +
                UserAll->HomeDirectory.Length + sizeof(WCHAR) +
                UserAll->Parameters.Length + sizeof(WCHAR) +
                UserAll->WorkStations.Length + sizeof(WCHAR) +
                LogonServer.Length + sizeof(WCHAR) +
                UserpSizeOfLogonHours( UserAll->LogonHours.UnitsPerWeek );

        break;


    case 20:
        Size += UserAll->UserName.Length + sizeof(WCHAR) +
                UserAll->FullName.Length + sizeof(WCHAR) +
                UserAll->AdminComment.Length + sizeof(WCHAR);

        break;

    default:
        NetStatus = ERROR_INVALID_LEVEL;
        goto Cleanup;

    }

    //
    // Ensure there is buffer space for this information.
    //

    Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );

    NetStatus = NetpAllocateEnumBuffer(
                    BufferDescriptor,
                    IsGet,
                    PrefMaxLen,
                    Size,
                    UserpRelocationRoutine,
                    Level );

    if (NetStatus != NERR_Success) {

        //
        // NetpAllocateEnumBuffer returns ERROR_MORE_DATA if this
        // entry doesn't fit into the buffer.
        //

        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "UserpGetInfo: NetpAllocateEnumBuffer returns %ld\n",
                NetStatus );
        }

        goto Cleanup;
    }

//
// Define macros to make copying bytes and zero terminated strings less
//  repetitive.
//

#define COPY_BYTES( _type, _fieldname, _inptr, _length, _align ) \
    if ( !NetpCopyDataToBuffer( \
                (_inptr), \
                (_length), \
                BufferDescriptor->FixedDataEnd, \
                &BufferDescriptor->EndOfVariableData, \
                &((_type)NewStruct)->_fieldname, \
                _align ) ){ \
    \
        NetStatus = NERR_InternalError; \
        IF_DEBUG( UAS_DEBUG_USER ) { \
            NetpDbgPrint( "UserpGetInfo: NetpCopyData returns %ld\n", \
                NetStatus ); \
        } \
        goto Cleanup; \
    }


#define COPY_STRING( _type, _fieldname, _string ) \
    if ( !NetpCopyStringToBuffer( \
                    (_string).Buffer, \
                    (_string).Length/sizeof(WCHAR), \
                    BufferDescriptor->FixedDataEnd, \
                    (LPWSTR *)&BufferDescriptor->EndOfVariableData, \
                    &((_type)NewStruct)->_fieldname) ) { \
    \
        NetStatus = NERR_InternalError; \
        IF_DEBUG( UAS_DEBUG_USER ) { \
            NetpDbgPrint( "UserpGetInfo: NetpCopyString returns %ld\n", \
                NetStatus ); \
        } \
        goto Cleanup; \
    }


    //
    // Place this entry into the return buffer.
    //
    // Fill in the information.  The array of fixed entries is
    // placed at the beginning of the allocated buffer.  The strings
    // pointed to by these fixed entries are allocated starting at
    // the end of the allocate buffer.
    //

    NewStruct = BufferDescriptor->FixedDataEnd;
    BufferDescriptor->FixedDataEnd += FixedSize;

    switch ( Level ) {
    case 3:
        {
            //
            // since _USER_INFO_2 structure is subset of _USER_INFO_3,
            // full-up the _USER_INFO_3 only fields first and then  fall
            // through for the common fields.
            //

            PUSER_INFO_3 usri3 = (PUSER_INFO_3) NewStruct;
            LARGE_INTEGER CurrentTime;

            NetpAssert( UserRelativeId == UserAll->UserId );

            usri3->usri3_user_id = UserRelativeId;
            usri3->usri3_primary_group_id = UserAll->PrimaryGroupId;

            COPY_STRING( PUSER_INFO_3, usri3_profile, UserAll->ProfilePath );

            COPY_STRING( PUSER_INFO_3,
                         usri3_home_dir_drive,
                         UserAll->HomeDirectoryDrive );

            //
            // If the password is currently expired,
            //  indicate so.
            //
            (VOID) NtQuerySystemTime( &CurrentTime );

            if ( RtlLargeIntegerGreaterThanOrEqualTo(
                    CurrentTime,
                    UserAll->PasswordMustChange ) ) {
                usri3->usri3_password_expired = TRUE;
            } else {
                usri3->usri3_password_expired = FALSE;
            }

        }

        //
        // FALL THROUGH FOR OTHER _USER_INFO_3 FIELDS
        //


    case 2:
        {

            PUSER_INFO_2 usri2 = (PUSER_INFO_2) NewStruct;

            usri2->usri2_auth_flags = AuthFlags;

            COPY_STRING( PUSER_INFO_2,
                         usri2_full_name,
                         UserAll->FullName );

            COPY_STRING( PUSER_INFO_2,
                         usri2_usr_comment,
                         UserAll->UserComment);

            COPY_STRING( PUSER_INFO_2,
                         usri2_parms,
                         UserAll->Parameters );

            COPY_STRING( PUSER_INFO_2,
                         usri2_workstations,
                         UserAll->WorkStations);

            if ( !RtlTimeToSecondsSince1970( &UserAll->LastLogon,
                                             &usri2->usri2_last_logon) ) {
                usri2->usri2_last_logon = 0;
            }

            if ( !RtlTimeToSecondsSince1970( &UserAll->LastLogoff,
                                             &usri2->usri2_last_logoff) ) {
                usri2->usri2_last_logoff = 0;
            }

            if ( !RtlTimeToSecondsSince1970( &UserAll->AccountExpires,
                                             &usri2->usri2_acct_expires) ) {
                usri2->usri2_acct_expires = TIMEQ_FOREVER;
            }

            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint( "UserpGetInfo: Account Expries %lx %lx %lx\n",
                            UserAll->AccountExpires.HighPart,
                            UserAll->AccountExpires.LowPart,
                            usri2->usri2_acct_expires);
            }


            usri2->usri2_max_storage = USER_MAXSTORAGE_UNLIMITED;

            usri2->usri2_units_per_week = UserAll->LogonHours.UnitsPerWeek;

            IF_DEBUG( UAS_DEBUG_USER ) {
                DWORD k;
                NetpDbgDisplayDword( "UserpGetInfo: units_per_week",
                                      usri2->usri2_units_per_week );
                NetpDbgPrint( "UserpGetInfo: LogonHours %lx\n",
                              UserAll->LogonHours.LogonHours);


                for ( k=0;
                      k<UserpSizeOfLogonHours(
                        UserAll->LogonHours.UnitsPerWeek);
                      k++ ) {
                    NetpDbgPrint( "%d ",
                        UserAll->LogonHours.LogonHours[k] );
                }
                NetpDbgPrint( "\n" );
            }





            COPY_BYTES( PUSER_INFO_2,
                        usri2_logon_hours,
                        UserAll->LogonHours.LogonHours,
                        UserpSizeOfLogonHours(
                            UserAll->LogonHours.UnitsPerWeek ),
                        sizeof(UCHAR) );
            BufferDescriptor->EndOfVariableData =
                ROUND_DOWN_POINTER( BufferDescriptor->EndOfVariableData,
                                    ALIGN_WCHAR );

            usri2->usri2_bad_pw_count = UserAll->BadPasswordCount;
            usri2->usri2_num_logons = UserAll->LogonCount;

            COPY_STRING( PUSER_INFO_2,
                         usri2_logon_server,
                         LogonServer );

            usri2->usri2_country_code = UserAll->CountryCode;
            usri2->usri2_code_page = UserAll->CodePage;

            /* Drop through to case 1 */
        }

    case 1:
        {
            PUSER_INFO_1 usri1 = (PUSER_INFO_1) NewStruct;

            COPY_STRING( PUSER_INFO_1, usri1_name, UserAll->UserName );
            usri1->usri1_password = NULL;

            usri1->usri1_password_age =
                NetpGetElapsedSeconds( &UserAll->PasswordLastSet );

            usri1->usri1_priv = Priv;

            COPY_STRING( PUSER_INFO_1, usri1_home_dir, UserAll->HomeDirectory );
            COPY_STRING( PUSER_INFO_1, usri1_comment, UserAll->AdminComment);


            usri1->usri1_flags = NetpAccountControlToFlags(
                                    UserAll->UserAccountControl,
                                    UserDacl );

            COPY_STRING( PUSER_INFO_1, usri1_script_path, UserAll->ScriptPath);

            break;
        }

    case 0:

        COPY_STRING( PUSER_INFO_0, usri0_name, UserName );
        break;

    case 10:

        COPY_STRING( PUSER_INFO_10, usri10_name, UserAll->UserName );
        COPY_STRING( PUSER_INFO_10, usri10_comment, UserAll->AdminComment );
        COPY_STRING( PUSER_INFO_10, usri10_usr_comment, UserAll->UserComment );
        COPY_STRING( PUSER_INFO_10, usri10_full_name, UserAll->FullName );

        break;

    case 11:
        {
            PUSER_INFO_11 usri11 = (PUSER_INFO_11) NewStruct;

            COPY_STRING( PUSER_INFO_11, usri11_name, UserAll->UserName );
            COPY_STRING( PUSER_INFO_11, usri11_comment, UserAll->AdminComment );
            COPY_STRING(PUSER_INFO_11, usri11_usr_comment,UserAll->UserComment);
            COPY_STRING( PUSER_INFO_11, usri11_full_name, UserAll->FullName );

            usri11->usri11_priv = Priv;
            usri11->usri11_auth_flags = AuthFlags;

            usri11->usri11_password_age =
                NetpGetElapsedSeconds( &UserAll->PasswordLastSet );


            COPY_STRING(PUSER_INFO_11, usri11_home_dir, UserAll->HomeDirectory);
            COPY_STRING( PUSER_INFO_11, usri11_parms, UserAll->Parameters );

            if ( !RtlTimeToSecondsSince1970( &UserAll->LastLogon,
                                             &usri11->usri11_last_logon) ) {
                usri11->usri11_last_logon = 0;
            }

            if ( !RtlTimeToSecondsSince1970( &UserAll->LastLogoff,
                                             &usri11->usri11_last_logoff) ) {
                usri11->usri11_last_logoff = 0;
            }

            usri11->usri11_bad_pw_count = UserAll->BadPasswordCount;
            usri11->usri11_num_logons = UserAll->LogonCount;

            COPY_STRING( PUSER_INFO_11, usri11_logon_server, LogonServer );

            usri11->usri11_country_code = UserAll->CountryCode;

            COPY_STRING( PUSER_INFO_11,
                         usri11_workstations,
                         UserAll->WorkStations );

            usri11->usri11_max_storage = USER_MAXSTORAGE_UNLIMITED;
            usri11->usri11_units_per_week = UserAll->LogonHours.UnitsPerWeek;

            COPY_BYTES( PUSER_INFO_11,
                        usri11_logon_hours,
                        UserAll->LogonHours.LogonHours,
                        UserpSizeOfLogonHours(
                            UserAll->LogonHours.UnitsPerWeek ),
                        sizeof(UCHAR) );
            BufferDescriptor->EndOfVariableData =
                ROUND_DOWN_POINTER( BufferDescriptor->EndOfVariableData,
                                    ALIGN_WCHAR );

            usri11->usri11_code_page = UserAll->CodePage;

            break;
        }

    case 20:
        {

            PUSER_INFO_20 usri20 = (PUSER_INFO_20) NewStruct;

            COPY_STRING( PUSER_INFO_20, usri20_name, UserAll->UserName );

            COPY_STRING( PUSER_INFO_20, usri20_full_name, UserAll->FullName );

            COPY_STRING( PUSER_INFO_20, usri20_comment, UserAll->AdminComment );

            usri20->usri20_user_id = UserRelativeId;

            usri20->usri20_flags = NetpAccountControlToFlags(
                                    UserAll->UserAccountControl,
                                    UserDacl );

            break;

        }

    default:
        NetStatus = ERROR_INVALID_LEVEL;
        goto Cleanup;

    }

    NetStatus = NERR_Success ;

    //
    // Clean up.
    //

Cleanup:

    //
    // Free Sam information buffers
    //

    if ( UserAll != NULL ) {
        Status = SamFreeMemory( UserAll );
        NetpAssert( NT_SUCCESS(Status) );
    }

    if ( UserHandle != NULL ) {
        (VOID) SamCloseHandle( UserHandle );
    }

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

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

    return NetStatus;

} // UserpGetInfo



//
// Each field in the SAM USER_ALL_INFORMATION structure (and each pseudo field)
// is described here.

struct _SAM_FIELD_DESCRIPTION {
    //
    // Non-zero to indicate which field in the SAM USER_ALL_INFORMATION
    // structure is being set.

    DWORD WhichField;

    //
    // Define the value to return in ParmError if this field is bad.
    //

    DWORD UasParmNum;

    //
    // Describe the byte offset of the field in the SAM USER_ALL_INFORMATION
    // structure.
    //

    DWORD SamOffset;

    //
    // The DesiredAccess mask includes both the access to read and the
    // access to write the appropriate field in the USER_ALL_INFORMATION
    //

    ACCESS_MASK DesiredAccess;

} SamFieldDescription[] =
{

#define SAM_UserNameField           0
    {   USER_ALL_USERNAME, USER_NAME_PARMNUM,
        offsetof(USER_ALL_INFORMATION, UserName),
        USER_WRITE_ACCOUNT
    },

#define SAM_FullNameField           1
    {   USER_ALL_FULLNAME, USER_FULL_NAME_PARMNUM,
        offsetof(USER_ALL_INFORMATION, FullName),
        USER_WRITE_ACCOUNT
    },

#define SAM_PrimaryGroupIdField     2
    {   USER_ALL_PRIMARYGROUPID, USER_PRIMARY_GROUP_PARMNUM,
        offsetof(USER_ALL_INFORMATION, PrimaryGroupId),
        USER_LIST_GROUPS | READ_CONTROL | WRITE_DAC |
            USER_WRITE_ACCOUNT
    },

#define SAM_AdminCommentField       3
    {   USER_ALL_ADMINCOMMENT, USER_COMMENT_PARMNUM,
        offsetof(USER_ALL_INFORMATION, AdminComment),
        USER_WRITE_ACCOUNT
    },

#define SAM_UserCommentField        4
    {   USER_ALL_USERCOMMENT, USER_USR_COMMENT_PARMNUM,
        offsetof(USER_ALL_INFORMATION, UserComment),
        USER_WRITE_PREFERENCES
    },

#define SAM_HomeDirectoryField      5
    {   USER_ALL_HOMEDIRECTORY, USER_HOME_DIR_PARMNUM,
        offsetof(USER_ALL_INFORMATION, HomeDirectory),
        USER_WRITE_ACCOUNT
    },

#define SAM_HomeDirectoryDriveField 6
    {   USER_ALL_HOMEDIRECTORYDRIVE, USER_HOME_DIR_DRIVE_PARMNUM,
        offsetof(USER_ALL_INFORMATION, HomeDirectoryDrive),
        USER_WRITE_ACCOUNT
    },

#define SAM_ScriptPathField         7
    {   USER_ALL_SCRIPTPATH, USER_SCRIPT_PATH_PARMNUM,
        offsetof(USER_ALL_INFORMATION, ScriptPath),
        USER_WRITE_ACCOUNT
    },

#define SAM_ProfilePathField        8
    {   USER_ALL_PROFILEPATH, USER_PROFILE_PARMNUM,
        offsetof(USER_ALL_INFORMATION, ProfilePath),
        USER_WRITE_ACCOUNT
    },

#define SAM_WorkstationsField       9
    {   USER_ALL_WORKSTATIONS, USER_WORKSTATIONS_PARMNUM,
        offsetof(USER_ALL_INFORMATION, WorkStations),
        USER_WRITE_ACCOUNT
    },

#define SAM_LogonHoursField        10
    {   USER_ALL_LOGONHOURS, USER_LOGON_HOURS_PARMNUM,
        offsetof(USER_ALL_INFORMATION, LogonHours.LogonHours),
        USER_WRITE_ACCOUNT
    },

#define SAM_UnitsPerWeekField      11
    {   USER_ALL_LOGONHOURS, USER_UNITS_PER_WEEK_PARMNUM,
        offsetof(USER_ALL_INFORMATION, LogonHours.UnitsPerWeek),
        USER_WRITE_ACCOUNT
    },

#define SAM_AccountExpiresField    12
    {   USER_ALL_ACCOUNTEXPIRES, USER_ACCT_EXPIRES_PARMNUM,
        offsetof(USER_ALL_INFORMATION, AccountExpires),
        USER_WRITE_ACCOUNT
    },

#define SAM_UserAccountControlField 13
    {   USER_ALL_USERACCOUNTCONTROL, USER_FLAGS_PARMNUM,
        offsetof(USER_ALL_INFORMATION, UserAccountControl),
        USER_WRITE_ACCOUNT | USER_READ_ACCOUNT | READ_CONTROL | WRITE_DAC
    },

#define SAM_ParametersField         14
    {   USER_ALL_PARAMETERS, USER_PARMS_PARMNUM,
        offsetof(USER_ALL_INFORMATION, Parameters),
        USER_WRITE_ACCOUNT
    },

#define SAM_CountryCodeField        15
    {   USER_ALL_COUNTRYCODE, USER_COUNTRY_CODE_PARMNUM,
        offsetof(USER_ALL_INFORMATION, CountryCode),
        USER_WRITE_PREFERENCES
    },

#define SAM_CodePageField           16
    {   USER_ALL_CODEPAGE, USER_CODE_PAGE_PARMNUM,
        offsetof(USER_ALL_INFORMATION, CodePage),
        USER_WRITE_PREFERENCES
    },

#define SAM_ClearTextPasswordField  17
    {   USER_ALL_NTPASSWORDPRESENT, USER_PASSWORD_PARMNUM,
        offsetof(USER_ALL_INFORMATION, NtPassword),
        USER_FORCE_PASSWORD_CHANGE
    },

#define SAM_PasswordExpiredField    18
    {   USER_ALL_PASSWORDEXPIRED, PARM_ERROR_UNKNOWN,
        offsetof(USER_ALL_INFORMATION, PasswordExpired),
        USER_FORCE_PASSWORD_CHANGE
    },

#define SAM_OwfPasswordField        19
    {   USER_ALL_LMPASSWORDPRESENT | USER_ALL_OWFPASSWORD,
            USER_PASSWORD_PARMNUM,
        offsetof(USER_ALL_INFORMATION, LmPassword),
        USER_FORCE_PASSWORD_CHANGE
    },

    //
    // The following levels are pseudo levels which merely define the
    //  access required to set a particuler UAS field.

#define SAM_AuthFlagsField          20
    {   0, PARM_ERROR_UNKNOWN,
        0,
        USER_LIST_GROUPS
    },

#define SAM_MaxStorageField         21
    {   0, USER_MAX_STORAGE_PARMNUM,
        0,
        USER_READ_GENERAL
    },
};

//
// 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 _UAS_SAM_TABLE {

    //
    // Describe the field types for UAS and SAM.
    //

    enum {
        UT_STRING,          // UAS is LPWSTR. SAM is UNICODE_STRING.
        UT_BOOLEAN,         // UAS is DWORD.  SAM is BOOLEAN.
        UT_USHORT,          // UAS is DWORD.  SAM is USHORT.
        UT_ULONG,           // UAS is DWORD.  SAM is ULONG.
        UT_TIME,            // UAS is seconds since 1970.  SAM is LARGE_INTEGER.
        UT_PRIV,            // Special case
        UT_ACCOUNT_CONTROL, // Special case
        UT_AUTH_FLAGS,      // Special case
        UT_MAX_STORAGE,     // Special case
        UT_OWF_PASSWORD,    // Special case
        UT_LOGON_HOURS,     // Special case
        UT_UNITS_PER_WEEK,  // Special case
        UT_CREATE_FULLNAME  // Special case
    } FieldType;

    //
    // The NetUser API detail level this field is in.
    //

    DWORD UasLevel;

    //
    // Index to the structure describing the Sam Field being changed.
    //

    DWORD SamField;


    //
    // Describe the byte offset of the field in the appropriate UAS
    // and SAM structures.
    //

    DWORD UasOffset;

} UserpUasSamTable[] =

{
    // Rename an account at info level 0 only.

    { UT_STRING, 0, SAM_UserNameField,
        offsetof(USER_INFO_1, usri1_name) },



    { UT_STRING, 1, SAM_ClearTextPasswordField,
        offsetof(USER_INFO_1, usri1_password) },

    { UT_STRING, 2, SAM_ClearTextPasswordField,
        offsetof(USER_INFO_2, usri2_password) },

    { UT_STRING, 3, SAM_ClearTextPasswordField,
        offsetof(USER_INFO_3, usri3_password) },

    { UT_STRING, 1003, SAM_ClearTextPasswordField,
        offsetof(USER_INFO_1003, usri1003_password) },



    { UT_OWF_PASSWORD, 21, SAM_OwfPasswordField,
        offsetof(USER_INFO_21, usri21_password[0]) },

    { UT_OWF_PASSWORD, 22, SAM_OwfPasswordField,
        offsetof(USER_INFO_22, usri22_password[0]) },



    { UT_PRIV, 1, SAM_AuthFlagsField,
        offsetof(USER_INFO_1, usri1_priv) },

    { UT_PRIV, 2, SAM_AuthFlagsField,
        offsetof(USER_INFO_2, usri2_priv) },

    { UT_PRIV, 22, SAM_AuthFlagsField,
        offsetof(USER_INFO_22, usri22_priv) },

#ifdef notdef
    //
    // usri3_priv is totally ignored for info level three.  The field is
    // supplied for compatibility with LM 2.x only and LM 2.x never uses
    // info level 3.
    //
    { UT_PRIV, 3, SAM_AuthFlagsField,
        offsetof(USER_INFO_3, usri3_priv) },
#endif // notdef

    { UT_PRIV, 1005, SAM_AuthFlagsField,
        offsetof(USER_INFO_1005, usri1005_priv) },



    { UT_STRING, 1, SAM_HomeDirectoryField,
        offsetof(USER_INFO_1, usri1_home_dir) },

    { UT_STRING, 2, SAM_HomeDirectoryField,
        offsetof(USER_INFO_2, usri2_home_dir) },

    { UT_STRING, 22, SAM_HomeDirectoryField,
        offsetof(USER_INFO_22, usri22_home_dir) },

    { UT_STRING, 3, SAM_HomeDirectoryField,
        offsetof(USER_INFO_3, usri3_home_dir) },

    { UT_STRING, 1006, SAM_HomeDirectoryField,
        offsetof(USER_INFO_1006, usri1006_home_dir) },


    { UT_STRING, 1, SAM_AdminCommentField,
        offsetof(USER_INFO_1, usri1_comment) },

    { UT_STRING, 2, SAM_AdminCommentField,
        offsetof(USER_INFO_2, usri2_comment) },

    { UT_STRING, 22, SAM_AdminCommentField,
        offsetof(USER_INFO_22, usri22_comment) },

    { UT_STRING, 3, SAM_AdminCommentField,
        offsetof(USER_INFO_3, usri3_comment) },

    { UT_STRING, 1007, SAM_AdminCommentField,
        offsetof(USER_INFO_1007, usri1007_comment) },


    { UT_ACCOUNT_CONTROL, 1, SAM_UserAccountControlField,
        offsetof(USER_INFO_1, usri1_flags) },

    { UT_ACCOUNT_CONTROL, 2, SAM_UserAccountControlField,
        offsetof(USER_INFO_2, usri2_flags) },

    { UT_ACCOUNT_CONTROL, 22, SAM_UserAccountControlField,
        offsetof(USER_INFO_22, usri22_flags) },

    { UT_ACCOUNT_CONTROL, 3, SAM_UserAccountControlField,
        offsetof(USER_INFO_3, usri3_flags) },

    { UT_ACCOUNT_CONTROL, 1008, SAM_UserAccountControlField,
        offsetof(USER_INFO_1008, usri1008_flags) },


    { UT_STRING, 1, SAM_ScriptPathField,
        offsetof(USER_INFO_1, usri1_script_path) },

    { UT_STRING, 2, SAM_ScriptPathField,
        offsetof(USER_INFO_2, usri2_script_path) },

    { UT_STRING, 22, SAM_ScriptPathField,
        offsetof(USER_INFO_22, usri22_script_path) },

    { UT_STRING, 3, SAM_ScriptPathField,
        offsetof(USER_INFO_3, usri3_script_path) },

    { UT_STRING, 1009, SAM_ScriptPathField,
        offsetof(USER_INFO_1009, usri1009_script_path) },


    { UT_AUTH_FLAGS, 2, SAM_AuthFlagsField,
        offsetof(USER_INFO_2, usri2_auth_flags) },

    { UT_AUTH_FLAGS, 22, SAM_AuthFlagsField,
        offsetof(USER_INFO_22, usri22_auth_flags) },

#ifdef notdef
    //
    // usri3_auth_flags is totally ignored for info level three.  The field is
    // supplied for compatibility with LM 2.x only and LM 2.x never uses
    // info level 3.
    //
    { UT_AUTH_FLAGS, 3, SAM_AuthFlagsField,
        offsetof(USER_INFO_3, usri3_auth_flags) },
#endif // notdef

    { UT_AUTH_FLAGS, 1010, SAM_AuthFlagsField,
        offsetof(USER_INFO_1010, usri1010_auth_flags) },



    { UT_CREATE_FULLNAME, 1, SAM_FullNameField,
        offsetof(USER_INFO_1, usri1_name) },

    { UT_STRING, 2, SAM_FullNameField,
        offsetof(USER_INFO_2, usri2_full_name) },

    { UT_STRING, 22, SAM_FullNameField,
        offsetof(USER_INFO_22, usri22_full_name) },

    { UT_STRING, 3, SAM_FullNameField,
        offsetof(USER_INFO_3, usri3_full_name) },

    { UT_STRING, 1011, SAM_FullNameField,
        offsetof(USER_INFO_1011, usri1011_full_name) },



    { UT_STRING, 2, SAM_UserCommentField,
        offsetof(USER_INFO_2, usri2_usr_comment) },

    { UT_STRING, 22, SAM_UserCommentField,
        offsetof(USER_INFO_22, usri22_usr_comment) },

    { UT_STRING, 3, SAM_UserCommentField,
        offsetof(USER_INFO_3, usri3_usr_comment) },

    { UT_STRING, 1012, SAM_UserCommentField,
        offsetof(USER_INFO_1012, usri1012_usr_comment) },


    { UT_STRING, 2, SAM_ParametersField,
        offsetof(USER_INFO_2, usri2_parms) },

    { UT_STRING, 22, SAM_ParametersField,
        offsetof(USER_INFO_22, usri22_parms) },

    { UT_STRING, 3, SAM_ParametersField,
        offsetof(USER_INFO_3, usri3_parms) },

    { UT_STRING, 1013, SAM_ParametersField,
        offsetof(USER_INFO_1013, usri1013_parms) },


    { UT_STRING, 2, SAM_WorkstationsField,
        offsetof(USER_INFO_2, usri2_workstations) },

    { UT_STRING, 22, SAM_WorkstationsField,
        offsetof(USER_INFO_22, usri22_workstations) },

    { UT_STRING, 3, SAM_WorkstationsField,
        offsetof(USER_INFO_3, usri3_workstations) },

    { UT_STRING, 1014, SAM_WorkstationsField,
        offsetof(USER_INFO_1014, usri1014_workstations) },


    { UT_TIME, 2, SAM_AccountExpiresField,
        offsetof(USER_INFO_2, usri2_acct_expires) },

    { UT_TIME, 22, SAM_AccountExpiresField,
        offsetof(USER_INFO_22, usri22_acct_expires) },

    { UT_TIME, 3, SAM_AccountExpiresField,
        offsetof(USER_INFO_3, usri3_acct_expires) },

    { UT_TIME, 1017, SAM_AccountExpiresField,
        offsetof(USER_INFO_1017, usri1017_acct_expires) },


#ifdef notdef // lm 2.1 gets this wrong when adding BDC accounts
    { UT_MAX_STORAGE, 2, SAM_MaxStorageField,
        offsetof(USER_INFO_2, usri2_max_storage) },

    { UT_MAX_STORAGE, 22, SAM_MaxStorageField,
        offsetof(USER_INFO_22, usri22_max_storage) },

    { UT_MAX_STORAGE, 3, SAM_MaxStorageField,
        offsetof(USER_INFO_3, usri3_max_storage) },
#endif // notdef

    { UT_MAX_STORAGE, 1018, SAM_MaxStorageField,
        offsetof(USER_INFO_1018, usri1018_max_storage) },


    { UT_UNITS_PER_WEEK, 2, SAM_UnitsPerWeekField,
        offsetof(USER_INFO_2, usri2_units_per_week) },

    { UT_UNITS_PER_WEEK, 22, SAM_UnitsPerWeekField,
        offsetof(USER_INFO_22, usri22_units_per_week) },

    { UT_UNITS_PER_WEEK, 3, SAM_UnitsPerWeekField,
        offsetof(USER_INFO_3, usri3_units_per_week) },

    { UT_UNITS_PER_WEEK, 1020, SAM_UnitsPerWeekField,
        offsetof(USER_INFO_1020, usri1020_units_per_week) },


    { UT_LOGON_HOURS, 2, SAM_LogonHoursField,
        offsetof(USER_INFO_2, usri2_logon_hours) },

    { UT_LOGON_HOURS, 22, SAM_LogonHoursField,
        offsetof(USER_INFO_22, usri22_logon_hours) },

    { UT_LOGON_HOURS, 3, SAM_LogonHoursField,
        offsetof(USER_INFO_3, usri3_logon_hours) },

    { UT_LOGON_HOURS, 1020, SAM_LogonHoursField,
        offsetof(USER_INFO_1020, usri1020_logon_hours) },


    { UT_USHORT, 2, SAM_CountryCodeField,
        offsetof(USER_INFO_2, usri2_country_code) },

    { UT_USHORT, 22, SAM_CountryCodeField,
        offsetof(USER_INFO_22, usri22_country_code) },

    { UT_USHORT, 3, SAM_CountryCodeField,
        offsetof(USER_INFO_3, usri3_country_code) },

    { UT_USHORT, 1024, SAM_CountryCodeField,
        offsetof(USER_INFO_1024, usri1024_country_code) },


    { UT_USHORT, 2, SAM_CodePageField,
        offsetof(USER_INFO_2, usri2_code_page) },

    { UT_USHORT, 22, SAM_CodePageField,
        offsetof(USER_INFO_22, usri22_code_page) },

    { UT_USHORT, 3, SAM_CodePageField,
        offsetof(USER_INFO_3, usri3_code_page) },

    { UT_USHORT, 1025, SAM_CodePageField,
        offsetof(USER_INFO_1025, usri1025_code_page) },



    { UT_ULONG, 3, SAM_PrimaryGroupIdField,
        offsetof(USER_INFO_3, usri3_primary_group_id) },

    { UT_ULONG, 1051, SAM_PrimaryGroupIdField,
        offsetof(USER_INFO_1051, usri1051_primary_group_id) },



    { UT_STRING, 3, SAM_ProfilePathField,
        offsetof(USER_INFO_3, usri3_profile) },

    { UT_STRING, 1052, SAM_ProfilePathField,
        offsetof(USER_INFO_1052, usri1052_profile) },

    { UT_STRING, 3, SAM_HomeDirectoryDriveField,
        offsetof(USER_INFO_3, usri3_home_dir_drive) },

    { UT_STRING, 1053, SAM_HomeDirectoryDriveField,
        offsetof(USER_INFO_1053, usri1053_home_dir_drive) },


    { UT_BOOLEAN, 3, SAM_PasswordExpiredField,
        offsetof(USER_INFO_3, usri3_password_expired) },

};


NET_API_STATUS
UserpSetInfo(
    IN SAM_HANDLE DomainHandle,
    IN PSID DomainId,
    IN SAM_HANDLE UserHandle OPTIONAL,
    IN SAM_HANDLE BuiltinDomainHandle OPTIONAL,
    IN ULONG UserRelativeId,
    IN LPWSTR UserName,
    IN DWORD Level,
    IN LPBYTE Buffer,
    IN ULONG WhichFieldsMask,
    OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
    )

/*++

Routine Description:

    Set the parameters on a user account in the user accounts database.

Arguments:

    DomainHandle - Domain Handle for the domain.

    PSID DomainId - Domain Sid for DomainHandle

    UserHandle - User Handle of the already open group.  If one is not
        specified, one will be openned then closed.  If one is specified,
        it must be open with adequate access.

    BuiltinDomainHandle - Domain Handle for the builtin domain.  Need only be
        specified for info level 1, 2, 3, 22, 1005 and 1010.  Need not
        be specified when a user is created.

    UserRelativeId - Relative Id of the user.

    UserName - Name of the user to set.

    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.

    NOTE: LogonServer field that is passed in UAS set structure is never
            used or validated. It is simply ignored.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;
    SAM_HANDLE LocalUserHandle = NULL;
    ACCESS_MASK DesiredAccess;
    DWORD UasSamIndex;


    USER_ALL_INFORMATION UserAll;

    //
    // Value of Fields from UAS structure (used for validation)
    //

    DWORD UasUserFlags;
    DWORD NewPriv;
    DWORD NewAuthFlags;

    BOOL ValidatePriv = FALSE;
    BOOL ValidateAuthFlags = FALSE;

    USHORT UasUnitsPerWeek;


    //
    // Variables for changing the DACL on the user.
    //

    PACL OldUserDacl = NULL;
    PACL NewUserDacl = NULL;

    BOOL UserDaclChanged = FALSE;
    BOOL HandleUserDacl = FALSE;


    //
    // Define several macros for accessing the various fields of the UAS
    // structure.  Each macro takes an index into the UserpUasSamTable
    // array and returns the value.
    //

#define GET_UAS_STRING_POINTER( _i ) \
        (*((LPWSTR *)(Buffer + UserpUasSamTable[_i].UasOffset)))

#define GET_UAS_DWORD( _i ) \
        (*((DWORD *)(Buffer + UserpUasSamTable[_i].UasOffset)))

#define GET_UAS_FIELD_ADDRESS( _i ) \
        (Buffer + UserpUasSamTable[_i].UasOffset)


    //
    // Define a macro which returns a pointer the appropriate
    // SamFieldDescription structure given an index into the UserpUasSamTable.
    //

#define SAM_FIELD( _i ) \
        SamFieldDescription[ UserpUasSamTable[_i].SamField ]


    //
    // Initialize
    //

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

    NetpSetParmError( PARM_ERROR_NONE );
    RtlZeroMemory( &UserAll, sizeof(UserAll) );


    //
    // Go through the list of valid info levels determining if the info level
    // is valid and computing the desired access to the user and copying the
    // UAS information into the SAM structure.
    //

    DesiredAccess = 0;
    for ( UasSamIndex=0 ;
        UasSamIndex<sizeof(UserpUasSamTable)/sizeof(UserpUasSamTable[0]);
        UasSamIndex++ ){

        LPBYTE SamField;


        //
        // If this field isn't one we're changing, just skip to the next one
        //

        if ( Level != UserpUasSamTable[UasSamIndex].UasLevel ) {
            continue;
        }


        //
        // Set up a pointer to the appropriate field in SAM's structure.
        //

        if ( SAM_FIELD(UasSamIndex).WhichField != 0 ) {
            SamField = ((LPBYTE)(&UserAll)) + SAM_FIELD(UasSamIndex).SamOffset;
        } else {
            SamField = NULL;
        }


        //
        // Validate the UAS field based on the field type.
        //

        switch ( UserpUasSamTable[UasSamIndex].FieldType ) {

        //
        // Default the fullname of the account to be the user name when
        // the user is created using level 1.
        //
        // Ignore this entry if not a "create" operation.
        //
        case UT_CREATE_FULLNAME:

            if ( UserHandle == NULL ) {
                continue;
            }

            /* DROP THROUGH to the UT_STRING case */

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

        case UT_STRING:

            if ( GET_UAS_STRING_POINTER( UasSamIndex ) == NULL ) {
                continue;
            }

            RtlInitUnicodeString(
                        (PUNICODE_STRING) SamField,
                        GET_UAS_STRING_POINTER(UasSamIndex) );

            break;


        //
        // Just save the UnitsPerWeek until UT_LOGON_HOURS can handle
        // both fields at once.
        //
        // Sam gets confused if we pass in one field without the other.
        //

        case UT_UNITS_PER_WEEK:

            UasUnitsPerWeek = (USHORT) GET_UAS_DWORD(UasSamIndex);

            //
            // If this is a create at info level 2 (e.g., dowlevel client),
            //  assume the caller specified UNITS_PER_WEEK.
            //
            // We don't special case SetInfo at level 2 because we don't
            //  want to corrupt the value assuming he did a query followed
            //  by a set.
            //

            if ( Level == 2 && UserHandle != NULL ) {
                UasUnitsPerWeek = UNITS_PER_WEEK;
            }

            if ( UasUnitsPerWeek > USHRT_MAX ) {
                NetpSetParmError( SAM_FIELD(UasSamIndex).UasParmNum );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Ushort too big Index:%ld Value:%ld\n",
                        UasSamIndex,
                        UasUnitsPerWeek );
                }
                goto Cleanup;
            }

            //
            // Ignore this field completely for now.
            //  Let UT_LOGON_HOURS define the desired access and Whichfields.
            continue;

        //
        // If the caller passed in a NULL pointer to the logon hours
        //  he doesn't want to change the logon hours.
        //

        case UT_LOGON_HOURS:

            if ( GET_UAS_STRING_POINTER( UasSamIndex ) == NULL ) {
                continue;
            }

            *((PUCHAR *)SamField) = (PUCHAR)GET_UAS_DWORD(UasSamIndex);
            UserAll.LogonHours.UnitsPerWeek = UasUnitsPerWeek;
            break;


        //
        // If the user is setting max storage, require him to set
        //  it to USER_MAXSTORAGE_UNLIMITED since SAM doesn't support
        //  max storage.
        //

        case UT_MAX_STORAGE:
            if ( GET_UAS_DWORD(UasSamIndex) != USER_MAXSTORAGE_UNLIMITED ) {

                NetpSetParmError( USER_MAX_STORAGE_PARMNUM );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint( "UserpSetInfo: Max storage is invalid\n" );
                }
                goto Cleanup;
            }

            // 'break' to make sure the user exists.
            break;


        //
        // Handle Account control
        //
        // Ensure all the required bits are on and only valid bits
        //  are on.
        //

        case UT_ACCOUNT_CONTROL: {

            UasUserFlags = GET_UAS_DWORD(UasSamIndex);

            if ( !( UasUserFlags & UF_SCRIPT) ||
                 (( UasUserFlags & ~UF_SETTABLE_BITS) != 0) ) {

                NetpSetParmError( USER_FLAGS_PARMNUM );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Invalid account control bits (1) \n" );
                }
                goto Cleanup;
            }

            //
            // If none of the account type bit is set in the usri_flag,
            // means that the caller does not want to change its account type.
            // break out now, and we will set the appropriate account
            // bit when we set the usri_flag.
            //

            if ( UasUserFlags & UF_ACCOUNT_TYPE_MASK ) {

                //
                // Account Types bits are exclusive, so make sure that
                // precisely one Account Type bit is set.
                //

                if ( !JUST_ONE_BIT( UasUserFlags & UF_ACCOUNT_TYPE_MASK )) {

                    NetpSetParmError( USER_FLAGS_PARMNUM );
                    NetStatus = ERROR_INVALID_PARAMETER;
                    IF_DEBUG( UAS_DEBUG_USER ) {
                        NetpDbgPrint(
                            "UserpSetInfo: Invalid account control bits (2) \n" );
                    }
                    goto Cleanup;
                }

            }


            //
            // If this is a 'create' operation,
            //  and the user has asked for the SAM defaults.
            //  we have no reason to change the DACL
            //

            if ( UserHandle != NULL &&
                 (UasUserFlags & UF_PASSWD_CANT_CHANGE) == 0 ) {
                break;
            }

            //
            // In all other cases, update the DACL to match the callers request.
            //

            HandleUserDacl = TRUE;
            break;

        }

        //
        // Copy a boolean to the SAM structure.
        //

        case UT_BOOLEAN:

            *((PBOOLEAN)SamField) = (BOOLEAN)
                (GET_UAS_DWORD(UasSamIndex)) ? TRUE : FALSE;
            break;


        //
        // Ensure unsigned shorts are really in range and
        //  copy it to the SAM structure.
        //

        case UT_USHORT:

            if ( GET_UAS_DWORD(UasSamIndex) > USHRT_MAX ) {
                NetpSetParmError( SAM_FIELD(UasSamIndex).UasParmNum );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Ushort too big Index:%ld Value:%ld\n",
                        UasSamIndex,
                        GET_UAS_DWORD(UasSamIndex) );
                }
                goto Cleanup;
            }

            *((PUSHORT)SamField) = (USHORT) GET_UAS_DWORD(UasSamIndex);
            break;

        //
        // Copy the unsigned long to the SAM structure
        //

        case UT_ULONG:

            *((PULONG)SamField) = (ULONG)GET_UAS_DWORD(UasSamIndex);
            break;

        //
        // Convert time to its SAM counterpart
        //

        case UT_TIME:

            if ( GET_UAS_DWORD(UasSamIndex) == TIMEQ_FOREVER ) {

                ((PLARGE_INTEGER) SamField)->LowPart = 0;
                ((PLARGE_INTEGER) SamField)->HighPart = 0;

            } else {
                RtlSecondsSince1970ToTime(
                    GET_UAS_DWORD(UasSamIndex),
                    (PLARGE_INTEGER) SamField );
            }

            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint( "UserpSetInfo: Index: %ld Time %lx %lx %lx\n",
                            UasSamIndex,
                            ((PLARGE_INTEGER) SamField)->HighPart,
                            ((PLARGE_INTEGER) SamField)->LowPart,
                            GET_UAS_DWORD(UasSamIndex) );
            }

            break;


        //
        // Copy the OWF password to the SAM structure.
        //
        case UT_OWF_PASSWORD:

            ((PUNICODE_STRING) SamField)->Buffer =
                (LPWSTR) (GET_UAS_FIELD_ADDRESS( UasSamIndex ));

            ((PUNICODE_STRING) SamField)->Length =
                    ((PUNICODE_STRING) SamField)->MaximumLength =
                        LM_OWF_PASSWORD_LENGTH;

            //
            // set that the LmPasswordField field to TRUE to indicate
            // that we filled LmPassword field.
            //

            UserAll.LmPasswordPresent = TRUE;
            UserAll.NtPasswordPresent = FALSE;


            break;


        //
        // Ensure the specified privilege is valid.
        //

        case UT_PRIV:

            NewPriv = GET_UAS_DWORD(UasSamIndex);

            if ( (NewPriv & ~USER_PRIV_MASK) != 0 ) {
                NetpSetParmError( SAM_FIELD(UasSamIndex).UasParmNum );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint( "UserpSetInfo: Invalid priv %ld\n", NewPriv );
                }
                goto Cleanup;
            }
            ValidatePriv = TRUE;
            break;


        //
        // Ensure the specified operator flags is valid.
        //

        case UT_AUTH_FLAGS:

            NewAuthFlags = GET_UAS_DWORD(UasSamIndex);
            if ( (NewAuthFlags & ~AF_SETTABLE_BITS) != 0 ) {
                NetpSetParmError( SAM_FIELD(UasSamIndex).UasParmNum );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint( "UserpSetInfo: Invalid auth_flag %lx\n",
                                  NewAuthFlags );
                }
                goto Cleanup;
            }
            ValidateAuthFlags = TRUE;
            break;

        //
        // All valid cases were explicitly checked above.
        //

        default:
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint(
                    "UserpSetInfo: Invalid field type on initial scan."
                    " Index:%ld\n", UasSamIndex );
            }

            NetStatus = NERR_InternalError;
            goto Cleanup;
        }

        //
        //
        // Accumulate the desired access to do all this functionality.

        DesiredAccess |= SAM_FIELD(UasSamIndex).DesiredAccess;

        //
        // Accumalate which fields are being changed in the
        //  USER_ALL_INFORMATION structure.

        UserAll.WhichFields |= SAM_FIELD(UasSamIndex).WhichField;

    }

    //
    // Check to be sure the user specified a valid Level.
    //
    // The search of the UserpUasSamTable should have resulted in
    // at least one match if the arguments are valid.
    //

    if ( DesiredAccess == 0 ) {
        NetpSetParmError( PARM_ERROR_UNKNOWN );
        NetStatus = ERROR_INVALID_PARAMETER;
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "UserpSetInfo: Desired Access == 0\n" );
        }
        goto Cleanup;
    }

    //
    // Open the user asking for accumulated desired access
    //
    // If a UserHandle was passed in, use it.
    //

    if ( ARGUMENT_PRESENT( UserHandle ) ) {
        LocalUserHandle = UserHandle;
    } else {

        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint( "UserpSetInfo: Desired Access %lX\n", DesiredAccess );
        }

        NetStatus = UserpOpenUser( DomainHandle,
                                   DesiredAccess,
                                   UserName,
                                   &LocalUserHandle,
                                   &UserRelativeId );

        if ( NetStatus != NERR_Success ) {
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint( "UserpSetInfo: UserpOpenUser returns %ld\n",
                    NetStatus );
            }
            goto Cleanup;
        }

    }

    //
    // If an ordinary user created this user (SamCreateUser2InDomain),
    // we must mask off the fields which a user cannot set.
    //
    UserAll.WhichFields &= WhichFieldsMask;


    //
    // Handle Account control
    //
    // Set the individual bits.  Notice that I don't change any of
    //  the bits which aren't defined by the UAS API.
    //

    if ( UserAll.WhichFields & USER_ALL_USERACCOUNTCONTROL ) {

        USER_CONTROL_INFORMATION *UserControl = NULL;

        //
        // Use the current value of UserAccountControl as the proposed
        //  new value of UserAccountControl.
        //

        Status = SamQueryInformationUser( LocalUserHandle,
                                          UserControlInformation,
                                          (PVOID *)&UserControl);

        if ( ! NT_SUCCESS( Status ) ) {
            IF_DEBUG( UAS_DEBUG_USER ) {
                NetpDbgPrint(
                    "UserpGetInfo: SamQueryInformationUser returns %lX\n",
                    Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }

        UserAll.UserAccountControl = UserControl->UserAccountControl;

        Status = SamFreeMemory( UserControl );
        NetpAssert( NT_SUCCESS(Status) );

        //
        // Leave all bits not defined by the UAS API alone,
        // including account type bits.
        //

        UserAll.UserAccountControl &= ~(USER_ACCOUNT_DISABLED |
                        USER_HOME_DIRECTORY_REQUIRED |
                        USER_PASSWORD_NOT_REQUIRED |
                        USER_DONT_EXPIRE_PASSWORD |
                        USER_ACCOUNT_AUTO_LOCKED);

        if (UasUserFlags & UF_ACCOUNTDISABLE) {
            UserAll.UserAccountControl |= USER_ACCOUNT_DISABLED;
        }

        if (UasUserFlags & UF_HOMEDIR_REQUIRED) {
            UserAll.UserAccountControl |= USER_HOME_DIRECTORY_REQUIRED;
        }

        if (UasUserFlags & UF_PASSWD_NOTREQD) {
            UserAll.UserAccountControl |= USER_PASSWORD_NOT_REQUIRED;
        }

        if (UasUserFlags & UF_DONT_EXPIRE_PASSWD) {
            UserAll.UserAccountControl |= USER_DONT_EXPIRE_PASSWORD;
        }

        if (UasUserFlags & UF_LOCKOUT) {
            UserAll.UserAccountControl |= USER_ACCOUNT_AUTO_LOCKED;
        }

        //
        // Set the account type bit.
        //
        // If no account type bit is set in user specified flag,
        //  then leave this bit as it is.
        //

        if( UasUserFlags & UF_ACCOUNT_TYPE_MASK ) {
            ULONG NewSamAccountType;
            ULONG OldSamAccountType;

            OldSamAccountType =
                (UserAll.UserAccountControl) & USER_ACCOUNT_TYPE_MASK;


            //
            // Determine what the new account type should be.
            //

            if ( UasUserFlags & UF_TEMP_DUPLICATE_ACCOUNT ) {
                NewSamAccountType = USER_TEMP_DUPLICATE_ACCOUNT;

            } else if ( UasUserFlags & UF_NORMAL_ACCOUNT ) {
                NewSamAccountType = USER_NORMAL_ACCOUNT;

            } else if (UasUserFlags & UF_INTERDOMAIN_TRUST_ACCOUNT){
                NewSamAccountType = USER_INTERDOMAIN_TRUST_ACCOUNT;

            } else if (UasUserFlags & UF_WORKSTATION_TRUST_ACCOUNT){
                NewSamAccountType = USER_WORKSTATION_TRUST_ACCOUNT;

            } else if ( UasUserFlags & UF_SERVER_TRUST_ACCOUNT ) {
                NewSamAccountType = USER_SERVER_TRUST_ACCOUNT;

            } else {

                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Invalid account type (3)\n");
                }

                NetStatus = NERR_InternalError;
                goto Cleanup;
            }

            //
            // If we are not creating this user,
            //  and either the old or the new account type is a machine account,
            //  don't allow the account type to change.
            //
            // Allow changes between 'normal' and 'temp_duplicate'
            //
            if ( UserHandle == NULL &&
                 NewSamAccountType != OldSamAccountType &&
                 ((OldSamAccountType & USER_MACHINE_ACCOUNT_MASK) ||
                 (NewSamAccountType & USER_MACHINE_ACCOUNT_MASK))) {

                NetpSetParmError( USER_FLAGS_PARMNUM );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Attempt to change account "
                        " type Old: %lx New: %lx\n",
                        OldSamAccountType,
                        NewSamAccountType );
                }
                goto Cleanup;
            }

            //
            // Use the new Account Type.
            //

            UserAll.UserAccountControl &= ~USER_ACCOUNT_TYPE_MASK;
            UserAll.UserAccountControl |= NewSamAccountType;

        //
        //  If SAM has none of its bits set,
        //      set USER_NORMAL_ACCOUNT.
        //
        } else if ((UserAll.UserAccountControl & USER_ACCOUNT_TYPE_MASK) == 0 ){
            UserAll.UserAccountControl |= USER_NORMAL_ACCOUNT;
        }

    }

    //
    // Validate the usriX_priv and usrix_auth_flags fields
    //

    if ( ValidatePriv || ValidateAuthFlags ) {

        DWORD OldPriv, OldAuthFlags;


        //
        // If this is a 'create' operation, just mandate that
        //  the values be reasonable.  These reasonable values
        //  are what UserpGetUserPriv probably would return, unless
        //  of course someone puts the 'user' group in one of the
        //  aliases.
        //

        if ( UserHandle != NULL ) {
            OldPriv = USER_PRIV_USER;
            OldAuthFlags = 0;

        //
        // On a 'set' operation, just get the previous values.
        //

        } else {

            NetStatus = UserpGetUserPriv(
                            BuiltinDomainHandle,
                            LocalUserHandle,
                            UserRelativeId,
                            DomainId,
                            &OldPriv,
                            &OldAuthFlags
                            );

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


        //
        // Ensure AUTH_FLAGS isn't being changed.
        //

        if ( ValidateAuthFlags ) {
            if ( NewAuthFlags != OldAuthFlags ) {
                NetpSetParmError( USER_AUTH_FLAGS_PARMNUM );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Old AuthFlag %ld New AuthFlag %ld\n",
                        OldAuthFlags,
                        NewAuthFlags );
                }
                goto Cleanup;
            }
        }


        //
        // Ensure PRIV isn't being changed.
        //

        if ( ValidatePriv ) {
            if ( NewPriv != OldPriv ) {
                NetpSetParmError( USER_PRIV_PARMNUM );
                NetStatus = ERROR_INVALID_PARAMETER;
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint(
                        "UserpSetInfo: Old Priv %ld New Priv %ld\n",
                        OldPriv,
                        NewPriv );
                }
                goto Cleanup;
            }
        }

    }



    //
    // Handle changes to the User Dacl
    //

    if ( HandleUserDacl ) {
        DWORD DaclSize;
        PACCESS_ALLOWED_ACE Ace;


        //
        // Get the DACL for the user record.
        //

        NetStatus = UserpGetDacl( LocalUserHandle,
                                  &OldUserDacl,
                                  &DaclSize );

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

        //
        // If there is no DACL, just ignore that fact.
        //

        if ( OldUserDacl != NULL ) {

            //
            //  Make a copy of the DACL that reflect the new UAS field.
            //
            NewUserDacl = NetpMemoryAllocate( DaclSize );

            if ( NewUserDacl == NULL ) {
                IF_DEBUG( UAS_DEBUG_USER ) {
                    NetpDbgPrint( "UserpSetInfo: no DACL memory %ld\n",
                        DaclSize );
                }
                NetStatus = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }

            NetpMoveMemory( NewUserDacl, OldUserDacl, DaclSize );

            //
            // The UF_PASSWD_CANT_CHANGE bit is implemented by the
            // ACL on the user object in SAM.  When
            // UF_PASSWD_CANT_CHANGE is on, the ACL doesn't allow
            // World or the user himself USER_CHANGE_PASSWORD access.
            // The ACL has three or four ACEs, each one an
            // ACCESS_ALLOWED ACE.  They are, in order, for
            // World, Administrators alias, optionally the Account
            // Operators alias, and finally the user himself.
            // The Account Operators ACE is present only if the
            // system is LanManNT, and then only if the user in
            // question is not a member of the Administrators
            // alias.  We set/clear the USER_CHANGE_PASSWORD access
            // bit in the first and last ACE.  This leaves
            // Administrators and Account Operators with
            // USER_ALL_ACCESS access.
            //
            // If the DACL for the user has been set by anyone
            // other than the NetUser APIs, this action may
            // not accurately reflect whether the password can
            // be changed.  We silently ignore ACLs we don't
            // recognize.
            //


            //
            // Point Ace to the first ACE.
            //

            Status = RtlGetAce(
                         NewUserDacl,
                         0,
                         (PVOID) &Ace
                         );

            //
            // Twiddle the USER_CHANGE_PASSWORD access bit.
            // This code is copied verbatim below.
            //

            if ( NT_SUCCESS(Status) ) {
                if ( Ace->Mask & USER_CHANGE_PASSWORD ) {
                    if ( UasUserFlags & UF_PASSWD_CANT_CHANGE ) {
                        Ace->Mask &= ~USER_CHANGE_PASSWORD;
                        UserDaclChanged = TRUE;
                    }
                } else {
                    if ( (UasUserFlags & UF_PASSWD_CANT_CHANGE) == 0 ) {
                        Ace->Mask |= USER_CHANGE_PASSWORD;
                        UserDaclChanged = TRUE;
                    }
                }

            }

            //
            // Do the same for the last ACE.
            //

            Status = RtlGetAce(
                         NewUserDacl,
                         NewUserDacl->AceCount - 1,
                         (PVOID) &Ace
                         );

            //
            // Twiddle the USER_CHANGE_PASSWORD access bit.
            // This code is copied verbatim from above.
            //

            if ( NT_SUCCESS(Status) ) {
                if ( Ace->Mask & USER_CHANGE_PASSWORD ) {
                    if ( UasUserFlags & UF_PASSWD_CANT_CHANGE ) {
                        Ace->Mask &= ~USER_CHANGE_PASSWORD;
                        UserDaclChanged = TRUE;
                    }
                } else {
                    if ( (UasUserFlags & UF_PASSWD_CANT_CHANGE) == 0 ) {
                        Ace->Mask |= USER_CHANGE_PASSWORD;
                        UserDaclChanged = TRUE;
                    }
                }

            }


            //
            // Set the DACL if it needs to be.
            //

            if ( UserDaclChanged ) {

                NetStatus = UserpSetDacl( LocalUserHandle, NewUserDacl );
                if ( NetStatus != NERR_Success ) {
                    goto Cleanup;
                }

            }
        }
    }




    //
    // If there is anything changed in the 'UserAll' structure,
    //  tell SAM about the changes.
    //

    NetpAssert( UserAll.WhichFields != 0 );

    Status = SamSetInformationUser(
                LocalUserHandle,
                UserAllInformation,
                &UserAll );

    if ( !NT_SUCCESS(Status) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpDbgPrint(
                "UserpSetInfo: SamSetInformationUser returns %lX\n",
                Status );
        }
        NetpSetParmError( PARM_ERROR_UNKNOWN );
        NetStatus = NetpNtStatusToApiStatus( Status );
        goto Cleanup;
    }


    NetStatus = NERR_Success;

    //
    // Clean up.
    //

Cleanup:


    //
    // If we've changed the DACL on the user and we've not been able
    //  to change everything, put the DACL back as we found it.
    //

    if ( NetStatus != NERR_Success && UserDaclChanged ) {
        NET_API_STATUS NetStatus2;

        NetStatus2 = UserpSetDacl( LocalUserHandle, OldUserDacl );
        ASSERT( NetStatus2 == NERR_Success );

    }

    //
    // If a handle to the user was opened by this routine,
    //  close it.
    //

    if (!ARGUMENT_PRESENT( UserHandle ) && LocalUserHandle != NULL) {
        (VOID) SamCloseHandle( LocalUserHandle );
    }


    //
    // Free any locally used recources.
    //

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

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


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

    return NetStatus;

} // UserpSetInfo

/*lint +e614 */
/*lint +e740 */
