//---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 1995
//
//  File:      cenumGroupCollection.cxx
//
//  Contents:  Windows NT 3.5 GroupCollection Enumeration Code
//
//
//
//
//
//
//  History:
//----------------------------------------------------------------------------
#include "winnt.hxx"
#pragma hdrstop

COMPUTER_GROUP_MEMBER CompMember;

//
// This assumes that addr is an LPBYTE type.
//
#define WORD_ALIGN_DOWN(addr) \
        addr = ((LPBYTE)((DWORD_PTR)addr & ~1))

DWORD ComputerGrpMemberStrings[]=

                             {
                             FIELD_OFFSET(COMPUTER_GROUP_MEMBER, Parent),
                             FIELD_OFFSET(COMPUTER_GROUP_MEMBER, Computer),
                             FIELD_OFFSET(COMPUTER_GROUP_MEMBER, Domain),
                             FIELD_OFFSET(COMPUTER_GROUP_MEMBER, Name),
                             0xFFFFFFFF
                             };


DECLARE_INFOLEVEL(GrpUt)
DECLARE_DEBUG(GrpUt)
#define GrpUtDebugOut(x) GrpUtInlineDebugOut x


BOOL
WinNTLocalGroupOpen(
    LPWSTR szDomainName,
    LPWSTR szComputerName,
    LPWSTR szGroupName,
    PHANDLE phGroup
    )
{

    WCHAR szTempBuffer[MAX_PATH];
    PINI_COMP_GROUP pIniCompGrp;
    HRESULT hr;


    if (!phGroup) {
        return(FALSE);
    }
    pIniCompGrp = (PINI_COMP_GROUP)AllocADsMem(
                                        sizeof(INI_COMP_GROUP)
                                        );
    if (!pIniCompGrp) {
        return(FALSE);
    }

    hr = MakeUncName(
            szComputerName,
            szTempBuffer
            );
    BAIL_ON_FAILURE(hr);

    if (!(pIniCompGrp->szUncCompName =  AllocADsStr(szTempBuffer))){
        goto error;
    }

    // to guard against the case of domainName == NULL for no
    // workstation services
    if (szDomainName != NULL) {
        if (!(pIniCompGrp->szDomainName = AllocADsStr(szDomainName))) {
            goto error;
        }
    }

    if (!(pIniCompGrp->szComputerName =  AllocADsStr(szComputerName))){
        goto error;
    }

    if (!(pIniCompGrp->szGroupName = AllocADsStr(szGroupName))){
        goto error;
    }


    *phGroup =  (HANDLE)pIniCompGrp;

    return(TRUE);


error:
    if (pIniCompGrp) {
        FreeIniCompGroup(pIniCompGrp);
    }

    *phGroup = NULL;

    return(FALSE);

}


BOOL
WinNTLocalGroupEnum(
    HANDLE hGroup,
    DWORD  dwRequested,
    LPBYTE * ppBuffer,
    PDWORD pdwReturned
    )
{

    LPCOMPUTER_GROUP_MEMBER * ppGroupMembers = NULL;
    DWORD i = 0;
    BOOL dwRet = FALSE;
    DWORD dwReturned = 0;
    DWORD dwSize = 0;
    LPCOMPUTER_GROUP_MEMBER pBuffer = NULL;
    LPBYTE pEnd = NULL;
    DWORD dwError;
    BOOL fretVal = FALSE;


    ppGroupMembers = (LPCOMPUTER_GROUP_MEMBER *)AllocADsMem(
                                sizeof(LPCOMPUTER_GROUP_MEMBER)* dwRequested
                                );
    if (!ppGroupMembers) {
        return(FALSE);
    }

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

        dwRet = WinNTLocalGroupGetObject(
                        hGroup,
                        &ppGroupMembers[dwReturned]
                        );
        if (!dwRet) {

            dwError = GetLastError();
            if (dwError == ERROR_INVALID_SID) {
                continue;
            }

            //
            // it was not because of a bad sid
            // so break out, nothing more can be done
            //

            break;


        }

        dwReturned++;

    }

    dwRet = ComputeLocalGroupDataSize(
                    ppGroupMembers,
                    dwReturned,
                    &dwSize
                    );

    pBuffer = (LPCOMPUTER_GROUP_MEMBER)AllocADsMem(
                        dwSize
                        );

    if (pBuffer) {

        fretVal = TRUE;

        pEnd = (LPBYTE)((LPBYTE)(pBuffer) + dwSize);

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

            pEnd = CopyIniCompGroupToCompGroup(
                            ppGroupMembers[i],
                            (LPBYTE)(pBuffer + i),
                            pEnd
                            );
        }
    }

    for (i = 0; i < dwReturned; i++ ) {
        FreeIntCompGroup(*(ppGroupMembers + i));
    }

    FreeADsMem(ppGroupMembers);

    //
    // Will correctl set to NULL if alloc failed.
    //
    *ppBuffer = (LPBYTE)pBuffer;
    *pdwReturned  = fretVal ? dwReturned : 0;

    if (!fretVal) {
        return(FALSE);
    }

    if (dwReturned == dwRequested){
        return(TRUE);
    }else {
        return(FALSE);
    }
}

