/*++

Copyright (c) 1991-1993  Microsoft Corporation

Module Name:

    rxuser.c

Abstract:

    Routines in this module implement the down-level User and Modals UAS _access
    functionality

    Contains RxNetUser routines:
        RxNetUserAdd
        RxNetUserDel
        RxNetUserEnum
        RxNetUserGetGroups
        RxNetUserGetInfo
        RxNetUserModalsGet
        RxNetUserModalsSet
        RxNetUserPasswordSet
        RxNetUserSetGroups
        RxNetUserSetInfo
        RxNetUserValidate2
        (GetUserDescriptors)
        (GetModalsDescriptors)
        GetLanmanSessionKey

Author:

    Richard Firth (rfirth) 20-May-1991

Environment:

    Win-32/flat address space
    Requires ANSI C extensions: slash-slash comments, long external names,
    _strupr() function.

Notes:

    Routines in this module assume that caller-supplied parameters have
    already been verified. No effort is made to further check the veracity
    of parms. Any actions causing exceptions must be trapped at a higher
    level. This applies to ALL parameters - strings, pointers, buffers, etc.

Revision History:

    20-May-1991 RFirth
        Created
    25-Sep-1991 JohnRo
        PC-LINT found a bug computing buflen for info level 11.
        Fixed UNICODE handling of buflen increments.
        Fixed MIPS build.
        Changed buflen name to bufsize to reflect NT/LAN naming convention.
        Made other changes suggested by PC-LINT.
    21-Nov-1991 JohnRo
        Removed NT dependencies to reduce recompiles.
    05-Dec-1991 RFirth
        Enum returns in TotalEntries (or EntriesLeft) the number of items to
        be enumerated BEFORE this call. Used to be number left after this call
    01-Apr-1992 JohnRo
        Use NetApiBufferAllocate() instead of private version.
    06-Apr-1992 JohnRo
        RAID 8927: usrmgr.exe: _access violation, memory corruption.
        (Fixed RxNetUserSetGroups when it called NetpMoveMemory.)
    02-Apr-1993 JohnRo
        RAID 5098: DOS app NetUserPasswordSet to downlevel gets NT return code.
        Made some changes suggested by PC-LINT 5.0

--*/

#include <nt.h>                 // Needed by NetUserPasswordSet
#include <ntrtl.h>              // Needed by NetUserPasswordSet
#include <nturtl.h>             // RtlConvertUiListToApiList
#include <crypt.h>              // Needed by NetUserPasswordSet
#include "downlevl.h"
#include <rxuser.h>
#include <lmaccess.h>
#include <stdlib.h>              // wcslen().
#include <ntddnfs.h>            // LMR_REQUEST_PACKET
#include <lmuse.h>              // USE_IPC
#include <netlibnt.h>           // NetpRdrFsControlTree
#include <loghours.h>           // NetpRotateLogonHours
#include <accessp.h>            // NetpConvertWorkstationList

//
// down-level encryption now on by default!
//

#define DOWN_LEVEL_ENCRYPTION

//
// BUGBUG: No one else defines the maximum workstation list length
//  so I will.
//

#define MAX_WORKSTATION_LIST 256

//
// local routine prototypes
//

DBGSTATIC
NET_API_STATUS
GetUserDescriptors(
    IN  DWORD   Level,
    IN  BOOL    Encrypted,
    OUT LPDESC* ppDesc16,
    OUT LPDESC* ppDesc32,
    OUT LPDESC* ppDescSmb
    );

DBGSTATIC
VOID
GetModalsDescriptors(
    IN  DWORD   Level,
    OUT LPDESC* ppDesc16,
    OUT LPDESC* ppDesc32,
    OUT LPDESC* ppDescSmb
    );

NET_API_STATUS
GetLanmanSessionKey(
    IN LPWSTR ServerName,
    OUT LPBYTE pSessionKey
    );


//
// Down-level remote API worker routines
//

NET_API_STATUS
RxNetUserAdd(
    IN  LPTSTR  ServerName,
    IN  DWORD   Level,
    IN  LPBYTE  Buffer,
    OUT LPDWORD ParmError OPTIONAL
    )

/*++

Routine Description:

    Adds a user to a down-level UAS database.

    Assumes
        1.  This code assumes that a USER_INFO_1 is a subset of a USER_INFO_2
            and that the fields in a USER_INFO_1 map 1-to-1 to a USER_INFO_2
        2.  Level has already been range-checked

Arguments:

    ServerName  - at which down-level server to run the NetUserAdd API
    Level       - of user info - 1 or 2
    Buffer      - containing info
    ParmError   - where to deposit id of failing info level

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - (return code from down-level API)

--*/

