/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    display.c

Abstract:

    NetQueryDisplay Information and NetGetDisplayInformationIndex API functions

Author:

    Cliff Van Dyke (cliffv) 14-Dec-1994

Environment:

    User mode only.
    Contains NT-specific code.
    Requires ANSI C extensions: slash-slash comments, long external names.

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#undef DOMAIN_ALL_ACCESS // defined in both ntsam.h and ntwinapi.h
#include <ntsam.h>
#include <ntlsa.h>

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

#include <accessp.h>
#include <align.h>
// #include <lmapibuf.h>
#include <lmaccess.h>
#include <lmerr.h>
// #include <limits.h>
#include <netdebug.h>
#include <netlib.h>
#include <netlibnt.h>
#include <rpcutil.h>
// #include <rxuser.h>
// #include <secobj.h>
// #include <stddef.h>
#include <uasp.h>

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

/*++

Routine Description:

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

Arguments:

    Level - Level of information in the  buffer.

    BufferDescriptor - Description of the new buffer.

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

Return Value:

    Returns the error code for the operation.

--*/

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

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

    switch (Level) {
    case 1:
        FixedSize = sizeof(NET_DISPLAY_USER);
        break;
    case 2:
        FixedSize = sizeof(NET_DISPLAY_MACHINE);
        break;
    case 3:
        FixedSize = sizeof(NET_DISPLAY_GROUP);
        break;

    default:
        NetpAssert( FALSE );
        return;

    }

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

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

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

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

        switch (Level) {
        case 1:
            RELOCATE_ONE( ((PNET_DISPLAY_USER)TheStruct)->usri1_name, Offset );
            RELOCATE_ONE( ((PNET_DISPLAY_USER)TheStruct)->usri1_comment, Offset );
            RELOCATE_ONE( ((PNET_DISPLAY_USER)TheStruct)->usri1_full_name, Offset );
            break;

        case 2:
            RELOCATE_ONE( ((PNET_DISPLAY_MACHINE)TheStruct)->usri2_name, Offset );
            RELOCATE_ONE( ((PNET_DISPLAY_MACHINE)TheStruct)->usri2_comment, Offset );
            break;

        case 3:
            RELOCATE_ONE( ((PNET_DISPLAY_GROUP)TheStruct)->grpi3_name, Offset );
            RELOCATE_ONE( ((PNET_DISPLAY_GROUP)TheStruct)->grpi3_comment, Offset );
            break;

        default:
            return;

        }
    }

    return;

} // DisplayRelocationRoutine



NET_API_STATUS NET_API_FUNCTION
NetQueryDisplayInformation(
    IN LPCWSTR ServerName OPTIONAL,
    IN DWORD Level,
    IN DWORD Index,
    IN DWORD EntriesRequested,
    IN DWORD PreferredMaximumLength,
    OUT LPDWORD ReturnedEntryCount,
    OUT PVOID   *SortedBuffer )

