/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    vrnetapi.c

Abstract:

    This module contains routines which support Vdm Redir lanman APIs and
    private functions:

        VrGetCDNames
        VrGetComputerName
        VrGetDomainName
        VrGetLogonServer
        VrGetUserName
        VrNetGetDCName
        VrNetMessageBufferSend
        VrNetNullTransactApi
        VrNetRemoteApi
        OemToUppercaseUnicode
        VrNetServiceControl
        VrNetServiceEnum
        VrNetServerEnum
        VrNetTransactApi
        VrNetUseAdd
        VrNetUseDel
        VrNetUseEnum
        VrNetUseGetInfo
        VrNetWkstaGetInfo
        (DumpWkstaInfo)
        VrNetWkstaSetInfo
        VrReturnAssignMode
        VrSetAssignMode
        VrGetAssignListEntry
        VrDefineMacro
        VrBreakMacro
        (VrpTransactVdm)
        EncryptPassword

Author:

    Richard L Firth (rfirth) 21-Oct-1991

Revision History:

    21-Oct-1991 rfirth
        Created

    02-May-1994 rfirth
        Upped password limit (& path length limit) to LM20_ values for
        VrDefineMacro

--*/

#include <nt.h>
#include <ntrtl.h>      // ASSERT, DbgPrint
#include <nturtl.h>
#include <windows.h>
#include <softpc.h>     // x86 virtual machine definitions
#include <vrdlctab.h>
#include <vdmredir.h>   // common Vdm Redir stuff
#include <lmcons.h>     // LM20_PATHLEN
#include <lmerr.h>      // lan manager error codes
#include <lmuse.h>      // USE_LOTS_OF_FORCE
#include <lmwksta.h>    // NetWkstaGetInfo
#include <lmserver.h>   // SV_TYPE_ALL
#include <lmapibuf.h>   // NetApiBufferFree
#include <vrnetapi.h>   // prototypes
#include <vrremote.h>   // VrRemoteApi prototypes
#include <packon.h>     // structures in apistruc.h are not dword-only
#include <apistruc.h>   // tr_packet
#include <packoff.h>    // switch back on structure packing
#include <apinums.h>    // remotable API numbers
#include <remdef.h>     // remotable API descriptors
#include <remtypes.h>   // remotable API descriptor characters
#include <rxp.h>        // RxpTransactSmb
#include <apiparam.h>   // XS_NET_USE_ADD
#include <xstypes.h>    // XS_PARAMETER_HEADER
#include <xsprocs.h>    // XsNetUseAdd etc
#include <string.h>     // Dos still dealing with ASCII
#include <netlibnt.h>   // NetpNtStatusToApiStatus()
#include "vrputil.h"    // VrpMapDosError()
#include "vrdebug.h"    // VrDebugFlags etc
#include "dlstruct.h"   // down-level structures
#include <rxuser.h>     // RxNetUser...
#include <lmaccess.h>   // USER_PASSWORD_PARMNUM
#include <crypt.h>      // Needed by NetUserPasswordSet

//
// private routine prototypes
//

#if DBG

VOID
DumpWkstaInfo(
    IN DWORD Level,
    IN LPBYTE Buffer
    );

#endif

NET_API_STATUS
VrpTransactVdm(
    IN  BOOL    NullSessionFlag
    );

#if DBG
PRIVATE
VOID
DumpTransactionPacket(
    IN  struct tr_packet* TransactionPacket,
    IN  BOOL    IsInput,
    IN  BOOL    DumpData
    );
#endif

//
// external functions
//

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

//
// internal functions (not necessarily private)
//

BOOL
OemToUppercaseUnicode(
    IN LPSTR AnsiStringPointer,
    OUT LPWSTR UnicodeStringPointer,
    IN DWORD MaxLength
    );

BOOL
EncryptPassword(
    IN LPWSTR ServerName,
    IN OUT LPBYTE Password
    );


//
// public routines
//

//
// net APIs now unicode
//

#define NET_UNICODE

VOID
VrGetCDNames(
    VOID
    )

/*++

Routine Description:

    Performs the private redir function to get the computer and domain names.
    These are usually stored in the redir after they are read out of lanman.ini

    NOTE:

        This code assumes that the pointers are valid and point to valid,
        writable memory which has enough reserved space for the types of
        strings to be written

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory
    The dos redir gets passed a buffer in es:di which contains 3 far pointers
    to:
        place to store computer name
        place to store primary domain controller name
        place to store logon domain name

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

    Note that in the dos redir function, there is no return code, so if this
    routine fails the results will be unpredictable

--*/

{
    struct I_CDNames* structurePointer;
    LPSTR stringPointer;
    LPWSTR infoString;
    NET_API_STATUS rc1;
    NET_API_STATUS rc2;
    ANSI_STRING ansiString;
    UNICODE_STRING unicodeString;
    LPWKSTA_INFO_100 wkstaInfo = NULL;
    LPWKSTA_USER_INFO_1 userInfo = NULL;
    CHAR ansiBuf[LM20_CNLEN+1];
    NTSTATUS status;
    register DWORD len;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetCDNames\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    rc1 = NetWkstaGetInfo(NULL, 100, (LPBYTE*)&wkstaInfo);
    rc2 = NetWkstaUserGetInfo(0, 1, (LPBYTE*)&userInfo);

    ansiString.MaximumLength = sizeof(ansiBuf);
    ansiString.Length = 0;
    ansiString.Buffer = ansiBuf;

    structurePointer = (struct I_CDNames*)POINTER_FROM_WORDS(getES(), getDI());
    stringPointer = POINTER_FROM_POINTER(&structurePointer->CDN_pszComputer);
    if (stringPointer) {
        *stringPointer = 0;
        if (rc1 == NERR_Success) {
            infoString = (LPWSTR)wkstaInfo->wki100_computername;
            len = wcslen(infoString);
            if (len <= LM20_CNLEN) {
                RtlInitUnicodeString(&unicodeString, infoString);
                status = RtlUnicodeStringToAnsiString(&ansiString, &unicodeString, FALSE);
                if (NT_SUCCESS(status)) {
                    RtlCopyMemory(stringPointer, ansiBuf, len+1);
                    _strupr(stringPointer);
                }
            }

        }
    }

    stringPointer = POINTER_FROM_POINTER(&structurePointer->CDN_pszPrimaryDomain);
    if (stringPointer) {
        *stringPointer = 0;
        if (rc1 == NERR_Success) {
            infoString = (LPWSTR)wkstaInfo->wki100_langroup;
            len = wcslen(infoString);
            if (len <= LM20_CNLEN) {
                RtlInitUnicodeString(&unicodeString, infoString);
                status = RtlUnicodeStringToAnsiString(&ansiString, &unicodeString, FALSE);
                if (NT_SUCCESS(status)) {
                    RtlCopyMemory(stringPointer, ansiBuf, len+1);
                    _strupr(stringPointer);
                }
            }
        }
    }

    stringPointer = POINTER_FROM_POINTER(&structurePointer->CDN_pszLogonDomain);
    if (stringPointer) {
        *stringPointer = 0;
        if (rc2 == NERR_Success) {
            infoString = (LPWSTR)userInfo->wkui1_logon_domain;
            len = wcslen(infoString);
            if (len <= LM20_CNLEN) {
                RtlInitUnicodeString(&unicodeString, infoString);
                status = RtlUnicodeStringToAnsiString(&ansiString, &unicodeString, FALSE);
                if (NT_SUCCESS(status)) {
                    RtlCopyMemory(stringPointer, ansiBuf, len+1);
                    _strupr(stringPointer);
                }
            }

        }
    }

    if (wkstaInfo) {
        NetApiBufferFree((LPVOID)wkstaInfo);
    }
    if (userInfo) {
        NetApiBufferFree((LPVOID)userInfo);
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetCDNames: computername=%s, PDCname=%s, logon domain=%s\n\n",
                POINTER_FROM_POINTER(&structurePointer->CDN_pszComputer)
                    ? POINTER_FROM_POINTER(&structurePointer->CDN_pszComputer)
                    : "",
                POINTER_FROM_POINTER(&structurePointer->CDN_pszPrimaryDomain)
                    ? POINTER_FROM_POINTER(&structurePointer->CDN_pszPrimaryDomain)
                    : "",
                POINTER_FROM_POINTER(&structurePointer->CDN_pszLogonDomain)
                    ? POINTER_FROM_POINTER(&structurePointer->CDN_pszLogonDomain)
                    : ""
                );
    }
#endif
}


VOID
VrGetComputerName(
    VOID
    )

/*++

Routine Description:

    Performs the private redir function to return the computer name stored in
    the redir

Arguments:

    ENTRY   ES:DI = buffer to copy computer name into

Return Value:

    None.

--*/

{
    BOOL    ok;
    CHAR    nameBuf[MAX_COMPUTERNAME_LENGTH+1];
    DWORD   nameLen;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetComputerName\n");
    }
#endif

    nameLen = sizeof(nameBuf)-1;
    ok = GetComputerName(nameBuf, &nameLen);
    if (!ok) {
        SET_ERROR(ERROR_NOT_SUPPORTED);
    } else {
        if (nameLen > LM20_CNLEN) {
            SET_ERROR(NERR_BufTooSmall);
#if DBG
            IF_DEBUG(NETAPI) {
                DbgPrint("VrGetComputerName returning ERROR %d!\n", getAX());
            }
#endif
        } else {
            strcpy(LPSTR_FROM_WORDS(getES(), getDI()), nameBuf);
            setAX(0);
            setCF(0);
#if DBG
            IF_DEBUG(NETAPI) {
                DbgPrint("VrGetComputerName returning %s\n", nameBuf);
            }
#endif
        }
    }
}


VOID
VrGetDomainName(
    VOID
    )

/*++

Routine Description:

    Performs the private redir function to return the primary domain name.
    This info is stored in the redir after being read from lanman.ini at
    configuration time

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetDomainName\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetDomainName - unsupported SVC\n");
    }
#endif

    SET_ERROR(ERROR_NOT_SUPPORTED);
}


VOID
VrGetLogonServer(
    VOID
    )

/*++

Routine Description:

    Performs the private redir function to return the name of the computer
    which logged this user onto the network

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetLogonServer\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetLogonServer - unsupported SVC\n");
    }
#endif

    SET_ERROR(ERROR_NOT_SUPPORTED);
}


VOID
VrGetUserName(
    VOID
    )

/*++

Routine Description:

    Performs the private redir function to return the logged on user name
    which is normally stored in the redir

Arguments:

    ENTRY   BX = 0  call doesn't care about buffer length (NetGetEnumInfo)
            BX = 1  call is for NetGetUserName, which does care about buffer length
            CX = buffer length, if BX = 1
            ES:DI = buffer

Return Value:

    None.

--*/

{
    NET_API_STATUS status;
    LPBYTE buffer;
    LPWKSTA_USER_INFO_0 pInfo;
    BOOL itFits;
    DWORD len;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetUserName\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    status = NetWkstaUserGetInfo(NULL, 0, &buffer);
    if (status == NERR_Success) {
        pInfo = (LPWKSTA_USER_INFO_0)buffer;
#ifdef DBCS /*fix for DBCS char sets*/
        len = (DWORD)NetpUnicodeToDBCSLen(pInfo->wkui0_username);
#else // !DBCS
        len = (DWORD)wcslen(pInfo->wkui0_username);
#endif // !DBCS
        if (getBX()) {
            itFits = (len) <= (DWORD)getCX()-1;
            if (itFits) {
                SET_SUCCESS();
            } else {
                SET_ERROR(NERR_BufTooSmall);
            }
        } else {
            itFits = TRUE;
        }
        if (itFits) {
#ifdef DBCS /*fix for DBCS charsets*/
            NetpCopyWStrToStrDBCS(LPSTR_FROM_WORDS(getES(), getDI()),
                                   pInfo->wkui0_username);
#else // !DBCS
            NetpCopyWStrToStr(LPSTR_FROM_WORDS(getES(), getDI()), pInfo->wkui0_username);
#endif // !DBCS
        }
        NetApiBufferFree(buffer);
    } else {
        SET_ERROR((WORD)status);
    }
}


VOID
VrNetGetDCName(
    VOID
    )

/*++

Routine Description:

    Performs local NetGetDCName on behalf of the Vdm client

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetDCName\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetGetDCName - unsupported SVC\n");
    }
#endif

    SET_ERROR(ERROR_NOT_SUPPORTED);
}


VOID
VrNetMessageBufferSend(
    VOID
    )

/*++

Routine Description:

    Performs local NetMessageBufferSend on behalf of the Vdm client

Arguments:

    Function 5F40h

    ENTRY    DS:DX = NetMessageBufferSendStruc:
                 char far*    NMBSS_NetName;
                 char far*    NMBSS_Buffer;
                 unsigned int NMBSS_BufSize;

Return Value:

    None. Results returned via VDM registers:
        CF = 0
            Success

        CF = 1
            AX = Error code

--*/

{
    NTSTATUS ntstatus;
    NET_API_STATUS status;
    XS_PARAMETER_HEADER header;
    XS_NET_MESSAGE_BUFFER_SEND parameters;
    struct NetMessageBufferSendStruc* structurePointer;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetMessageBufferSend\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    structurePointer = (struct NetMessageBufferSendStruc*)
                            POINTER_FROM_WORDS(getDS(), getDX());

    parameters.Recipient = LPSTR_FROM_POINTER(&structurePointer->NMBSS_NetName);
    parameters.Buffer = LPBYTE_FROM_POINTER(&structurePointer->NMBSS_Buffer);
    parameters.BufLen = READ_WORD(&structurePointer->NMBSS_BufSize);

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetMessageBufferSend(&header, &parameters, NULL, NULL);
    if (ntstatus != STATUS_SUCCESS) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = header.Status;
    }
    if (status) {
        SET_ERROR(VrpMapDosError(status));

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetMessageBufferSend: returning %d\n", status);
    }