{
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;
    DWORD   buflen;
    DWORD   badparm;
    DWORD   len;
    DWORD   pwdlen;
    CHAR    ansiPassword[LM20_PWLEN+1];
    DWORD   lmOwfPasswordLen;
    LPTSTR   cleartext;
    NET_API_STATUS NetStatus;

#ifdef DOWN_LEVEL_ENCRYPTION

    LM_OWF_PASSWORD lmOwfPassword;
    LM_SESSION_KEY lanmanKey;
    ENCRYPTED_LM_OWF_PASSWORD encryptedLmOwfPassword;
    NTSTATUS Status;

#endif

    BYTE logonHours[21];
    PBYTE callersLogonHours = NULL;

    WCHAR Workstations[MAX_WORKSTATION_LIST+1];
    LPWSTR callersWorkstations = NULL;

    //
    // BUGBUG - put this check in NetUser RPC client stubs
    //

    if (Level < 1 || Level > 2) {
        return ERROR_INVALID_LEVEL;
    }

    if (ParmError == NULL) {
        ParmError = &badparm;
    }
    *ParmError = PARM_ERROR_NONE;

    //
    // calculate the size of the data to be transferred on the wire. See
    // assumption in rubric. We also allow ourselves the luxury of trapping
    // any strings which may break the down-level limits so we can return a
    // nice parameter error number. If a string breaks a down-level limit, we
    // just get back an ERROR_INVALID_PARAMETER, which is not very helpful
    //

    buflen = (Level == 1) ? sizeof(USER_INFO_1) : sizeof(USER_INFO_2);

    len = POSSIBLE_WCSLEN(((PUSER_INFO_1)Buffer)->usri1_name);
    if (len > LM20_UNLEN) {
        *ParmError = USER_NAME_PARMNUM;
        return ERROR_INVALID_PARAMETER;
    }
    buflen += len + 1;

    if (len = POSSIBLE_WCSLEN(((PUSER_INFO_1)Buffer)->usri1_password)) {
        if (len > LM20_PWLEN) {
            *ParmError = USER_PASSWORD_PARMNUM;
            return ERROR_INVALID_PARAMETER;
        }
        buflen += len + 1;
    }
    pwdlen = len;

    if (len = POSSIBLE_WCSLEN(((PUSER_INFO_1)Buffer)->usri1_home_dir)) {
        if (len > LM20_PATHLEN) {
            *ParmError = USER_HOME_DIR_PARMNUM;
            return ERROR_INVALID_PARAMETER;
        }
        buflen += len + 1;
    }

    if (len = POSSIBLE_WCSLEN(((PUSER_INFO_1)Buffer)->usri1_comment)) {
        if (len > LM20_MAXCOMMENTSZ) {
            *ParmError = USER_COMMENT_PARMNUM;
            return ERROR_INVALID_PARAMETER;
        }
        buflen += len + 1;
    }

    if (len = POSSIBLE_WCSLEN(((PUSER_INFO_1)Buffer)->usri1_script_path)) {
        if (len > LM20_PATHLEN) {
            *ParmError = USER_SCRIPT_PATH_PARMNUM;
            return ERROR_INVALID_PARAMETER;
        }
        buflen += len + 1;
    }

    if (Level == 2) {
        if (len = POSSIBLE_WCSLEN(((PUSER_INFO_2)Buffer)->usri2_full_name)) {
            if (len > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_FULL_NAME_PARMNUM;
                return ERROR_INVALID_PARAMETER;
            }
            buflen += len + 1;
        }

        if (len = POSSIBLE_WCSLEN(((PUSER_INFO_2)Buffer)->usri2_usr_comment)) {
            if (len > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_USR_COMMENT_PARMNUM;
                return ERROR_INVALID_PARAMETER;
            }
            buflen += len + 1;
        }

        if (len = POSSIBLE_WCSLEN(((PUSER_INFO_2)Buffer)->usri2_parms)) {
            if (len > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_PARMS_PARMNUM;
                return ERROR_INVALID_PARAMETER;
            }
            buflen += len + 1;
        }

        if (len = POSSIBLE_WCSLEN(((PUSER_INFO_2)Buffer)->usri2_workstations)) {

            if (len > MAX_WORKSTATION_LIST) {
                *ParmError = USER_WORKSTATIONS_PARMNUM;
                return ERROR_INVALID_PARAMETER;
            }
            buflen += len + 1;

        }
    }

    if (pwdlen) {

        //
        // copy the cleartext password out of the buffer - we will replace it with
        // the encrypted version, but need to put the cleartext back before
        // returning control to the caller
        //

        cleartext = ((PUSER_INFO_1)Buffer)->usri1_password;

        //
        // Calculate the one-way function of the password
        //

        RtlUnicodeToMultiByteN(ansiPassword,
                                sizeof(ansiPassword),
                                &lmOwfPasswordLen,
                                ((PUSER_INFO_1)Buffer)->usri1_password,
                                pwdlen * sizeof(WCHAR)
                                );
        ansiPassword[lmOwfPasswordLen] = 0;
        (VOID) _strupr(ansiPassword);

#ifdef DOWN_LEVEL_ENCRYPTION

        NetStatus = NERR_Success;
        Status = RtlCalculateLmOwfPassword(ansiPassword, &lmOwfPassword);
        if (NT_SUCCESS(Status)) {
            NetStatus = GetLanmanSessionKey((LPWSTR)ServerName, (LPBYTE)&lanmanKey);
            if (NetStatus == NERR_Success) {
                Status = RtlEncryptLmOwfPwdWithLmSesKey(&lmOwfPassword,
                                                        &lanmanKey,
                                                        &encryptedLmOwfPassword
                                                        );
                if (NT_SUCCESS(Status)) {
                    ((PUSER_INFO_1)Buffer)->usri1_password = (LPTSTR)&encryptedLmOwfPassword;
                }
            }
        }
        if (NetStatus != NERR_Success)
            return NetStatus;
        else if (!NT_SUCCESS(Status)) {
            return RtlNtStatusToDosError(Status);
        }

#else

        ((PUSER_INFO_1)Buffer)->usri1_password = (LPTSTR)ansiPassword;

#endif

    } else {
        lmOwfPasswordLen = 0;
    }

    //
    // we have checked all the parms we can. If any other parameter breaks at
    // the down-level server then we just have to be content with an unknown
    // parameter error
    //

    *ParmError = PARM_ERROR_UNKNOWN;

#ifdef DOWN_LEVEL_ENCRYPTION

    (void)GetUserDescriptors(Level, TRUE, &pDesc16, &pDesc32, &pDescSmb);

#else

    (void)GetUserDescriptors(Level, FALSE, &pDesc16, &pDesc32, &pDescSmb);

#endif

    //
    // if this level supports logon hours, then convert the caller supplied
    // logon hours from GMT to local time
    //

    if (Level == 2 && ((PUSER_INFO_2)Buffer)->usri2_logon_hours) {
        callersLogonHours = ((PUSER_INFO_2)Buffer)->usri2_logon_hours;
        RtlCopyMemory(logonHours,
                      ((PUSER_INFO_2)Buffer)->usri2_logon_hours,
                      sizeof(logonHours)
                      );

        //
        // shuffle the bitmap and point the logon_hours field in the structure
        // at the shuffled version
        //

        NetpRotateLogonHours(logonHours, UNITS_PER_WEEK, FALSE);
        ((PUSER_INFO_2)Buffer)->usri2_logon_hours = logonHours;
    }


    //
    // Convert the list of workstations from being comma separated
    //  to being space separated.  Ditch workstation names containing
    //  spaces.
    if (Level == 2 && ((PUSER_INFO_2)Buffer)->usri2_workstations) {
        UNICODE_STRING WorkstationString;

        callersWorkstations = ((PUSER_INFO_2)Buffer)->usri2_workstations;
        wcscpy( Workstations, callersWorkstations );

        RtlInitUnicodeString( &WorkstationString, Workstations );
        NetpConvertWorkstationList( &WorkstationString );

        ((PUSER_INFO_2)Buffer)->usri2_workstations = Workstations;

    }


    NetStatus = NERR_Success;
    NetStatus =  RxRemoteApi(API_WUserAdd2,              // which API it is
                             ServerName,                 // which server its at
                             REMSmb_NetUserAdd2_P,       // parameter descriptor
                             pDesc16, pDesc32, pDescSmb, // data descriptors
                             NULL, NULL, NULL,           // no aux descriptors reqd.
                             FALSE,                      // need to be logged on
                             Level,                      // caller parms
                             Buffer,
                             buflen,                     // and one we created

#ifdef DOWN_LEVEL_ENCRYPTION

                             1,                          // encryption on
                             lmOwfPasswordLen            // length of cleartext

#else

                             0,
                             pwdlen

#endif
                             );

    //
    // BUGBUG - some kind of fallback here?
    //

    //
    // copy the original password back to the user's buffer
    //

    if (pwdlen) {
        ((PUSER_INFO_1)Buffer)->usri1_password = cleartext;
    }

    //
    // and the original logon hours
    //

    if (callersLogonHours) {
        ((PUSER_INFO_2)Buffer)->usri2_logon_hours = callersLogonHours;
    }

    //
    // and the original workstation list
    //

    if ( callersWorkstations ) {
        ((PUSER_INFO_2)Buffer)->usri2_workstations = callersWorkstations;
    }

    return NetStatus;
}


NET_API_STATUS
RxNetUserDel(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName
    )

/*++

Routine Description:

    Removes a user from a down-level server's UAS (User Account Subsystem)
    database

    Assumes
        1.  UserName has been checked for valid pointer to valid string

Arguments:

    ServerName  - on which (down-level) server to run the API
    UserName    - which user to delete

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_PARAMETER
                    UserName > LM20_UNLEN
                  (return code from remoted NetUserDel)

--*/

{
    if (STRLEN(UserName) > LM20_UNLEN) {
        return ERROR_INVALID_PARAMETER;
    }

    return RxRemoteApi(API_WUserDel,            // api being remoted
                        ServerName,             // where to remote it
                        REMSmb_NetUserDel_P,    // parameter descriptor
                        NULL, NULL, NULL,       // data descriptors
                        NULL, NULL, NULL,       // aux data descriptors
                        FALSE,                  // this call needs user logged on
                        UserName                // remote API parms...
                        );
}


NET_API_STATUS
RxNetUserEnum(
    IN  LPTSTR  ServerName,
    IN  DWORD   Level,
    OUT LPBYTE* Buffer,
    IN  DWORD   PrefMaxLen,
    OUT LPDWORD EntriesRead,
    OUT LPDWORD EntriesLeft,
    IN OUT LPDWORD ResumeHandle OPTIONAL
    )

/*++

Routine Description:

    Returns user information from a down-level server's UAS database

Arguments:

    ServerName  - where to run the API
    Level       - of info required - 0, 1, 2, or 10
    Buffer      - place to return a pointer to an allocated buffer
    PrefMaxLen  - caller's preferred maximum buffer size
    EntriesRead - number of <Level> info entries being returned in the buffer
    EntriesLeft - number of entries left after this one
    ResumeHandle- used to resume enumeration (Ignored)

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_PARAMETER
                    non-NULL ResumeHandle
                  ERROR_NOT_ENOUGH_MEMORY
                    NetApiBufferAllocate failed (?!)
                  (return code from down-level API)

--*/

