/*++
Copyright (c) 1991  Microsoft Corporation

Module Name:

    group.c

Abstract:

    NetGroup API functions

Author:

    Cliff Van Dyke (cliffv) 05-Mar-1991

Environment:

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

Revision History:

    17-Apr-1991 (cliffv)
        Incorporated review comments.

    17-Jan-1992 (madana)
        Added support to change group account name.

    20-Jan-1992 (madana)
        Sundry API changes
--*/

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

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

#include <access.h>
#include <align.h>
#include <icanon.h>
#include <lmapibuf.h>
#include <lmaccess.h>
#include <lmerr.h>
#include <netdebug.h>
#include <netlib.h>
#include <netlibnt.h>
#include <rpcutil.h>
#include <rxgroup.h>
#include <secobj.h>
#include <stddef.h>
#include <uasp.h>
#include <wcstr.h>

/*lint -e614 */  /* Auto aggregate initializers need not be constant */

// Lint complains about casts of one structure type to another.
// That is done frequently in the code below.
/*lint -e740 */  /* don't complain about unusual cast */ \




NET_API_STATUS NET_API_FUNCTION
NetGroupAdd(
    IN LPWSTR ServerName OPTIONAL,
    IN DWORD Level,
    IN LPBYTE Buffer,
    OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
    )

/*++

Routine Description:

    Create a group account in the user accounts database.

Arguments:

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

    Level - Level of information provided.  Must be 0, 1 or 2.

    Buffer - A pointer to the buffer containing the group information
        structure.

    ParmError - Optional pointer to a DWORD to return the index of the
        first parameter in error when ERROR_INVALID_PARAMETER is returned.
        If NULL, the parameter is not returned on error.

Return Value:

    Error code for the operation.

--*/

{
    LPWSTR GroupName;
    UNICODE_STRING GroupNameString;
    LPWSTR GroupComment;
    DWORD GroupAttributes;
    NET_API_STATUS NetStatus;
    NTSTATUS Status;
    SAM_HANDLE DomainHandle, GroupHandle;
    ULONG RelativeId;


    //
    // Initialize
    //

    NetpSetParmError( PARM_ERROR_NONE );

    //
    // Validate Level parameter and fields of structures.
    //

    switch (Level) {
    case 0:
        GroupName = ((PGROUP_INFO_0)Buffer)->grpi0_name;
        GroupComment = NULL;
        break;

    case 1:
        GroupName = ((PGROUP_INFO_1)Buffer)->grpi1_name;
        GroupComment = ((PGROUP_INFO_1)Buffer)->grpi1_comment;
        break;

    case 2:
        GroupName = ((PGROUP_INFO_2)Buffer)->grpi2_name;
        GroupComment = ((PGROUP_INFO_2)Buffer)->grpi2_comment;
        GroupAttributes = ((PGROUP_INFO_2)Buffer)->grpi2_attributes;
        break;

    default:
        return ERROR_INVALID_LEVEL;
    }

    //
    // Don't allow creation of the group names that will confuse LM 2.x BDCs.
    //

    if ( UaspNameCompare( GroupName, L"users", NAMETYPE_GROUP ) == 0 ||
         UaspNameCompare( GroupName, L"guests", NAMETYPE_GROUP ) == 0 ||
         UaspNameCompare( GroupName, L"admins", NAMETYPE_GROUP ) == 0 ||
         UaspNameCompare( GroupName, L"local", NAMETYPE_GROUP ) == 0 ) {

        NetStatus = NERR_SpeGroupOp;
        goto Cleanup;
    }

    //
    // Open the Domain asking for DOMAIN_CREATE_GROUP access.
    //

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

    if ( NetStatus != NERR_Success ) {
        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupAdd: Cannot UaspOpenDomain %ld\n", NetStatus );
        }
        goto Cleanup;
    }


    //
    // Create the Group with the specified group name
    // (and a default security descriptor).
    //

    RtlInitUnicodeString( &GroupNameString, GroupName );

    Status = SamCreateGroupInDomain( DomainHandle,
                                     &GroupNameString,
                                     DELETE | GROUP_WRITE_ACCOUNT,
                                     &GroupHandle,
                                     &RelativeId );


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




    //
    // Set the Admin Comment on the group.
    //

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

        GROUP_ADM_COMMENT_INFORMATION AdminComment;

        RtlInitUnicodeString( &AdminComment.AdminComment, GroupComment );

        Status = SamSetInformationGroup( GroupHandle,
                                         GroupAdminCommentInformation,
                                         &AdminComment );

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

            Status = SamDeleteGroup( GroupHandle );
            NetpAssert( NT_SUCCESS(Status) );

            goto Cleanup;
        }

    }

    // Set the attributes on the group.
    //

    if ( (Level == 2) ) {

        GROUP_ATTRIBUTE_INFORMATION  GroupAttributeInfo;

        GroupAttributeInfo.Attributes = GroupAttributes;

        Status = SamSetInformationGroup( GroupHandle,
                                         GroupAttributeInformation,
                                         &GroupAttributeInfo );

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

            Status = SamDeleteGroup( GroupHandle );
            NetpAssert( NT_SUCCESS(Status) );

            goto Cleanup;
        }

    }

    //
    // Close the created group.
    //

    (VOID) SamCloseHandle( GroupHandle );

    NetStatus = NERR_Success;

    //
    // Clean up
    //