/*++

Routine Description:

    This routine provides fast return of information commonly
    needed to be displayed in user interfaces.

    NT User Interface has a requirement for quick enumeration of
    accounts for display in list boxes.

    This API is remotable. The Server can be any NT workstation or server.
    The server cannot be a Lanman or WFW machine.

    NT 3.1 workstations and servers do not support Level 3.
    NT 3.1 workstations and servers do not support an infinitely large
    EntriesRequested.


Parameters:


    ServerName - A pointer to a string containing the name of the remote
        server on which the function is to execute.  A NULL pointer
        or string specifies the local machine.

    Level - Level of information provided.  Must be

            1 --> Return all Local and Global (normal) user accounts
            2 --> Return all Workstation and Server (BDC) user accounts
            3 --> Return all Global Groups.

    Index - The index of the first entry to be retrieved.  Pass zero on
        the first call. Pass the 'next_index' field of the last entry
        returned on the previous call.

    EntriesRequested - Specifies an upper limit on the number of entries
        to be returned .

    PreferedMaximumLength - A recommended upper limit to the number of
        bytes to be returned.  The returned information is allocated by
        this routine.

    ReturnedEntryCount - Number of entries returned by this call.  Zero
        indicates there are no entries with an index as large as that
        specified.

        Entries may be returned for a return status of either NERR_Success
        or ERROR_MORE_DATA.

    SortedBuffer - Receives a pointer to a buffer containing a sorted
        list of the requested information.  This buffer is allocated
        by this routine and contains the following structure:

            1 --> An array of ReturnedEntryCount elements of type
                  NET_DISPLAY_USER.  This is followed by the bodies of the
                  various strings pointed to from within the
                  NET_DISPLAY_USER structures.

            2 --> An array of ReturnedEntryCount elements of type
                  NET_DISPLAY_MACHINE.  This is followed by the bodies of the
                  various strings pointed to from within the
                  NET_DISPLAY_MACHINE structures.

            3 --> An array of ReturnedEntryCount elements of type
                  NET_DISPLAY_GROUP.  This is followed by the bodies of the
                  various strings pointed to from within the
                  NET_DISPLAY_GROUP structures.


Return Values:

    NERR_Success - normal, successful completion.  There are no more entries
        to be returned.

    ERROR_ACCESS_DENIED - The caller doesn't have access to the requested
        information.

    ERROR_INVALID_LEVEL - The requested level of information
        is not legitimate for this service.

    ERROR_MORE_DATA - More entries are available.  That is, the last entry
        returned in SortedBuffer is not the last entry available.  More
        entries will be returned by calling again with the Index parameter
        set to the 'next_index' field of the last entry in SortedBuffer.


--*/
{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;
    NET_API_STATUS SavedStatus;

    BUFFER_DESCRIPTOR BufferDescriptor;
    DWORD i;

    SAM_HANDLE  DomainHandle = NULL;

    DOMAIN_DISPLAY_INFORMATION DisplayInformation;
    DWORD FixedSize;
    LPBYTE FixedData;


    DWORD SamTotalBytesAvailable;
    DWORD SamTotalBytesReturned;
    DWORD SamReturnedEntryCount;
    PVOID SamSortedBuffer;

    //
    // Validate Level parameter
    //

    *ReturnedEntryCount = 0;
    *SortedBuffer = NULL;
    BufferDescriptor.Buffer = NULL;

    switch (Level) {
    case 1:
        DisplayInformation = DomainDisplayUser;
        FixedSize = sizeof(NET_DISPLAY_USER);
        break;
    case 2:
        DisplayInformation = DomainDisplayMachine;
        FixedSize = sizeof(NET_DISPLAY_MACHINE);
        break;
    case 3:
        DisplayInformation = DomainDisplayGroup;
        FixedSize = sizeof(NET_DISPLAY_GROUP);
        break;

    default:
        return ERROR_INVALID_LEVEL;

    }

    //
    // Open the Account Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LIST_ACCOUNTS,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                NULL );

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

    //
    // Pass the call to SAM
    //

    Status = SamQueryDisplayInformation (
                        DomainHandle,
                        DisplayInformation,
                        Index,
                        EntriesRequested,
                        PreferredMaximumLength,
                        &SamTotalBytesAvailable,
                        &SamTotalBytesReturned,
                        &SamReturnedEntryCount,
                        &SamSortedBuffer );

    if ( !NT_SUCCESS( Status ) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpKdPrint((
                "NetQueryDisplayInformation: SamQueryDisplayInformation returned %lX\n",
                Status ));
        }

        //
        // NT 3.1 systems returned STATUS_NO_MORE_ENTRIES if Index is too large.
        //

        if ( Status == STATUS_NO_MORE_ENTRIES ) {
            NetStatus = NERR_Success;
            goto Cleanup;
        }
        NetStatus = NetpNtStatusToApiStatus( Status );
        goto Cleanup;
    }

    //
    // Remember what status to return on success.
    //

    if ( Status == STATUS_MORE_ENTRIES ) {
        SavedStatus = ERROR_MORE_DATA;
    } else {
        SavedStatus = NERR_Success;
    }

    //
    // Loop for each entry
    //

    for ( i = 0; i < SamReturnedEntryCount; i++ ) {

        PDOMAIN_DISPLAY_USER DomainDisplayUser;
        PDOMAIN_DISPLAY_MACHINE DomainDisplayMachine;
        PDOMAIN_DISPLAY_GROUP DomainDisplayGroup;

        DWORD Size;


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

        Size = FixedSize;
        switch (Level) {
        case 1:
            DomainDisplayUser = &((PDOMAIN_DISPLAY_USER)(SamSortedBuffer))[i];
            Size += DomainDisplayUser->LogonName.Length + sizeof(WCHAR) +
                    DomainDisplayUser->AdminComment.Length + sizeof(WCHAR) +
                    DomainDisplayUser->FullName.Length + sizeof(WCHAR);
            break;

        case 2:
            DomainDisplayMachine = &((PDOMAIN_DISPLAY_MACHINE)(SamSortedBuffer))[i];
            Size += DomainDisplayMachine->Machine.Length + sizeof(WCHAR) +
                    DomainDisplayMachine->Comment.Length + sizeof(WCHAR);
            break;
        case 3:
            DomainDisplayGroup = &((PDOMAIN_DISPLAY_GROUP)(SamSortedBuffer))[i];

            Size += DomainDisplayGroup->Group.Length + sizeof(WCHAR) +
                    DomainDisplayGroup->Comment.Length + sizeof(WCHAR);
            break;

        default:
            NetStatus = ERROR_INVALID_LEVEL;
            goto Cleanup;

        }

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

        Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );

        NetStatus = NetpAllocateEnumBuffer(
                        &BufferDescriptor,
                        FALSE,      // Enumeration
                        0xFFFFFFFF, // PrefMaxLen (already limited by SAM)
                        Size,
                        DisplayRelocationRoutine,
                        Level );

        if (NetStatus != NERR_Success) {

            //
            // NetpAllocateEnumBuffer returns ERROR_MORE_DATA if this
            // entry doesn't fit into the buffer.
            //
            if ( NetStatus == ERROR_MORE_DATA ) {
                NetStatus = NERR_InternalError;
            }

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

            goto Cleanup;
        }

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

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


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

        FixedData = BufferDescriptor.FixedDataEnd;
        BufferDescriptor.FixedDataEnd += FixedSize;

        switch (Level) {
        case 1: {
            PNET_DISPLAY_USER NetDisplayUser = (PNET_DISPLAY_USER)FixedData;

            COPY_STRING( &NetDisplayUser->usri1_name,
                         DomainDisplayUser->LogonName );

            COPY_STRING( &NetDisplayUser->usri1_comment,
                         DomainDisplayUser->AdminComment );

            NetDisplayUser->usri1_flags = NetpAccountControlToFlags(
                                    DomainDisplayUser->AccountControl,
                                    NULL );

            COPY_STRING( &NetDisplayUser->usri1_full_name,
                         DomainDisplayUser->FullName );

            NetDisplayUser->usri1_user_id = DomainDisplayUser->Rid;
            NetDisplayUser->usri1_next_index = DomainDisplayUser->Index;

            break;
        }

        case 2: {
            PNET_DISPLAY_MACHINE NetDisplayMachine = (PNET_DISPLAY_MACHINE)FixedData;

            COPY_STRING( &NetDisplayMachine->usri2_name,
                         DomainDisplayMachine->Machine );

            COPY_STRING( &NetDisplayMachine->usri2_comment,
                         DomainDisplayMachine->Comment );

            NetDisplayMachine->usri2_flags = NetpAccountControlToFlags(
                                    DomainDisplayMachine->AccountControl,
                                    NULL );

            NetDisplayMachine->usri2_user_id = DomainDisplayMachine->Rid;
            NetDisplayMachine->usri2_next_index = DomainDisplayMachine->Index;

            break;
        }

        case 3: {
            PNET_DISPLAY_GROUP NetDisplayGroup = (PNET_DISPLAY_GROUP)FixedData;

            COPY_STRING( &NetDisplayGroup->grpi3_name,
                         DomainDisplayGroup->Group );

            COPY_STRING( &NetDisplayGroup->grpi3_comment,
                         DomainDisplayGroup->Comment );

            NetDisplayGroup->grpi3_group_id = DomainDisplayGroup->Rid;
            NetDisplayGroup->grpi3_attributes = DomainDisplayGroup->Attributes;
            NetDisplayGroup->grpi3_next_index = DomainDisplayGroup->Index;

            break;
        }

        default:
            NetStatus = ERROR_INVALID_LEVEL;
            goto Cleanup;

        }

        //
        // Indicate that more information was returned.
        //

        (*ReturnedEntryCount) ++;

    }

    NetStatus = SavedStatus;

    //
    // Clean up.
    //