{
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;
    NET_API_STATUS  rc;
    LPBYTE  bufptr;
    DWORD   entries_read, total_avail;
    DWORD   last_resume_handle, new_resume_handle = 0;

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

    rc = GetUserDescriptors(Level, FALSE, &pDesc16, &pDesc32, &pDescSmb);
    if (rc != NO_ERROR) {
        return(rc);
    }
    bufptr = NULL;

    //
    // try NetUserEnum2 (supports resume handle) with the requested amount
    // of data. If the down-level server doesn't support this API then try
    // NetUserEnum
    //

    if (ARGUMENT_PRESENT(ResumeHandle)) {
        last_resume_handle = *ResumeHandle;
    } else {
        last_resume_handle = 0;
    }

    //
    // irrespective of whether we can resume the enumeration, down-level
    // servers can't generate >64K-1 bytes of data
    //

    if (PrefMaxLen > 65535) {
        PrefMaxLen = 65535;
    }
    rc = RxRemoteApi(API_WUserEnum2,
                     ServerName,
                     REMSmb_NetUserEnum2_P,
                     pDesc16, pDesc32, pDescSmb,
                     NULL, NULL, NULL,
                     ALLOCATE_RESPONSE,             // RxRemoteApi allocates buffer
                     Level,
                     &bufptr,
                     PrefMaxLen,                    // size of caller's buffer
                     last_resume_handle,            // last key returned
                     &new_resume_handle,            // returns this key
                     &entries_read,                 // number returned
                     &total_avail                   // total available at server
                     );

    //
    // WinBall returns ERROR_NOT_SUPPORTED. LM < 2.1 returns NERR_InvalidAPI?
    // WinBall returns ERROR_NOT_SUPPORTED because it is share level, so this
    // whole API fails. Therefore, no need to account (no pun intended) for
    // WinBall
    //

    //
    // RLF 10/01/92. Seemingly, IBM LAN Server returns Internal Error (2140).
    // We'll handle that one too....
    //

    if (rc == NERR_InvalidAPI || rc == NERR_InternalError) {

        //
        // the down-level server doesn't support NetUserEnum2. Fall-back to
        // NetUserEnum & try to get as much data as available
        //

        rc = RxRemoteApi(API_WUserEnum,
                         ServerName,
                         REMSmb_NetUserEnum_P,
                         pDesc16, pDesc32, pDescSmb,
                         NULL, NULL, NULL,
                         ALLOCATE_RESPONSE,     // RxRemoteApi allocates buffer
                         Level,
                         &bufptr,
                         65535,                 // get as much data as possible
                         &entries_read,
                         &total_avail
                         );
    } else if (rc == NERR_Success || rc == ERROR_MORE_DATA) {

        //
        // return the resume handle if NetUserEnum2 succeeded & the caller
        // supplied a ResumeHandle parameter
        //

        if (ARGUMENT_PRESENT(ResumeHandle)) {
            *ResumeHandle = new_resume_handle;
        }
    }
    if (rc && rc != ERROR_MORE_DATA) {
        if (bufptr != NULL) {
            (void) NetApiBufferFree(bufptr);
        }
    } else {

        //
        // if level supports logon hours, convert from local time to GMT. Level
        // 2 is the only level of user info handled by this routine that knows
        // about logon hours
        //
        // if level support workstation list, convert from blank separated to
        // comma separated list.  Level 2 is the only level of user info
        // handled by this routine that know about the workstation list.
        //

        if (Level == 2) {

            DWORD numRead;
            LPUSER_INFO_2 ptr = (LPUSER_INFO_2)bufptr;

            for (numRead = entries_read; numRead; --numRead) {
                NetpRotateLogonHours(ptr->usri2_logon_hours, UNITS_PER_WEEK, TRUE);
                if ( ptr->usri2_workstations != NULL ) {
                    UNICODE_STRING BlankSeparated;
                    UNICODE_STRING CommaSeparated;
                    NTSTATUS Status;

                    RtlInitUnicodeString( &BlankSeparated, ptr->usri2_workstations );

                    Status = RtlConvertUiListToApiList(
                                &BlankSeparated,
                                &CommaSeparated,
                                TRUE );         // Allow Blanks as delimiters

                    if ( !NT_SUCCESS(Status)) {
                        return RtlNtStatusToDosError(Status);
                    }

                    if ( CommaSeparated.Length > 0 ) {
                        NetpAssert ( wcslen( ptr->usri2_workstations ) <=
                                     wcslen( CommaSeparated.Buffer ) );
                        if ( wcslen( ptr->usri2_workstations ) <=
                             wcslen( CommaSeparated.Buffer ) ) {
                            wcscpy( ptr->usri2_workstations,
                                    CommaSeparated.Buffer );
                        }
                    }

                }
                ++ptr;
            }
        }
        *Buffer = bufptr;
        *EntriesRead = entries_read;
        *EntriesLeft = total_avail;
    }
    return rc;
}


NET_API_STATUS
RxNetUserGetGroups(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName,
    IN  DWORD   Level,
    OUT LPBYTE* Buffer,
    IN  DWORD   PrefMaxLen,
    OUT LPDWORD EntriesRead,
    OUT LPDWORD EntriesLeft
    )

/*++

Routine Description:

    Get the list of groups in a UAS database to which a particular user belongs

Arguments:

    ServerName  - where to run the API
    UserName    - which user to get info for
    Level       - of info requested - Must Be Zero
    Buffer      - where to deposit the buffer we allocate containing the info
    PrefMaxLen  - caller's preferred maximum buffer size
    EntriesRead - number of entries being returned in Buffer
    EntriesLeft - number of entries left to get

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_LEVEL
                  ERROR_INVALID_PARAMETER

--*/

{
    NET_API_STATUS  rc;
    DWORD   entries_read, total_avail;
    LPBYTE  bufptr;


    UNREFERENCED_PARAMETER(Level);
    UNREFERENCED_PARAMETER(PrefMaxLen);

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

    if (STRLEN(UserName) > LM20_UNLEN) {
        return ERROR_INVALID_PARAMETER;
    }

    bufptr = NULL;
    rc = RxRemoteApi(API_WUserGetGroups,
                        ServerName,
                        REMSmb_NetUserGetGroups_P,
                        REM16_user_info_0,
                        REM32_user_info_0,
                        REMSmb_user_info_0,
                        NULL, NULL, NULL,
                        ALLOCATE_RESPONSE,
                        UserName,                   // API parameters
                        0,                          // fixed level
                        &bufptr,
                        65535,
                        &entries_read,
                        &total_avail                // supplied by us
                        );
    if (rc) {
        if (bufptr != NULL) {
            (void) NetApiBufferFree(bufptr);
        }
    } else {
        *Buffer = bufptr;
        *EntriesRead = entries_read;
        *EntriesLeft = total_avail;
    }
    return rc;
}


NET_API_STATUS
RxNetUserGetInfo(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName,
    IN  DWORD   Level,
    OUT LPBYTE* Buffer
    )

/*++

Routine Description:

    Get information about a particular user from a down-level server

    Assumes:
        1.  UserName is a valid pointer to a valid string

Arguments:

    ServerName  - where to run the API
    UserName    - which user to get info on
    Level       - what level of info required - 0, 1, 2, 10, 11
    Buffer      - where to return buffer containing info

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_LEVEL
                  ERROR_INVALID_PARAMETER

--*/