Cleanup:
    UaspCloseDomain( DomainHandle );

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

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

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupAdd: returns %ld\n", NetStatus );
    }
    return NetStatus;

} // NetGroupAdd


NET_API_STATUS NET_API_FUNCTION
NetGroupAddUser(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN LPWSTR UserName
    )

/*++

Routine Description:

    Give an existing user account membership in an existing group.

Arguments:

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

    GroupName - Name of the group to which the user is to be given membership.

    UserName - Name of the user to be given group membership.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;

    //
    // Call the routine shared by NetGroupAddUser and NetGroupDelUser
    //

    NetStatus = GrouppChangeMember( ServerName, GroupName, UserName, TRUE);

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupAddUser( ServerName, GroupName, UserName );

    UASP_DOWNLEVEL_END( NetStatus );


    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupAddUser: returns %ld\n", NetStatus );
    }
    return NetStatus;

} // NetGroupAddUser


NET_API_STATUS NET_API_FUNCTION
NetGroupDel(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName
    )

/*++

Routine Description:

    Delete a Group

Arguments:

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

    GroupName - Name of the group to delete.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;

    //
    // Call a common routine to delete all of the memberships in this group
    // and then delete the group itself.
    //

    NetStatus = GrouppSetUsers( ServerName,
                                GroupName,
                                0,              // Level
                                NULL,           // No new members
                                0,              // Number of members desired
                                TRUE );         // Delete the group when done

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupDel( ServerName, GroupName );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupDel: returns %ld\n", NetStatus );
    }
    return NetStatus;

} // NetGroupDel


NET_API_STATUS NET_API_FUNCTION
NetGroupDelUser(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN LPWSTR UserName
    )

/*++

Routine Description:

    Remove a user from a particular group.

Arguments:

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

    GroupName - Name of the group from which the user is to be removed.

    UserName - Name of the user to be removed from the group.

Return Value:

    Error code for the operation.

--*/

{

    NET_API_STATUS NetStatus;

    //
    // Call the routine shared by NetGroupAddUser and NetGroupDelUser
    //

    NetStatus = GrouppChangeMember( ServerName, GroupName, UserName, FALSE );

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupDelUser( ServerName, GroupName, UserName );

    UASP_DOWNLEVEL_END;

    return NetStatus;

} // NetGroupDelUser


NET_API_STATUS NET_API_FUNCTION
NetGroupEnum(
    IN LPWSTR ServerName OPTIONAL,
    IN DWORD Level,
    OUT LPBYTE *Buffer,
    IN DWORD PrefMaxLen,
    OUT LPDWORD EntriesRead,
    OUT LPDWORD EntriesLeft,
    IN OUT LPDWORD ResumeHandle OPTIONAL
    )

/*++

Routine Description:

    Retrieve information about each group on a server.

Arguments:

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

    Level - Level of information required. 0, 1 and 2 are valid.

    Buffer - Returns a pointer to the return information structure.
        Caller must deallocate buffer using NetApiBufferFree.

    PrefMaxLen - Prefered maximum length of returned data.

    EntriesRead - Returns the actual enumerated element count.

    EntriesLeft - Returns the total entries available to be enumerated.

    ResumeHandle -  Used to continue an existing search.  The handle should
        be zero on the first call and left unchanged for subsequent calls.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;

    PDOMAIN_DISPLAY_GROUP SamQDIEnum;   // Sam returned buffer
    PSAM_RID_ENUMERATION SamEnum;
    PGROUP_INFO_0 grpi0;
    PGROUP_INFO_0 grpi0_temp = NULL;

    BUFFER_DESCRIPTOR BufferDescriptor;
    PDOMAIN_GENERAL_INFORMATION DomainGeneral;

    //
    // Declare Opaque group enumeration handle.
    //

    struct _UAS_ENUM_HANDLE {
        SAM_HANDLE  DomainHandle;

        ULONG SamEnumHandle;                    // Current Sam Enum Handle
        PDOMAIN_DISPLAY_GROUP SamQDIEnum;          // Sam returned buffer
        PSAM_RID_ENUMERATION SamEnum;
        ULONG Index;                            // Index to current entry
        ULONG Count;                            // Total Number of entries
        ULONG TotalRemaining;

        BOOL SamAllDone;                        // True, if Sam has completed
        BOOL fUseSamQDI;

    } *UasEnumHandle = NULL;

    //
    // If this is a resume, get the resume handle that the caller passed in.
    //

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


    if ( ARGUMENT_PRESENT( ResumeHandle ) && *ResumeHandle != 0 ) {
/*lint -e511 */  /* Size incompatibility */
        UasEnumHandle = (struct _UAS_ENUM_HANDLE *) *ResumeHandle;