#endif

    } else {
        setCF(0);
    }
}


VOID
VrNetNullTransactApi(
    VOID
    )

/*++

Routine Description:

    Performs a transaction IOCTL using the NULL session for a Vdm client

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
    DWORD status;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetNullTransactApi\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    status = VrpTransactVdm(TRUE);
    if (status) {
        SET_ERROR((WORD)status);
    } else {
        setCF(0);
    }
}


VOID
VrNetRemoteApi(
    VOID
    )

/*++

Routine Description:

    This routine is invoked when a dos program in a Vdm makes a lanman API
    call which in turn calls the redir NetIRemoteAPI function to send the
    request to a lanman server

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
    DWORD ApiNumber;
    BOOL NullSessionFlag;
    NET_API_STATUS status;
    LPBYTE ServerNamePointer = LPBYTE_FROM_WORDS(getES(), getBX());

#define ParameterDescriptor LPSTR_FROM_WORDS(getDS(), getSI())
#define DataDescriptor      LPSTR_FROM_WORDS(getDS(), getDI())
#define AuxDescriptor       LPSTR_FROM_WORDS(getDS(), getDX())

    ApiNumber = (DWORD)getCX();
    NullSessionFlag = ApiNumber & USE_NULL_SESSION_FLAG;
    ApiNumber &= ~USE_NULL_SESSION_FLAG;

    //
    // get pointers to the various descriptors which are readable from 32-bit
    // context and call the routine to perform the 16-bit remote api function
    //

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetRemoteApi: ApiNumber=%d, ServerName=%s\n"
                 "ParmDesc=%s, DataDesc=%s, AuxDesc=%s\n",
                 ApiNumber,
                 LPSTR_FROM_POINTER(ServerNamePointer),
                 ParameterDescriptor,
                 DataDescriptor,
                 AuxDescriptor
                 );
    }
#endif

    //
    // RLF 04/21/93
    //
    // Yikes! It looks like passwords entered in DOS programs are not getting
    // encrypted. What a security hole. Let's block it-
    //
    // if this is a NetUserPasswordSet2 then we call the RxNetUserPasswordSet
    // function to remotely change the password. This function takes care of
    // correctly encrypting the password and sending the request over the NULL
    // session. In this case, ServerNamePointer points at the server name
    // parameter in the following PASCAL calling convention stack frame in DOS
    // memory:
    //
    // tos: far pointer to new password (OEM string)
    //      far pointer to old password (OEM string)
    //      far pointer to user name    (OEM string)
    //      far pointer to server name  (OEM string) <- ServerNamePointer
    //

    if (ApiNumber == API_WUserPasswordSet2) {

        WCHAR uServerName[LM20_UNCLEN + 1];
        WCHAR uUserName[LM20_UNLEN + 1];
        WCHAR uOldPassword[LM20_PWLEN + 1];
        WCHAR uNewPassword[LM20_PWLEN + 1];
        NTSTATUS ntStatus;
        DWORD length;
        LPSTR ansiStringPointer;

        ansiStringPointer = LPSTR_FROM_POINTER(ServerNamePointer);
        ntStatus = RtlOemToUnicodeN(uServerName,
                                    sizeof(uServerName) - sizeof(uServerName[0]),
                                    &length,
                                    ansiStringPointer,
                                    strlen(ansiStringPointer)
                                    );
        if (NT_SUCCESS(ntStatus)) {
            uServerName[length/sizeof(uServerName[0])] = 0;
        } else {
            status = ERROR_INVALID_PARAMETER;
            goto VrNetRemoteApi_exit;
        }

        //
        // copy, upper case and convert to UNICODE, user name
        //

        ServerNamePointer -= sizeof(LPSTR);
        ansiStringPointer = LPSTR_FROM_POINTER(ServerNamePointer);
        if (!OemToUppercaseUnicode(ansiStringPointer,
                                   uUserName,
                                   ARRAY_ELEMENTS(uUserName) - 1)) {
            status = ERROR_INVALID_PARAMETER;
            goto VrNetRemoteApi_exit;
        }

        //
        // copy, upper case and convert to UNICODE, old password
        //

        ServerNamePointer -= sizeof(LPSTR);
        ansiStringPointer = LPSTR_FROM_POINTER(ServerNamePointer);
        if (!OemToUppercaseUnicode(ansiStringPointer,
                                   uOldPassword,
                                   ARRAY_ELEMENTS(uOldPassword) - 1)) {
            status = ERROR_INVALID_PARAMETER;
            goto VrNetRemoteApi_exit;
        }

        //
        // copy, upper case and convert to UNICODE, new password
        //

        ServerNamePointer -= sizeof(LPSTR);
        ansiStringPointer = LPSTR_FROM_POINTER(ServerNamePointer);
        if (!OemToUppercaseUnicode(ansiStringPointer,
                                   uNewPassword,
                                   ARRAY_ELEMENTS(uNewPassword) - 1)) {
            status = ERROR_INVALID_PARAMETER;
            goto VrNetRemoteApi_exit;
        }

        //
        // make the call to the down-level password set function
        //

        status = RxNetUserPasswordSet((LPTSTR)uServerName,
                                      (LPTSTR)uUserName,
                                      (LPTSTR)uOldPassword,
                                      (LPTSTR)uNewPassword
                                      );
    } else {

        CHAR aPassword[ENCRYPTED_PWLEN];
        LPBYTE parameterPointer;
        LPBYTE passwordPointer = NULL;
        DWORD passwordEncrypted;
        DWORD passwordLength;

        //
        // we are going to remote the API as requested. However, if the request
        // is NetUserAdd2 or NetUserSetInfo2 then we check to see if a password
        // is being sent over the wire. We may need to encrypt the password on
        // behalf of the DOS app
        //

        if (ApiNumber == API_WUserAdd2 || ApiNumber == API_WUserSetInfo2) {

            //
            // API request is to add a user or set a user's info. The former will
            // contain a password which needs to be encrypted, the latter MAY
            // contain a password which needs to be encrypted if the request is
            // to set all the information, or just the password
            //

            DWORD level;
            DWORD parmNum = PARMNUM_ALL;
            LPBYTE dataLengthPointer;

            //
            // in the case of NetUserAdd2, the stack frame in DOS memory looks like
            // this:
            //
            // tos: original password length
            //      password encryption flag
            //      buffer length
            //      far pointer to buffer containing user_info_1 or user_info_2
            //      info level
            //      far pointer to server name  <- ServerNamePointer
            //
            // and the NetUserSetInfo2 stack looks like this:
            //
            // tos: original password length
            //      password encryption flag
            //      parameter number
            //      buffer length
            //      far pointer to user_info_1 or user_info_2 or single parameter
            //      info level
            //      far pointer to user name
            //      far pointer to server name  <- ServerNamePointer
            //

            parameterPointer = ServerNamePointer;
            if (ApiNumber == API_WUserSetInfo2) {

                //
                // for SetInfo: bump the stack parameter pointer past the user
                // name pointer
                //

                parameterPointer -= sizeof(LPSTR);
            }

            //
            // bump the stack parameter pointer to the level parameter and
            // retrieve it
            //

            parameterPointer -= sizeof(WORD);
            level = (DWORD)READ_WORD(parameterPointer);

            //
            // bump the stack parameter pointer to point to the buffer address
            //

            parameterPointer -= sizeof(LPBYTE);
            passwordPointer = parameterPointer;

            //
            // move the stack parameter pointer to the password encryption flag
            // in the case of UserAdd2 or the parmNum parameter in the case of
            // SetInfo2. If SetInfo2, retrieve the parmNum parameter and move
            // the parameterPointer to point at the password encryption flag
            //

            parameterPointer -= sizeof(WORD);
            if (ApiNumber == API_WUserSetInfo2) {
                dataLengthPointer = parameterPointer;
            }
            parameterPointer -= sizeof(WORD);
            if (ApiNumber == API_WUserSetInfo2) {
                parmNum = (DWORD)READ_WORD(parameterPointer);
                parameterPointer -= sizeof(WORD);
            }

            //
            // get the password encryption flag and cleartext password length
            // from the DOS stack frame. Leave the stack frame pointer pointing
            // at the location for the encryption flag: we'll need to replace
            // this with TRUE and restore it before we return control
            //

            passwordEncrypted = (DWORD)READ_WORD(parameterPointer);
            passwordLength = (DWORD)READ_WORD(parameterPointer - sizeof(WORD));

            //
            // if the DOS app has already encrypted the password (how'd it do that?)
            // then we'll leave the password alone. Otherwise, we need to read
            // out the cleartext password from the user_info_1 or _2 structure
            // or SetInfo buffer, encrypt it and write back the encrypted
            // password, submit the request, then replace the encrypted password
            // in DOS memory with the original cleartext password.
            //
            // Note: passwordEncrypted might be 0 because this is a SetInfo2
            // call which is NOT setting the password
            //

            if (!passwordEncrypted
            && (parmNum == PARMNUM_ALL || parmNum == USER_PASSWORD_PARMNUM)
            && (level == 1 || level == 2)) {

                LM_OWF_PASSWORD lmOwfPassword;
                LM_SESSION_KEY lanmanKey;
                ENCRYPTED_LM_OWF_PASSWORD encryptedLmOwfPassword;
                NTSTATUS ntStatus;
                WCHAR uServerName[LM20_CNLEN + 1];
                DWORD length;
                LPSTR lpServerName;

                //
                // get into passwordPointer the address of the buffer. If UserAdd2
                // or SetInfo2 with PARMNUM_ALL, this is the address of a
                // user_info_1 or _2 structure, and we need to bump the pointer
                // again to be the address of the password field within the
                // structure.
                //
                // If the request is SetInfo2 with USER_PASSWORD_PARMNUM then
                // the buffer is the address of the password to set.
                //
                // Otherwise, this is a SetInfo2 call which is not setting a
                // password, so we don't have anything left to do
                //
                // if this is a SetInfo2 call with USER_PASSWORD_PARMNUM then
                // we have a slight kludge to perform. We need to encrypt the
                // password in 16-bit memory space, but if we just copy over
                // the cleartext password then we risk blatting over whatever
                // is in memory after the password. We have reserved a 16-byte
                // buffer in REDIR.EXE at CS:AX for this very purpose
                //

                if (parmNum == USER_PASSWORD_PARMNUM) {
                    RtlCopyMemory(POINTER_FROM_WORDS(getCS(), getAX()),
                                  LPBYTE_FROM_POINTER(passwordPointer),
                                  ENCRYPTED_PWLEN
                                  );

                    //
                    // set the address of the buffer in the stack to point to
                    // the encryption buffer in REDIR.EXE
                    //

                    WRITE_WORD(passwordPointer, getAX());
                    WRITE_WORD(passwordPointer+2, getCS());
                    passwordPointer = POINTER_FROM_WORDS(getCS(), getAX());
                } else {
                    passwordPointer = LPBYTE_FROM_POINTER(passwordPointer);
                }

                //
                // BUGBUG - I have no idea (currently) if we ever get a NULL
                //          pointer, but I think it is wrong. If we do, just
                //          skip ahead and remote the function - let the
                //          server handle it
                //

                if (!passwordPointer) {
                    goto VrNetRemoteApi_do_remote;
                }

                //
                // if passwordPointer currently points at a user_info_1 or
                // user_info_2 structure, bump it to point at the password
                // field within the structure
                //

                if (parmNum == PARMNUM_ALL) {
                    passwordPointer += (DWORD)&((struct user_info_1*)0)->usri1_password[0];
                }

                //
                // if the password is NULL_USERSETINFO_PASSWD (14 spaces and
                // terminating 0) there is nothing to do
                //

                if (!strcmp(passwordPointer, NULL_USERSETINFO_PASSWD)) {
                    passwordPointer = NULL;
                    goto VrNetRemoteApi_do_remote;
                }

                //
                // okay, let's do some encryption (exciting isn't it?)
                //

                RtlCopyMemory(aPassword,
                              passwordPointer,
                              sizeof(((struct user_info_1*)0)->usri1_password)
                              );

                //
                // BUGBUG, this isn't necessarily the correct upper-case function
                //

                _strupr(aPassword);

                //
                // convert the ANSI server name to UNICODE for GetLanmanSessionKey
                //

                lpServerName = LPSTR_FROM_POINTER(ServerNamePointer);
                ntStatus = RtlOemToUnicodeN(uServerName,
                                            sizeof(uServerName) - sizeof(uServerName[0]),
                                            &length,
                                            lpServerName,
                                            strlen(lpServerName)
                                            );
                if (NT_SUCCESS(ntStatus)) {
                    uServerName[length/sizeof(uServerName[0])] = 0;
                } else {
                    status = ERROR_INVALID_PARAMETER;
                    goto VrNetRemoteApi_exit;
                }

                ntStatus = RtlCalculateLmOwfPassword(aPassword, &lmOwfPassword);
                if (NT_SUCCESS(ntStatus)) {
                    ntStatus = GetLanmanSessionKey((LPWSTR)uServerName, (LPBYTE)&lanmanKey);
                    if (NT_SUCCESS(ntStatus)) {
                        ntStatus = RtlEncryptLmOwfPwdWithLmSesKey(&lmOwfPassword,
                                                                  &lanmanKey,
                                                                  &encryptedLmOwfPassword
                                                                  );
                        if (NT_SUCCESS(ntStatus)) {
                            RtlCopyMemory(passwordPointer,
                                          &encryptedLmOwfPassword,
                                          sizeof(encryptedLmOwfPassword)
                                          );

                            //
                            // fake it
                            //

                            WRITE_WORD(parameterPointer, 1);

                            //
                            // if this is SetInfo2 with USER_PASSWORD_PARMNUM
                            // then we don't need to copy back the cleartext
                            // password because we have not modified the
                            // original buffer in the app's space
                            //
                            // We also have to change the size of data being
                            // passed in to the size of the encrypted password
                            //

                            if (parmNum == USER_PASSWORD_PARMNUM) {
                                WRITE_WORD(dataLengthPointer, ENCRYPTED_PWLEN);
                                passwordPointer = NULL;
                            }
                        }
                    }
                }

                //
                // if we fell by the wayside, quit out
                //

                if (!NT_SUCCESS(ntStatus)) {
                    status = RtlNtStatusToDosError(ntStatus);
                    goto VrNetRemoteApi_exit;
                }
            } else {

                //
                // we are not encrypting the password - set the pointer back
                // to NULL. Used as a flag after call to VrRemoteApi
                //

                passwordPointer = NULL;
            }
        }

        //
        // build a transaction request from the caller's parameters
        //

VrNetRemoteApi_do_remote:

        status = VrRemoteApi(ApiNumber,
                             ServerNamePointer,
                             ParameterDescriptor,
                             DataDescriptor,
                             AuxDescriptor,
                             NullSessionFlag
                             );

        //
        // if we replaced a cleartext password with an encrypted password in
        // DOS memory, then undo the change before giving control back to DOS
        //

        if (passwordPointer) {
            RtlCopyMemory(passwordPointer,
                          aPassword,
                          sizeof(((struct user_info_1*)0)->usri1_password)
                          );
            WRITE_WORD(parameterPointer, 0);
        }
    }

VrNetRemoteApi_exit:

    if (status != NERR_Success) {
        SET_ERROR((WORD)status);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("Error: VrNetRemoteApi returning %u\n", (DWORD)getAX());
        }
#endif

    } else {
        setCF(0);
    }
}


BOOL
OemToUppercaseUnicode(
    IN LPSTR AnsiStringPointer,
    OUT LPWSTR UnicodeStringPointer,
    IN DWORD MaxLength
    )

/*++

Routine Description:

    given a string in OEM character set, upper cases it then converts it to
    UNICODE

Arguments:

    AnsiStringPointer       - pointer to 8-bit string to convert
    UnicodeStringPointer    - pointer to resultant 16-bit (UNICODE) string
    MaxLength               - maximum output buffer length in # of characters,
                              NOT including terminating NUL

Return Value:

    BOOL
        TRUE    - string converted
        FALSE   - failed for some reason (string too long, Rtl function failed)

--*/