{
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;
    DWORD   buflen;
    LPBYTE  bufptr;
    DWORD   total_avail;
    NET_API_STATUS  rc;


    *Buffer = NULL;

    if (STRLEN(UserName) > LM20_UNLEN) {
        return ERROR_INVALID_PARAMETER;
    }

    //
    // work out the anount of buffer space we need to return the down-level
    // structure as its 32-bit equivalent
    //

    switch (Level) {
    case 0:
        buflen = sizeof(USER_INFO_0) + STRING_SPACE_REQD(UNLEN + 1);
        break;

    case 1:
        buflen = sizeof(USER_INFO_1)
            + STRING_SPACE_REQD(UNLEN + 1)              // usri1_name
            + STRING_SPACE_REQD(ENCRYPTED_PWLEN)        // usri1_password
            + STRING_SPACE_REQD(LM20_PATHLEN + 1)       // usri1_home_dir
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri1_comment
            + STRING_SPACE_REQD(LM20_PATHLEN + 1);      // usri1_script_path
        break;

    case 2:
        buflen = sizeof(USER_INFO_2)
            + STRING_SPACE_REQD(UNLEN + 1)              // usri2_name
            + STRING_SPACE_REQD(ENCRYPTED_PWLEN)        // usri2_password
            + STRING_SPACE_REQD(LM20_PATHLEN + 1)       // usri2_home_dir
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri2_comment
            + STRING_SPACE_REQD(LM20_PATHLEN + 1)       // usri2_script_path
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri2_full_name
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri2_usr_comment
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri2_parms
            + STRING_SPACE_REQD(MAX_WORKSTATION_LIST)   // usri2_workstations
            + STRING_SPACE_REQD(MAX_PATH + 1)        // usri2_logon_server

            //
            // BUGBUG - is there a manifest for this 21?
            //

            + 21;                                       // usri2_logon_hours
        break;

    case 10:
        buflen = sizeof(USER_INFO_10)
            + STRING_SPACE_REQD(UNLEN + 1)              // usri10_name
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri10_comment
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri10_usr_comment
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1); // usri10_full_name
        break;

    case 11:
        buflen = sizeof(USER_INFO_11)
            + STRING_SPACE_REQD(UNLEN + 1)              // usri11_name
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri11_comment
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri11_usr_comment
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri11_full_name
            + STRING_SPACE_REQD(LM20_PATHLEN + 1)       // usri11_home_dir
            + STRING_SPACE_REQD(LM20_MAXCOMMENTSZ + 1)  // usri11_parms
            + STRING_SPACE_REQD(MAX_PATH + 1)           // usri11_logon_server
            + STRING_SPACE_REQD(MAX_WORKSTATION_LIST)   // usri11_workstations

            //
            // BUGBUG - is there a manifest for this 21?
            //

            + 21;                                       // usri11_logon_hours
        break;

    default:
        return(ERROR_INVALID_LEVEL);
    }
    buflen = DWORD_ROUNDUP(buflen);
    if (rc = NetApiBufferAllocate(buflen, (LPVOID *) &bufptr)) {
        return rc;
    }
    (void)GetUserDescriptors(Level, FALSE, &pDesc16, &pDesc32, &pDescSmb);
    rc = RxRemoteApi(API_WUserGetInfo,
                     ServerName,
                     REMSmb_NetUserGetInfo_P,
                     pDesc16, pDesc32, pDescSmb,
                     NULL, NULL, NULL,
                     FALSE,
                     UserName,
                     Level,
                     bufptr,
                     buflen,
                     &total_avail
                     );
    if (rc) {
        (void) NetApiBufferFree(bufptr);
    } else {

        //
        // Convert the logon hours bitmap to UTC/GMT
        // Convert the workstation list from blank separated to comma separated
        //

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

            PBYTE logonHours;
            LPWSTR Workstations;

            if (Level == 2) {
                logonHours = ((PUSER_INFO_2)bufptr)->usri2_logon_hours;
                Workstations = ((PUSER_INFO_2)bufptr)->usri2_workstations;
            } else {
                logonHours = ((PUSER_INFO_11)bufptr)->usri11_logon_hours;
                Workstations = ((PUSER_INFO_11)bufptr)->usri11_workstations;
            }
            NetpRotateLogonHours(logonHours, UNITS_PER_WEEK, TRUE);

            if ( Workstations != NULL ) {
                UNICODE_STRING BlankSeparated;
                UNICODE_STRING CommaSeparated;
                NTSTATUS Status;

                RtlInitUnicodeString( &BlankSeparated, Workstations );

                Status = RtlConvertUiListToApiList(
                            &BlankSeparated,
                            &CommaSeparated,
                            TRUE );         // Allow Blanks as delimiters

                if ( !NT_SUCCESS(Status)) {
                    return RtlNtStatusToDosError(Status);
                }

                if ( CommaSeparated.Length > 0 ) {
                    NetpAssert ( wcslen( Workstations ) <=
                                 wcslen( CommaSeparated.Buffer ) );
                    if ( wcslen(Workstations) <= wcslen(CommaSeparated.Buffer)){
                        wcscpy( Workstations, CommaSeparated.Buffer );
                    }
                }

            }
        }
        *Buffer = bufptr;
    }
    return rc;
}


NET_API_STATUS
RxNetUserModalsGet(
    IN  LPTSTR  ServerName,
    IN  DWORD   Level,
    OUT LPBYTE* Buffer
    )

/*++

Routine Description:

    Returns global information about all users and groups in a down-level UAS
    database

    Assumes
        1.  Level has been validated

Arguments:

    ServerName  - where to run the API
    Level       - of info required - 0 or 1
    Buffer      - where to deposit the returned info

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_LEVEL
                  ERROR_INVALID_PARAMETER

--*/

{
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;
    DWORD   buflen;
    LPBYTE  bufptr;
    NET_API_STATUS  rc;
    DWORD   total_avail;

    if (Level > 1) {
        return ERROR_INVALID_LEVEL;
    }

    *Buffer = NULL;
    buflen = Level ? sizeof(USER_MODALS_INFO_1) : sizeof(USER_MODALS_INFO_0);
    buflen += Level ? STRING_SPACE_REQD(MAX_PATH + 1) : 0;
    buflen = DWORD_ROUNDUP(buflen);
    if (rc = NetApiBufferAllocate(buflen, (LPVOID *) &bufptr)) {
        return rc;
    }
    GetModalsDescriptors(Level, &pDesc16, &pDesc32, &pDescSmb);
    rc = RxRemoteApi(API_WUserModalsGet,
                        ServerName,
                        REMSmb_NetUserModalsGet_P,
                        pDesc16, pDesc32, pDescSmb,
                        NULL, NULL, NULL,
                        FALSE,
                        Level,
                        bufptr,
                        buflen,
                        &total_avail
                        );
    if (rc) {
        (void) NetApiBufferFree(bufptr);
    } else {
        *Buffer = bufptr;
    }
    return rc;
}


NET_API_STATUS
RxNetUserModalsSet(
    IN  LPTSTR  ServerName,
    IN  DWORD   Level,
    IN  LPBYTE  Buffer,
    OUT LPDWORD ParmError OPTIONAL
    )

/*++

Routine Description:

    Sets global information for all users and groups in a down-level UAS
    database

    Assumes
        1.  Level parameter already verified

Arguments:

    ServerName  - where to run the API
    Level       - level of information being supplied - 0, 1, 1001-1007
    Buffer      - pointer to buffer containing input information
    ParmError   - pointer to place to store index of failing info

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_PARAMETER
                    One of the fields in the input structure was invalid

--*/

{
    DWORD   parmnum;
    DWORD   badparm;
    DWORD   buflen;
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;


    //
    // check for bad addresses and set ParmError to a known default
    //

    if (ParmError == NULL) {
        ParmError = &badparm;
    }
    *ParmError = PARM_ERROR_NONE;

    if (Level) {
        if (Level == 1) {
            parmnum = PARMNUM_ALL;
            buflen = sizeof(USER_MODALS_INFO_1)
                + POSSIBLE_STRLEN(((PUSER_MODALS_INFO_1)Buffer)->usrmod1_primary);
        } else {

            //
            // Convert info levels 1006, 1007 to corresponding parmnums (1, 2)
            // at old info level 1
            //

            if (Level >= MODALS_ROLE_INFOLEVEL) {
                parmnum = Level - (MODALS_ROLE_INFOLEVEL - 1);
                Level = 1;
                switch (parmnum) {
                    case 1: // MODALS_ROLE_PARMNUM
                        buflen = sizeof(DWORD);
                        break;

                    case 2: // MODALS_PRIMARY_PARMNUM
                        buflen = STRLEN( (LPTSTR) Buffer);
                        if (buflen > MAX_PATH) {
                            *ParmError = MODALS_PRIMARY_INFOLEVEL;
                            return ERROR_INVALID_PARAMETER;
                        }
                        break;

                    default:
#if DBG
                        NetpKdPrint(("error: RxNetUserModalsSet.%d: bad parmnum %d\n",
                        __LINE__,
                        parmnum
                        ));
#endif
                        return ERROR_INVALID_LEVEL;
                }
            } else if (Level >= MODALS_MIN_PASSWD_LEN_INFOLEVEL) {

                //
                // Convert info levels 1001-1005 to equivalent parmnums at
                // level 0
                //

                parmnum = Level - PARMNUM_BASE_INFOLEVEL;
                Level = 0;
                buflen = sizeof(DWORD);
            } else {
#if DBG
                NetpKdPrint(("error: RxNetUserModalsSet.%d: bad level %d\n",
                __LINE__,
                Level
                ));
#endif
                return ERROR_INVALID_LEVEL;
            }
        }
    } else {
        parmnum = PARMNUM_ALL;
        buflen = sizeof(USER_MODALS_INFO_0);
    }

    *ParmError = PARM_ERROR_UNKNOWN;
    GetModalsDescriptors(Level, &pDesc16, &pDesc32, &pDescSmb);
    return RxRemoteApi(API_WUserModalsSet,
                        ServerName,
                        REMSmb_NetUserModalsSet_P,
                        pDesc16, pDesc32, pDescSmb,
                        NULL, NULL, NULL,
                        FALSE,
                        Level,                          // API parms
                        Buffer,
                        buflen,                         // supplied by us
                        MAKE_PARMNUM_PAIR(parmnum, parmnum) // ditto
                        );
}