BOOL
WinNTLocalGroupGetObject(
    HANDLE hGroup,
    LPCOMPUTER_GROUP_MEMBER * ppGroupMember
    )
{

    BOOL dwRet = FALSE;
    PINI_COMP_GROUP pIniCompGrp = (PINI_COMP_GROUP)hGroup;
    NET_API_STATUS nasStatus = 0;

    if ((!pIniCompGrp->_pBuffer) ||
        (pIniCompGrp->_dwCurrentObject == pIniCompGrp->_dwObjectReturned)) {

        if (pIniCompGrp->_bNoMore) {

            //
            // No more objects to return
            //
            return(FALSE);
        }

        if (pIniCompGrp->_pBuffer) {
            NetApiBufferFree(pIniCompGrp->_pBuffer);
            pIniCompGrp->_pBuffer = NULL;
        }

        pIniCompGrp->_dwObjectReturned = 0;
        pIniCompGrp->_dwCurrentObject = 0;
        pIniCompGrp->_dwTotalObjects = 0;

        nasStatus = NetLocalGroupGetMembers(
                            pIniCompGrp->szUncCompName,
                            pIniCompGrp->szGroupName,
                            2,
                            &pIniCompGrp->_pBuffer,
                            MAX_PREFERRED_LENGTH,
                            &pIniCompGrp->_dwObjectReturned,
                            &pIniCompGrp->_dwTotalObjects,
                            &pIniCompGrp->_dwResumeHandle
                            );
        if ((nasStatus != ERROR_SUCCESS) && (nasStatus != ERROR_MORE_DATA)){
            SetLastError(nasStatus);
            return(FALSE);
        }

        if (nasStatus != ERROR_MORE_DATA) {
            pIniCompGrp->_bNoMore = TRUE;
        }

        //
        // If there are no more objects to return,
        // return FALSE
        //
        if (!pIniCompGrp->_dwObjectReturned) {
            return(FALSE);
        }

    }

    while ( dwRet != TRUE &&
            (pIniCompGrp->_dwCurrentObject < pIniCompGrp->_dwTotalObjects))
     {

       dwRet = BuildLocalGroupMember(
                hGroup,
                (LPBYTE)((LPLOCALGROUP_MEMBERS_INFO_2)pIniCompGrp->_pBuffer
                                           + pIniCompGrp->_dwCurrentObject),
                 ppGroupMember
                );

       if (dwRet == FALSE) {
         if (GetLastError() == ERROR_INVALID_SID) {
           pIniCompGrp->_dwCurrentObject++;
           continue;
           //
           // proceed to the top of the while loop
           //
         }
         else
           goto error;
       }
    }
    //
    // the while loop
    //
    if (dwRet == FALSE)
       goto error;

    pIniCompGrp->_dwCurrentObject++;

    return(TRUE);

error:

    return(FALSE);
}


BOOL
WinNTLocalGroupClose(
    HANDLE hGroup
    )
{

    PINI_COMP_GROUP pIniCompGrp = (PINI_COMP_GROUP)hGroup;

    if (pIniCompGrp) {
        FreeIniCompGroup(pIniCompGrp);
    }
    return(TRUE);
}