{
    DWORD stringLength;
    char scratchpad[UNLEN + 1]; // UNLEN is the largest type of string we'll get
    NTSTATUS ntStatus;
    DWORD length;

    stringLength = strlen(AnsiStringPointer);
    if (stringLength > MaxLength) {
        return FALSE;
    }
    strcpy(scratchpad, AnsiStringPointer);

    //
    // BUGBUG - this is not necessarily the correct upper-case function
    //

    _strupr(scratchpad);
    ntStatus = RtlOemToUnicodeN(UnicodeStringPointer,
                                MaxLength * sizeof(*UnicodeStringPointer),
                                &length,
                                scratchpad,
                                stringLength
                                );
    if (NT_SUCCESS(ntStatus)) {
        UnicodeStringPointer[length/sizeof(*UnicodeStringPointer)] = 0;
        return TRUE;
    } else {
        return FALSE;
    }
}


VOID
VrNetServerEnum(
    VOID
    )

/*++

Routine Description:

    Handles NetServerEnum and NetServerEnum2

Arguments:

    NetServerEnum

    ENTRY   AL = 4Ch
            BL = level (0 or 1)
            CX = size of buffer
            ES:DI = buffer

    EXIT    CF = 1
                AX = Error code:
                    NERR_BufTooSmall
                    ERROR_MORE_DATA

            CF = 0
                BX = entries read
                CX = total available


    NetServerEnum2

    ENTRY   AL = 53h
            DS:SI = NetServerEnum2Struct:
                DW  Level
                DD  Buffer
                DW  Buflen
                DD  Type
                DD  Domain

    EXIT    CF = 1
                AX = Error code:
                    NERR_BufTooSmall
                    ERROR_MORE_DATA

            CF = 0
                BX = entries read
                CX = total available

Return Value:

    None.

--*/

{
    BYTE callType = getAL();

    struct NetServerEnum2Struct* structPtr;
    LPBYTE buffer;
    WORD bufferSegment;
    WORD bufferOffset;
    LPDESC descriptor;
    WORD level;
    WORD buflen;
    DWORD serverType;
    LPSTR domain;

    XS_NET_SERVER_ENUM_2 parameters;
    XS_PARAMETER_HEADER header;
    NTSTATUS ntstatus;
    NET_API_STATUS status;

//    LPBYTE enumPtr;
//    DWORD nRead;
//    DWORD nAvail;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetServerEnum: type=0x%02x\n", callType);
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    if (callType == 0x4c) {

        //
        // call is NetServerEnum
        //

        bufferSegment = getES();
        bufferOffset = getDI();
        buffer = LPBYTE_FROM_WORDS(bufferSegment, bufferOffset);
        buflen = (WORD)getCX();
        level = (WORD)getBL();
        serverType = SV_TYPE_ALL;
        domain = NULL;
    } else {

        //
        // call is NetServerEnum2
        //

        structPtr = (struct NetServerEnum2Struct*)POINTER_FROM_WORDS(getDS(), getSI());
        bufferSegment = GET_SEGMENT(&structPtr->NSE_buf);
        bufferOffset = GET_OFFSET(&structPtr->NSE_buf);
        buffer = POINTER_FROM_WORDS(bufferSegment, bufferOffset);
        buflen = READ_WORD(&structPtr->NSE_buflen);
        level = READ_WORD(&structPtr->NSE_level);
        serverType = READ_DWORD(&structPtr->NSE_type);
        domain = LPSTR_FROM_POINTER(&structPtr->NSE_domain);
    }

    //
    // set the returned EntriesRead (BX) and TotalAvail (CX) to zero here for
    // the benefit of the 16-bit Windows NETAPI.DLL!NetServerEnum2
    // This function tries to unpack BX entries from the enum buffer as
    // soon as control is returned after the call to the redir via DoIntx. BUT
    // IT DOESN'T LOOK AT THE RETURN CODE FIRST. As Sam Kinnison used to say
    // AAAAAAAAAAAAAAARRRRRGH AAAAAAARGHH AAAAAAARGHHHHHHH!!!!!!!
    //

    setBX(0);
    setCX(0);

    //
    // first, check level - both types only handle 0 or 1
    //

    switch (level) {
    case 0:
        descriptor = REM16_server_info_0;
        break;

    case 1:
        descriptor = REM16_server_info_1;
        break;

    //
    // levels 2 & 3 not used in enum
    //

    default:

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetServerEnum - invalid level %d. Returning early\n", level);
        }
#endif

        SET_ERROR(ERROR_INVALID_LEVEL);
        return;
    }

    parameters.Level = level;
    parameters.Buffer = buffer;
    parameters.BufLen = buflen;
    parameters.ServerType = serverType;
    parameters.Domain = domain;

#if DBG

    IF_DEBUG(NETAPI) {
        DbgPrint("buffer @%04x:%04x, length=%d, level=%d, type=0x%08x, domain=%s\n",
                bufferSegment, bufferOffset, parameters.BufLen, level,
                parameters.ServerType, parameters.Domain
                );
    }

#endif

//    //
//    // I_BrowserServerEnum which XsNetServerEnum2 calls requires a transport
//    // name. If we don't give it one, it'll return ERROR_INVALID_PARAMETER
//    //
//
//    status = NetWkstaTransportEnum(NULL,
//                                   0,
//                                   &enumPtr,
//                                   -1L,         // we'll take everything
//                                   &nRead,      // number returned
//                                   &nAvail,     // total available
//                                   NULL         // no resume handle
//                                   );
//    if (status != NERR_Success) {
//
//#if DBG
//        IF_DEBUG(NETAPI) {
//            DbgPrint("VrNetServerEnum: Error: NetWkstaTransportEnum returns %d\n", status);
//        }
//#endif
//
//        SET_ERROR(status);
//        return;
//    }

    header.Status = 0;
    header.ClientMachineName = NULL;

    //
    // use the first enumerated transport name
    //

//    header.ClientTransportName = ((LPWKSTA_TRANSPORT_INFO_0)enumPtr)->wkti0_transport_name;
    header.ClientTransportName = NULL;

    ntstatus = XsNetServerEnum2(&header, &parameters, descriptor, NULL);
    if (!NT_SUCCESS(ntstatus)) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = (NET_API_STATUS)header.Status;
    }
    if (status == NERR_Success) {
        SET_SUCCESS();
    } else {
        SET_ERROR((WORD)status);
    }
    if (status == NERR_Success || status == ERROR_MORE_DATA) {
        if (parameters.EntriesRead) {
            VrpConvertReceiveBuffer(buffer,
                                    bufferSegment,
                                    bufferOffset,
                                    header.Converter,
                                    parameters.EntriesRead,
                                    descriptor,
                                    NULL
                                    );
        }
        setBX(parameters.EntriesRead);
        setCX(parameters.TotalAvail);
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetServerEnum: returning %d for NetServerEnum2\n", getAX());
        if (getAX() == NERR_Success || getAX() == ERROR_MORE_DATA) {
            DbgPrint("EntriesRead=%d, TotalAvail=%d\n",
                        parameters.EntriesRead,
                        parameters.TotalAvail
                        );
        }
    }
#endif

//    //
//    // free up the buffer returned by NetWkstaTransportEnum
//    //
//
//    NetApiBufferFree(enumPtr);
}


VOID
VrNetServiceControl(
    VOID
    )

/*++

Routine Description:

    We allow the interrogate function for specific services. The other functions
    are pause, continue and stop (uninstall) which we disallow

Arguments:

    Function 5F42

    DL = opcode:
        0 = interrogate SUPPORTED
        1 = pause service * NOT SUPPORTED *
        2 = continue service * NOT SUPPORTED *
        3 = uninstall service * NOT SUPPORTED *
        4 - 127 = reserved
        127 - 255 = OEM defined
    DH = OEM defined argument
    ES:BX = NetServiceControl structure:
        char far* service name
        unsigned short buffer length
        char far* buffer

Return Value:

    None.

--*/

{
    BYTE opcode = getDL();
    BYTE oemArg = getDH();
    struct NetServiceControlStruc* structPtr = (struct NetServiceControlStruc*)
                                                POINTER_FROM_WORDS(getES(), getBX());

    LPSTR serviceName = READ_FAR_POINTER(&structPtr->NSCS_Service);
    WORD buflen = READ_WORD(&structPtr->NSCS_BufLen);
    LPSTR buffer = READ_FAR_POINTER(&structPtr->NSCS_BufferAddr);
    WORD seg = GET_SEGMENT(&structPtr->NSCS_BufferAddr);
    WORD off = GET_OFFSET(&structPtr->NSCS_BufferAddr);

    XS_NET_SERVICE_CONTROL parameters;
    XS_PARAMETER_HEADER header;
    NTSTATUS ntstatus;
    NET_API_STATUS status;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetServiceControl: Service=%s, Opcode=%d, OemArg=%d, Buffer @%04x:%04x, len=%d\n",
                serviceName,
                opcode,
                oemArg,
                seg,
                off,
                buflen
                );
    }
#endif

    if (opcode > 4) {
        SET_ERROR(NERR_ServiceCtlNotValid);
        return;
    }

    //
    // we are disallowing anything other than 0 (interrogate) by returning
    // ERROR_INVALID_PARAMETER, which may be a new error code
    //

    if (opcode) {
        SET_ERROR(ERROR_INVALID_PARAMETER);
        return;
    }

    //
    // KLUDGE - if the service name is NETPOPUP then we return NERR_ServiceNotInstalled.
    // LANMAN.DRV checks to see if this service is loaded. If it is then
    // it sticks Load=WinPopUp in WIN.INI. We don't want it to do this
    //

    if (!_stricmp(serviceName, NETPOPUP_SERVICE)) {

        //
        // roll our own service_info_2 structure
        //

        if (buflen >= sizeof(struct service_info_2)) {
            SET_ERROR(NERR_ServiceNotInstalled);
        } else {
            SET_ERROR(NERR_BufTooSmall);
        }
        return;
    }

    //
    // leave the work to XsNetServiceControl
    //

    parameters.Service = serviceName;
    parameters.OpCode = opcode;
    parameters.Arg = oemArg;
    parameters.Buffer = buffer;
    parameters.BufLen = buflen;

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetServiceControl(&header, &parameters, REM16_service_info_2, NULL);
    if (!NT_SUCCESS(ntstatus)) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = (NET_API_STATUS)header.Status;
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetServiceControl: returning %d\n", status);
    }
#endif

    if (status == NERR_Success || status == ERROR_MORE_DATA) {

        //
        // there are no pointers in a service_info_2 structure, so there is
        // no need to call VrpConvertReceiveBuffer. Also, we are not going to
        // allow the DOS process to pause, continue, start or stop any of our
        // 32-bit services, so we must tell the DOS app that the service
        // cannot accept these controls: zero out bit 4
        // (SERVICE_NOT_UNINSTALLABLE) and bit 5 (SERVICE_NOT_PAUSABLE)
        //

        ((struct service_info_2*)buffer)->svci2_status &= 0xff0f;
        SET_OK((WORD)status);
    } else {
        SET_ERROR((WORD)status);
    }
}