NET_API_STATUS
RxNetUserPasswordSet(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName,
    IN  LPTSTR  OldPassword,
    IN  LPTSTR  NewPassword
    )

/*++

Routine Description:

    Changes the password associated with a user account in a down-level UAS
    database

    Assumes
        1.  The pointer parameters have already been verified

Arguments:

    ServerName  - where to change the password
    UserName    - which user account to change it for
    OldPassword - the current password
    NewPassword - the new password

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_PARAMETER
                    UserName, OldPassword or NewPassword would break down-level
                    limits

--*/

{
    NTSTATUS Status;
    NET_API_STATUS NetStatus;
    BOOL           TryNullSession = TRUE;       // Try null session first.
    ULONG BytesWritten;

#ifdef DOWN_LEVEL_ENCRYPTION

    CHAR OldAnsiPassword[LM20_PWLEN+1];
    CHAR NewAnsiPassword[LM20_PWLEN+1];
    LM_OWF_PASSWORD OldOwfPassword;
    LM_OWF_PASSWORD NewOwfPassword;
    ENCRYPTED_LM_OWF_PASSWORD OldEncryptedWithNew;
    ENCRYPTED_LM_OWF_PASSWORD NewEncryptedWithOld;

#else

    CHAR OldAnsiPassword[ENCRYPTED_PWLEN];
    CHAR NewAnsiPassword[ENCRYPTED_PWLEN];

#endif


    //
    // Reel in some easy errors before they get far.
    //

    if ((STRLEN(UserName) > LM20_UNLEN)
        || (STRLEN(OldPassword) > LM20_PWLEN)
        || (STRLEN(NewPassword) > LM20_PWLEN)) {
        return ERROR_INVALID_PARAMETER;
    }

    //
    // The passwords are sent in 16-byte ANSI buffers,
    // so convert them from Unicode to multibyte.
    //

#ifndef DOWN_LEVEL_ENCRYPTION

    //
    // this required because we always send fixed size char buffers, not strings
    //

    RtlZeroMemory(OldAnsiPassword, sizeof(OldAnsiPassword));
    RtlZeroMemory(NewAnsiPassword, sizeof(NewAnsiPassword));

#endif

    RtlUnicodeToMultiByteN(
        OldAnsiPassword,
        sizeof(OldAnsiPassword),
        &BytesWritten,
        OldPassword,
        wcslen(OldPassword) * sizeof(WCHAR)
        );
    OldAnsiPassword[BytesWritten] = 0;

    RtlUnicodeToMultiByteN(
        NewAnsiPassword,
        sizeof(NewAnsiPassword),
        &BytesWritten,
        NewPassword,
        wcslen(NewPassword) * sizeof(WCHAR)
        );
    NewAnsiPassword[BytesWritten] = 0;

    //
    // twould seem that down-level servers require passwords to be in upper
    // case (ie canonicalized) when they are decrypted. Same applies for
    // cleartext
    //

    (VOID) _strupr(OldAnsiPassword);
    (VOID) _strupr(NewAnsiPassword);

#ifdef DOWN_LEVEL_ENCRYPTION

    //
    // Calculate the one-way functions of the passwords.
    //

    Status = RtlCalculateLmOwfPassword(OldAnsiPassword, &OldOwfPassword);
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }
    Status = RtlCalculateLmOwfPassword(NewAnsiPassword, &NewOwfPassword);
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }

    //
    // Cross-encrypt the passwords.
    //

    Status = RtlEncryptLmOwfPwdWithLmOwfPwd(&OldOwfPassword,
                                            &NewOwfPassword,
                                            &OldEncryptedWithNew
                                            );
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }
    Status = RtlEncryptLmOwfPwdWithLmOwfPwd(&NewOwfPassword,
                                            &OldOwfPassword,
                                            &NewEncryptedWithOld
                                            );
    if (!NT_SUCCESS(Status)) {
        goto Cleanup;
    }

#else

    //
    // Status hasn't been initialized, but is tested below to determine if we
    // should pick up the NetStatus from Status.
    //

    Status = STATUS_SUCCESS;

#endif  // DOWN_LEVEL_ENCRYPTION

TryTheEncryptedApi:

    NetStatus = RxRemoteApi(API_WUserPasswordSet2,
                            ServerName,
                            REMSmb_NetUserPasswordSet2_P,
                            NULL, NULL, NULL,           // no data - just parms
                            NULL, NULL, NULL,           // no aux data
                            (TryNullSession ? NO_PERMISSION_REQUIRED : 0),
                            UserName,                   // parameters...

#ifdef DOWN_LEVEL_ENCRYPTION

                            &OldEncryptedWithNew,
                            &NewEncryptedWithOld,
                            TRUE,                       // data encrypted?

#else

                            OldAnsiPassword,
                            NewAnsiPassword,
                            FALSE,                      // passwords not encrypted

#endif

                            strlen(OldAnsiPassword)
                            );

        //
        // LarryO says null session might have wrong credentials, so we
        // should retry with non-null session.
        //

        if ( TryNullSession && (Status == ERROR_SESSION_CREDENTIAL_CONFLICT) ) {

            TryNullSession = FALSE;
            goto TryTheEncryptedApi;     // retry this one.
        }


    //
    // If the encrypted attempt fails with NERR_InvalidAPI, try plaintext
    //

    if (NetStatus == NERR_InvalidAPI) {

TryThePlainTextApi:

        TryNullSession = TRUE;           // Try null session first.

        NetStatus = RxRemoteApi(API_WUserPasswordSet,
                                ServerName,
                                REMSmb_NetUserPasswordSet_P,
                                NULL, NULL, NULL,           // no data - just parms
                                NULL, NULL, NULL,           // no aux data
                                (TryNullSession ? NO_PERMISSION_REQUIRED : 0),
                                UserName,                   // parameters...
                                OldAnsiPassword,
                                NewAnsiPassword,
                                FALSE                       // data encrypted?
                                );
        //
        // LarryO says null session might have wrong credentials, so we
        // should retry with non-null session.
        //

        if ( TryNullSession && (Status == ERROR_SESSION_CREDENTIAL_CONFLICT) ) {

            TryNullSession = FALSE;
            goto TryThePlainTextApi;     // retry this one.
        }
    }

#ifdef DOWN_LEVEL_ENCRYPTION

    Cleanup:

#endif

    if (!NT_SUCCESS(Status)) {
        NetStatus = RtlNtStatusToDosError(Status);
    }

    return NetStatus;
}


NET_API_STATUS
RxNetUserSetGroups(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName,
    IN  DWORD   Level,
    IN  LPBYTE  Buffer,
    IN  DWORD   Entries
    )

/*++

Routine Description:

    Makes a user a member of the listed groups. This routine is virtually
    identical to RxNetGroupSetUsers and most of the code was lifted from there

Arguments:

    ServerName  - where to run the API
    UserName    - which user to include
    Level       - Must Be Zero (MBZ)
    Buffer      - pointer to buffer containing a list of GROUP_INFO_0 structures
    Entries     - number of GROUP_INFO_0 structures in Buffer

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_LEVEL
                  ERROR_INVALID_PARAMETER

--*/