void
FreeIniCompGroup(
    PINI_COMP_GROUP pIniCompGrp
    )
{
    if (pIniCompGrp->szDomainName) {

        FreeADsStr(pIniCompGrp->szDomainName);
    }

    if (pIniCompGrp->szComputerName) {

        FreeADsStr(pIniCompGrp->szComputerName);
    }


    if (pIniCompGrp->szGroupName) {

        FreeADsStr(pIniCompGrp->szGroupName);
    }

    if (pIniCompGrp->szUncCompName) {

        FreeADsStr(pIniCompGrp->szUncCompName);
    }


    if (pIniCompGrp->_pBuffer) {

        NetApiBufferFree(pIniCompGrp->_pBuffer);
    }


    if (pIniCompGrp) {

       FreeADsMem(pIniCompGrp);
    }

   return;
}

void
FreeIntCompGroup(
    LPCOMPUTER_GROUP_MEMBER pCompGroupMember
    )
{
    if (pCompGroupMember->Parent) {

        FreeADsMem(pCompGroupMember->Parent);

    }


    if (pCompGroupMember->Computer) {

        FreeADsStr(pCompGroupMember->Computer);
    }


    if (pCompGroupMember->Domain) {

        FreeADsStr(pCompGroupMember->Domain);

    }


    if (pCompGroupMember->Name) {

        FreeADsStr(pCompGroupMember->Name);
    }


    if (pCompGroupMember->Sid) {

        FreeADsMem(pCompGroupMember->Sid);
    }


    FreeADsMem(pCompGroupMember);


}



BOOL
ComputeLocalGroupDataSize(
        LPCOMPUTER_GROUP_MEMBER * ppGroupMembers,
        DWORD  dwReturned,
        PDWORD pdwSize
        )
{

    DWORD i = 0;
    DWORD cb = 0;
    LPCOMPUTER_GROUP_MEMBER pMember = NULL;

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

        pMember = *(ppGroupMembers + i);

        cb += sizeof(COMPUTER_GROUP_MEMBER);

        if (pMember->Parent) {
            cb += wcslen(pMember->Parent)*sizeof(WCHAR) + sizeof(WCHAR);
        }

        if (pMember->Computer) {
            cb += wcslen(pMember->Computer)*sizeof(WCHAR) + sizeof(WCHAR);
        }

        if (pMember->Domain) {
            cb += wcslen(pMember->Domain)*sizeof(WCHAR) + sizeof(WCHAR);
        }

        if (pMember->Name) {
            cb += wcslen(pMember->Name)*sizeof(WCHAR) + sizeof(WCHAR);
        }

        if (pMember->Sid) {
            cb += GetLengthSid(pMember->Sid);
        }
    }

    *pdwSize = cb;
    return(TRUE);
}


LPBYTE
CopyIniCompGroupToCompGroup(
    LPCOMPUTER_GROUP_MEMBER  pIntCompGrp,
    LPBYTE  pExtCompGrp,
    LPBYTE  pEnd
    )
{
    LPWSTR   SourceStrings[sizeof(COMPUTER_GROUP_MEMBER)/sizeof(LPWSTR)];
    LPWSTR   *pSourceStrings=SourceStrings;
    LPCOMPUTER_GROUP_MEMBER pCompGrpMember = (LPCOMPUTER_GROUP_MEMBER)pExtCompGrp;
    DWORD dwSidLength = 0;

    memset(SourceStrings, 0, sizeof(COMPUTER_GROUP_MEMBER));
    *pSourceStrings++ = pIntCompGrp->Parent;
    *pSourceStrings++ = pIntCompGrp->Computer;
    *pSourceStrings++ = pIntCompGrp->Domain;
    *pSourceStrings++ = pIntCompGrp->Name;

    pEnd = PackStrings(
                SourceStrings,
                pExtCompGrp,
                ComputerGrpMemberStrings,
                pEnd
                );

    pCompGrpMember->Type = pIntCompGrp->Type;
    pCompGrpMember->ParentType = pIntCompGrp->ParentType;

    if (pIntCompGrp->Sid) {
        dwSidLength = GetLengthSid(pIntCompGrp->Sid);

        pEnd -= dwSidLength;

        memcpy(pEnd,
               pIntCompGrp->Sid,
               dwSidLength
               );
               
        pCompGrpMember->Sid = pEnd;
               
    }

    return pEnd;
}


