/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software    2004-2008
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        lpobject.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Local Authentication Provider
 *
 *        Object Management Routines
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewisesoftware.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 */

#include "includes.h"


static
DWORD
LocalDirQueryMemberOfInternal(
    IN HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN PSTR pszSid,
    IN OUT PLW_HASH_TABLE pGroupHash
    );


static
DWORD
LocalDirFindObjectsInternal(
    IN HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN OPTIONAL LSA_OBJECT_TYPE ObjectType,
    IN LSA_QUERY_TYPE QueryType,
    IN DWORD dwCount,
    IN LSA_QUERY_LIST QueryList,
    IN OUT PLSA_SECURITY_OBJECT* ppObjects
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    SAMDB_OBJECT_TYPE objectType = SAMDB_OBJECT_TYPE_UNKNOWN;
    SAMDB_QUERY_TYPE queryType = SAMDB_QUERY_TYPE_UNKNOWN;
    PSAMDB_QUERY queryList = NULL;
    PSAMDB_OBJECT_INFO* objects = NULL;
    DWORD dwIndex = 0;

    /* FIXME: support generic queries */
    switch (ObjectType)
    {
    case LSA_OBJECT_TYPE_UNDEFINED:
        objectType = SAMDB_OBJECT_TYPE_UNKNOWN;
        break;

    case LSA_OBJECT_TYPE_USER:
        objectType = SAMDB_OBJECT_TYPE_USER;
        break;

    case LSA_OBJECT_TYPE_GROUP:
        objectType = SAMDB_OBJECT_TYPE_LOCAL_GROUP;
        break;

    default:
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    switch (QueryType)
    {
    case LSA_QUERY_TYPE_BY_ALIAS:
    case LSA_QUERY_TYPE_BY_NT4:
        queryType = SAMDB_QUERY_TYPE_NT_NAME;
        break;
    case LSA_QUERY_TYPE_BY_SID:
        queryType = SAMDB_QUERY_TYPE_SID;
        break;
    case LSA_QUERY_TYPE_BY_UNIX_ID:
        queryType = SAMDB_QUERY_TYPE_UNIX_ID;
        break;
    case LSA_QUERY_TYPE_BY_UPN:
    case LSA_QUERY_TYPE_BY_DN:
    default:
        dwError = LW_ERROR_NOT_HANDLED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwAllocateMemory(
                      sizeof(*queryList) * dwCount,
                      OUT_PPVOID(&queryList));
    BAIL_ON_LSA_ERROR(dwError);

    for (dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        switch (QueryType)
        {
        case LSA_QUERY_TYPE_BY_ALIAS:
        case LSA_QUERY_TYPE_BY_NT4:
            dwError = LwMbsToWc16s(
                                QueryList.ppszStrings[dwIndex],
                                &queryList[dwIndex].Name);
            BAIL_ON_LSA_ERROR(dwError);
            break;

        case LSA_QUERY_TYPE_BY_SID:
            ntStatus = RtlAllocateSidFromCString(
                                &queryList[dwIndex].Sid,
                                QueryList.ppszStrings[dwIndex]);
            dwError = LwNtStatusToWin32Error(ntStatus);
            BAIL_ON_LSA_ERROR(dwError);
            break;

        case LSA_QUERY_TYPE_BY_UNIX_ID:
            queryList[dwIndex].Id = QueryList.pdwIds[dwIndex];
            break;

        case LSA_QUERY_TYPE_BY_UPN:
        case LSA_QUERY_TYPE_BY_DN:
        default:
            dwError = LW_ERROR_NOT_HANDLED;
            BAIL_ON_LSA_ERROR(dwError);
        }
    }

    dwError = SamDbFindObjects(
                     objectType,
                     queryType,
                     dwCount,
                     queryList,
                     &objects);
    BAIL_ON_LSA_ERROR(dwError);

    for (dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        if (objects[dwIndex] == NULL)
        {
            ppObjects[dwIndex] = NULL;
            continue;
        }

        dwError = LocalMarshalSamDbObjectToSecurityObject(
                             objects[dwIndex],
                             &ppObjects[dwIndex]);
        BAIL_ON_LSA_ERROR(dwError);
    }

error:
    if (objects)
    {
        SamDbFreeObjects(dwCount, objects);
    }

    if (queryList)
    {
        for (dwIndex = 0 ; dwIndex <  dwCount ; dwIndex++)
        {
            switch (queryType)
            {
                case SAMDB_QUERY_TYPE_NT_NAME:
                case SAMDB_QUERY_TYPE_NT_OR_UNIX_NAME:
                    LW_SAFE_FREE_MEMORY(queryList[dwIndex].Name);
                    break;
                case SAMDB_QUERY_TYPE_SID:
                    RTL_FREE(&queryList[dwIndex].Sid);
                    break;
                case SAMDB_QUERY_TYPE_UNIX_ID:
                    // Nothing to do
                    break;
                default:
                    LSA_ASSERT(FALSE);
            }
        }
        LW_SAFE_FREE_MEMORY(queryList);
    }

    return dwError;
}

DWORD
LocalDirFindObjects(
    IN HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN OPTIONAL LSA_OBJECT_TYPE ObjectType,
    IN LSA_QUERY_TYPE QueryType,
    IN DWORD dwCount,
    IN LSA_QUERY_LIST QueryList,
    OUT PLSA_SECURITY_OBJECT** pppObjects
    )
{
    DWORD dwError = 0;
    PLSA_SECURITY_OBJECT* ppObjects = NULL;

    dwError = LwAllocateMemory(
        sizeof(*ppObjects) * dwCount,
        OUT_PPVOID(&ppObjects));
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LocalDirFindObjectsInternal(
        hProvider,
        FindFlags,
        ObjectType,
        QueryType,
        dwCount,
        QueryList,
        ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    *pppObjects = ppObjects;

cleanup:

    return dwError;

error:

    *pppObjects = NULL;

    if (ppObjects)
    {
        LsaUtilFreeSecurityObjectList(dwCount, ppObjects);
    }

    goto cleanup;
}

typedef struct _LOCAL_ENUM_HANDLE
{
    HANDLE hProvider;
    enum
    {
        LOCAL_ENUM_HANDLE_OBJECTS,
        LOCAL_ENUM_HANDLE_MEMBERS
    } type;

    PSID LocalGroupSid;
    SAMDB_ENUM_HANDLE EnumHandle;
    DWORD Count;
    BOOLEAN Done;

} *LOCAL_ENUM_HANDLE, **PLOCAL_ENUM_HANDLE;

DWORD
LocalDirOpenEnumObjects(
    IN HANDLE hProvider,
    OUT PHANDLE phEnum,
    IN LSA_FIND_FLAGS FindFlags,
    IN LSA_OBJECT_TYPE ObjectType,
    IN OPTIONAL PCSTR pszDomainName
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_OBJECT_TYPE objectType = SAMDB_OBJECT_TYPE_UNKNOWN;
    PWSTR pwszDomainName = NULL;
    PSAMDB_OBJECT_INFO* domainObjects = NULL;
    PSID domainSid = NULL;
    SAMDB_ENUM_HANDLE enumHandle = NULL;
    LOCAL_ENUM_HANDLE hEnum = NULL;

    dwError = LwAllocateMemory(sizeof(*hEnum), OUT_PPVOID(&hEnum));
    BAIL_ON_LSA_ERROR(dwError);

    hEnum->hProvider = hProvider;
    hEnum->type = LOCAL_ENUM_HANDLE_OBJECTS;

    switch (ObjectType)
    {
    case LSA_OBJECT_TYPE_UNDEFINED:
        objectType = SAMDB_OBJECT_TYPE_UNKNOWN;
        break;
    case LSA_OBJECT_TYPE_USER:
        objectType = SAMDB_OBJECT_TYPE_USER;
        break;
    case LSA_OBJECT_TYPE_GROUP:
        objectType = SAMDB_OBJECT_TYPE_LOCAL_GROUP;
        break;
    default:
        dwError = LW_ERROR_NOT_HANDLED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (pszDomainName)
    {
        SAMDB_QUERY domainQuery = {0};

        dwError = LwMbsToWc16s(
                         pszDomainName,
                         &pwszDomainName);
        BAIL_ON_LSA_ERROR(dwError);

        domainQuery.Name = pwszDomainName;

        dwError = SamDbFindObjects(
                         SAMDB_OBJECT_TYPE_DOMAIN,
                         SAMDB_QUERY_TYPE_NT_NAME,
                         1,
                         &domainQuery,
                         &domainObjects);
        BAIL_ON_LSA_ERROR(dwError);

        domainSid = domainObjects[0]->Sid;
    }

    dwError = SamDbOpenEnumObjects(
                        domainSid,
                        objectType,
                        SAMDB_ENUM_SORT_ORDER_NONE,
                        &enumHandle);
    BAIL_ON_LSA_ERROR(dwError);

    hEnum->EnumHandle = enumHandle;
    hEnum->Count      = 0;

    *phEnum = hEnum;

error:
    if (dwError)
    {
        if (hEnum)
        {
            LocalDirCloseEnum(hEnum);
        }
    }

    if (domainObjects)
    {
        SamDbFreeObjects(1, domainObjects);
    }

    LW_SAFE_FREE_MEMORY(pwszDomainName);

    return dwError;
}


DWORD
LocalDirEnumObjects(
    IN HANDLE hEnum,
    IN DWORD dwMaxObjectsCount,
    OUT PDWORD pdwObjectsCount,
    OUT PLSA_SECURITY_OBJECT** pppObjects
    )
{
    DWORD dwError = ERROR_SUCCESS;
    LOCAL_ENUM_HANDLE pEnum = (LOCAL_ENUM_HANDLE) hEnum;
    DWORD lsaObjectsCount = 0;
    PLSA_SECURITY_OBJECT* ppLsaObjects = NULL;
    DWORD dwIndex = 0;
    DWORD objectsCount = 0;
    PSAMDB_OBJECT_INFO* objects = NULL;

    dwError = SamDbEnumObjects(
                     pEnum->EnumHandle,
                     dwMaxObjectsCount,
                     &objectsCount,
                     &objects);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwAllocateMemory(
                   sizeof(*ppLsaObjects) * objectsCount,
                   OUT_PPVOID(&ppLsaObjects));
    BAIL_ON_LSA_ERROR(dwError);

    for (dwIndex = 0; dwIndex < objectsCount; dwIndex++)
    {
        if (objects[dwIndex]->Type != SAMDB_OBJECT_TYPE_USER &&
            objects[dwIndex]->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
        {
            //
            // We may have hit a domain object, if we're enumerating
            // all available objects, so skip it
            //
            continue;
        }

        dwError = LocalMarshalSamDbObjectToSecurityObject(
                      objects[dwIndex],
                      &ppLsaObjects[lsaObjectsCount]);
        BAIL_ON_LSA_ERROR(dwError);

        lsaObjectsCount++;
    }

    pEnum->Count += objectsCount;

error:
    if (dwError)
    {
        if (ppLsaObjects)
        {
            LsaUtilFreeSecurityObjectList(
                      lsaObjectsCount,
                      ppLsaObjects);
        }

        *pdwObjectsCount = 0;
        *pppObjects = NULL;
    }

    if (objects)
    {
        SamDbFreeObjects(objectsCount, objects);
    }

    *pdwObjectsCount = lsaObjectsCount;
    *pppObjects = ppLsaObjects;

    return dwError;
}


VOID
LocalDirCloseEnum(
    IN OUT HANDLE hEnum
    )
{
    LOCAL_ENUM_HANDLE pEnum = (LOCAL_ENUM_HANDLE) hEnum;

    if (pEnum)
    {
        if (pEnum->EnumHandle)
        {
            SamDbCloseEnum(pEnum->EnumHandle);
        }

        RTL_FREE(&pEnum->LocalGroupSid);

        LwFreeMemory(pEnum);
    }
}

DWORD
LocalDirOpenEnumMembers(
    IN HANDLE hProvider,
    OUT PHANDLE phEnum,
    IN LSA_FIND_FLAGS FindFlags,
    IN PCSTR pszSid
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    LOCAL_ENUM_HANDLE hEnum = NULL;
    PSID pSid = NULL;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* ppObjectInfo = NULL;

    ntStatus = RtlAllocateSidFromCString(
                          &pSid,
                          pszSid);
    BAIL_ON_NT_STATUS(ntStatus);

    query.Sid = pSid;
    dwError = SamDbFindObjects(
                    SAMDB_OBJECT_TYPE_UNKNOWN,
                    SAMDB_QUERY_TYPE_SID,
                    1,
                    &query,
                    &ppObjectInfo);
    BAIL_ON_LSA_ERROR(dwError);

    if (!ppObjectInfo || !ppObjectInfo[0])
    {
        dwError = LW_ERROR_NO_SUCH_OBJECT;
        BAIL_ON_LSA_ERROR(dwError);
    }
    else if (ppObjectInfo[0]->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        dwError = LW_ERROR_NO_SUCH_GROUP;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwAllocateMemory(sizeof(*hEnum), OUT_PPVOID(&hEnum));
    BAIL_ON_LSA_ERROR(dwError);

    hEnum->hProvider = hProvider;
    hEnum->type = LOCAL_ENUM_HANDLE_MEMBERS;
    hEnum->Count = 0;
    hEnum->Done = FALSE;
    hEnum->LocalGroupSid = pSid;
    pSid = NULL;

error:
    if (dwError || ntStatus)
    {
        RTL_FREE(&pSid);

        if (hEnum)
        {
            LocalDirCloseEnum(hEnum);
            hEnum = NULL;
        }
    }

    if (ppObjectInfo)
    {
        SamDbFreeObjects(1, ppObjectInfo);
    }

    if (dwError == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        dwError = LwNtStatusToWin32Error(ntStatus);
    }

    *phEnum = hEnum;

    return dwError;
}

DWORD
LocalDirEnumMembers(
    IN HANDLE hEnum,
    IN DWORD dwMaxMemberSidCount,
    OUT PDWORD pdwMemberSidCount,
    OUT PSTR** pppszMemberSids
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    LOCAL_ENUM_HANDLE pEnum = (LOCAL_ENUM_HANDLE) hEnum;
    DWORD membersCount = 0;
    PSID* members = NULL;
    DWORD memberSidsCount = 0;
    PSTR* ppszMemberSids = NULL;
    DWORD memberIndex = 0;
    PSTR pszSid = NULL;

    //
    // Local provider queries members in one call so we need
    // to store the number of members returned last time in order
    // to simulate enumeration.
    //
    if (pEnum->Done)
    {
        dwError = ERROR_NO_MORE_ITEMS;
        goto error;
    }

    dwError = SamDbQueryMembers(
                       pEnum->LocalGroupSid,
                       &membersCount,
                       &members);
    if (dwError == ERROR_NO_SUCH_GROUP)
    {
        dwError = LW_ERROR_NO_SUCH_GROUP;
    }
    BAIL_ON_LSA_ERROR(dwError);

    memberSidsCount = membersCount;

    dwError = LwAllocateMemory(
                   sizeof(*ppszMemberSids) * memberSidsCount,
                   OUT_PPVOID(&ppszMemberSids));
    BAIL_ON_LSA_ERROR(dwError);

    for (memberIndex = 0; memberIndex < memberSidsCount; memberIndex++)
    {
        ntStatus = RtlAllocateCStringFromSid(
                             &pszSid,
                             members[memberIndex]);
        BAIL_ON_NT_STATUS(ntStatus);

        dwError = LwAllocateString(
                             pszSid,
                             &(ppszMemberSids[memberIndex]));
        BAIL_ON_LSA_ERROR(dwError);

        RTL_FREE(&pszSid);
    }

    pEnum->Count += membersCount;
    pEnum->Done   = TRUE;

error:
    if (dwError || ntStatus)
    {
        if (ppszMemberSids)
        {
            LwFreeStringArray(
                     ppszMemberSids,
                     memberSidsCount);
        }

        memberSidsCount = 0;
        ppszMemberSids  = NULL;
    }

    if (members)
    {
        SamDbFreeSidList(
                membersCount,
                members);
    }

    RTL_FREE(&pszSid);

    if (dwError == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        dwError = LwNtStatusToWin32Error(ntStatus);
    }

    *pdwMemberSidCount = memberSidsCount;
    *pppszMemberSids   = ppszMemberSids;

    return dwError;
}

static
DWORD
LocalDirQueryMemberOfInternal(
    IN HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN PSTR pszSid,
    IN OUT PLW_HASH_TABLE pGroupHash
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSID objectSid = NULL;
    DWORD membershipsCount = 0;
    PSID* memberships = NULL;
    DWORD membershipIndex = 0;
    PSTR pszGroupSid = NULL;
    PSTR pszPreviousGroupSid = NULL;

    ntStatus = RtlAllocateSidFromCString(
                       &objectSid,
                       pszSid);
    BAIL_ON_NT_STATUS(ntStatus);

    dwError = SamDbQueryMemberOf(
                       objectSid,
                       &membershipsCount,
                       &memberships);
    if (dwError == ERROR_NO_SUCH_USER)
    {
        // No memberships found for this SID so just return
        // with an empty list
        dwError = ERROR_SUCCESS;
        goto error;
    }
    else if (dwError != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(dwError);
    }

    for (membershipIndex = 0;
         membershipIndex < membershipsCount;
         membershipIndex++)
    {
        ntStatus = RtlAllocateCStringFromSid(
                           &pszGroupSid,
                           memberships[membershipIndex]);
        BAIL_ON_NT_STATUS(ntStatus);

        dwError = LwHashGetValue(
                           pGroupHash,
                           pszGroupSid,
                           OUT_PPVOID(&pszPreviousGroupSid));
        if (dwError == ERROR_NOT_FOUND)
        {
            dwError = LwHashSetValue(
                               pGroupHash,
                               pszGroupSid,
                               pszGroupSid);
            BAIL_ON_LSA_ERROR(dwError);

            dwError = LocalDirQueryMemberOfInternal(
                               hProvider,
                               FindFlags,
                               pszGroupSid,
                               pGroupHash);
            pszGroupSid = NULL;
            BAIL_ON_LSA_ERROR(dwError);
        }
        else
        {
            BAIL_ON_LSA_ERROR(dwError);
        }

        LW_SAFE_FREE_MEMORY(pszGroupSid);
    }

error:
    if (memberships)
    {
        SamDbFreeSidList(
                 membershipsCount,
                 memberships);
    }

    RTL_FREE(&objectSid);

    if (dwError == ERROR_SUCCESS &&
        ntStatus != STATUS_SUCCESS)
    {
        dwError = LwNtStatusToWin32Error(ntStatus);
    }

    return dwError;
}


DWORD
LocalDirQueryMemberOf(
    IN HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN DWORD dwSidCount,
    IN PSTR* ppszSids,
    OUT PDWORD pdwGroupSidCount,
    OUT PSTR** pppszGroupSids
    )
{
    DWORD dwError = ERROR_SUCCESS;
    DWORD dwIndex = 0;
    PLW_HASH_TABLE pGroupHash = NULL;
    LW_HASH_ITERATOR hashIterator = {0};
    LW_HASH_ENTRY* pHashEntry = NULL;
    DWORD dwGroupSidCount = 0;
    PSTR* ppszGroupSids = NULL;

    dwError = LwHashCreate(
                    13,
                    LwHashCaselessStringCompare,
                    LwHashCaselessStringHash,
                    NULL,
                    NULL,
                    &pGroupHash);
    BAIL_ON_LSA_ERROR(dwError);

    for (dwIndex = 0; dwIndex < dwSidCount; dwIndex++)
    {
        dwError = LocalDirQueryMemberOfInternal(
            hProvider,
            FindFlags,
            ppszSids[dwIndex],
            pGroupHash);
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwGroupSidCount = (DWORD) LwHashGetKeyCount(pGroupHash);

    if (dwGroupSidCount)
    {
        dwError = LwAllocateMemory(
            sizeof(*ppszGroupSids) * dwGroupSidCount,
            OUT_PPVOID(&ppszGroupSids));
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwHashGetIterator(pGroupHash, &hashIterator);
        BAIL_ON_LSA_ERROR(dwError);

        for(dwIndex = 0; (pHashEntry = LwHashNext(&hashIterator)) != NULL; dwIndex++)
        {
            ppszGroupSids[dwIndex] = (PSTR) pHashEntry->pValue;
            pHashEntry->pValue = NULL;
        }
    }

    *pdwGroupSidCount = dwGroupSidCount;
    *pppszGroupSids = ppszGroupSids;

cleanup:

    if (pGroupHash)
    {
        if (LwHashGetIterator(pGroupHash, &hashIterator) == 0)
        {
            while ((pHashEntry = LwHashNext(&hashIterator)))
            {
                LW_SAFE_FREE_MEMORY(pHashEntry->pValue);
            }
        }

        LwHashSafeFree(&pGroupHash);
    }

    return dwError;

error:

    *pdwGroupSidCount = 0;
    *pppszGroupSids = NULL;

    if (ppszGroupSids)
    {
        LwFreeStringArray(ppszGroupSids, dwGroupSidCount);
    }

    goto cleanup;
}

DWORD
LocalDirFindObjectByGenericName(
    HANDLE hProvider,
    IN LSA_FIND_FLAGS FindFlags,
    IN LSA_OBJECT_TYPE ObjectType,
    PCSTR pszName,
    PLSA_SECURITY_OBJECT* ppObject
    )
{
    DWORD dwError = 0;
    PLSA_SECURITY_OBJECT* ppObjects = NULL;
    LSA_QUERY_LIST QueryList;
    LSA_QUERY_TYPE QueryType = 0;
    PLSA_LOGIN_NAME_INFO pLoginInfo = NULL;

    BAIL_ON_INVALID_HANDLE(hProvider);

    dwError = LsaSrvCrackDomainQualifiedName(
        pszName,
        &pLoginInfo);
    BAIL_ON_LSA_ERROR(dwError);

    switch (pLoginInfo->nameType)
    {
    case NameType_NT4:
        QueryType = LSA_QUERY_TYPE_BY_NT4;
        break;
    case NameType_UPN:
        QueryType = LSA_QUERY_TYPE_BY_UPN;
        break;
    case NameType_Alias:
        QueryType = LSA_QUERY_TYPE_BY_ALIAS;
        break;
    default:
        dwError = LW_ERROR_INTERNAL;
        BAIL_ON_LSA_ERROR(dwError);
    }

    QueryList.ppszStrings = &pszName;

    dwError = LocalFindObjects(
        hProvider,
        FindFlags,
        ObjectType,
        QueryType,
        1,
        QueryList,
        &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    if (ppObjects[0] == NULL)
    {
        switch (ObjectType)
        {
        case LSA_OBJECT_TYPE_USER:
            dwError = LW_ERROR_NO_SUCH_USER;
            break;
        case LSA_OBJECT_TYPE_GROUP:
            dwError = LW_ERROR_NO_SUCH_GROUP;
            break;
        default:
            dwError = LW_ERROR_NO_SUCH_OBJECT;
        }
        BAIL_ON_LSA_ERROR(dwError);
    }

    *ppObject = ppObjects[0];
    ppObjects[0] = NULL;

cleanup:

    if (pLoginInfo)
    {
        LsaSrvFreeNameInfo(pLoginInfo);
    }

    LsaUtilFreeSecurityObjectList(1, ppObjects);

    return dwError;

error:

    goto cleanup;
}