/*lint +e511 */  /* Size incompatibility */

    //
    // If this is not a resume, allocate and initialize a resume handle.
    //

    } else {

        //
        // Allocate a resume handle.
        //

        UasEnumHandle = NetpMemoryAllocate( sizeof(struct _UAS_ENUM_HANDLE) );

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

        //
        // Initialize all the fields in the newly allocated resume handle
        //  to indicate that SAM has never yet been called.
        //

        UasEnumHandle->DomainHandle = NULL;
        UasEnumHandle->SamEnumHandle = 0;
        UasEnumHandle->SamQDIEnum = NULL;
        UasEnumHandle->SamEnum = NULL;
        UasEnumHandle->Index = 0;
        UasEnumHandle->Count = 0;
        UasEnumHandle->TotalRemaining = 0;
        UasEnumHandle->SamAllDone = FALSE;
        UasEnumHandle->fUseSamQDI = TRUE;

        //
        // Open the Domain.
        //

        NetStatus = UaspOpenDomain( ServerName,
                                    DOMAIN_LOOKUP |
                                        DOMAIN_LIST_ACCOUNTS |
                                        DOMAIN_READ_OTHER_PARAMETERS,
                                    TRUE,   // Account Domain
                                    &UasEnumHandle->DomainHandle,
                                    NULL );

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

        //
        // Get the total number of groups from SAM
        //

        Status = SamQueryInformationDomain( UasEnumHandle->DomainHandle,
                                            DomainGeneralInformation,
                                            (PVOID *)&DomainGeneral );

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

        UasEnumHandle->TotalRemaining = DomainGeneral->GroupCount;
        Status = SamFreeMemory( DomainGeneral );
        NetpAssert( NT_SUCCESS(Status) );

    }


    //
    // Loop for each group
    //
    // Each iteration of the loop below puts one more entry into the array
    // returned to the caller.  The algorithm is split into 3 parts.  The
    // first part checks to see if we need to retrieve more information from
    // SAM.  We then get the description of several group from SAM in a single
    // call.  The second part sees if there is room for this entry in the
    // buffer we'll return to the caller.  If not, a larger buffer is allocated
    // for return to the caller.  The third part puts the entry in the
    // buffer.
    //

    for ( ;; ) {
        DWORD FixedSize;
        DWORD Size;
        ULONG TotalAvail;
        ULONG TotalReturned;

        //
        // Get more group information from SAM
        //
        // Handle when we've already consumed all of the information
        // returned on a previous call to SAM.  This is a 'while' rather
        // than an if to handle the case where SAM returns zero entries.
        //

        while ( UasEnumHandle->Index >= UasEnumHandle->Count ) {

            //
            // If we've already gotten everything from SAM,
            //      return all done status to our caller.
            //

            if ( UasEnumHandle->SamAllDone ) {
                NetStatus = NERR_Success;
                goto Cleanup;
            }

            //
            // Free any previous buffer returned from SAM.
            //

            if ( UasEnumHandle->SamQDIEnum != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->SamQDIEnum );
                NetpAssert( NT_SUCCESS(Status) );

                UasEnumHandle->SamQDIEnum = NULL;
            }

            //
            // Do the actual enumeration
            //

            if ( UasEnumHandle->fUseSamQDI )
            {
                Status = SamQueryDisplayInformation(
                            UasEnumHandle->DomainHandle,
                            DomainDisplayGroup,
                            UasEnumHandle->SamEnumHandle,
                            0xffffffff, //query as many as PrefMaxLen can handle
                            PrefMaxLen,
                            &TotalAvail,
                            &TotalReturned,
                            &UasEnumHandle->Count,
                            (PVOID *)&UasEnumHandle->SamQDIEnum );

                if ( !NT_SUCCESS( Status ) ) {
                    if ( Status == STATUS_INVALID_INFO_CLASS ) {
                        UasEnumHandle->fUseSamQDI = FALSE;
                    } else {
                        NetStatus = NetpNtStatusToApiStatus( Status );
                        goto Cleanup;
                    }
                }

                UasEnumHandle->SamEnumHandle += UasEnumHandle->Count;

            }

            if ( !UasEnumHandle->fUseSamQDI ) {
                Status = SamEnumerateGroupsInDomain(
                            UasEnumHandle->DomainHandle,
                            &UasEnumHandle->SamEnumHandle,
                            (PVOID *)&UasEnumHandle->SamEnum,
                            PrefMaxLen,
                            &UasEnumHandle->Count );

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

            //
            // Adjust TotalRemaining as we get better information
            //

            if (UasEnumHandle->TotalRemaining < UasEnumHandle->Count) {
                UasEnumHandle->TotalRemaining = UasEnumHandle->Count;
            }

            //
            // If SAM says there is more information, just ensure he returned
            // something to us on this call.
            //

            if ( Status == STATUS_MORE_ENTRIES ) {
                if ( UasEnumHandle->Count == 0 ) {
                    NetStatus = NERR_BufTooSmall;
                    goto Cleanup;
                }

            //
            // If SAM says he's returned all of the information,
            //  remember not to ask SAM for more.
            //

            } else {
                UasEnumHandle->SamAllDone = TRUE;
            }

            UasEnumHandle->Index = 0;
        }

        //
        // ASSERT:  UasEnumHandle identifies the next entry to return
        //          from SAM.
        //

        if ( UasEnumHandle->fUseSamQDI ) {
            SamQDIEnum = &UasEnumHandle->SamQDIEnum[UasEnumHandle->Index];
        } else {
            SamEnum = &UasEnumHandle->SamEnum[UasEnumHandle->Index];
        }


        //
        // Place this entry into the return buffer.
        //
        // Determine the size of the data passed back to the caller
        //

        switch (Level) {
        case 0:
            FixedSize = sizeof(GROUP_INFO_0);
            Size = sizeof(GROUP_INFO_0) +
                (UasEnumHandle->fUseSamQDI ? SamQDIEnum->Group.Length : SamEnum->Name.Length) +
                sizeof(WCHAR);
            break;

        case 1:


            FixedSize = sizeof(GROUP_INFO_1);

            if ( UasEnumHandle->fUseSamQDI ) {
                Size = sizeof(GROUP_INFO_1) +
                   SamQDIEnum->Group.Length + sizeof(WCHAR) +
                   SamQDIEnum->Comment.Length + sizeof(WCHAR);
            } else {
                NetStatus = GrouppGetInfo( UasEnumHandle->DomainHandle,
                                           SamEnum->RelativeId,
                                           Level,
                                           (PVOID *)&grpi0_temp);

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

                Size = sizeof(GROUP_INFO_1) +
                        SamEnum->Name.Length + sizeof(WCHAR) +
                        (wcslen(((PGROUP_INFO_1)grpi0_temp)->grpi1_comment) +
                            1) * sizeof(WCHAR);

            }
                   
                   
            break;

        case 2:

            FixedSize = sizeof(GROUP_INFO_2);

            if ( UasEnumHandle->fUseSamQDI ) {
                Size = sizeof(GROUP_INFO_2) +
                        SamQDIEnum->Group.Length + sizeof(WCHAR) +
                        SamQDIEnum->Comment.Length + sizeof(WCHAR);
            } else {
                NetStatus = GrouppGetInfo( UasEnumHandle->DomainHandle,
                                           SamEnum->RelativeId,
                                           Level,
                                           (PVOID *)&grpi0_temp);

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

                Size = sizeof(GROUP_INFO_2) +
                        SamEnum->Name.Length + sizeof(WCHAR) +
                        (wcslen(((PGROUP_INFO_2)grpi0_temp)->grpi2_comment) +
                            1) * 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,      // Not a 'get' operation
                        PrefMaxLen,
                        Size,
                        GrouppRelocationRoutine,
                        Level );

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

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

        //
        // Copy the common group name
        //

        NetpAssert( offsetof( GROUP_INFO_0, grpi0_name ) ==
                    offsetof( GROUP_INFO_1, grpi1_name ) );

        NetpAssert( offsetof( GROUP_INFO_1, grpi1_name ) ==
                    offsetof( GROUP_INFO_2, grpi2_name ) );

        NetpAssert( offsetof( GROUP_INFO_1, grpi1_comment ) ==
                    offsetof( GROUP_INFO_2, grpi2_comment ) );

        grpi0 = (PGROUP_INFO_0)(BufferDescriptor.FixedDataEnd);
        BufferDescriptor.FixedDataEnd += FixedSize;

        //
        // Fill in the Level dependent fields
        //

        switch ( Level ) {
        case 2:
            ((PGROUP_INFO_2)grpi0)->grpi2_group_id =
                            UasEnumHandle->fUseSamQDI
                                ? SamQDIEnum->Rid
                                : ((PGROUP_INFO_2)grpi0_temp)->grpi2_group_id;

            ((PGROUP_INFO_2)grpi0)->grpi2_attributes =
                            UasEnumHandle->fUseSamQDI
                                ? SamQDIEnum->Attributes
                                : ((PGROUP_INFO_2)grpi0_temp)->grpi2_attributes;

            /* FALL THROUGH FOR THE OTHER FIELDS */

        case 1:
            if ( !NetpCopyStringToBuffer(
                        UasEnumHandle->fUseSamQDI
                            ? SamQDIEnum->Comment.Buffer
                            : ((PGROUP_INFO_1)grpi0_temp)->grpi1_comment,
                        UasEnumHandle->fUseSamQDI
                            ? SamQDIEnum->Comment.Length/sizeof(WCHAR)
                            : wcslen(((PGROUP_INFO_1)grpi0_temp)->grpi1_comment),
                        BufferDescriptor.FixedDataEnd,
                        (LPWSTR *)&BufferDescriptor.EndOfVariableData,
                        &((PGROUP_INFO_1)grpi0)->grpi1_comment) ) {

                NetStatus = NERR_InternalError;
                goto Cleanup;
            }

            if ( !UasEnumHandle->fUseSamQDI ) {
                MIDL_user_free( grpi0_temp );
                grpi0_temp = NULL;
            }

            /* FALL THROUGH FOR THE NAME FIELD */

        case 0:

            if ( !NetpCopyStringToBuffer(
                            UasEnumHandle->fUseSamQDI
                                ? SamQDIEnum->Group.Buffer
                                : SamEnum->Name.Buffer,
                            UasEnumHandle->fUseSamQDI
                                ? SamQDIEnum->Group.Length/sizeof(WCHAR)
                                : SamEnum->Name.Length/sizeof(WCHAR),
                            BufferDescriptor.FixedDataEnd,
                            (LPWSTR *)&BufferDescriptor.EndOfVariableData,
                            &(grpi0->grpi0_name))){

                NetStatus = NERR_InternalError;
                goto Cleanup;
            }

            break;


        default:
            NetStatus = ERROR_INVALID_LEVEL;
            goto Cleanup;

        }

        //
        // ASSERT: The current entry has been completely copied to the
        //  return buffer.
        //

        (*EntriesRead)++;

        UasEnumHandle->Index ++;
        UasEnumHandle->TotalRemaining --;
    }

    //
    // Clean up.
    //

Cleanup:

    //
    // Free any locally used resources.
    //

    if ( grpi0_temp != NULL ) {
        MIDL_user_free( grpi0_temp );
    }

    //
    // Set EntriesLeft to the number left to return plus those that
    //  we returned on this call.
    //

    if ( UasEnumHandle != NULL ) {
        *EntriesLeft = UasEnumHandle->TotalRemaining + *EntriesRead;
    }

    //
    // If we're done or the caller doesn't want an enumeration handle,
    //  free the enumeration handle.
    //

    if ( NetStatus != ERROR_MORE_DATA || !ARGUMENT_PRESENT( ResumeHandle ) ) {

        if ( UasEnumHandle != NULL ) {
            if ( UasEnumHandle->DomainHandle != NULL ) {
                UaspCloseDomain( UasEnumHandle->DomainHandle );
            }

            if ( UasEnumHandle->SamQDIEnum != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->SamQDIEnum );
                NetpAssert( NT_SUCCESS(Status) );
            }

            if ( UasEnumHandle->SamEnum != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->SamEnum );
                NetpAssert( NT_SUCCESS(Status) );
            }

            NetpMemoryFree( UasEnumHandle );
            UasEnumHandle = NULL;
        }

    }

    //
    // 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;
        }
        *EntriesRead = 0;
        *EntriesLeft = 0;
    }

    //
    // Set the output parameters
    //

    *Buffer = BufferDescriptor.Buffer;
    if ( ARGUMENT_PRESENT( ResumeHandle ) ) {
        *ResumeHandle = (DWORD) UasEnumHandle;
    }


    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

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

    UASP_DOWNLEVEL_END;


    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupEnum: returns %ld\n", NetStatus );
    }

    return NetStatus;

} // NetGroupEnum