{
    NET_API_STATUS  rc;
    LPGROUP_INFO_0  group_info;
    DWORD   i;
    DWORD   buflen;
    LPBYTE  newbuf;

    //
    // BUGBUG - make the enumerator descriptors and structures common and
    //          usable by this code and also the RxNetGroupSetUsers routine
    //

    static  LPDESC  group_0_enumerator_desc16 = "B21BN";    // same as UNLEN
    static  LPDESC  group_0_enumerator_desc32 = "zQA";

    //
    // This structure is required because the remoting code (particularly down
    // level) can only handle there being >1 auxiliary structure, vs >1
    // primary. Hence we have to convert the caller's supplied buffer of
    // erstwhile primary structures to auxiliaries by forcing the structure
    // below in at the head of the buffer, hence becoming the primary and
    // providing an aux structure count (groan)
    //

    struct group_0_enumerator {
        LPTSTR  user_name;      // which user to set groups for
        DWORD   group_count;    // number of GROUP_INFO_0 structures in buffer
    };

    //
    // BUGBUG - put these checks in DLL stub?
    //

    if (Level) {
        return ERROR_INVALID_LEVEL; // MBZ, remember?
    }

    if (STRLEN(UserName) > LM20_UNLEN) {
        return ERROR_INVALID_PARAMETER;
    }

    //
    // iterate through the buffer, checking that each GROUP_INFO_0
    // structure contains a pointer to a valid string which is in the
    // correct range
    //

    group_info = (LPGROUP_INFO_0)Buffer;
    for (i=0; i<Entries; ++i) {
        if (!VALID_STRING(group_info->grpi0_name)) {
            return ERROR_INVALID_PARAMETER;
        }
        if (wcslen(group_info->grpi0_name) > LM20_GNLEN) {
            return ERROR_INVALID_PARAMETER;
        }
        ++group_info;
    }

    //
    // allocate a buffer large enough to fit in <Entries> number of
    // GROUP_INFO_0 structures, and 1 group_0_enumerator structure.
    // Don't worry about string space - unfortunately the Rxp and Rap routines
    // called by RxRemoteApi will allocate yet another buffer, do yet another
    // copy and this time copy in the strings from user space. Hopefully, this
    // routine won't get called too often
    //

    buflen = Entries * sizeof(GROUP_INFO_0) + sizeof(struct group_0_enumerator);
    buflen = DWORD_ROUNDUP(buflen);
    if (rc = NetApiBufferAllocate(buflen, (LPVOID *) &newbuf)) {
        return rc;  // aieegh! Failed to allocate memory?
    }

    ((struct group_0_enumerator*)newbuf)->user_name = UserName;
    ((struct group_0_enumerator*)newbuf)->group_count = Entries;

    if (Entries > 0) {
        // Append the group entries to the header we just built.
        NetpMoveMemory(
                newbuf + sizeof(struct group_0_enumerator),  // dest
                Buffer,                                      // src
                buflen - sizeof(struct group_0_enumerator)); // byte count
    }

    rc = RxRemoteApi(API_WUserSetGroups,
                    ServerName,
                    REMSmb_NetUserSetGroups_P,
                    group_0_enumerator_desc16,  // the "fudged" 16-bit data descriptor
                    group_0_enumerator_desc32,  // the "fudged" 32-bit data descriptor
                    group_0_enumerator_desc16,  // SMB desc same as 16-bit
                    REM16_group_info_0,         // "new" 16-bit aux descriptor
                    REM32_group_info_0,         // "new" 32-bit aux descriptor
                    REMSmb_group_info_0,        // SMB aux descriptor
                    FALSE,                      // this API requires user security
                    UserName,                   // parm 1
                    0,                          // info level must be 0
                    newbuf,                     // "fudged" buffer
                    buflen,                     // length of "fudged" buffer
                    Entries                     // number of GROUP_USERS_INFO_0
                    );
    NetpMemoryFree(newbuf);
    return rc;
}


NET_API_STATUS
RxNetUserSetInfo(
    IN  LPTSTR  ServerName,
    IN  LPTSTR  UserName,
    IN  DWORD   Level,
    IN  LPBYTE  Buffer,
    OUT LPDWORD ParmError OPTIONAL
    )

/*++

Routine Description:

    Sets information in a user account in a down-level UAS database

    Assumes:
        1.  UserName is a valid pointer to a valid string,
            Level is in the range below,
            Buffer is a valid pointer
            ParmError is a valid pointer

Arguments:

    ServerName  - where to run the API
    UserName    - which user to change info for
    Level       - of info supplied - 1-2, 1003, 1005-1014, 1017-1018, 1020, 1023-1025
    Buffer      - if PARMNUM_ALL, pointer to buffer containing info,
                  else pointer to pointer to buffer containing info
    ParmError   - which parameter was bad

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - ERROR_INVALID_LEVEL
                  ERROR_INVALID_PARAMETER

--*/