VOID
VrNetServiceEnum(
    VOID
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    None.

Return Value:

    None.

--*/

{
    SET_ERROR(ERROR_NOT_SUPPORTED);
}


VOID
VrNetTransactApi(
    VOID
    )

/*++

Routine Description:

    Performs a transaction on behalf of the Vdm

Arguments:

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
    DWORD   status;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetTransactApi\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    status = VrpTransactVdm(FALSE);
    if (status) {
        SET_ERROR((WORD)status);
    } else {
        setCF(0);
    }
}


VOID
VrNetUseAdd(
    VOID
    )

/*++

Routine Description:

    Performs local NetUseAdd on behalf of the Vdm client

Arguments:

    Function 5F47h
    ENTRY   BX = level
            CX = buffer length
            DS:SI = server name for remote call (MBZ)
            ES:DI = buffer containing use_info_1 structure

Return Value:

    None.

--*/

{
    NET_API_STATUS status;
    XS_NET_USE_ADD parameters;
    XS_PARAMETER_HEADER header;
    NTSTATUS ntstatus;
    LPSTR   computerName;
    LPBYTE  buffer;
    WORD    level;
    BOOL    allocated;
    DWORD   buflen;
    DWORD   auxOffset;
    char    myDescriptor[sizeof(REM16_use_info_1)];
    char    myDataBuffer[sizeof(struct use_info_1) + LM20_PWLEN + 1];

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetUseAdd\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    //
    // ensure the computer name designates the local machine (NULL)
    //

    computerName = LPSTR_FROM_WORDS(getDS(), getSI());

    level = (WORD)getBX();
    if (level != 1) {

        //
        // level must be 1 for an add
        //

        SET_ERROR(ERROR_INVALID_LEVEL);
        return;
    }

    //
    // preset the modifiable descriptor string
    //

    strcpy(myDescriptor, REM16_use_info_1);

    //
    // pack the use_info_1 buffer as if we were getting ready to ship it over
    // the net. Return errors
    //

    buffer = LPBYTE_FROM_WORDS(getES(), getDI());
    buflen = (DWORD)getCX();

    //
    // copy the DOS buffer to 32-bit memory. Do this to avoid irritating problem
    // of getting an already packed buffer from the client, and not being able
    // to do anything with it
    //

    RtlCopyMemory(myDataBuffer, buffer, sizeof(struct use_info_1));
    buffer = myDataBuffer;
    buflen = sizeof(myDataBuffer);
    status = VrpPackSendBuffer(&buffer,
                &buflen,
                &allocated,
                myDescriptor,   // modifiable descriptor
                NULL,           // AuxDescriptor
                VrpGetStructureSize(REM16_use_info_1, &auxOffset),
                (DWORD)-1,      // AuxOffset (-1 means there is no aux char 'N')
                0,              // AuxSize
                FALSE,          // not a SetInfo call
                TRUE            // OkToModifyDescriptor
                );
    if (status) {
        SET_ERROR(VrpMapDosError(status));
        return;
    }

    parameters.Level = level;
    parameters.Buffer = buffer;
    parameters.BufLen = (WORD)buflen;

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetUseAdd(&header, &parameters, myDescriptor, NULL);
    if (ntstatus != STATUS_SUCCESS) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {

        //
        // no error generated in XsNetUseAdd. Get the status of the NetUseAdd
        // proper from the header
        //

        status = (NET_API_STATUS)header.Status;
    }
    if (status != NERR_Success) {
#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("Error: VrNetUseAdd: XsNetUseAdd returns %u\n", status);
        }
#endif
        SET_ERROR((WORD)status);
    } else {
        setCF(0);
    }

    //
    // if VrpPackSendBuffer allocated a new buffer then free it
    //

    if (allocated) {
        LocalFree(buffer);
    }
}


VOID
VrNetUseDel(
    VOID
    )

/*++

Routine Description:

    Performs local NetUseDel on behalf of the Vdm client

Arguments:

    Function 5F48h

    ENTRY   BX = force flag
            DS:SI = server name for remote call (MBZ)
            ES:DI = use name

Return Value:

    None.

--*/

{
    NTSTATUS    ntstatus;
    NET_API_STATUS  status;
    WORD    force;
    LPSTR   name;
    XS_NET_USE_DEL  parameters;
    XS_PARAMETER_HEADER header;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetUseDel\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    force = (WORD)getBX();
    if (force > USE_LOTS_OF_FORCE) {
        SET_ERROR(ERROR_INVALID_PARAMETER);
        return;
    }

    name = LPSTR_FROM_WORDS(getDS(), getSI());

    //
    // make sure name is local
    //

    name = LPSTR_FROM_WORDS(getES(), getDI());

    parameters.UseName = name;
    parameters.Force = force;

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetUseDel(&header, &parameters, NULL, NULL);

    //
    // if XsNetUseDel failed then map the NT error returned into a Net error
    // else get the result of the NetUseDel proper from the header structure
    //

    if (ntstatus != STATUS_SUCCESS) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = (NET_API_STATUS)header.Status;
    }
    if (status != NERR_Success) {
        SET_ERROR(VrpMapDosError(status));
    } else {
        setCF(0);
    }
}


VOID
VrNetUseEnum(
    VOID
    )

/*++

Routine Description:

    Performs local NetUseEnum on behalf of the Vdm client

Arguments:

    Function 5F46h

    ENTRY   BX = level of info required - 0 or 1
            CX = buffer length
            ES:DI = buffer for enum info

Return Value:

    None.

--*/

{
    NTSTATUS    ntstatus;
    NET_API_STATUS status;
    WORD    level = getBX();
    XS_NET_USE_ENUM parameters;
    XS_PARAMETER_HEADER header;
    LPDESC  dataDesc;
    LPBYTE  receiveBuffer;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetUseEnum\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    if (level <= 1) {
        dataDesc = (level == 1) ? REM16_use_info_1 : REM16_use_info_0;
        parameters.Level  = level;
        receiveBuffer = POINTER_FROM_WORDS(getES(), getDI());
        parameters.Buffer = receiveBuffer;
        parameters.BufLen = getCX();

        header.Status = 0;
        header.ClientMachineName = NULL;
        header.ClientTransportName = NULL;

        ntstatus = XsNetUseEnum(&header, &parameters, dataDesc, NULL);

        //
        // if XsNetUseEnum didn't have any problems, convert the actual status
        // code to that returned in the header
        //

        if (ntstatus != STATUS_SUCCESS) {
            status = NetpNtStatusToApiStatus(ntstatus);
        } else {
            status = (DWORD)header.Status;
        }
    } else {
        status = ERROR_INVALID_LEVEL;
    }

    //
    // NetUseEnum sets these even in the event of failure. We do the same
    //

    setCX(parameters.EntriesRead);
    setDX(parameters.TotalAvail);

    //
    // if we're returning data, convert the pointer offsets to something
    // meaningful
    //

    if (((status == NERR_Success) || (status == ERROR_MORE_DATA))
        && parameters.EntriesRead) {
        VrpConvertReceiveBuffer(receiveBuffer,
            (WORD)getES(),
            (WORD)getDI(),
            (WORD)header.Converter,
            parameters.EntriesRead,
            dataDesc,
            NULL
            );
    }

    //
    // only return carry clear if no error occurred. Even if ERROR_MORE_DATA
    // set CF
    //

    if (status) {
        SET_ERROR(VrpMapDosError(status));
    } else {
        setCF(0);
    }
}


VOID
VrNetUseGetInfo(
    VOID
    )

/*++

Routine Description:

    Performs local NetUseGetInfo on behalf of the Vdm client

Arguments:

    Function 5F49h

    ENTRY   DS:DX = NetUseGetInfoStruc:
                const char FAR* NUGI_usename;
                short           NUGI_level;
                char FAR*       NUGI_buffer;
                unsigned short  NUGI_buflen;

Return Value:

    None.

--*/

{
    NTSTATUS    ntstatus;
    NET_API_STATUS status;
    XS_NET_USE_GET_INFO parameters;
    XS_PARAMETER_HEADER header;
    struct NetUseGetInfoStruc* structurePointer;
    WORD    level;
    LPDESC  dataDesc;
    LPBYTE  receiveBuffer;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetUseGetInfo\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    //
    // pull info out of Vdm context
    //

    structurePointer = (struct NetUseGetInfoStruc*)
                            POINTER_FROM_WORDS(getDS(), getDX());
    level = structurePointer->NUGI_level;

    //
    // level can be 0 or 1
    //

    if (level <= 1) {
        dataDesc = (level == 1) ? REM16_use_info_1 : REM16_use_info_0;

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: dataDesc=%s\n", dataDesc);
        }
#endif

        parameters.UseName= POINTER_FROM_POINTER(&(structurePointer->NUGI_usename));

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: UseName=%s\n", parameters.UseName);
        }
#endif

        parameters.Level  = level;

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: level=%d\n", level);
        }
#endif

        receiveBuffer = POINTER_FROM_POINTER(&(structurePointer->NUGI_buffer));

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: receiveBuffer=%x\n", receiveBuffer);
        }
#endif

        parameters.Buffer = receiveBuffer;
        parameters.BufLen = READ_WORD(&structurePointer->NUGI_buflen);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: BufLen=%d\n", parameters.BufLen);
        }
#endif

        header.Status = 0;
        header.ClientMachineName = NULL;
        header.ClientTransportName = NULL;

        ntstatus = XsNetUseGetInfo(&header, &parameters, dataDesc, NULL);
        if (ntstatus != STATUS_SUCCESS) {
            status = NetpNtStatusToApiStatus(ntstatus);
        } else {
            status = header.Status;
        }
    } else {
        status = ERROR_INVALID_LEVEL;
    }

    if ((status == NERR_Success)
        || (status == ERROR_MORE_DATA)
        || (status == NERR_BufTooSmall)
        ) {
        setDX(parameters.TotalAvail);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetUseGetInfo: TotalAvail=%d\n", parameters.TotalAvail);
        }
#endif

        if ((status == NERR_Success) || (status == ERROR_MORE_DATA)) {
            VrpConvertReceiveBuffer(
                receiveBuffer,
                GET_SELECTOR(&(structurePointer->NUGI_buffer)),
                GET_OFFSET(&(structurePointer->NUGI_buffer)),
                (WORD)header.Converter,
                1,
                dataDesc,
                NULL
                );
        }
    } else {

        //
        // the first thing NetUseGetInfo does is set the returned total available
        // count to 0. Lets be compatible!
        //

        setDX(0);

    }
    if (status) {
        SET_ERROR(VrpMapDosError(status));
    } else {
        setCF(0);
    }
}


VOID
VrNetWkstaGetInfo(
    VOID
    )

/*++

Routine Description:

    Performs local NetWkstaGetInfo on behalf of the Vdm client

Arguments:

    Function 5F44h

    ENTRY   BX = level (0, 1 or 10)
            CX = size of caller's buffer
            DS:SI = computer name for remote call (IGNORED)
            ES:DI = caller's buffer

Return Value:

    CF = 0
        DX = size of buffer required to honour request

    CF = 1
        AX = error code

--*/

{
    DWORD   level;
    DWORD   bufLen;
    LPBYTE  buffer;
    LPDESC  dataDesc;
    NET_API_STATUS status;
    NTSTATUS ntStatus;
    XS_PARAMETER_HEADER header;
    XS_NET_WKSTA_GET_INFO parameters;
    WORD    bufferSegment;
    WORD    bufferOffset;
    INT     bufferLeft;
    DWORD   totalAvail;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("\nVrNetWkstaGetInfo\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

    level = (DWORD)getBX();
    switch (level) {
    case 0:
        dataDesc = REMSmb_wksta_info_0;
        break;

    case 1:
        dataDesc = REMSmb_wksta_info_1;
        break;

    case 10:
        dataDesc = REMSmb_wksta_info_10;
        break;

    default:
        SET_ERROR(ERROR_INVALID_LEVEL);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetWkstaGetInfo: Error: returning %d for level %d\n",
                     getAX(),
                     level
                     );
        }
#endif

        return;
    }

    bufLen = (DWORD)getCX();
    bufferSegment = getES();
    bufferOffset = getDI();
    buffer = LPBYTE_FROM_WORDS(bufferSegment, bufferOffset);

    if (bufLen && !buffer) {
        SET_ERROR(ERROR_INVALID_PARAMETER);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetWkstaGetInfo: Error: buffer=NULL, buflen=%d\n", bufLen);
        }
#endif

        return;
    }

    //
    // clear out the caller's buffer - just in case XsNetWkstaGetInfo forgets
    // to fill in some fields
    //

    if (bufLen) {
        RtlZeroMemory(buffer, bufLen);
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetWkstaGetInfo: level=%d, bufLen = %d (0x%x), buffer = %x:%x\n",
            level, bufLen, bufLen, bufferSegment, bufferOffset
            );
    }
#endif

    parameters.Level = (WORD)level;
    parameters.Buffer = buffer;
    parameters.BufLen = (WORD)bufLen;

    header.Status = 0;
    header.Converter = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;
    header.EncryptionKey = NULL;

    ntStatus = XsNetWkstaGetInfo(&header, &parameters, dataDesc, NULL);
    if (!NT_SUCCESS(ntStatus)) {
        status = NetpNtStatusToApiStatus(ntStatus);
    } else {
        status = (NET_API_STATUS)header.Status;
    }
    if (status != NERR_Success) {
        SET_ERROR((WORD)status);
    } else {
        setCF(0);
        setAX((WORD)status);
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetWkstaGetInfo: status after XsNetWkstaGetInfo=%d, TotalAvail=%d\n",
                 status,
                 parameters.TotalAvail
                 );
//        DumpWkstaInfo(level, buffer);
    }