NET_API_STATUS NET_API_FUNCTION
NetGroupGetInfo(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN DWORD Level,
    OUT LPBYTE *Buffer
    )

/*++

Routine Description:

    Retrieve information about a particular group.

Arguments:

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

    GroupName - Name of the group to get information about.

    Level - Level of information required. 0, 1 and 2 are valid.

    Buffer - Returns a pointer to the return information structure.
        Caller must deallocate buffer using NetApiBufferFree.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;
    SAM_HANDLE DomainHandle = NULL;

    ULONG RelativeId;           // Relative Id of the group

    //
    // Open the Domain
    //

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

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

    //
    // Validate the group name and get the relative ID.
    //

    NetStatus = GrouppOpenGroup( DomainHandle,
                                 0,         // DesiredAccess
                                 GroupName,
                                 NULL,      // GroupHandle
                                 &RelativeId );

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

    //
    // Get the Information about the group.
    //

    NetStatus = GrouppGetInfo( DomainHandle,
                               RelativeId,
                               Level,
                               (PVOID *)Buffer);

    //
    // Clean up.
    //

Cleanup:
    UaspCloseDomain( DomainHandle );

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupGetInfo( ServerName, GroupName, Level, Buffer );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupGetInfo: returns %ld\n", NetStatus );
    }

    return NetStatus;

} // NetGroupGetInfo


NET_API_STATUS NET_API_FUNCTION
NetGroupGetUsers(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN DWORD Level,
    OUT LPBYTE *Buffer,
    IN DWORD PrefMaxLen,
    OUT LPDWORD EntriesRead,
    OUT LPDWORD EntriesLeft,
    IN OUT LPDWORD ResumeHandle
    )

/*++

Routine Description:

    Enumerate the users which are members of a particular group.

Arguments:

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

    GroupName - The name of the group whose members are to be listed.

    Level - Level of information required. 0 and 1 are valid.

    Buffer - Returns a pointer to the return information structure.
        Caller must deallocate buffer using NetApiBufferFree.

    PrefMaxLen - Prefered maximum length of returned data.

    EntriesRead - Returns the actual enumerated element count.

    EntriesLeft - Returns the total entries available to be enumerated.

    ResumeHandle -  Used to continue an existing search.  The handle should
        be zero on the first call and left unchanged for subsequent calls.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;
    NTSTATUS Status2;

    DWORD FixedSize;        // The fixed size of each new entry.
    DWORD Size;
    BUFFER_DESCRIPTOR BufferDescriptor;

    PGROUP_USERS_INFO_0 grui0;

    //
    // Declare Opaque group member enumeration handle.
    //

    struct _UAS_ENUM_HANDLE {
        SAM_HANDLE  DomainHandle;
        SAM_HANDLE  GroupHandle;

        PUNICODE_STRING Names;                  // Names of each member
        PSID_NAME_USE NameUse;                  // Usage of each member
        PULONG Attributes;                      // Attributes of each member

        ULONG Index;                            // Index to current entry
        ULONG Count;                            // Total Number of entries

    } *UasEnumHandle = NULL;

    //
    // Validate Parameters
    //

    BufferDescriptor.Buffer = NULL;
    *Buffer = NULL;
    *EntriesRead = 0;
    *EntriesLeft = 0;
    switch (Level) {
    case 0:
        FixedSize = sizeof(GROUP_USERS_INFO_0);
        break;

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

    default:
        NetStatus = ERROR_INVALID_LEVEL;
        goto Cleanup;
    }

    //
    // If this is a resume, get the resume handle that the caller passed in.
    //

    if ( ARGUMENT_PRESENT( ResumeHandle ) && *ResumeHandle != 0 ) {
/*lint -e511 */  /* Size incompatibility */
        UasEnumHandle = (struct _UAS_ENUM_HANDLE *) *ResumeHandle;
