/* -*- 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
 * 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:
 *
 *        samdbenum.c
 *
 * Abstract:
 *
 *
 *      Likewise SAM Database Provider
 *
 *      SAM objects enumeration routines
 *
 * Authors: Rafal Szczesniak (rafal@likewise.com)
 *
 */

#include "includes.h"


static
DWORD
SamDbBuildSelectObjectsStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    SAMDB_ENUM_SORT_ORDER SortOrder,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount,
    PSAMDB_ATTRIBUTE *pOutputAttributes,
    PDWORD pOutputAttributesCount
    );

static
DWORD
SamDbGetNextObject(
    struct _SAMDB_ENUM_HANDLE_DATA* EnumContext
    );


DWORD
SamDbOpenEnumObjects(
    IN OPTIONAL PSID DomainSid,
    IN SAMDB_OBJECT_TYPE ObjectType,
    IN SAMDB_ENUM_SORT_ORDER SortOrder,
    OUT PSAMDB_ENUM_HANDLE pEnumHandle
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    SAMDB_ENUM_HANDLE enumHandle = NULL;

    error = SamDbAcquireContext(&dbContext);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbOpenEnumObjectsInternal(
                        dbContext,
                        DomainSid,
                        ObjectType,
                        SortOrder,
                        &enumHandle);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    if (error)
    {
        if (dbContext)
        {
            SamDbReleaseContext(dbContext);
        }
    }

    *pEnumHandle = enumHandle;

    return error;
}