{
    DWORD   parmnum;
    DWORD   badparm;
    DWORD   buflen;
    DWORD   stringlen;
    LPWSTR  pointer;    // general pointer to string for range checking
    LPDESC  pDesc16;
    LPDESC  pDesc32;
    LPDESC  pDescSmb;
    DWORD   passwordEncrypted = FALSE;
    DWORD   originalPasswordLength = 0;
    CHAR    ansiPassword[LM20_PWLEN+1];
    DWORD   lmOwfPasswordLen;
    LPTSTR  cleartext;
    LPTSTR* lpClearText;
    NET_API_STATUS NetStatus;

#ifdef DOWN_LEVEL_ENCRYPTION

    LM_OWF_PASSWORD lmOwfPassword;
    LM_SESSION_KEY lanmanKey;
    ENCRYPTED_LM_OWF_PASSWORD encryptedLmOwfPassword;
    NTSTATUS Status;

#endif

    BYTE logonHours[21];
    PBYTE callersLogonHours = NULL;
    PBYTE* lpCallersLogonHours;

    WCHAR Workstations[MAX_WORKSTATION_LIST+1];
    LPWSTR callersWorkstations = NULL;
    LPWSTR *lpCallersWorkstations;

    if (ParmError == NULL) {
        ParmError = &badparm;
    }
    *ParmError = PARM_ERROR_NONE;

    if (STRLEN(UserName) > LM20_UNLEN) {
        NetStatus = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    }

    //
    // throw out invalid level parameter
    //

    if ((Level > 2 && Level < USER_PASSWORD_INFOLEVEL)

        // 2 < Level < 1003

    || (Level > USER_PASSWORD_INFOLEVEL && Level < USER_PRIV_INFOLEVEL)

        // 1003 < Level < 1005  : Check compiler generates == 1004

    || (Level > USER_WORKSTATIONS_INFOLEVEL && Level < USER_ACCT_EXPIRES_INFOLEVEL)

        // 1014 < Level < 1017

    || (Level > USER_MAX_STORAGE_INFOLEVEL && Level < USER_LOGON_HOURS_INFOLEVEL)

        // 1018 < Level < 1020  : Check compiler generates == 1019

    || (Level > USER_LOGON_HOURS_INFOLEVEL && Level < USER_LOGON_SERVER_INFOLEVEL)

        // 1020 < Level < 1023

    || (Level > USER_CODE_PAGE_INFOLEVEL)) {

        // Level < 1025

        NetStatus = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    //
    // default to Level 2 for the descriptors (Level 2 works for level 1 also)
    //

    pDesc16 = REM16_user_info_2;
    pDesc32 = REM32_user_info_2;
    pDescSmb = REMSmb_user_info_2;

    if (Level < PARMNUM_BASE_INFOLEVEL) {
        parmnum = PARMNUM_ALL;
        if (Level == 1) {
            pDesc16 = REM16_user_info_1;
            pDesc32 = REM32_user_info_1;
            pDescSmb = REMSmb_user_info_1;
            buflen = sizeof(USER_INFO_1);
        } else {

            //
            // BUGBUG - is there a manifest for this 21?
            //

            buflen = sizeof(USER_INFO_2) + 21;
        }
    } else {
        parmnum = Level - PARMNUM_BASE_INFOLEVEL;

        //
        // Because info level 1 is a subset of info level 2, setting the level
        // to 2 is ok for those parmnums which can be set at level 1 AND 2.
        // Set pointer = Buffer so that in the parmnum != PARMNUM_ALL case, we
        // just check the length of whatever pointer points at
        //

        Level = 2;
        pointer = *(LPWSTR*) Buffer;
        buflen = 0;
    }

    if (parmnum == PARMNUM_ALL) {
        if (pointer = ((PUSER_INFO_1)(LPVOID)Buffer)->usri1_name) {
            if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_UNLEN) {
                *ParmError = USER_NAME_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);
        }
    }

    //
    // BUGBUG - what about NULL pointers here????
    //

    if ((parmnum == PARMNUM_ALL) || (parmnum == USER_PASSWORD_PARMNUM)) {
        if (parmnum == PARMNUM_ALL) {
            pointer = ((PUSER_INFO_1)Buffer)->usri1_password;
        }
        if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_PWLEN) {
            *ParmError = USER_PASSWORD_PARMNUM;
            NetStatus = ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
        buflen += STRING_SPACE_REQD(stringlen + 1);

        //
        // original password length is length of unencrypted string in
        // characters, excluding terminating NUL
        //

        originalPasswordLength = stringlen;

        //
        // lpClearText is address of pointer to cleartext password
        //

        lpClearText = (parmnum == PARMNUM_ALL)
                        ? (LPTSTR*)&((PUSER_INFO_1)Buffer)->usri1_password
                        : (LPTSTR*)Buffer;

        //
        // copy the cleartext password out of the buffer - we will replace it with
        // the encrypted version, but need to put the cleartext back before
        // returning control to the caller
        //

        cleartext = *lpClearText;
    }

    if ((parmnum == PARMNUM_ALL) || (parmnum == USER_HOME_DIR_PARMNUM)) {
        if (parmnum == PARMNUM_ALL) {
            pointer = ((PUSER_INFO_1)Buffer)->usri1_home_dir;
        }
        if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_PATHLEN) {
            *ParmError = USER_HOME_DIR_PARMNUM;
            NetStatus = ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
        buflen += STRING_SPACE_REQD(stringlen + 1);
    }

    if ((parmnum == PARMNUM_ALL) || (parmnum == USER_COMMENT_PARMNUM)) {
        if (parmnum == PARMNUM_ALL) {
            pointer = ((PUSER_INFO_1)Buffer)->usri1_comment;
        }
        if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_MAXCOMMENTSZ) {
            *ParmError = USER_COMMENT_PARMNUM;
            NetStatus = ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
        buflen += STRING_SPACE_REQD(stringlen + 1);
    }

    if ((parmnum == PARMNUM_ALL) || (parmnum == USER_SCRIPT_PATH_PARMNUM)) {
        if (parmnum == PARMNUM_ALL) {
            pointer = ((PUSER_INFO_1)Buffer)->usri1_script_path;
        }
        if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_PATHLEN) {
            *ParmError = USER_SCRIPT_PATH_PARMNUM;
            NetStatus = ERROR_INVALID_PARAMETER;
            goto Cleanup;
        }
        buflen += STRING_SPACE_REQD(stringlen + 1);
    }

    //
    // the next set of checks only need to be done if we are setting PARMNUM_ALL
    // with a Level of 2 or if the parmnum implicitly requires Level 2 (ie parms
    // >= 10)
    //

    if (Level == 2) {
        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_FULL_NAME_PARMNUM)) {
            if (parmnum == PARMNUM_ALL) {
                pointer = ((PUSER_INFO_2)Buffer)->usri2_full_name;
            }
            if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_FULL_NAME_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);
        }

        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_USR_COMMENT_PARMNUM)) {
            if (parmnum == PARMNUM_ALL) {
                pointer = ((PUSER_INFO_2)Buffer)->usri2_usr_comment;
            }
            if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_USR_COMMENT_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);
        }

        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_PARMS_PARMNUM)) {
            if (parmnum == PARMNUM_ALL) {
                pointer = ((PUSER_INFO_2)Buffer)->usri2_parms;
            }
            if ((stringlen = POSSIBLE_WCSLEN(pointer)) > LM20_MAXCOMMENTSZ) {
                *ParmError = USER_PARMS_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);
        }

        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_WORKSTATIONS_PARMNUM)) {
            UNICODE_STRING WorkstationString;
            if (parmnum == PARMNUM_ALL) {
                lpCallersWorkstations = &((PUSER_INFO_2)Buffer)->usri2_workstations;
            } else {
                lpCallersWorkstations = &((PUSER_INFO_1014)Buffer)->usri1014_workstations;
            }
            callersWorkstations = *lpCallersWorkstations;

            //
            // BUGBUG - get the correct value/manifest for this field
            //

            if ((stringlen = POSSIBLE_WCSLEN(callersWorkstations)) > MAX_WORKSTATION_LIST) {
                *ParmError = USER_WORKSTATIONS_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);

            //
            // Convert the list of workstations from being comma separated
            //  to being space separated.  Ditch workstation names containing
            //  spaces.

            if ( callersWorkstations != NULL ) {
                wcscpy( Workstations, callersWorkstations );
                RtlInitUnicodeString( &WorkstationString, Workstations );
                NetpConvertWorkstationList( &WorkstationString );
                *lpCallersWorkstations = Workstations;
            }

        }

        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_LOGON_SERVER_PARMNUM)) {
            if (parmnum == PARMNUM_ALL) {
                pointer = ((PUSER_INFO_2)Buffer)->usri2_logon_server;
            }
            if ((stringlen = POSSIBLE_WCSLEN(pointer)) > MAX_PATH) {
                *ParmError = USER_LOGON_SERVER_PARMNUM;
                NetStatus = ERROR_INVALID_PARAMETER;
                goto Cleanup;
            }
            buflen += STRING_SPACE_REQD(stringlen + 1);
        }


        //
        // if the caller is setting the logon hours then we need to substitute
        // shuffled bits for the logon hours bitmap
        //

        if ((parmnum == PARMNUM_ALL) || (parmnum == USER_LOGON_HOURS_PARMNUM)) {
            if (parmnum == PARMNUM_ALL) {
                lpCallersLogonHours = (PBYTE*)&((PUSER_INFO_2)Buffer)->usri2_logon_hours;
            } else {
                lpCallersLogonHours = (PBYTE*)&((PUSER_INFO_1020)Buffer)->usri1020_logon_hours;
            }
            callersLogonHours = *lpCallersLogonHours;
            RtlCopyMemory(logonHours, callersLogonHours, sizeof(logonHours));

            //
            // shuffle the bitmap and point the logon_hours field in the structure
            // at the shuffled version
            //

            NetpRotateLogonHours(logonHours, UNITS_PER_WEEK, FALSE);
            *lpCallersLogonHours = logonHours;
        }
    }

    //
    // we have covered all the parameters that we are able to from this end. The
    // down-level APIs don't know about the ParmError concept (it is, after all,
    // a highly developed notion, too highbrow for the LanManDerthals...) so if
    // we get back an ERROR_INVALID_PARAMETER, the caller will just have to be
    // content with PARM_ERROR_UNKNOWN, and try to figure it out from there
    //

    *ParmError = PARM_ERROR_UNKNOWN;

    //
    // if originalPasswordLength is non-zero then we must be supplying a password;
    // perform the encryption machinations
    //

    if (originalPasswordLength) {

        //
        // Calculate the one-way function of the password
        //

        RtlUnicodeToMultiByteN(ansiPassword,
                                sizeof(ansiPassword),
                                &lmOwfPasswordLen,
                                *lpClearText,
                                originalPasswordLength * sizeof(WCHAR)
                                );
        ansiPassword[lmOwfPasswordLen] = 0;
        (VOID) _strupr(ansiPassword);   // down-level wants upper-cased passwords

#ifdef DOWN_LEVEL_ENCRYPTION

        Status = RtlCalculateLmOwfPassword(ansiPassword, &lmOwfPassword);
        if (NT_SUCCESS(Status)) {
            NetStatus = GetLanmanSessionKey((LPWSTR)ServerName, (LPBYTE)&lanmanKey);
            if (NetStatus == NERR_Success) {
                Status = RtlEncryptLmOwfPwdWithLmSesKey(&lmOwfPassword,
                                                        &lanmanKey,
                                                        &encryptedLmOwfPassword
                                                        );
                if (NT_SUCCESS(Status)) {
                    *lpClearText = (LPTSTR)&encryptedLmOwfPassword;
                    passwordEncrypted = TRUE;
                    if (parmnum == USER_PASSWORD_PARMNUM) {
                        buflen = sizeof(encryptedLmOwfPassword);
                    }
                }
            }
        }
        if (NetStatus != NERR_Success) {
            goto Cleanup;
        }
        else if (!NT_SUCCESS(Status)) {
            NetStatus = RtlNtStatusToDosError(Status);
            goto Cleanup;
        }

#else

        *lpClearText = (LPTSTR)ansiPassword;

#endif

    }

    //
    // New! Improved! Now, even better, RxNetUserSetInfo will use SetInfo2
    // to fix the most stubborn user set info problems (ie password)
    //

    NetStatus = RxRemoteApi(API_WUserSetInfo2,
                        ServerName,
                        REMSmb_NetUserSetInfo2_P,
                        pDesc16, pDesc32, pDescSmb, // data descriptors
                        NULL, NULL, NULL,           // no aux data
                        FALSE,                      // must be logged on
                        UserName,                   // parameters...
                        Level,

                        //
                        // if we are sending the whole structure, then Buffer
                        // points to the structure, else Buffer points to a
                        // pointer to the field to set; RxRemoteApi expects
                        // a pointer to the data
                        //

                        parmnum == PARMNUM_ALL || parmnum == USER_PASSWORD_PARMNUM
                            ? Buffer
                            : *(LPBYTE*)Buffer,
                        buflen,                     // supplied by us

                        //
                        // in this case, the field index and parm num are the
                        // same value
                        //

                        MAKE_PARMNUM_PAIR(parmnum, parmnum),

                        //
                        // add those extraneous WWs: whether the data is
                        // encrypted and the original password length. (By
                        // deduction: password is the only data that is
                        // encrypted)
                        //

                        passwordEncrypted,
                        originalPasswordLength
                        );