/*lint +e511 */  /* Size incompatibility */

    //
    // If this is not a resume, allocate and initialize a resume handle.
    //

    } else {
        PULONG MemberIds;           // Member IDs returned from SAM


        //
        // Allocate a resume handle.
        //

        UasEnumHandle = NetpMemoryAllocate( sizeof(struct _UAS_ENUM_HANDLE) );

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

        //
        // Initialize all the fields in the newly allocated resume handle
        //  to indicate that SAM has never yet been called.
        //

        UasEnumHandle->DomainHandle = NULL;
        UasEnumHandle->GroupHandle = NULL;
        UasEnumHandle->Names = NULL;
        UasEnumHandle->NameUse = NULL;
        UasEnumHandle->Attributes = NULL;
        UasEnumHandle->Index = 0;
        UasEnumHandle->Count = 0;

        //
        // Open the Domain
        //

        NetStatus = UaspOpenDomain( ServerName,
                                    DOMAIN_LOOKUP,
                                    TRUE,   // Account Domain
                                    &UasEnumHandle->DomainHandle,
                                    NULL );

        if ( NetStatus != NERR_Success ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupGetUsers: UaspOpenDomain returns %ld\n",
                    NetStatus );
            }
            goto Cleanup;
        }

        //
        // Open the group asking for GROUP_LIST_MEMBER access.
        //

        NetStatus = GrouppOpenGroup( UasEnumHandle->DomainHandle,
                                     GROUP_LIST_MEMBERS,
                                     GroupName,
                                     &UasEnumHandle->GroupHandle,
                                     NULL );    // Relative ID

        if ( NetStatus != NERR_Success ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupGetUsers: GrouppOpenGroup returns %ld\n",
                    NetStatus );
            }
            goto Cleanup;
        }

        //
        // Get the group membership information from SAM
        //

        Status = SamGetMembersInGroup(
            UasEnumHandle->GroupHandle,
            &MemberIds,
            &UasEnumHandle->Attributes,
            &UasEnumHandle->Count );

        if ( !NT_SUCCESS( Status ) ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupGetUsers: SamGetMembersInGroup returned %lX\n",
                    Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }

        if ( UasEnumHandle->Count == 0 ) {
            NetStatus = NERR_Success;
            goto Cleanup;
        }

        //
        // Determine the names and name usage for all the returned
        //  relative Ids.
        //


        Status = SamLookupIdsInDomain( UasEnumHandle->DomainHandle,
                                       UasEnumHandle->Count,
                                       MemberIds,
                                       &UasEnumHandle->Names,
                                       &UasEnumHandle->NameUse );

        Status2 = SamFreeMemory( MemberIds );
        NetpAssert( NT_SUCCESS(Status2) );


        if ( !NT_SUCCESS( Status ) ) {

                IF_DEBUG( UAS_DEBUG_GROUP ) {
                    NetpDbgPrint(
                        "NetGroupGetUsers: SamLookupIdsInDomain returned %lX\n",
                        Status );
                }

                if ( Status == STATUS_NONE_MAPPED ) {
                    NetStatus = NERR_GroupNotFound;
                    goto Cleanup;
                }

                NetStatus = NetpNtStatusToApiStatus( Status );
                goto Cleanup;
            }

    }


    //
    // Loop for each member
    //

    while ( UasEnumHandle->Index < UasEnumHandle->Count ) {

        //
        // ASSERT:  UasEnumHandle identifies the next entry to return
        //          from SAM.
        //

        //
        // Ignore members which aren't a user.
        //

        if ( UasEnumHandle->NameUse[UasEnumHandle->Index] != SidTypeUser ) {
            continue;
        }

        //
        // Place this entry into the return buffer.
        //  Compute the total size of this entry.
        //

        Size = FixedSize +
            UasEnumHandle->Names[UasEnumHandle->Index].Length + sizeof(WCHAR);

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

        Size = ROUND_UP_COUNT( Size, ALIGN_WCHAR );

        NetStatus = NetpAllocateEnumBuffer(
                        &BufferDescriptor,
                        FALSE,      // Not a 'get' operation
                        PrefMaxLen,
                        Size,
                        GrouppMemberRelocationRoutine,
                        Level );

        if (NetStatus != NERR_Success) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupGetUsers: NetpAllocateEnumBuffer returns %ld\n",
                    NetStatus );
            }
            goto Cleanup;
        }

        //
        // Copy the common member name
        //

        NetpAssert( offsetof( GROUP_USERS_INFO_0,  grui0_name ) ==
                    offsetof( GROUP_USERS_INFO_1, grui1_name ) );

        grui0 = (PGROUP_USERS_INFO_0)BufferDescriptor.FixedDataEnd;
        BufferDescriptor.FixedDataEnd += FixedSize;

        if ( ! NetpCopyStringToBuffer(
                        UasEnumHandle->Names[UasEnumHandle->Index].Buffer,
                        UasEnumHandle->Names[UasEnumHandle->Index].Length
                            /sizeof(WCHAR),
                        BufferDescriptor.FixedDataEnd,
                        (LPWSTR *)&BufferDescriptor.EndOfVariableData,
                        &grui0->grui0_name) ) {

            NetStatus = NERR_InternalError;
            goto Cleanup;
        }


        //
        // Fill in the Level dependent fields
        //

        switch ( Level ) {
        case 0:
            break;

        case 1:

            //
            // Return the attributes for this particular membership
            //

            ((PGROUP_USERS_INFO_1)grui0)->grui1_attributes =
                UasEnumHandle->Attributes[UasEnumHandle->Index];

            break;

        default:
            NetStatus = ERROR_INVALID_LEVEL;
            goto Cleanup;

        }

        //
        // ASSERT: The current entry has been completely copied to the
        //  return buffer.
        //

        UasEnumHandle->Index ++;
        (*EntriesRead)++;
    }

    //
    // All entries have be returned to the caller.
    //

    NetStatus = NERR_Success;


    //
    // Clean up.
    //