DWORD
SamDbOpenEnumObjectsInternal(
    PSAMDB_CONTEXT DbContext,
    OPTIONAL PSID DomainSid,
    SAMDB_OBJECT_TYPE ObjectType,
    SAMDB_ENUM_SORT_ORDER SortOrder,
    PSAMDB_ENUM_HANDLE pEnumHandle
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_UNKNOWN;
    SAMDB_ENUM_HANDLE enumHandle = NULL;
    ULONG domainSidLength = 0;
    PSID domainSid = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;
    PSAMDB_ATTRIBUTE outputAttributes = NULL;
    DWORD outputAttributesCount = 0;
    LONG64 sequenceNumber = 0;

    BAIL_ON_INVALID_POINTER(pEnumHandle, error);

    objectClass = SamDbMapObjectTypeToObjectClass(ObjectType);

    error = LwAllocateMemory(
                  sizeof(*enumHandle),
                  OUT_PPVOID(&enumHandle));
    BAIL_ON_SAMDB_ERROR(error);

    if (DomainSid)
    {
        domainSidLength = RtlLengthSid(DomainSid);
        error = LwAllocateMemory(
                      domainSidLength,
                      OUT_PPVOID(&domainSid));
        BAIL_ON_SAMDB_ERROR(error);

        ntStatus = RtlCopySid(
                      domainSidLength,
                      domainSid,
                      DomainSid);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    error = SamDbBuildSelectObjectsStatement(
                  DbContext,
                  objectClass,
                  SortOrder,
                  &sqlStatement,
                  &inputAttributes,
                  &inputAttributesCount,
                  &outputAttributes,
                  &outputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    if (inputAttributesCount > 0)
    {
        error = SamDbBindAttributeValues(
                      DbContext,
                      sqlStatement,
                      inputAttributes,
                      inputAttributesCount);
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = SamDbGetSequenceNumber(
                  DbContext,
                  DomainSid,
                  &sequenceNumber);
    BAIL_ON_SAMDB_ERROR(error);

    enumHandle->ObjectClass     = objectClass;
    enumHandle->DomainSid       = domainSid;
    enumHandle->DbContext       = DbContext;
    enumHandle->SqlStatement    = sqlStatement;
    enumHandle->Attributes      = outputAttributes;
    enumHandle->AttributesCount = outputAttributesCount;
    enumHandle->ResumeAt        = 0;
    enumHandle->SequenceNumber  = sequenceNumber;
    enumHandle->SortOrder       = SortOrder;

    do
    {
        error = SamDbGetNextObject(enumHandle);
        if (error == ERROR_NO_MORE_ITEMS)
        {
            error = ERROR_SUCCESS;
        }
        // ERROR_MORE_DATA means we need to skip an object
        // and access another one
    } while (error == ERROR_MORE_DATA);

cleanup:
error:
    if (error || ntStatus)
    {
        if (enumHandle)
        {
            SamDbCloseEnumInternal(enumHandle);
        }

        enumHandle = NULL;
    }

    if (inputAttributes)
    {
        SamDbFreeAttributes(
                   inputAttributes,
                   inputAttributesCount);
    }

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

    *pEnumHandle = enumHandle;

    return error;
}


#define SAMDB_SQL_SELECT_OBJECT_PREFIX     "SELECT "
#define SAMDB_SQL_SELECT_OBJECT_FROM       " FROM " SAM_DB_OBJECTS_TABLE
#define SAMDB_SQL_SELECT_OBJECT_WHERE      " WHERE "
#define SAMDB_SQL_SELECT_OBJECT_ORDER      "ORDER BY "
#define SAMDB_SQL_SELECT_OBJECT_SORT_TYPE  "ASC"


static
DWORD
SamDbBuildSelectObjectsStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    SAMDB_ENUM_SORT_ORDER SortOrder,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount,
    PSAMDB_ATTRIBUTE *pOutputAttributes,
    PDWORD pOutputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD enumStatus = ERROR_SUCCESS;
    DWORD inputAttributesCount = 0;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributeIndex = 0;
    DWORD outputAttributesCount = 0;
    PSAMDB_ATTRIBUTE outputAttributes = NULL;
    DWORD outputAttributeIndex = 0;
    size_t sqlStatementSize = 512;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 8192;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PDWORD pObjectClass = NULL;

    SAMDB_ATTRIBUTE inputAttrDef[] = {
        {
            SAMDB_ATTR_OBJECT_CLASS,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
    };

    SAMDB_ATTRIBUTE outputAttrDef[] = {
        {
            SAMDB_ATTR_OBJECT_CLASS,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_DOMAIN,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_SAM_ACCOUNT_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_LIKEWISE_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_FULL_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_DESCRIPTION,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_COMMENT,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_UID,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_ACCOUNT_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_GECOS,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_HOME_DIR,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_HOME_DRIVE,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_LOGON_SCRIPT,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_PROFILE_PATH,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_WORKSTATIONS,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_PARAMETERS,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_SHELL,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_PASSWORD_LAST_SET,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_ACCOUNT_EXPIRY,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_LM_HASH,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_NT_HASH,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_PRIMARY_GROUP,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_GID,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_COUNTRY_CODE,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_CODE_PAGE,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_MAX_PWD_AGE,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_MIN_PWD_AGE,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_LAST_LOGON,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_LAST_LOGOFF,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_LOGON_COUNT,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_BAD_PASSWORD_COUNT,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_MIN_PWD_LENGTH,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_PWD_HISTORY_LENGTH,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_SEQUENCE_NUMBER,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
    };

    error = LwAllocateMemory(
                    sizeof(outputAttrDef),
                    OUT_PPVOID(&outputAttributes));
    BAIL_ON_SAMDB_ERROR(error);

    outputAttributesCount = sizeof(outputAttrDef)/sizeof(outputAttrDef[0]);

    error = LwAllocateMemory(
                    sizeof(inputAttrDef),
                    OUT_PPVOID(&inputAttributes));
    BAIL_ON_SAMDB_ERROR(error);

    //
    // Create SQL statement string (SELECT ... )
    //
    error = LwAllocateMemory(
                    sizeof(sqlStatementString[0]) * sqlStatementSize,
                    OUT_PPVOID(&sqlStatementString));
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s",
                    SAMDB_SQL_SELECT_OBJECT_PREFIX);
    BAIL_ON_SAMDB_ERROR(error);

    for (outputAttributeIndex = 0;
         outputAttributeIndex < outputAttributesCount;
         outputAttributeIndex++)
    {
        //
        // Append SQL column names separated with ","
        //
        outputAttributes[outputAttributeIndex] =
            outputAttrDef[outputAttributeIndex];

        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      (outputAttributeIndex < outputAttributesCount - 1) ? "%s," : "%s",
                      outputAttributes[outputAttributeIndex].DbColumnName);
        BAIL_ON_SAMDB_ERROR(error);
    }

    //
    // Filter the enumeration through a given objectclass or just enumerate
    // everything that can be found in the database
    //
    if (ObjectClass != SAMDB_OBJECT_CLASS_UNKNOWN)
    {
        error = LwAllocateMemory(
                          sizeof(*pObjectClass),
                          OUT_PPVOID(&pObjectClass));
        BAIL_ON_SAMDB_ERROR(error);

        *pObjectClass = ObjectClass;

        inputAttributes[inputAttributeIndex] = inputAttrDef[0];
        inputAttributes[inputAttributeIndex].Value = pObjectClass;
        inputAttributes[inputAttributeIndex].FreeFn = SamDbAttributeFreePointer;

        //
        // Append SQL statement (FROM samdbobjects WHERE ...)
        //
        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "%s%s%s = ?1",
                        SAMDB_SQL_SELECT_OBJECT_FROM,
                        SAMDB_SQL_SELECT_OBJECT_WHERE,
                        inputAttributes[inputAttributeIndex++].DbColumnName);
        BAIL_ON_SAMDB_ERROR(error);
    }
    else
    {
        //
        // Append SQL statement (FROM samdbobjects ...)
        //
        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "%s",
                        SAMDB_SQL_SELECT_OBJECT_FROM);
        BAIL_ON_SAMDB_ERROR(error);
    }

    inputAttributesCount = inputAttributeIndex;

    if (SortOrder == SAMDB_ENUM_SORT_ORDER_ASCENDING)
    {
        PSTR sortColumn = NULL;

        if (ObjectClass == SAMDB_OBJECT_CLASS_USER ||
            ObjectClass == SAMDB_OBJECT_CLASS_LOCAL_GROUP)
        {
            sortColumn = SAM_DB_COL_SAM_ACCOUNT_NAME;
        }
        else if (ObjectClass == SAMDB_OBJECT_CLASS_DOMAIN)
        {
            sortColumn = SAM_DB_COL_DOMAIN;
        }

        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      " %s%s %s;",
                      SAMDB_SQL_SELECT_OBJECT_ORDER,
                      sortColumn,
                      SAMDB_SQL_SELECT_OBJECT_SORT_TYPE);
        BAIL_ON_SAMDB_ERROR(error);
    }
    else
    {
        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      ";");
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = sqlite3_prepare_v2(
                      DbContext->DbHandle,
                      sqlStatementString,
                      -1,
                      &sqlStatement,
                      NULL);
    if (error)
    {
        SAMDB_LOG_DEBUG("Error when preparing SQL statement:\n%s",
                        sqlStatementString);
    }
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