#endif

    //
    // This next bit of code will add the per-user information only if there
    // is space to add all of it - XsNetWkstaGetInfo returns either all the
    // variable data, or none of it. This is incorrect, but we'll play along.
    //
    // Assumes that the variable data is packed into the buffer starting at
    // the end of the fixed structure + 1
    //
    // Irrespective of whether data is returned, we have to update the
    // TotalAvail parameter to reflect the adjusted amount of data
    //

    totalAvail = parameters.TotalAvail;
    bufferLeft = (INT)(bufLen - totalAvail);

    if ((status == NERR_Success)
    || (status == ERROR_MORE_DATA)
    || (status == NERR_BufTooSmall)) {

        //
        // because of NT's ability to instantaneously support more than one
        // user, XsNetWkstaGetInfo no longer returns information pertinent to
        // the current user. Thus, we have to furnish the information from
        // this user's context:
        //
        //              field\level     0  1  10
        //              ------------------------
        //              user name       x  x  x
        //              logon server    x  x
        //              logon domain       x  x
        //              other domains      x  x
        //
        // all this info is returned from NetWkstaUserGetInfo, level 1
        //

        LPBYTE info;
        NET_API_STATUS net_status;
        char username[LM20_UNLEN + 1];
        char logonServer[LM20_UNCLEN + 1];
        char logonDomain[LM20_DNLEN + 1];
        char otherDomains[512]; // arbitrary
        DWORD len;
        LPWSTR UNALIGNED str;
        //BOOL nullPointer;
        BOOL addSlashes;

//// TEST_DATA
//        static INT testindex = 0;
//        static WCHAR* testnames[] = {
//            NULL,
//            NULL,
//            L"",
//            L"",
//            L"A",
//            L"A",
//            L"AB",
//            L"AB",
//            L"ABC",
//            L"ABC",
//            L"ABCDEFGHIJKLMNO",
//            L"ABCDEFGHIJKLMNO",
//            L"\\\\",
//            L"\\\\",
//            L"\\\\A",
//            L"\\\\A",
//            L"\\\\AB",
//            L"\\\\AB",
//            L"\\\\ABC",
//            L"\\\\ABC",
//            L"\\\\ABCDEFGHIJKLMNO",
//            L"\\\\ABCDEFGHIJKLMNO"
//            };
//// TEST_DATA

        //
        // first off, modify the pointers for any data returned by
        // XsNetWkstaGetInfo
        //

        if (status == NERR_Success) {

//#if DBG
//            IF_DEBUG(NETAPI) {
//                DbgPrint("VrNetWkstaGetInfo: calling VrpConvertReceiveBuffer: Converter=%04x\n",
//                            header.Converter
//                            );
//            }
//#endif

            VrpConvertReceiveBuffer(
                buffer,
                bufferSegment,
                bufferOffset,
                (WORD)header.Converter,
                1,
                dataDesc,
                NULL
                );
        }

        //
        // get the per-user information
        //

        net_status = NetWkstaUserGetInfo(NULL, 1, &info);
        if (net_status == NERR_Success) {

//#if DBG
//            IF_DEBUG(NETAPI) {
//                DbgPrint("NetWkstaUserGetInfo:\n"
//                         "user name     %ws\n"
//                         "logon domain  %ws\n"
//                         "other domains %ws\n"
//                         "logon server  %ws\n"
//                         "\n",
//                         ((PWKSTA_USER_INFO_1)info)->wkui1_username,
//                         ((PWKSTA_USER_INFO_1)info)->wkui1_logon_domain,
//                         ((PWKSTA_USER_INFO_1)info)->wkui1_oth_domains,
//                         ((PWKSTA_USER_INFO_1)info)->wkui1_logon_server
//                         );
//            }
//#endif

            //
            // username for all levels
            //

            str = (LPWSTR)((PWKSTA_USER_INFO_1)info)->wkui1_username;
            if (!str) {
                str = L"";
            }
            //nullPointer = ((level == 10)
            //                ? ((struct wksta_info_10*)buffer)->wki10_username
            //                : ((struct wksta_info_0*)buffer)->wki0_username
            //                ) == NULL;
            //len = wcslen(str) + nullPointer ? 1 : 0;
#ifdef DBCS /*fix for DBCS char sets*/
            len = (DWORD)NetpUnicodeToDBCSLen(str) + 1;
#else // !DBCS
            len = wcslen(str) + 1;
#endif // !DBCS
            bufferLeft -= len;
            totalAvail += len;

            if (len <= sizeof(username)) {
#ifdef DBCS /*fix for DBCS char sets*/
                NetpCopyWStrToStrDBCS(username, str);
#else // !DBCS
                NetpCopyWStrToStr(username, str);
#endif // !DBCS
            } else {
                username[0] = 0;
            }

            //
            // logon_server for levels 0 & 1
            //

            if (level <= 1) {
                str = (LPWSTR)((PWKSTA_USER_INFO_1)info)->wkui1_logon_server;

//// TEST_CODE
//                if (testindex < sizeof(testnames)/sizeof(testnames[0])) {
//                    str = testnames[testindex++];
//                }
//// TEST_CODE

                if (!str) {
                    str = L"";
                }
#ifdef DBCS /*fix for DBCS char sets*/
                len = (DWORD)NetpUnicodeToDBCSLen(str) + 1;
#else // !DBCS
                len = wcslen(str) + 1;
#endif // !DBCS

                //
                // DOS returns "\\logon_server" whereas NT returns "logon_server".
                // We need to account for the extra backslashes (but only if not
                // NULL string)
                // At this time, len includes +1 for terminating \0, so even a
                // NULL string has length 1
                //

                addSlashes = TRUE;
                if (len >= 3 && IS_PATH_SEPARATOR(str[0]) && IS_PATH_SEPARATOR(str[1])) {
                    addSlashes = FALSE;
                } else if (len == 1) {  // NULL string
                    addSlashes = FALSE;
                }
                if (addSlashes) {
                    len += 2;
                }

                bufferLeft -= len;
                totalAvail += len;

                if (len <= sizeof(logonServer)) {

                    INT offset = 0;

                    if (addSlashes) {
                        logonServer[0] = logonServer[1] = '\\';
                        offset = 2;
                    }
#ifdef DBCS /*fix for DBCS char sets*/
                    NetpCopyWStrToStrDBCS(&logonServer[offset], str);
#else // !DBCS
                    NetpCopyWStrToStr(&logonServer[offset], str);
#endif // !DBCS
                } else {
                    logonServer[0] = 0;
                }
            }

            //
            // logon_domain and oth_domains for levels 1 & 10
            //

            if (level >= 1) {
                str = (LPWSTR)((PWKSTA_USER_INFO_1)info)->wkui1_logon_domain;
                if (!str) {
                    str = L"";
                }
#ifdef DBCS /*fix for DBCS char sets*/
                len = (DWORD)NetpUnicodeToDBCSLen(str) + 1;
#else // !DBCS
                len = wcslen(str) + 1;
#endif // !DBCS
                bufferLeft -= len;
                totalAvail += len;

                if (len <= sizeof(logonDomain)) {
#ifdef DBCS /*fix for DBCS char sets*/
                    NetpCopyWStrToStrDBCS(logonDomain, str);
#else // !DBCS
                    NetpCopyWStrToStr(logonDomain, str);
#endif // !DBCS
                } else {
                    logonDomain[0] = 0;
                }

                str = (LPWSTR)((PWKSTA_USER_INFO_1)info)->wkui1_oth_domains;
                if (!str) {
                    str = L"";
                }
#ifdef DBCS /*fix for DBCS char sets*/
                len = (DWORD)NetpUnicodeToDBCSLen(str) + 1;
#else // !DBCS
                len = wcslen(str) + 1;
#endif // !DBCS
                bufferLeft -= len;
                totalAvail += len;

                if (len <= sizeof(otherDomains)) {
#ifdef DBCS /*fix for DBCS char sets*/
                    NetpCopyWStrToStrDBCS(otherDomains, str);
#else // !DBCS
                    NetpCopyWStrToStr(otherDomains, str);
#endif // !DBCS
                } else {
                    otherDomains[0] = 0;
                }
            }

            //
            // if there's enough space in the buffer then copy the strings
            //

            if (status == NERR_Success && bufferLeft >= 0) {

                WORD offset = bufferOffset + parameters.TotalAvail;
                LPSTR UNALIGNED ptr = POINTER_FROM_WORDS(bufferSegment, offset);

                //
                // username for all levels
                //

                strcpy(ptr, username);
                len = strlen(username) + 1;

                if (level <= 1) {

                    //
                    // levels 0 & 1 have username field at same offset
                    //

                    WRITE_WORD(&((struct wksta_info_1*)buffer)->wki1_username, offset);
                    WRITE_WORD((LPWORD)&((struct wksta_info_1*)buffer)->wki1_username+1, bufferSegment);
                } else {
                    WRITE_WORD(&((struct wksta_info_10*)buffer)->wki10_username, offset);
                    WRITE_WORD((LPWORD)&((struct wksta_info_10*)buffer)->wki10_username+1, bufferSegment);
                }
                ptr += len;
                offset += (WORD)len;

                //
                // logon_server for levels 0 & 1
                //

                if (level <= 1) {

                    strcpy(ptr, logonServer);
                    len = strlen(logonServer) + 1;

                    //
                    // levels 0 & 1 have logon_server field at same offset
                    //

                    WRITE_WORD(&((struct wksta_info_1*)buffer)->wki1_logon_server, offset);
                    WRITE_WORD((LPWORD)&((struct wksta_info_1*)buffer)->wki1_logon_server+1, bufferSegment);
                    ptr += len;
                    offset += (WORD)len;
                }

                //
                // logon_domain and oth_domains for levels 1 & 10
                //

                if (level >= 1) {
                    if (level == 1) {
                        strcpy(ptr, logonDomain);
                        len = strlen(logonDomain) + 1;
                        WRITE_WORD(&((struct wksta_info_1*)buffer)->wki1_logon_domain, offset);
                        WRITE_WORD((LPWORD)&((struct wksta_info_1*)buffer)->wki1_logon_domain+1, bufferSegment);
                        ptr += len;
                        offset += (WORD)len;
                        strcpy(ptr, otherDomains);
                        WRITE_WORD(&((struct wksta_info_1*)buffer)->wki1_oth_domains, offset);
                        WRITE_WORD((LPWORD)&((struct wksta_info_1*)buffer)->wki1_oth_domains+1, bufferSegment);
                    } else {
                        strcpy(ptr, logonDomain);
                        len = strlen(logonDomain) + 1;
                        WRITE_WORD(&((struct wksta_info_10*)buffer)->wki10_logon_domain, offset);
                        WRITE_WORD((LPWORD)&((struct wksta_info_10*)buffer)->wki10_logon_domain+1, bufferSegment);
                        ptr += len;
                        offset += (WORD)len;
                        strcpy(ptr, otherDomains);
                        WRITE_WORD(&((struct wksta_info_10*)buffer)->wki10_oth_domains, offset);
                        WRITE_WORD((LPWORD)&((struct wksta_info_10*)buffer)->wki10_oth_domains+1, bufferSegment);
                    }
                }
            } else if (status == NERR_Success) {

                //
                // the additional data will overflow the caller's buffer:
                // return ERROR_MORE_STATUS and NULL out any pointer fields
                // that XsNetWkstaGetInfo managed to set
                //

                switch (level) {
                case 1:
                    WRITE_FAR_POINTER(&((struct wksta_info_1*)buffer)->wki1_logon_domain, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_1*)buffer)->wki1_oth_domains, NULL);

                    //
                    // FALL THROUGH TO LEVEL 0 FOR REST OF FIELDS
                    //

                case 0:
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_root, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_computername, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_username, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_langroup, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_logon_server, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_0*)buffer)->wki0_wrkheuristics, NULL);
                    break;

                case 10:
                    WRITE_FAR_POINTER(&((struct wksta_info_10*)buffer)->wki10_computername, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_10*)buffer)->wki10_username, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_10*)buffer)->wki10_langroup, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_10*)buffer)->wki10_logon_domain, NULL);
                    WRITE_FAR_POINTER(&((struct wksta_info_10*)buffer)->wki10_oth_domains, NULL);
                    break;
                }
                status = ERROR_MORE_DATA;
                SET_ERROR((WORD)status);
            }

            //
            // free the wksta user info buffer
            //

            NetApiBufferFree((LPVOID)info);

        } else {

#if DBG
            IF_DEBUG(NETAPI) {
                DbgPrint("VrNetWkstaGetInfo: NetWkstaUserGetInfo returns %d\n", net_status);
            }
#endif

        }

        //
        // update the amount of data available when we return NERR_Success,
        // ERROR_MORE_DATA or NERR_BufTooSmall
        //

        setDX((WORD)totalAvail);

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrNetWkstaGetInfo: TotalAvail=%d\n", getDX());
        }
#endif

    }

    //
    // if we got data back, then we must change the version number from
    // 3.0 to 2.1 so lanman.drv thinks it is compatible with this version
    // of LM
    //

    if (status == NERR_Success || status == ERROR_MORE_DATA) {
        switch (level) {
        case 0:
            ((struct wksta_info_0*)buffer)->wki0_ver_major = LANMAN_EMULATION_MAJOR_VERSION;
            ((struct wksta_info_0*)buffer)->wki0_ver_minor = LANMAN_EMULATION_MINOR_VERSION;
            break;

        case 1:
            ((struct wksta_info_1*)buffer)->wki1_ver_major = LANMAN_EMULATION_MAJOR_VERSION;
            ((struct wksta_info_1*)buffer)->wki1_ver_minor = LANMAN_EMULATION_MINOR_VERSION;
            break;

        case 10:
            ((struct wksta_info_10*)buffer)->wki10_ver_major = LANMAN_EMULATION_MAJOR_VERSION;
            ((struct wksta_info_10*)buffer)->wki10_ver_minor = LANMAN_EMULATION_MINOR_VERSION;
            break;
        }
    }

#if DBG

    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetWkstaGetInfo: return status=%d, TotalAvail=%d\n", getAX(), getDX());
    }

    if (status == NERR_Success || status == ERROR_MORE_DATA) {
        IF_DEBUG(NETAPI) {
            DumpWkstaInfo(level, buffer);
        }
    }

#endif

}

#if DBG

#define POSSIBLE_STRING(s)  ((s) ? (s) : "")