Cleanup:

    //
    // Set EntriesLeft to the number left to return plus those that
    //  we returned on this call.
    //

    if ( UasEnumHandle != NULL ) {
        *EntriesLeft = (UasEnumHandle->Count - UasEnumHandle->Index)
             + *EntriesRead;
    }

    //
    // If we're done or the caller doesn't want an enumeration handle,
    //  free the enumeration handle.
    //

    if ( NetStatus != ERROR_MORE_DATA || !ARGUMENT_PRESENT( ResumeHandle ) ) {

        if ( UasEnumHandle != NULL ) {
            if ( UasEnumHandle->GroupHandle != NULL ) {
                (VOID) SamCloseHandle( UasEnumHandle->GroupHandle );
            }

            if ( UasEnumHandle->DomainHandle != NULL ) {
                UaspCloseDomain( UasEnumHandle->DomainHandle );
            }

            if ( UasEnumHandle->NameUse != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->NameUse );
                NetpAssert( NT_SUCCESS(Status) );
            }

            if ( UasEnumHandle->Names != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->Names );
                NetpAssert( NT_SUCCESS(Status) );
            }

            if ( UasEnumHandle->Attributes != NULL ) {
                Status = SamFreeMemory( UasEnumHandle->Attributes );
                NetpAssert( NT_SUCCESS(Status) );
            }

            NetpMemoryFree( UasEnumHandle );
            UasEnumHandle = NULL;
        }

    }

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

    *Buffer = BufferDescriptor.Buffer;
    if ( ARGUMENT_PRESENT( ResumeHandle ) ) {
        NetpAssert( sizeof(UasEnumHandle) <= sizeof(DWORD) );
        *ResumeHandle = (DWORD) UasEnumHandle;
    }

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

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

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupGetUsers: returns %ld\n", NetStatus );
    }


    return NetStatus;

} // NetGroupGetUsers