BOOL
BuildLocalGroupMember(
    HANDLE hGroup,
    LPBYTE lpBuffer,
    LPCOMPUTER_GROUP_MEMBER * ppGroupMember
    )
{
    LPINI_COMP_GROUP pGroup = (LPINI_COMP_GROUP)hGroup;
    LPCOMPUTER_GROUP_MEMBER pGroupMember = NULL;
    LPLOCALGROUP_MEMBERS_INFO_2 pGrpMem = (LPLOCALGROUP_MEMBERS_INFO_2)lpBuffer;
    WCHAR szADsParent[MAX_PATH];
    LPWSTR pMemberName = NULL;
    LPWSTR pszSIDName = NULL;
    DWORD cblen = 0, dwLen = 0, dwLenDomAndName = 0;
    DWORD dwSidType = 0;
    DWORD dwSidLength = 0;
    BOOL fRet = FALSE;
    BOOL fError = FALSE;

    pGroupMember = (LPCOMPUTER_GROUP_MEMBER)AllocADsMem(
                                       sizeof(COMPUTER_GROUP_MEMBER)
                                       );
    if (!pGroupMember) {

        goto error;
    }

    dwSidType = pGrpMem->lgrmi2_sidusage;

    pMemberName = wcschr(pGrpMem->lgrmi2_domainandname, L'\\');

    cblen = wcslen(pGroup->szComputerName);

    //
    // Check to see if the lengthe of the domain name component in
    // lgrmi2_domainandname and the comptuername are the same if not
    // it cannot be a computer member object. We do this to catch the case
    // where foo.foodom is computer name. foodom\user will incorrectly
    // be identified as a local user rather than domain user
    //
    if (pMemberName) {
        *pMemberName = L'\0';
        dwLenDomAndName = wcslen(pGrpMem->lgrmi2_domainandname);
        *pMemberName = L'\\';
    }
    else {
        dwLenDomAndName = cblen;
    }


    if ((dwLenDomAndName == cblen) && !_wcsnicmp(pGroup->szComputerName, pGrpMem->lgrmi2_domainandname, cblen)) {

        //
        // This is the local user case
        //

      if (pMemberName) {

            pMemberName++;
        }
        else {

            pMemberName = pGrpMem->lgrmi2_domainandname ;
        }

        pGroupMember->Name = AllocADsStr(pMemberName);
        pGroupMember->Computer = AllocADsStr(pGroup->szComputerName);
        pGroupMember->Domain = AllocADsStr(pGroup->szDomainName);

        if (pGroupMember->Domain != NULL) {

            wsprintf(
                szADsParent,
                L"%s://%s/%s",
                szProviderName,
                pGroup->szDomainName,
                pGroup->szComputerName
                );

        } else {

            // Again we may have a null domain name for the case
            // where there are no workstations services
            wsprintf(
                szADsParent,
                L"%s://%s",
                szProviderName,
                pGroup->szComputerName
                );
        }

        pGroupMember->Parent = AllocADsStr(szADsParent);
        pGroupMember->ParentType = WINNT_COMPUTER_ID;
        //
        // Need to look at SID to see if this is a local group
        // in which case the sid will be alias.
        //

        if (dwSidType == SidTypeAlias) {
            pGroupMember->Type = WINNT_LOCALGROUP_ID;
        }
        else if (dwSidType == SidTypeUser) {
            pGroupMember->Type = WINNT_USER_ID;
        } else  {
            //
            // Unknown ??
            //
            SetLastError(ERROR_INVALID_SID);
            BAIL_ON_FAILURE(E_FAIL);

        }


    }else {

        //
        // This is the domain user, domain group case
        //

        pMemberName = wcschr(pGrpMem->lgrmi2_domainandname, L'\\');

        if (pMemberName) {

                *pMemberName = L'\0';
                pMemberName++;
                pGroupMember->Domain = AllocADsStr(pGrpMem->lgrmi2_domainandname);
                pGroupMember->Computer = NULL;


                wsprintf(
                    szADsParent,
                    L"%s://%s",
                    szProviderName,
                    pGrpMem->lgrmi2_domainandname
                    );
        }
        else {

            //
            // if name is well name like 'EveryOne' without the domain prefix,
            // we end up with using the local computer name
            //
            pMemberName = pGrpMem->lgrmi2_domainandname ;
            pGroupMember->Domain = NULL;
            pGroupMember->Computer = AllocADsStr(L"") ;

            wsprintf(
                szADsParent,
                L"WinNT:"
                );
        }

        pGroupMember->Name = AllocADsStr(pMemberName);


        pGroupMember->Parent = AllocADsStr(szADsParent);

        switch (dwSidType) {
        case SidTypeUser:
            pGroupMember->Type = WINNT_USER_ID;
            break;

        case SidTypeGroup:
        case SidTypeWellKnownGroup :
        case SidTypeAlias :
            pGroupMember->Type = WINNT_GROUP_ID;
            break;

        case SidTypeUnknown:
        case SidTypeDeletedAccount:

#if !defined(WIN95)
            //
            // In this case we want to use the stringized SID.
            // We use functions in sddl.h.
            //
            fRet = ConvertSidToStringSidWrapper(
                        pGrpMem->lgrmi2_sid,
                        &pszSIDName
                        );

            if (!fRet || !pszSIDName) {
                //
                // Not much we can do here
                //
                SetLastError(ERROR_INVALID_SID);
                fError = TRUE;
            } else {
                //
                // We are always going to return just the SID.
                //
                if (pGroupMember->Name) {
                    FreeADsStr(pGroupMember->Name);
                    pGroupMember->Name = NULL;
                }

                if (pGroupMember->Parent) {
                    FreeADsStr(pGroupMember->Parent);
                    pGroupMember->Parent = NULL;
                }

                if (pGroupMember->Domain) {
                    FreeADsStr(pGroupMember->Domain);
                    pGroupMember->Domain = NULL;
                }

                //
                // Got be either user so default to user.
                //
                pGroupMember->Type = WINNT_USER_ID;

                pGroupMember->Name = AllocADsStr(pszSIDName);
                pGroupMember->Parent = AllocADsStr(L"WinNT:");
                if (!pGroupMember->Name || ! pGroupMember->Parent) {
                    //
                    // Not enough mem - rather than ignore like we do
                    // in the rest of the places in this fn, will
                    // set the last error and hope we recover.
                    //
                    SetLastError(ERROR_INVALID_SID);
                    fError = TRUE;
                }
            }
#else
            SetLastError(ERROR_INVALID_SID);
            fError = TRUE;
#endif
            if (pszSIDName) {
                LocalFree(pszSIDName);
            }
            if (fError)
                goto error;
            break;

        default:
            SetLastError(ERROR_INVALID_SID);
            goto error;
            break;

        }

        //
        // Need to special case this as we cannot have a domain
        // name that is NULL.
        //
        if (dwSidType == SidTypeDeletedAccount
            || dwSidType == SidTypeUnknown) {
            pGroupMember->ParentType = WINNT_COMPUTER_ID;
        }
        else {
            pGroupMember->ParentType = WINNT_DOMAIN_ID;
        }

    }

    //
    // Copy the SID
    //
    if (pGrpMem->lgrmi2_sid) {

        //
        // On NT4 for some reason GetLengthSID does not set lasterror to 0
        //
        SetLastError(NO_ERROR);

        dwSidLength = GetLengthSid(pGrpMem->lgrmi2_sid);

        //
        // This is an extra check to make sure that we have the
        // correct length.
        //
        if (GetLastError() != NO_ERROR) {
            SetLastError(ERROR_INVALID_SID);
            BAIL_ON_FAILURE(E_FAIL);        
        }

        pGroupMember->Sid = AllocADsMem(dwSidLength);
        if (!pGroupMember->Sid) {
            SetLastError(ERROR_OUTOFMEMORY);
            BAIL_ON_FAILURE(E_OUTOFMEMORY);
        }

        memcpy(pGroupMember->Sid, pGrpMem->lgrmi2_sid, dwSidLength);
    }

    *ppGroupMember =  pGroupMember;
    return(TRUE);

error:

    if (pGroupMember) {

        FreeIntCompGroup(pGroupMember);
    }

    *ppGroupMember = NULL;
    return(FALSE);

}


LPBYTE
PackStrings(
    LPWSTR *pSource,
    LPBYTE pDest,
    DWORD *DestOffsets,
    LPBYTE pEnd
    )
{
    DWORD cbStr;
    WORD_ALIGN_DOWN(pEnd);

    while (*DestOffsets != -1) {
        if (*pSource) {
            cbStr = wcslen(*pSource)*sizeof(WCHAR) + sizeof(WCHAR);
            pEnd -= cbStr;
            CopyMemory( pEnd, *pSource, cbStr);
            *(LPWSTR *)(pDest+*DestOffsets) = (LPWSTR)pEnd;
        } else {
            *(LPWSTR *)(pDest+*DestOffsets)=0;
        }
        pSource++;
        DestOffsets++;
    }
    return pEnd;
}