VOID
DumpWkstaInfo(
    IN DWORD level,
    IN LPBYTE buffer
    )
{
    switch (level) {
    case 0:
    case 1:

        //
        // DbgPrint resets the test machine if we try it with this
        // string & these args all at once!
        //

        DbgPrint(   "reserved1      %04x\n",
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_reserved_1)
                    );

        DbgPrint(   "reserved2      %08x\n",
                    READ_DWORD(&((struct wksta_info_0*)buffer)->wki0_reserved_2)
                    );

        DbgPrint(   "lanroot        %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_root),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_root),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_root))
                    );

        DbgPrint(   "computername   %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_computername),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_computername),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_computername))
                    );

        DbgPrint(   "username       %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_username),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_username),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_username))
                    );

        DbgPrint(   "langroup       %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_langroup),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_langroup),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_langroup))
                    );

        DbgPrint(   "ver major      %02x\n"
                    "ver minor      %02x\n"
                    "reserved3      %08x\n"
                    "charwait       %04x\n"
                    "chartime       %08x\n"
                    "charcount      %04x\n",
                    READ_BYTE(&((struct wksta_info_0*)buffer)->wki0_ver_major),
                    READ_BYTE(&((struct wksta_info_0*)buffer)->wki0_ver_minor),
                    READ_DWORD(&((struct wksta_info_0*)buffer)->wki0_reserved_3),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_charwait),
                    READ_DWORD(&((struct wksta_info_0*)buffer)->wki0_chartime),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_charcount)
                    );

        DbgPrint(   "reserved4      %04x\n"
                    "reserved5      %04x\n"
                    "keepconn       %04x\n"
                    "keepsearch     %04x\n"
                    "maxthreads     %04x\n"
                    "maxcmds        %04x\n",
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_reserved_4),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_reserved_5),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_keepconn),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_keepsearch),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_maxthreads),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_maxcmds)
                    );

        DbgPrint(   "reserved6      %04x\n"
                    "numworkbuf     %04x\n"
                    "sizworkbuf     %04x\n"
                    "maxwrkcache    %04x\n"
                    "sesstimeout    %04x\n"
                    "sizerror       %04x\n",
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_reserved_6),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_numworkbuf),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_sizworkbuf),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_maxwrkcache),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_sesstimeout),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_sizerror)
                    );

        DbgPrint(   "numalerts      %04x\n"
                    "numservices    %04x\n"
                    "errlogsz       %04x\n"
                    "printbuftime   %04x\n"
                    "numcharbuf     %04x\n"
                    "sizcharbuf     %04x\n",
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_numalerts),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_numservices),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_errlogsz),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_printbuftime),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_numcharbuf),
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_sizcharbuf)
                    );

        DbgPrint(   "logon server   %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_logon_server),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_logon_server),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_logon_server))
                    );

        DbgPrint(   "wrkheuristics  %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_0*)buffer)->wki0_wrkheuristics),
                    GET_OFFSET(&((struct wksta_info_0*)buffer)->wki0_wrkheuristics),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_0*)buffer)->wki0_wrkheuristics))
                    );

        DbgPrint(   "mailslots      %04x\n",
                    READ_WORD(&((struct wksta_info_0*)buffer)->wki0_mailslots)
                    );

        if (level == 1) {
            DbgPrint(
                    "logon domain   %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_1*)buffer)->wki1_logon_domain),
                    GET_OFFSET(&((struct wksta_info_1*)buffer)->wki1_logon_domain),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_1*)buffer)->wki1_logon_domain))
                    );
            DbgPrint(
                    "other domains  %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_1*)buffer)->wki1_oth_domains),
                    GET_OFFSET(&((struct wksta_info_1*)buffer)->wki1_oth_domains),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_1*)buffer)->wki1_oth_domains))
                    );

            DbgPrint(
                    "numdgrambuf    %04x\n",
                    ((struct wksta_info_1*)buffer)->wki1_numdgrambuf
                    );
        }
        break;

    case 10:
        DbgPrint(   "computername   %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_10*)buffer)->wki10_computername),
                    GET_OFFSET(&((struct wksta_info_10*)buffer)->wki10_computername),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_10*)buffer)->wki10_computername))
                    );

        DbgPrint(   "username       %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_10*)buffer)->wki10_username),
                    GET_OFFSET(&((struct wksta_info_10*)buffer)->wki10_username),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_10*)buffer)->wki10_username))
                    );

        DbgPrint(   "langroup       %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_10*)buffer)->wki10_langroup),
                    GET_OFFSET(&((struct wksta_info_10*)buffer)->wki10_langroup),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_10*)buffer)->wki10_langroup))
                    );

        DbgPrint(   "ver major      %02x\n"
                    "ver minor      %02x\n"
                    "logon domain   %04x:%04x \"%s\"\n",
                    READ_BYTE(&((struct wksta_info_10*)buffer)->wki10_ver_major),
                    READ_BYTE(&((struct wksta_info_10*)buffer)->wki10_ver_minor),
                    GET_SEGMENT(&((struct wksta_info_10*)buffer)->wki10_logon_domain),
                    GET_OFFSET(&((struct wksta_info_10*)buffer)->wki10_logon_domain),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_10*)buffer)->wki10_logon_domain))
                    );

        DbgPrint(   "other domains  %04x:%04x \"%s\"\n",
                    GET_SEGMENT(&((struct wksta_info_10*)buffer)->wki10_oth_domains),
                    GET_OFFSET(&((struct wksta_info_10*)buffer)->wki10_oth_domains),
                    POSSIBLE_STRING(LPSTR_FROM_POINTER(&((struct wksta_info_10*)buffer)->wki10_oth_domains))
                    );
        break;
    }
    DbgPrint("\n");
}

#endif


VOID
VrNetWkstaSetInfo(
    VOID
    )

/*++

Routine Description:

    Performs local NetUseEnum on behalf of the Vdm client

Arguments:

    None.

Return Value:

    None.

--*/

{
#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetWkstaSetInfo\n");
        IF_DEBUG(BREAKPOINT) {
            DbgBreakPoint();
        }
    }
#endif

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrNetWkstaSetInfo - unsupported SVC\n");
    }
#endif

    SET_ERROR(ERROR_NOT_SUPPORTED);
}


VOID
VrReturnAssignMode(
    VOID
    )

/*++

Routine Description:

    Returns net pause/continue status

Arguments:

    Function 5F00h

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
}


VOID
VrSetAssignMode(
    VOID
    )

/*++

Routine Description:

    Pauses or continues net (drive/printer) redirection

Arguments:

    Function 5F01h

    None. All arguments are extracted from the Vdm context registers/memory

Return Value:

    None. Results returned via VDM registers or in VDM memory, according to
    request

--*/

{
}

//
// DefineMacroDriveUserWords - the old DefineMacro call (int 21h/ax=5f03h)
// allows the caller to associate a (16-bit) word value with the assignment.
// This value can be returned from GetAssignListEntry (int 21h/ax=5f02h).
// NetUse doesn't support this, so we fake it
//
// DefineMacroPrintUserWords - same idea for printers; we reserve 8 max
//

static WORD DefineMacroDriveUserWords[26];
static WORD DefineMacroPrintUserWords[8];


VOID
VrGetAssignListEntry(
    VOID
    )

/*++

Routine Description:

    Old version of NetUseGetInfo. In DOS this function performs the following:

        look along CDS list for entry # bx with IS_NET bit set
        if found
            return local device name and remote net name
        else
            look along list of printers for entry # bx
            if found
                return local device name and remote net name
            endif
        endif

        Every time a drive entry is found with IS_NET set or a printer entry
        found, bx is decremented. When bx reaches 0, then that's the entry to
        return

        NOTE: This function DOES NOT support UNC connections

Arguments:

    Function 5F02h (GetAssignList)
    Function 5F05h (GetAssignList2)

    ENTRY   BX = which item to return (starts @ 0)
            DS:SI points to local redirection name
            ES:DI points to remote redirection name
            AL != 0 means return LSN in BP (GetAssignList2)?

Return Value:

    CF = 0
        BL = macro type (3 = printer, 4 = drive)
        BH = 'interesting' bits         ** UNSUPPORTED **
        AX = net name ID                ** UNSUPPORTED **
        CX = user word
        DX = max xmit size              ** UNSUPPORTED **
        BP = LSN if AL != 0 on entry    ** UNSUPPORTED **
        DS:SI has device name
        ES:DI has net path
    CF = 1
        AX = ERROR_NO_MORE_FILES

--*/

{
    NTSTATUS ntstatus;
    NET_API_STATUS status;
    XS_NET_USE_ENUM parameters;
    XS_PARAMETER_HEADER header;
    LPBYTE receiveBuffer;
    DWORD entryNumber;
    struct use_info_1* driveInfo[26];
    struct use_info_1* printInfo[8];    // is overkill, 3 is more like it
    struct use_info_1* infoPtr;
    struct use_info_1* infoBase;
    DWORD index;
    DWORD i;
    LPSTR remoteName;
    WORD userWord;
    DWORD converter;
    WORD wstatus;
    LPSTR dosPointer;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrGetAssignListEntry\n");
        VrDumpRealMode16BitRegisters(FALSE);
    }
#endif

    //
    // maximum possible enumeration buffer size = 26 * (26 + 256 + 3) = 7410
    // which we'll round to 8K, which is overkill. Decided to allocate 2K
    //

#define ASSIGN_LIST_BUFFER_SIZE 2048

    receiveBuffer = (LPBYTE)LocalAlloc(LMEM_FIXED, ASSIGN_LIST_BUFFER_SIZE);
    if (receiveBuffer == NULL) {

        //
        // BUGBUG - possibly incompatible error code
        //

        SET_ERROR((WORD)ERROR_NOT_ENOUGH_MEMORY);
        return;
    }

    parameters.Level  = 1;
    parameters.Buffer = receiveBuffer;
    parameters.BufLen = ASSIGN_LIST_BUFFER_SIZE;

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetUseEnum(&header, &parameters, REM16_use_info_1, NULL);

    //
    // if XsNetUseEnum didn't have any problems, convert the actual status
    // code to that returned in the header
    //

    if (ntstatus != STATUS_SUCCESS) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = (DWORD)header.Status;

        //
        // we really want to brute-force this, so make sure we have all the
        // data
        //

#if DBG

        IF_DEBUG(NETAPI) {
            if (status != NERR_Success) {
                DbgPrint("VrGetAssignListEntry: XsNetUseEnum returns header.Status == %d\n", status);
            }
        }

        if (status == NERR_Success) {
            ASSERT(parameters.EntriesRead == parameters.TotalAvail);
        }

#endif

    }

    entryNumber = getBX();
    if (status == NERR_Success) {

        //
        // only do the following if the bx'th entry is in the list
        //

        if (parameters.EntriesRead > entryNumber) {

            //
            // we need to emulate the action of the DOS Redirector: we need to
            // sort the entries into ascending drive entries followed by
            // ascending printer entries. There were no such things as UNC
            // connections in the original (3.1) version of DOS, so we ignore
            // any in our list. Also ignored are IPC connections and comms
            // connections
            //

            RtlZeroMemory(driveInfo, sizeof(driveInfo));
            RtlZeroMemory(printInfo, sizeof(printInfo));
            infoPtr = (struct use_info_1*)receiveBuffer;

            //
            // XsNetUseEnum returns pointers in the structure as actual offsets
            // from the start of the buffer + a converter word. We have to
            // recalculate the actual pointers as
            //
            //  start of enum buffer + (pointer offset - converter dword)
            //
            // we have to convert the 16-bit converter word to a dword for
            // 32-bit pointer arithmetic
            //        driveInfo[index] = infoBase + ((DWORD)infoPtr->ui1_remote - converter);
            //

            infoBase = infoPtr;
            converter = (DWORD)header.Converter;

            for (i = 0; i < parameters.EntriesRead; ++i) {

                //
                // ignore UNCs - local name is NULL string (\0)
                //

                if (infoPtr->ui1_asg_type == USE_DISKDEV && infoPtr->ui1_local[0]) {
                    index = toupper(infoPtr->ui1_local[0])-'A';
                    driveInfo[index] = infoPtr;

#if DBG
                    IF_DEBUG(NETAPI) {
                        DbgPrint("Index=%d Drive=%s Netname=%s\n",
                                index,
                                infoPtr->ui1_local,
                                (LPSTR)infoBase + ((DWORD)infoPtr->ui1_remote - converter)
                                );
                    }
#endif

                } else if (infoPtr->ui1_asg_type == USE_SPOOLDEV && infoPtr->ui1_local[0]) {

                    //
                    // NOTE: assume there was never, is not, and will never be
                    // such a thing as LPT0:
                    //

                    index = infoPtr->ui1_local[3] - '1';
                    printInfo[index] = infoPtr;

#if DBG
                    IF_DEBUG(NETAPI) {
                        DbgPrint("Index=%d Printer=%s Netname=%s\n",
                                index,
                                infoPtr->ui1_local,
                                (LPSTR)infoBase + ((DWORD)infoPtr->ui1_remote - converter)
                                );
                    }
#endif

                }
                ++infoPtr;
            }

            //
            // now look along the list(s) for the bx'th (in entryNumber) entry
            //

            ++entryNumber;
            for (i = 0; i < ARRAY_ELEMENTS(driveInfo); ++i) {
                if (driveInfo[i]) {
                    --entryNumber;
                    if (!entryNumber) {
                        infoPtr = driveInfo[i];
                        userWord = DefineMacroDriveUserWords[i];
                        break;
                    }
                }
            }

            //
            // if entryNumber was not reduced to 0 then check the printers
            //

            if (entryNumber) {
                for (i = 0; i < ARRAY_ELEMENTS(printInfo); ++i) {
                    if (printInfo[i]) {
                        --entryNumber;
                        if (!entryNumber) {
                            infoPtr = printInfo[i];
                            userWord = DefineMacroPrintUserWords[i];
                            break;
                        }
                    }
                }
            }

            //
            // if entryNumber is 0 then we found the bx'th entry. Return it.
            //

            if (!entryNumber) {

#if DBG
                IF_DEBUG(NETAPI) {
                    DbgPrint("LocalName=%s, RemoteName=%s, UserWord=%04x\n",
                            infoPtr->ui1_local,
                            (LPSTR)infoBase + ((DWORD)infoPtr->ui1_remote - converter),
                            userWord
                            );
                }
#endif

                //
                // copy the strings to DOS memory, making sure to upper case
                // them and convert / to \.
                //

                strcpy(POINTER_FROM_WORDS(getDS(), getSI()), infoPtr->ui1_local);
                dosPointer = LPSTR_FROM_WORDS(getES(), getDI());
                remoteName = (LPSTR)infoBase + ((DWORD)infoPtr->ui1_remote - converter);
                wstatus = VrpTranslateDosNetPath(&remoteName, &dosPointer);

#if DBG
                IF_DEBUG(NETAPI) {
                    if (wstatus != 0) {
                        DbgPrint("VrGetAssignListEntry: wstatus == %d\n", wstatus);
                    }
                }
#endif

                setBL((BYTE)(infoPtr->ui1_asg_type == 0 ? 4 : 3));
                setCX(userWord);

                //
                // return some innocuous (?!) values for the unsupported
                // returned parameters
                //

                setBH((BYTE)(infoPtr->ui1_status ? 1 : 0));   // 'interesting' bits (?)
            } else {
                status = ERROR_NO_MORE_FILES;
            }
        } else {
            status = ERROR_NO_MORE_FILES;
        }
    }

    //
    // only return carry clear if no error occurred. Even if ERROR_MORE_DATA
    // set CF
    //

    if (status) {
        SET_ERROR(VrpMapDosError(status));
    } else {
        setCF(0);
    }

    //
    // free resources
    //

    LocalFree(receiveBuffer);
}