NET_API_STATUS NET_API_FUNCTION
NetGroupSetInfo(
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN DWORD Level,
    IN LPBYTE Buffer,
    OUT LPDWORD ParmError OPTIONAL // Name required by NetpSetParmError
    )

/*++

Routine Description:

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

Arguments:

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

    GroupName - Name of the group to modify.

    Level - Level of information provided.  Must be 0, 1, 2, 1002 or 1005.

    Buffer - A pointer to the buffer containing the group information
        structure.

    ParmError - Optional pointer to a DWORD to return the index of the
        first parameter in error when ERROR_INVALID_PARAMETER is returned.
        If NULL, the parameter is not returned on error.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;
    NTSTATUS Status;
    SAM_HANDLE DomainHandle = NULL;
    SAM_HANDLE GroupHandle = NULL;

    //
    // Initialize
    //

    NetpSetParmError( PARM_ERROR_NONE );

    //
    // Open the Domain
    //

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

    if ( NetStatus != NERR_Success ) {
        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupSetInfo: UaspOpenDomain returns %ld\n",
                      NetStatus );
        }
        goto Cleanup;
    }

    //
    // Open the group
    //

    NetStatus = GrouppOpenGroup( DomainHandle,
                                 GROUP_WRITE_ACCOUNT|GROUP_READ_INFORMATION,
                                 GroupName,
                                 &GroupHandle,
                                 NULL );   // Relative ID

    if ( NetStatus != NERR_Success ) {
        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupSetInfo: GrouppOpenGroup returns %ld\n",
                      NetStatus );
        }
        goto Cleanup;
    }

    //
    // Change the group
    //

    switch (Level) {

        //
        // changing group name
        //

    case 0:
    {
        LPWSTR  NewGroupName;
        GROUP_NAME_INFORMATION  NewSamGroupName;

        NewGroupName =  ((PGROUP_INFO_0)Buffer)->grpi0_name;

        if (NewGroupName == NULL) {

            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint( "NetGroupSetInfo: Group Name is NULL\n" );
            }
            NetStatus = NERR_Success;
            goto Cleanup;

        }

        //
        // Validate the new group name
        //

        RtlInitUnicodeString( &NewSamGroupName.Name, NewGroupName );

        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupSetInfo: Renaming Group Account to %wZ\n",
                            &NewSamGroupName.Name);
        }

        Status = SamSetInformationGroup( GroupHandle,
                                         GroupNameInformation,
                                         &NewSamGroupName );

        if ( !NT_SUCCESS(Status) ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint( "NetGroupSetInfo: SamSetInformationGroup %lX\n",
                          Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }
        break;
    }

        //
        // Set the group attributes
        //

    case 2:
    case 1005: {
        GROUP_ATTRIBUTE_INFORMATION Attributes;
        PGROUP_ATTRIBUTE_INFORMATION OldAttributes;

        //
        // Get the information out of the passed in structures.
        //

        if( Level == 1005 ) {
            Attributes.Attributes =
                ((PGROUP_INFO_1005)Buffer)->grpi1005_attributes;
        } else {
            Attributes.Attributes =
                ((PGROUP_INFO_2)Buffer)->grpi2_attributes;
        }

        //
        // Get the current attributes so we can restore them in case of
        // error.
        //

        //
        // ?? OldAttributes gotten here is never used below.
        //

        Status = SamQueryInformationGroup( GroupHandle,
                                           GroupAttributeInformation,
                                           (PVOID*)&OldAttributes );

        if ( !NT_SUCCESS(Status) ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupSetInfo: SamQueryInformationGroup %lX\n",
                    Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }


        //
        // Set the current attributes.
        //

        Status = SamSetInformationGroup( GroupHandle,
                                         GroupAttributeInformation,
                                         &Attributes );

        if ( !NT_SUCCESS(Status) ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint(
                    "NetGroupSetInfo: SamSetInformationGroup Attribute %lX\n",
                    Status );
            }
            NetpSetParmError( GROUP_ATTRIBUTES_PARMNUM );
            NetStatus = NetpNtStatusToApiStatus( Status );

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

            goto Cleanup;
        }

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

        if( Level == 1005 ) {
            break;
        }

        //
        // for level 2, FALL THROUGH TO SET THE COMMENT FIELD
        //

    }

        //
        // Set the group comment
        //

    case 1:
    case 1002:
    {
        LPWSTR   GroupComment;
        GROUP_ADM_COMMENT_INFORMATION AdminComment;

        //
        // Get the new group comment
        //

        if ( Level == 1002 ) {
            GroupComment = ((PGROUP_INFO_1002)Buffer)->grpi1002_comment;
        } else if ( Level == 2 ) {
            GroupComment = ((PGROUP_INFO_2)Buffer)->grpi2_comment;
        } else {
            GroupComment = ((PGROUP_INFO_1)Buffer)->grpi1_comment;
        }

        if (GroupComment == NULL) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint( "NetGroupSetInfo: Group comment is NULL\n" );
            }
            NetStatus = NERR_Success;
            goto Cleanup;
        }

        //
        // Validate the group comment
        //

        RtlInitUnicodeString( &AdminComment.AdminComment, GroupComment );
        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupSetInfo: Setting AdminComment to %wZ\n",
                      &AdminComment.AdminComment );
        }

        Status = SamSetInformationGroup( GroupHandle,
                                         GroupAdminCommentInformation,
                                         &AdminComment );

        if ( !NT_SUCCESS(Status) ) {
            IF_DEBUG( UAS_DEBUG_GROUP ) {
                NetpDbgPrint( "NetGroupSetInfo: SamSetInformationGroup %lX\n",
                          Status );
            }
            NetStatus = NetpNtStatusToApiStatus( Status );
            goto Cleanup;
        }
        break;
    }

    default:
        NetStatus = ERROR_INVALID_LEVEL;
        IF_DEBUG( UAS_DEBUG_GROUP ) {
            NetpDbgPrint( "NetGroupSetInfo: Invalid Level %ld\n", Level );
        }
        goto Cleanup;

    }

    NetStatus = NERR_Success;

    //
    // Clean up.
    //

Cleanup:
    if (GroupHandle != NULL) {
        (VOID) SamCloseHandle( GroupHandle );
    }

    UaspCloseDomain( DomainHandle );

    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupSetInfo( ServerName,
                                       GroupName,
                                       Level,
                                       Buffer,
                                       ParmError );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupSetInfo: returns %ld\n", NetStatus );
    }

    return NetStatus;

} // NetGroupSetInfo


NET_API_STATUS NET_API_FUNCTION
NetGroupSetUsers (
    IN LPWSTR ServerName OPTIONAL,
    IN LPWSTR GroupName,
    IN DWORD Level,
    IN LPBYTE Buffer,
    IN DWORD NewMemberCount
    )

/*++

Routine Description:

    Set the list of members of a group.

    The SAM API allows only one member to be added or deleted at a time.
    This API allows all of the members of a group to be specified en-masse.
    This API is careful to always leave the group membership in the SAM
    database in a reasonable state.  It does by mergeing the list of
    old and new members, then only changing those memberships which absolutely
    need changing.

    Group membership is restored to its previous state (if possible) if
    an error occurs during changing the group membership.

Arguments:

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

    GroupName - Name of the group to modify.

    Level - Level of information provided.  Must be 0 or 1.

    Buffer - A pointer to the buffer containing an array of NewMemberCount
        the group membership information structures.

    NewMemberCount - Number of entries in Buffer.

Return Value:

    Error code for the operation.

--*/

{
    NET_API_STATUS NetStatus;

    //
    // Call a routine shared by NetGroupDel to do all the actual work of
    // changing the membership.
    //

    NetStatus = GrouppSetUsers( ServerName,
                                GroupName,
                                Level,
                                Buffer,
                                NewMemberCount,
                                FALSE );    // Don't delete the group when done


    //
    // Handle downlevel.
    //

    UASP_DOWNLEVEL_BEGIN( ServerName, NetStatus )

        NetStatus = RxNetGroupSetUsers( ServerName,
                                        GroupName,
                                        Level,
                                        Buffer,
                                        NewMemberCount );

    UASP_DOWNLEVEL_END;

    IF_DEBUG( UAS_DEBUG_GROUP ) {
        NetpDbgPrint( "NetGroupSetUsers: returns %ld\n", NetStatus );
    }

    return NetStatus;

} // NetGroupSetUsers
/*lint +e614 */
/*lint +e740 */