Cleanup:
    //
    // copy the original password back to the user's buffer if we set a password
    //

    if (originalPasswordLength) {
        *lpClearText = cleartext;
    }

    //
    // restore the original logon hours string
    //

    if (callersLogonHours) {
        *lpCallersLogonHours = callersLogonHours;
    }

    //
    // restore the original workstation list
    //

    if ( callersWorkstations != NULL) {
        *lpCallersWorkstations = callersWorkstations;
    }
    return NetStatus;
}


//NET_API_STATUS
//RxNetUserValidate2
//    /** CANNOT BE REMOTED **/
//{
//
//}


DBGSTATIC
NET_API_STATUS
GetUserDescriptors(
    IN  DWORD   Level,
    IN  BOOL    Encrypted,
    OUT LPDESC* ppDesc16,
    OUT LPDESC* ppDesc32,
    OUT LPDESC* ppDescSmb
    )

/*++

Routine Description:

    Returns pointers to descriptor strings for user info structures based on
    level of info required for RxNetUser routines

Arguments:

    Level       - of info being requested
    Encrypted   - TRUE if info structure contains encrypted password
    ppDesc16    - where to return pointer to 16-bit data descriptor
    ppDesc32    - where to return pointer to 32-bit data descriptor
    ppDescSmb   - where to return pointer to SMB data descriptor

Return Value:

    ERROR_INVALID_LEVEL - If the level was not in the list.

    NO_ERROR - If the operation was successful.

--*/

{
    switch (Level) {
    case 0:
        *ppDesc16 = REM16_user_info_0;
        *ppDesc32 = REM32_user_info_0;
        *ppDescSmb = REMSmb_user_info_0;
        break;

    case 1:
        *ppDesc16 = REM16_user_info_1;
        *ppDesc32 = Encrypted ? REM32_user_info_1 : REM32_user_info_1_NOCRYPT;
        *ppDescSmb = REMSmb_user_info_1;
        break;

    case 2:
        *ppDesc16 = REM16_user_info_2;
        *ppDesc32 = Encrypted ? REM32_user_info_2 : REM32_user_info_2_NOCRYPT;
        *ppDescSmb = REMSmb_user_info_2;
        break;

    case 10:
        *ppDesc16 = REM16_user_info_10;
        *ppDesc32 = REM32_user_info_10;
        *ppDescSmb = REMSmb_user_info_10;
        break;

    case 11:
        *ppDesc16 = REM16_user_info_11;
        *ppDesc32 = REM32_user_info_11;
        *ppDescSmb = REMSmb_user_info_11;
        break;

    default:
        return(ERROR_INVALID_LEVEL);
    }
    return(NO_ERROR);
}


DBGSTATIC
VOID
GetModalsDescriptors(
    IN  DWORD   Level,
    OUT LPDESC* ppDesc16,
    OUT LPDESC* ppDesc32,
    OUT LPDESC* ppDescSmb
    )

/*++

Routine Description:

    Returns pointers to descriptor strings for modals info structures based on
    level of info required for RxNetUserModals routines

Arguments:

    Level       - of info being requested
    ppDesc16    - where to return pointer to 16-bit data descriptor
    ppDesc32    - where to return pointer to 32-bit data descriptor
    ppDescSmb   - where to return pointer to SMB data descriptor

Return Value:

    None.

--*/

{
    switch (Level) {
    case 0:
        *ppDesc16 = REM16_user_modals_info_0;
        *ppDesc32 = REM32_user_modals_info_0;
        *ppDescSmb = REMSmb_user_modals_info_0;
        break;

    case 1:
        *ppDesc16 = REM16_user_modals_info_1;
        *ppDesc32 = REM32_user_modals_info_1;
        *ppDescSmb = REMSmb_user_modals_info_1;
        break;
    }
}


NET_API_STATUS
GetLanmanSessionKey(
    IN LPWSTR ServerName,
    OUT LPBYTE pSessionKey
    )

/*++

Routine Description:

    Retrieves the LM session key for the connection from the redir FSD

Arguments:

    ServerName  - name of server to get session key for
    pSessionKey - pointer to where session key will be deposited

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure -

--*/

{
    NTSTATUS ntStatus;
    HANDLE hToken;
    TOKEN_STATISTICS stats;
    ULONG length;
    LMR_REQUEST_PACKET request;
    LMR_CONNECTION_INFO_2 connectInfo;
    NET_API_STATUS apiStatus;
    WCHAR connectionName[MAX_PATH];

    ntStatus = NtOpenProcessToken(NtCurrentProcess(), GENERIC_READ, &hToken);
    if (NT_SUCCESS(ntStatus)) {

        //
        // Get the logon id of the current thread
        //

        ntStatus = NtQueryInformationToken(hToken,
                                            TokenStatistics,
                                            (PVOID)&stats,
                                            sizeof(stats),
                                            &length
                                            );
        if (NT_SUCCESS(ntStatus)) {

            RtlCopyLuid(&request.LogonId, &stats.AuthenticationId);
            request.Type = GetConnectionInfo;
            request.Version = REQUEST_PACKET_VERSION;
            request.Level = 2;

            wcscpy(connectionName, ServerName);
            wcscat(connectionName, L"\\IPC$");

            apiStatus = NetpRdrFsControlTree(connectionName,
                                                NULL,
                                                USE_WILDCARD,
                                                FSCTL_LMR_GET_CONNECTION_INFO,
                                                NULL,
                                                (LPVOID)&request,
                                                sizeof(request),
                                                (LPVOID)&connectInfo,
                                                sizeof(connectInfo),
                                                FALSE
                                                );
            if (apiStatus == NERR_Success) {
                RtlMoveMemory(pSessionKey,
                                &connectInfo.LanmanSessionKey,
                                sizeof(connectInfo.LanmanSessionKey)
                                );
            }
        }
        NtClose(hToken);
    }
    if (!NT_SUCCESS(ntStatus)) {
        apiStatus = NetpNtStatusToApiStatus(ntStatus);
    }
    return apiStatus;
}