VOID
VrDefineMacro(
    VOID
    )

/*++

Routine Description:

    Old version of NetUseAdd. Convert to NetUseAdd

Arguments:

    Function 5F03h

    ENTRY   BL = device type
                3 = printer
                4 = drive
            bit 7 on means use the wksta password when connecting   ** UNSUPPORTED **
            CX = user word
            DS:SI = local device
                Can be NUL device name, indicating UNC use
            ES:DI = remote name

Return Value:

    CF = 0
        success

    CF = 1
        AX = ERROR_INVALID_PARAMETER (87)
             ERROR_INVALID_PASSWORD (86)
             ERROR_INVALID_DRIVE (15)
             ERROR_ALREADY_ASSIGNED (85)
             ERROR_PATH_NOT_FOUND (3)
             ERROR_ACCESS_DENIED (5)
             ERROR_NOT_ENOUGH_MEMORY (8)
             ERROR_NO_MORE_FILES (18)
             ERROR_REDIR_PAUSED (72)

--*/

{
    NET_API_STATUS status;
    XS_NET_USE_ADD parameters;
    XS_PARAMETER_HEADER header;
    NTSTATUS ntstatus;
    BYTE bl;
    LPSTR netStringPointer;
    WORD index;

    //
    // modifiable descriptor string
    //

    char descriptor[sizeof(REM16_use_info_1)];

    //
    // buffer for use_info_1 plus remote string plus password
    //

    char useBuffer[sizeof(struct use_info_1) + LM20_PATHLEN + 1 + LM20_PWLEN + 1];
    WORD wstatus;
    LPBYTE variableData;
    DWORD len;
    LPSTR dosString;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrDefineMacro \"%s\" == \"%s\"\n",
                 LPSTR_FROM_WORDS(getDS(), getSI()),
                 LPSTR_FROM_WORDS(getES(), getDI())
                 );
    }
#endif

    bl = getBL();
    if (bl == 3) {
        ((struct use_info_1*)useBuffer)->ui1_asg_type = 1;  // USE_SPOOLDEV
    } else if (bl == 4) {
        ((struct use_info_1*)useBuffer)->ui1_asg_type = 0;  // USE_DISKDEV
    } else {
        SET_ERROR(ERROR_INVALID_PARAMETER);
        return;
    }

    //
    // copy the standard 16-bit use_info_1 structure descriptor to the
    // modifiable descriptor string: if we discover a NUL password then we
    // set the ui1_password field to NULL and the corresponding descriptor
    // character to 'O'
    //

    strcpy(descriptor, REM16_use_info_1);

    //
    // check the local name length
    //

    dosString = LPSTR_FROM_WORDS(getDS(), getSI());
    if (dosString) {
        if ((len = strlen(dosString) + 1) > LM20_DEVLEN + 1) {
            SET_ERROR(ERROR_INVALID_PARAMETER);
            return;
        }

        //
        // copy the local device name into the use_info_1 structure
        //

        RtlCopyMemory(((struct use_info_1*)useBuffer)->ui1_local, dosString, len);

        //
        // BUGBUG - Code Page, Kanji, DBCS, Locale?
        //

        _strupr(((struct use_info_1*)useBuffer)->ui1_local);
    } else {
        ((struct use_info_1*)useBuffer)->ui1_local[0] = 0;
    }

    //
    // copy the remote name to the end of the use_info_1 structure. If there's
    // an error, return it
    //

    netStringPointer = POINTER_FROM_WORDS(getES(), getDI());
    variableData = (LPBYTE)&((struct use_info_1*)useBuffer)[1];
    ((struct use_info_1*)useBuffer)->ui1_remote = variableData;
    wstatus = VrpTranslateDosNetPath(&netStringPointer, &variableData);
    if (wstatus) {
        SET_ERROR(wstatus);
        return;
    }

    //
    // if there was a password with this remote name, copy it to the end of
    // the variable data area
    //

    if (*netStringPointer) {
        if ((len = strlen(netStringPointer) + 1) > LM20_PWLEN + 1) {
            SET_ERROR(ERROR_INVALID_PASSWORD);
            return;
        } else {
            ((struct use_info_1*)useBuffer)->ui1_password = netStringPointer;
            RtlCopyMemory(variableData, netStringPointer, len);
        }
    } else {

        //
        // there is no password - set the password pointer field to NULL and
        // change the descriptor character for this field to 'O' signifying
        // that there will be no string in the variable data for this field
        //

        ((struct use_info_1*)useBuffer)->ui1_password = NULL;
        descriptor[4] = REM_NULL_PTR;   // 'O'
    }

    parameters.Level = 1;
    parameters.Buffer = useBuffer;
    parameters.BufLen = sizeof(useBuffer);

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetUseAdd(&header, &parameters, descriptor, NULL);

    if (!NT_SUCCESS(ntstatus)) {
        status = NetpNtStatusToApiStatus(ntstatus);

#if DBG
        if (!NT_SUCCESS(ntstatus)) {
            IF_DEBUG(NETAPI) {
                DbgPrint("VrDefineMacro: Error: XsNetUseAdd returns %x\n", ntstatus);
            }
        }
#endif

    } else {

        //
        // no error generated in XsNetUseAdd. Get the status of the NetUseAdd
        // proper from the header
        //

        status = (NET_API_STATUS)header.Status;
    }
    if (status != NERR_Success) {

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("Error: VrDefineMacro: XsNetUseAdd returns %u\n", status);
        }
#endif

        SET_ERROR((WORD)status);
    } else {

        //
        // set the user word in the appropriate list
        //

        if (bl == 3) {
            index = ((struct use_info_1*)useBuffer)->ui1_local[3] - '0';
            DefineMacroPrintUserWords[index] = getCX();
        } else if (((struct use_info_1*)useBuffer)->ui1_local[0]) {

            //
            // note that we already upper-cased the device name
            //

            index = ((struct use_info_1*)useBuffer)->ui1_local[0] - 'A';
            DefineMacroDriveUserWords[index] = getCX();
        }

        //
        // BUGBUG - don't record user word for UNC connections????
        //

        setCF(0);
    }
}


VOID
VrBreakMacro(
    VOID
    )

/*++

Routine Description:

    Old version of NetUseDel. Convert to NetUseDel

Arguments:

    Function 5F04h

    ENTRY   DS:SI = buffer containing device name of redirection to break

Return Value:

    CF = 0
        success

    CF = 1
        AX = ERROR_PATH_NOT_FOUND (3)
             ERROR_ACCESS_DENIED (5)
             ERROR_NOT_ENOUGH_MEMORY (8)
             ERROR_REDIR_PAUSED (72)
             ERROR_NO_MORE_FILES (18)

--*/

{
    NTSTATUS ntstatus;
    NET_API_STATUS status;
    XS_NET_USE_DEL parameters;
    XS_PARAMETER_HEADER header;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrBreakMacro %s\n", LPSTR_FROM_WORDS(getDS(), getSI()));
    }
#endif

    parameters.UseName = LPSTR_FROM_WORDS(getDS(), getSI());
    parameters.Force = USE_LOTS_OF_FORCE;

    header.Status = 0;
    header.ClientMachineName = NULL;
    header.ClientTransportName = NULL;

    ntstatus = XsNetUseDel(&header, &parameters, NULL, NULL);

    //
    // if XsNetUseDel failed then map the NT error returned into a Net error
    // else get the result of the NetUseDel proper from the header structure
    //

    if (ntstatus != STATUS_SUCCESS) {
        status = NetpNtStatusToApiStatus(ntstatus);
    } else {
        status = (NET_API_STATUS)header.Status;
        if (status != NERR_Success) {
            SET_ERROR(VrpMapDosError(status));
        } else {
            setCF(0);
        }
    }
}


//
// private routines
//

NET_API_STATUS
VrpTransactVdm(
    IN BOOL NullSessionFlag
    )

/*++

Routine Description:

    Performs transaction request for NetTransactAPI and NetNullTransactAPI

Arguments:

    NullSessionFlag - TRUE if the transaction request will use a NULL session

    VDM DS:SI points at a transaction descriptor structure:

        far pointer to transaction name (\\COMPUTER\PIPE\LANMAN)
        far pointer to password for connection
        far pointer to send parameter buffer
        far pointer to send data buffer
        far pointer to receive set-up buffer
        far pointer to receive parameter buffer
        far pointer to receive data buffer
        unsigned short send parameter buffer length
        unsigned short send data buffer length
        unsigned short receive parameter buffer length
        unsigned short receive data buffer length
        unsigned short receive set-up buffer length
        unsigned short flags
        unsigned long  timeout
        unsigned short reserved
        unsigned short send set-up buffer length

Return Value:

    NET_API_STATUS
        Success - NERR_Success
        Failure - return code from RxpTransactSmb

--*/

{
    struct tr_packet* transactionPacket;
    DWORD receiveBufferLen;
    NET_API_STATUS status;
    char computerName[LM20_UNCLEN+1];
    LPSTR pipeName;
    DWORD i;
    LPWSTR uncName;
    UNICODE_STRING uString;
    ANSI_STRING aString;
    NTSTATUS ntstatus;
    LPBYTE parameterBuffer;
    LPBYTE pSendParameters;
    LPBYTE pReceiveParameters;
    WORD sendParameterLen;
    WORD receiveParameterLen;
    WORD apiNumber;

#if DBG
    BOOL dumpRxData;
    IF_DEBUG(NETAPI) {
        DbgPrint("VrpTransactVdm: tr_packet @ %04x:%04x\n", getDS(), getSI());
    }
#endif

    transactionPacket = (struct tr_packet*)POINTER_FROM_WORDS(getDS(), getSI());

#if DBG
    IF_DEBUG(NETAPI) {
        DumpTransactionPacket(transactionPacket, TRUE, TRUE);
    }
#endif

    receiveBufferLen = (DWORD)READ_WORD(&transactionPacket->tr_rdlen);

    //
    // try to extract the UNC computer name from the pipe name
    //

    pipeName = LPSTR_FROM_POINTER(&transactionPacket->tr_name);
    if (IS_ASCII_PATH_SEPARATOR(pipeName[0]) && IS_ASCII_PATH_SEPARATOR(pipeName[1])) {
        computerName[0] = computerName[1] = '\\';
        for (i = 2; i < sizeof(computerName)-1; ++i) {
            if (IS_ASCII_PATH_SEPARATOR(pipeName[i])) {
                break;
            }
            computerName[i] = pipeName[i];
        }
        if (IS_ASCII_PATH_SEPARATOR(pipeName[i])) {
            computerName[i] = '\0';
            pipeName = computerName;
        }
    }

    RtlInitAnsiString(&aString, pipeName);
    ntstatus = RtlAnsiStringToUnicodeString(&uString, &aString, (BOOLEAN)TRUE);
    if (!NT_SUCCESS(ntstatus)) {

#if DBG
        IF_DEBUG(NETAPI) {
            DbgPrint("VrpTransactVdm: Unexpected situation: RtlAnsiStringToUnicodeString returns %x\n", ntstatus);
        }
#endif

        return ERROR_NOT_ENOUGH_MEMORY;
    }
    uncName = uString.Buffer;

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrpTransactVdm: UncName=%ws\n", uncName);
    }