Cleanup:

    //
    // Free up all resources, we reopen them if the caller calls again.
    //

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

    //
    // If we're not returning data to the caller,
    //  free the return buffer.
    //

    if ( NetStatus != NERR_Success && NetStatus != ERROR_MORE_DATA ) {
        if ( BufferDescriptor.Buffer != NULL ) {
            MIDL_user_free( BufferDescriptor.Buffer );
            BufferDescriptor.Buffer = NULL;
        }
    }

    //
    // Set the output parameters
    //

    *SortedBuffer = BufferDescriptor.Buffer;

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

    return NetStatus;

}


NET_API_STATUS NET_API_FUNCTION
NetGetDisplayInformationIndex(
    IN LPCWSTR ServerName OPTIONAL,
    IN DWORD Level,
    IN LPCWSTR Prefix,
    OUT LPDWORD Index )

/*++

Routine Description:

    This routine returns the index of the first display information entry
    alphabetically equal to or following Prefix.

Parameters:


    ServerName - A pointer to a string containing the name of the remote
        server on which the function is to execute.  A NULL pointer
        or string specifies the local machine.

    Level - Level of information queried.  Must be

            1 --> all Local and Global (normal) user accounts
            2 --> all Workstation and Server (BDC) user accounts
            3 --> all Global Groups.


    Prefix - Prefix to be searched for

    Index - The index of the entry found.

Return Values:

    NERR_Success - normal, successful completion.  The specified index
        was returned.

    ERROR_ACCESS_DENIED - The caller doesn't have access to the requested
        information.

    ERROR_INVALID_LEVEL - The requested level of information
        is not legitimate for this service.

--*/
{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;

    SAM_HANDLE  DomainHandle = NULL;

    DOMAIN_DISPLAY_INFORMATION DisplayInformation;
    UNICODE_STRING PrefixString;

    //
    // Validate Level parameter
    //

    switch (Level) {
    case 1:
        DisplayInformation = DomainDisplayUser;
        break;
    case 2:
        DisplayInformation = DomainDisplayMachine;
        break;
    case 3:
        DisplayInformation = DomainDisplayGroup;
        break;

    default:
        return ERROR_INVALID_LEVEL;

    }

    //
    // Open the Account Domain.
    //

    NetStatus = UaspOpenDomain( ServerName,
                                DOMAIN_LIST_ACCOUNTS,
                                TRUE,   // Account Domain
                                &DomainHandle,
                                NULL );

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

    //
    // Pass the call to SAM
    //

    RtlInitUnicodeString( &PrefixString, Prefix );

    Status = SamGetDisplayEnumerationIndex (
                        DomainHandle,
                        DisplayInformation,
                        &PrefixString,
                        Index );

    if ( !NT_SUCCESS( Status ) ) {
        IF_DEBUG( UAS_DEBUG_USER ) {
            NetpKdPrint((
                "NetGetDisplayInformationIndex: SamGetDisplayEnumerationIndex returned %lX\n",
                Status ));
        }

        NetStatus = NetpNtStatusToApiStatus( Status );
        goto Cleanup;
    }

    Status = NERR_Success;

    //
    // Clean up.
    //

Cleanup:

    //
    // Free up all resources, we reopen them if the caller calls again.
    //

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

    //
    // Set the output parameters
    //

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

    return NetStatus;

}