cleanup:
    if (error)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }
    }

    LW_SAFE_FREE_MEMORY(sqlStatementString);

    *pSqlStatement = sqlStatement;
    *pInputAttributes = inputAttributes;
    *pInputAttributesCount = inputAttributesCount;
    *pOutputAttributes = outputAttributes;
    *pOutputAttributesCount = outputAttributesCount;

    if (error == ERROR_SUCCESS)
    {
        error = enumStatus;
    }

    return error;
}


static
DWORD
SamDbGetNextObject(
    struct _SAMDB_ENUM_HANDLE_DATA* EnumContext
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD sqliteStatus = 0;
    DWORD attributeIndex = 0;
    CHAR attrObjectClassName[] = SAMDB_ATTR_OBJECT_CLASS;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_UNKNOWN;

    if (EnumContext->Object)
    {
        SamDbFreeObject(EnumContext->Object);
        EnumContext->Object = NULL;
    }

    error = sqlite3_step(EnumContext->SqlStatement);
    if (error != SQLITE_ROW &&
        error != SQLITE_DONE)
    {
        SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                        "while %u or %u was expected",
                        error,
                        SQLITE_ROW,
                        SQLITE_DONE);
        BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, EnumContext->SqlStatement);
    }
    else
    {
        sqliteStatus = error;
        error = ERROR_SUCCESS;
    }

    if (sqliteStatus == SQLITE_DONE)
    {
        EnumContext->Object = NULL;
        goto cleanup;
    }

    error = SamDbGetOutputAttributeValues(
                 EnumContext->DbContext,
                 EnumContext->SqlStatement,
                 EnumContext->Attributes,
                 EnumContext->AttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    //
    // If no object class has been specified look for the attribute
    // to find it. This is necessary information for correct object
    // marshaling.
    //
    if (EnumContext->ObjectClass == SAMDB_OBJECT_CLASS_UNKNOWN)
    {
        for (attributeIndex = 0;
             attributeIndex < EnumContext->AttributesCount;
             attributeIndex++)
        {
            if (RtlCStringIsEqual(
                     attrObjectClassName,
                     EnumContext->Attributes[attributeIndex].DbColumnName,
                     TRUE))
            {
                PDWORD pObjectClass =
                    (PDWORD)EnumContext->Attributes[attributeIndex].Value;
                objectClass = *pObjectClass;

                break;
            }
        }
    }
    else
    {
        objectClass = EnumContext->ObjectClass;
    }

    switch (objectClass)
    {
    case SAMDB_OBJECT_CLASS_DOMAIN:
        error = SamDbMarshalDomainObject(
                        EnumContext->Attributes,
                        EnumContext->AttributesCount,
                        &EnumContext->Object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_USER:
        error = SamDbMarshalUserObject(
                        EnumContext->Attributes,
                        EnumContext->AttributesCount,
                        &EnumContext->Object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_LOCAL_GROUP:
        error = SamDbMarshalLocalGroupObject(
                        EnumContext->Attributes,
                        EnumContext->AttributesCount,
                        &EnumContext->Object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_FOREIGN_SECURITY_PRINCIPAL:
        // Skip this object and enforce fetching another one
        error = ERROR_MORE_DATA;
        goto cleanup;

    default:
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Invalid object type (%u) found in enumeration "
                        "context.",
                        (unsigned int)EnumContext->ObjectClass);
        goto cleanup;
    }

cleanup:

    SamDbFreeAttributesContents(
        EnumContext->Attributes,
        EnumContext->AttributesCount);

    if (error == ERROR_SUCCESS &&
        sqliteStatus == SQLITE_ROW)
    {
        error = ERROR_SUCCESS;
    }
    else if (error == ERROR_SUCCESS &&
             sqliteStatus == SQLITE_DONE)
    {
        error = ERROR_NO_MORE_ITEMS;
    }

    return error;
}


VOID
SamDbCloseEnum(
    SAMDB_ENUM_HANDLE EnumHandle
    )
{
    PSAMDB_CONTEXT dbContext = EnumHandle->DbContext;

    SamDbCloseEnumInternal(EnumHandle);

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }
}


VOID
SamDbCloseEnumInternal(
    SAMDB_ENUM_HANDLE EnumHandle
    )
{
    if (EnumHandle->SqlStatement)
    {
        sqlite3_finalize(EnumHandle->SqlStatement);
    }

    // DbContext is released in SamDbCloseEnum
    EnumHandle->DbContext = NULL;

    if (EnumHandle->Attributes)
    {
        SamDbFreeAttributes(
                   EnumHandle->Attributes,
                   EnumHandle->AttributesCount);
    }

    LW_SAFE_FREE_MEMORY(EnumHandle->DomainSid);
    LW_SAFE_FREE_MEMORY(EnumHandle);
}


DWORD
SamDbEnumObjects(
    IN SAMDB_ENUM_HANDLE EnumHandle,
    IN DWORD MaxCount,
    OUT PDWORD pObjectsCount,
    OUT PSAMDB_OBJECT_INFO** pObjects
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD enumStatus = ERROR_SUCCESS;
    struct _SAMDB_ENUM_HANDLE_DATA* enumContext =
        (struct _SAMDB_ENUM_HANDLE_DATA*)EnumHandle;
    LONG64 sequenceNumber = 0;
    DWORD resumeAt = 0;
    DWORD enumObjectsSize = 0;
    PSAMDB_OBJECT_INFO object = NULL;
    PSAMDB_OBJECT_INFO* objects = NULL;

    BAIL_ON_INVALID_POINTER(EnumHandle, error);

    error = SamDbGetSequenceNumber(
                  enumContext->DbContext,
                  enumContext->DomainSid,
                  &sequenceNumber);
    BAIL_ON_SAMDB_ERROR(error);

    if (enumContext->SequenceNumber != sequenceNumber)
    {
        //
        // Looks like we're out of sync with the database contents
        // since the time when enum handle was opened. Enforce
        // the enumeration all over again.
        //
        error = ERROR_INVALID_DATA;
        BAIL_ON_SAMDB_ERROR(error);
    }

    resumeAt = enumContext->ResumeAt;

    if (MaxCount == (DWORD)-1)
    {
        enumObjectsSize = sizeof(*objects) * 64;
    }
    else
    {
        enumObjectsSize = sizeof(*objects) * MaxCount;
    }

    error = LwAllocateMemory(
                    enumObjectsSize,
                    OUT_PPVOID(&objects));
    BAIL_ON_SAMDB_ERROR(error);

    while (enumContext->Object &&
           resumeAt < enumContext->ResumeAt + MaxCount)
    {
        object = enumContext->Object;
        enumContext->Object = NULL;

        if (enumContext->DomainSid == NULL ||
            RtlIsPrefixSid(
                    enumContext->DomainSid,
                    object->Sid))
        {
            DWORD objectIndex = resumeAt - enumContext->ResumeAt;

            if ((objectIndex + 1) * sizeof(*objects) > enumObjectsSize)
            {
                PSAMDB_OBJECT_INFO* newObjects = NULL;

                enumObjectsSize *= 2;

                error = LwReallocMemory(
                                   objects,
                                   OUT_PPVOID(&newObjects),
                                   enumObjectsSize);
                BAIL_ON_SAMDB_ERROR(error);

                objects = newObjects;
                newObjects = NULL;
            }

            objects[objectIndex] = object;
            resumeAt++;
        }
        else
        {
            SamDbFreeObject(object);
            object = NULL;
        }

        do
        {
            error = SamDbGetNextObject(enumContext);
            if (error == ERROR_NO_MORE_ITEMS)
            {
                enumStatus = error;
                error = ERROR_SUCCESS;
            }
            // ERROR_MORE_DATA means we need to skip an object
            // and access another one
        } while (error == ERROR_MORE_DATA);
    }

cleanup:
    if (error)
    {
        SamDbFreeObjects(
                resumeAt - enumContext->ResumeAt,
                objects);
        objects = NULL;
    }

    *pObjectsCount = resumeAt - enumContext->ResumeAt;
    *pObjects = objects;

    if (enumContext->Object &&
        enumStatus == ERROR_SUCCESS &&
        MaxCount == (resumeAt - enumContext->ResumeAt))
    {
        //
        // Number of enumerated objects has reached MaxCount.
        // There's another object waiting in the context.
        // SamDbGetNextObject says there's more.
        // Verdict: There will be more objects to enumerate.
        //
        error = ERROR_MORE_DATA;
    }
    else if (!enumContext->Object &&
             enumStatus == ERROR_NO_MORE_ITEMS &&
             resumeAt - enumContext->ResumeAt > 0)
    {
        //
        // Number of enumerated object may have reached MaxCount.
        // There's no objects waiting in the context.
        // SamDbGetNextObject says there's no more objects.
        // Verdict: It is done.
        //
        error = ERROR_SUCCESS;
    }
    else if (!enumContext->Object &&
             resumeAt - enumContext->ResumeAt == 0)
    {
        //
        // Number of enumerated object is zero.
        // There's no objects waiting in the context.
        // Verdict: There's nothing to enumerate any longer.
        //
        error = ERROR_NO_MORE_ITEMS;
    }

    enumContext->ResumeAt = resumeAt;

    return error;
}