#endif

    //
    // if the app supplies different send and receive parameter buffer pointers
    // we have to collapse them into the same buffer
    //

    pSendParameters = LPBYTE_FROM_POINTER(&transactionPacket->tr_spbuf);
    pReceiveParameters = LPBYTE_FROM_POINTER(&transactionPacket->tr_rpbuf);
    sendParameterLen = READ_WORD(&transactionPacket->tr_splen);
    receiveParameterLen = READ_WORD(&transactionPacket->tr_rplen);
    if (pSendParameters != pReceiveParameters) {
        parameterBuffer = (LPBYTE)LocalAlloc(
                                    LMEM_FIXED,
                                    max(sendParameterLen, receiveParameterLen)
                                    );
        if (parameterBuffer == NULL) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
        RtlMoveMemory(parameterBuffer, pSendParameters, sendParameterLen);
        pSendParameters = pReceiveParameters = parameterBuffer;
    } else {
        parameterBuffer = NULL;
    }

    //
    // in the case of remoted NetUserAdd2, NetUserPasswordSet2 and NetUserSetInfo2
    // we have to encrypt any passwords if not already encrypted. We will change
    // data in the parameter and send data buffer. Since we assume that this call
    // is coming from the NET function library and not from the app, it should
    // be okay to modify these buffers and not restore them before this function
    // is complete
    //

    apiNumber = READ_WORD(pSendParameters);
    if (apiNumber == API_WUserAdd2
    || apiNumber == API_WUserPasswordSet2
    || apiNumber == API_WUserSetInfo2) {

        LPBYTE parameterPointer = pSendParameters + sizeof(WORD);
        LPBYTE passwordPointer;
        DWORD parmNum = PARMNUM_ALL;

        //
        // skip over parameter descriptor and data descriptor
        //

        parameterPointer += strlen(parameterPointer) + 1;
        parameterPointer += strlen(parameterPointer) + 1;

        //
        // the next thing in the parameter buffer for SetInfo2 and PasswordSet2
        // is the user name: skip it
        //

        if (apiNumber != API_WUserAdd2) {
            parameterPointer += strlen(parameterPointer) + 1;
        }

        //
        // if this is PasswordSet2 then parameterPointer is pointing at the
        // old and new passwords. Remember this address and scan forward to
        // the password encryption flag/new cleartext password length
        //
        // if this is AddUser2, we are pointing at the level which we are not
        // interested in; skip forward to the encryption flag/cleartext password
        // length
        //
        // if this is SetInfo2, we are pointing at the level which we are not
        // interested in; skip forward to the parmnum. Record that. Then skip
        // forward again to the encryption flag/cleartext password length
        //

        if (apiNumber == API_WUserPasswordSet2) {
            passwordPointer = parameterPointer;
            parameterPointer += ENCRYPTED_PWLEN * 2;
        } else {
            parameterPointer += sizeof(WORD);
            if (apiNumber == API_WUserSetInfo2) {
                parmNum = (DWORD)READ_WORD(parameterPointer);
                parameterPointer += sizeof(WORD);
            }

            //
            // in the case of NetUserAdd2 and NetUserSetInfo2, the data buffer
            // contains the password. If the SetInfo2 is using PARMNUM_ALL then
            // the password is in the same place as for AddUser2: in a user_info_1
            // or user_info_2 structure. Luckily, the password is at the same
            // offset for both structures.
            //
            // If this is SetInfo2 with USER_PASSWORD_PARMNUM then the send data
            // pointer points at the password
            //

            passwordPointer = LPBYTE_FROM_POINTER(&transactionPacket->tr_sdbuf);
            if (parmNum == PARMNUM_ALL) {
                passwordPointer += (DWORD)&((struct user_info_1*)0)->usri1_password;
            }
        }

        //
        // only perform encryption if parmNum is PARMNUM_ALL or USER_PASSWORD_PARMNUM
        //

        if (parmNum == PARMNUM_ALL || parmNum == USER_PASSWORD_PARMNUM) {

            //
            // in all cases, parameterPointer points at the encryption flag
            //

            if (!READ_WORD(parameterPointer)) {

                WORD cleartextLength;

                //
                // the password(s) is (are) not already encrypted (surprise!). We
                // have to do it. If encryption fails for any reason, return an
                // internal error. We do not want to fail-back to putting clear-text
                // passwords on the wire in this case
                //

                cleartextLength = (WORD)strlen(passwordPointer);

                //
                // NetUserPasswordSet2 requires a different method than the
                // other 2
                //

                if (apiNumber == API_WUserPasswordSet2) {

                    NTSTATUS ntStatus;
                    LPBYTE oldPasswordPointer = passwordPointer;
                    ENCRYPTED_LM_OWF_PASSWORD oldEncryptedWithNew;
                    ENCRYPTED_LM_OWF_PASSWORD newEncryptedWithOld;

                    ntStatus = RtlCalculateLmOwfPassword(
                                    passwordPointer,
                                    (PLM_OWF_PASSWORD)passwordPointer
                                    );
                    if (!NT_SUCCESS(ntStatus)) {
                        status = NERR_InternalError;
                        goto VrpTransactVdm_exit;
                    }
                    passwordPointer += ENCRYPTED_PWLEN;
                    cleartextLength = (WORD)strlen(passwordPointer);
                    ntStatus = RtlCalculateLmOwfPassword(
                                    passwordPointer,
                                    (PLM_OWF_PASSWORD)passwordPointer
                                    );
                    if (!NT_SUCCESS(ntStatus)) {
                        status = NERR_InternalError;
                        goto VrpTransactVdm_exit;
                    }

                    //
                    // for PasswordSet2, we need to double-encrypt the passwords
                    //

                    ntStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
                                    (PLM_OWF_PASSWORD)oldPasswordPointer,
                                    (PLM_OWF_PASSWORD)passwordPointer,
                                    &oldEncryptedWithNew
                                    );
                    if (!NT_SUCCESS(ntStatus)) {
                        status = NERR_InternalError;
                        goto VrpTransactVdm_exit;
                    }
                    ntStatus = RtlEncryptLmOwfPwdWithLmOwfPwd(
                                    (PLM_OWF_PASSWORD)passwordPointer,
                                    (PLM_OWF_PASSWORD)oldPasswordPointer,
                                    &newEncryptedWithOld
                                    );
                    if (!NT_SUCCESS(ntStatus)) {
                        status = NERR_InternalError;
                        goto VrpTransactVdm_exit;
                    }
                    RtlCopyMemory(oldPasswordPointer,
                                  &oldEncryptedWithNew,
                                  sizeof(oldEncryptedWithNew)
                                  );
                    RtlCopyMemory(passwordPointer,
                                  &newEncryptedWithOld,
                                  sizeof(newEncryptedWithOld)
                                  );
                } else {
                    if (!EncryptPassword(uncName, passwordPointer)) {
                        status = NERR_InternalError;
                        goto VrpTransactVdm_exit;
                    }
                }

                //
                // set the password encrypted flag in the parameter buffer
                //

                WRITE_WORD(parameterPointer, 1);

                //
                // record the length of the cleartext password (the new one in case
                // of PasswordSet2)
                //

                WRITE_WORD(parameterPointer + sizeof(WORD), cleartextLength);
            }
        }
    }

    status = RxpTransactSmb(
                (LPTSTR)uncName,
                NULL,           // transport name
                pSendParameters,
                (DWORD)sendParameterLen,
                LPBYTE_FROM_POINTER(&transactionPacket->tr_sdbuf),
                (DWORD)READ_WORD(&transactionPacket->tr_sdlen),
                pReceiveParameters,
                (DWORD)receiveParameterLen,
                LPBYTE_FROM_POINTER(&transactionPacket->tr_rdbuf),
                &receiveBufferLen,
                NullSessionFlag
                );

    //
    // if we received data, set the received data length in the structure
    //

    if (status == NERR_Success || status == ERROR_MORE_DATA) {
        WRITE_WORD(&transactionPacket->tr_rdlen, receiveBufferLen);
    }

    //
    // if we munged the parameter buffer then copy the returned parameters to
    // the app's supplied buffer
    //

    if (parameterBuffer) {
        RtlMoveMemory(LPBYTE_FROM_POINTER(&transactionPacket->tr_rpbuf),
                      pReceiveParameters,
                      receiveParameterLen
                      );
    }

#if DBG
    IF_DEBUG(NETAPI) {
        DbgPrint("VrpTransactVdm: returning %d\n\n", status);
        if (status == NERR_Success || status == ERROR_MORE_DATA) {
            dumpRxData = TRUE;
        } else {
            dumpRxData = FALSE;
        }
        DumpTransactionPacket(transactionPacket, FALSE, dumpRxData);
    }
#endif

VrpTransactVdm_exit:

    RtlFreeUnicodeString(&uString);

    if (parameterBuffer) {
        LocalFree((HLOCAL)parameterBuffer);
    }

    return status;
}


BOOL
EncryptPassword(
    IN LPWSTR ServerName,
    IN OUT LPBYTE Password
    )

/*++

Routine Description:

    Encrypts an ANSI password

Arguments:

    ServerName  - pointer to UNICODE server name. Server is where we are going
                  to send the encrypted password
    Password    - pointer to buffer containing on input an ANSI password (<= 14
                  characters, plus NUL), and on output contains the 16-byte
                  encrypted password

Return Value:

    BOOL
        TRUE    - Password has been encrypted
        FALSE   - couldn't encrypt password. Password is in unknown state

--*/

{
    NTSTATUS ntStatus;
    LM_OWF_PASSWORD lmOwfPassword;
    LM_SESSION_KEY lanmanKey;

    _strupr(Password);
    ntStatus = RtlCalculateLmOwfPassword(Password, &lmOwfPassword);
    if (NT_SUCCESS(ntStatus)) {
        ntStatus = GetLanmanSessionKey(ServerName, (LPBYTE)&lanmanKey);
        if (NT_SUCCESS(ntStatus)) {
            ntStatus = RtlEncryptLmOwfPwdWithLmSesKey(&lmOwfPassword,
                                                      &lanmanKey,
                                                      (PENCRYPTED_LM_OWF_PASSWORD)Password
                                                      );
        }
    }
    return NT_SUCCESS(ntStatus);
}

#if DBG
PRIVATE
VOID
DumpTransactionPacket(
    IN struct tr_packet* TransactionPacket,
    IN BOOL IsInput,
    IN BOOL DumpData
    )
{
    LPBYTE password;
    WORD parmSeg;
    WORD parmOff;
    WORD dataSeg;
    WORD dataOff;
    DWORD parmLen;
    DWORD dataLen;
    char passwordBuf[8*3+1];

    password = LPBYTE_FROM_POINTER(&TransactionPacket->tr_passwd);
    if (password) {
        sprintf(passwordBuf, "%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x",
                password[0],
                password[1],
                password[2],
                password[3],
                password[4],
                password[5],
                password[6],
                password[7]
                );
    } else {
        passwordBuf[0] = 0;
    }

    DbgPrint(   "DumpTransactionPacket(%08x)\n"
                "name               %04x:%04x \"%s\"\n"
                "password           %04x:%04x %s\n"
                "send parm buffer   %04x:%04x\n"
                "send data buffer   %04x:%04x\n"
                "rcv setup buffer   %04x:%04x\n"
                "rcv parm buffer    %04x:%04x\n"
                "rcv data buffer    %04x:%04x\n"
                "send parm len      %04x\n"
                "send data len      %04x\n"
                "rcv parm len       %04x\n"
                "rcv data len       %04x\n"
                "rcv setup len      %04x\n"
                "flags              %04x\n"
                "timeout            %08x (%d)\n"
                "reserved           %04x\n"
                "send setup len     %04x\n"
                "\n",
                TransactionPacket,
                GET_SEGMENT(&TransactionPacket->tr_name),
                GET_OFFSET(&TransactionPacket->tr_name),
                LPSTR_FROM_POINTER(&TransactionPacket->tr_name),
                GET_SEGMENT(&TransactionPacket->tr_passwd),
                GET_OFFSET(&TransactionPacket->tr_passwd),
                passwordBuf,
                GET_SEGMENT(&TransactionPacket->tr_spbuf),
                GET_OFFSET(&TransactionPacket->tr_spbuf),
                GET_SEGMENT(&TransactionPacket->tr_sdbuf),
                GET_OFFSET(&TransactionPacket->tr_sdbuf),
                GET_SEGMENT(&TransactionPacket->tr_rsbuf),
                GET_OFFSET(&TransactionPacket->tr_rsbuf),
                GET_SEGMENT(&TransactionPacket->tr_rpbuf),
                GET_OFFSET(&TransactionPacket->tr_rpbuf),
                GET_SEGMENT(&TransactionPacket->tr_rdbuf),
                GET_OFFSET(&TransactionPacket->tr_rdbuf),
                READ_WORD(&TransactionPacket->tr_splen),
                READ_WORD(&TransactionPacket->tr_sdlen),
                READ_WORD(&TransactionPacket->tr_rplen),
                READ_WORD(&TransactionPacket->tr_rdlen),
                READ_WORD(&TransactionPacket->tr_rslen),
                READ_WORD(&TransactionPacket->tr_flags),
                READ_DWORD(&TransactionPacket->tr_timeout),
                READ_DWORD(&TransactionPacket->tr_timeout),
                READ_WORD(&TransactionPacket->tr_resvd),
                READ_WORD(&TransactionPacket->tr_sslen)
                );
    if (IsInput) {
        parmLen = (DWORD)READ_WORD(&TransactionPacket->tr_splen);
        dataLen = (DWORD)READ_WORD(&TransactionPacket->tr_sdlen);
        parmSeg = GET_SEGMENT(&TransactionPacket->tr_spbuf);
        parmOff = GET_OFFSET(&TransactionPacket->tr_spbuf);
        dataSeg = GET_SEGMENT(&TransactionPacket->tr_sdbuf);
        dataOff = GET_OFFSET(&TransactionPacket->tr_sdbuf);
    } else {
        parmLen = (DWORD)READ_WORD(&TransactionPacket->tr_rplen);
        dataLen = (DWORD)READ_WORD(&TransactionPacket->tr_rdlen);
        parmSeg = GET_SEGMENT(&TransactionPacket->tr_rpbuf);
        parmOff = GET_OFFSET(&TransactionPacket->tr_rpbuf);
        dataSeg = GET_SEGMENT(&TransactionPacket->tr_rdbuf);
        dataOff = GET_OFFSET(&TransactionPacket->tr_rdbuf);
    }
    if (DumpData) {
        if (IsInput) {
            IF_DEBUG(TRANSACT_TX) {
                if (parmLen) {
                    DbgPrint("Send Parameters:\n");
                    VrDumpDosMemory('B', parmLen, parmSeg, parmOff);
                }
                if (dataLen) {
                    DbgPrint("Send Data:\n");
                    VrDumpDosMemory('B', dataLen, dataSeg, dataOff);
                }
            }
        } else {
            IF_DEBUG(TRANSACT_RX) {
                if (parmLen) {
                    DbgPrint("Received Parameters:\n");
                    VrDumpDosMemory('B', parmLen, parmSeg, parmOff);
                }
                if (dataLen) {
                    DbgPrint("Received Data:\n");
                    VrDumpDosMemory('B', dataLen, dataSeg, dataOff);
                }
            }
        }
    }
}
#endif