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

#include "includes.h"


static
DWORD
SamDbParseNtQualifiedName(
    PWSTR NtQualifiedName,
    PWSTR* pDomainName,
    PWSTR* pAccountName
    );

static
DWORD
SamDbBuildSelectObjectStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    SAMDB_QUERY_TYPE QueryType,
    PSAMDB_QUERY Query,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount,
    PSAMDB_ATTRIBUTE* pOutputAttributes,
    PDWORD pOutputAttributesCount
    );


DWORD
SamDbFindObjects(
    IN SAMDB_OBJECT_TYPE ObjectType,
    IN SAMDB_QUERY_TYPE QueryType,
    IN DWORD QueryCount,
    IN PSAMDB_QUERY QueryList,
    OUT PSAMDB_OBJECT_INFO** pObjects
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_UNKNOWN;
    PSAMDB_OBJECT_INFO* objects = NULL;
    PSAMDB_OBJECT_INFO object = NULL;
    DWORD queryIndex = 0;

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

    objectClass = SamDbMapObjectTypeToObjectClass(ObjectType);

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

    for (queryIndex = 0; queryIndex < QueryCount; queryIndex++)
    {
        error = SamDbFindObjectInternal(
                      dbContext,
                      objectClass,
                      QueryType,
                      &(QueryList[queryIndex]),
                      &object);
        if (error == ERROR_FILE_NOT_FOUND)
        {
            error = ERROR_SUCCESS;
            object = NULL;
        }
        BAIL_ON_SAMDB_ERROR(error);

        objects[queryIndex] = object;
    }

cleanup:
    if (error)
    {
        SamDbFreeObjects(
                  QueryCount,
                  objects);
        objects = NULL;
    }

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }

    *pObjects = objects;

    return error;
}


DWORD
SamDbFindObjectInternal(
    IN PSAMDB_CONTEXT DbContext,
    IN SAMDB_OBJECT_CLASS ObjectClass,
    IN SAMDB_QUERY_TYPE QueryType,
    IN PSAMDB_QUERY Query,
    OUT PSAMDB_OBJECT_INFO* pObject
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR domainName = NULL;
    PWSTR accountName = NULL;
    PSAMDB_OBJECT_INFO domainObject = NULL;
    PSID domainSid = NULL;
    SAMDB_QUERY query = {0};
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;
    PSAMDB_ATTRIBUTE outputAttributes = NULL;
    DWORD outputAttributesCount = 0;
    PSAMDB_OBJECT_INFO object = NULL;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_UNKNOWN;
    CHAR objectTypeAttrName[] = SAMDB_ATTR_OBJECT_CLASS;
    DWORD attributeIndex = 0;

    //
    // Check if NT name passed is a qualified name including
    // domain name (DOMAIN\Account). If so, the name needs to
    // be parsed to get domain and account name separated.
    // Domain name can then be used for filtering out accounts
    // that happen to have the same account name but are from
    // a different domain.
    //
    if (QueryType == SAMDB_QUERY_TYPE_NT_NAME ||
        QueryType == SAMDB_QUERY_TYPE_NT_OR_UNIX_NAME)
    {
        error = SamDbParseNtQualifiedName(
                         Query->Name,
                         &domainName,
                         &accountName);
        BAIL_ON_SAMDB_ERROR(error);

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

            domainQuery.Name = domainName;
            error = SamDbFindObjectInternal(
                             DbContext,
                             SAMDB_OBJECT_CLASS_DOMAIN,
                             SAMDB_QUERY_TYPE_NT_NAME,
                             &domainQuery,
                             &domainObject);
            BAIL_ON_SAMDB_ERROR(error);

            ntStatus = RtlDuplicateSid(
                             &domainSid,
                             domainObject->Sid);
            BAIL_ON_NT_STATUS(ntStatus);
        }

        query.Name = accountName;
    }
    else
    {
        query.Name = Query->Name;
    }

    error = SamDbBuildSelectObjectStatement(
                  DbContext,
                  ObjectClass,
                  QueryType,
                  &query,
                  &sqlStatement,
                  &inputAttributes,
                  &inputAttributesCount,
                  &outputAttributes,
                  &outputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbBindAttributeValues(
                  DbContext,
                  sqlStatement,
                  inputAttributes,
                  inputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_step(sqlStatement);
    if (error == SQLITE_ROW)
    {
        error = ERROR_SUCCESS;
    }
    else if (error == SQLITE_DONE)
    {
        // No objects found so just return status code and
        // NULL object pointer
        error = ERROR_FILE_NOT_FOUND;
        goto cleanup;
    }
    else
    {
        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, sqlStatement);
    }

    error = SamDbGetOutputAttributeValues(
                 DbContext,
                 sqlStatement,
                 outputAttributes,
                 outputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    for (attributeIndex = 0;
         attributeIndex < outputAttributesCount;
         attributeIndex++)
    {
        if (RtlCStringIsEqual(
                     outputAttributes[attributeIndex].DbColumnName,
                     objectTypeAttrName,
                     TRUE))
        {
            PDWORD pClass = (PDWORD)outputAttributes[attributeIndex].Value;
            objectClass = *pClass;
            break;
        }
    }

    switch (objectClass)
    {
    case SAMDB_OBJECT_CLASS_DOMAIN:
        error = SamDbMarshalDomainObject(
                        outputAttributes,
                        outputAttributesCount,
                        &object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_USER:
        error = SamDbMarshalUserObject(
                        outputAttributes,
                        outputAttributesCount,
                        &object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_LOCAL_GROUP:
        error = SamDbMarshalLocalGroupObject(
                        outputAttributes,
                        outputAttributesCount,
                        &object);
        BAIL_ON_SAMDB_ERROR(error);
        break;

    case SAMDB_OBJECT_CLASS_FOREIGN_SECURITY_PRINCIPAL:
        // This object class is internal to samdb and cannot be
        // marshaled to a valid object info
        error = ERROR_FILE_NOT_FOUND;
        goto cleanup;

    default:
        error = ERROR_INTERNAL_ERROR;
        SAMDB_LOG_ERROR("Unknown object class %u found in the database",
                        (unsigned int)objectClass);
        goto cleanup;
    }

    //
    // If domain SID is available double-check if this is the right
    // user or local group
    //
    if (objectClass == SAMDB_OBJECT_CLASS_USER ||
        objectClass == SAMDB_OBJECT_CLASS_LOCAL_GROUP)
    {
        if (domainSid &&
            !RtlIsStrictPrefixSid(
                        domainSid,
                        object->Sid))
        {
            error = ERROR_FILE_NOT_FOUND;
            goto cleanup;
        }
    }

cleanup:
error:
    if (error)
    {
        if (object)
        {
            SamDbFreeObject(object);
            object = NULL;
        }
    }

    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

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

    if (outputAttributes)
    {
        SamDbFreeAttributes(
                      outputAttributes,
                      outputAttributesCount);
    }

    if (domainObject)
    {
        SamDbFreeObject(domainObject);
    }

    LW_SAFE_FREE_MEMORY(domainName);
    LW_SAFE_FREE_MEMORY(accountName);
    RTL_FREE(&domainSid);

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

    *pObject = object;

    return error;
}


static
DWORD
SamDbParseNtQualifiedName(
    PWSTR NtQualifiedName,
    PWSTR* pDomainName,
    PWSTR* pAccountName
    )
{
    DWORD error = ERROR_SUCCESS;
    WCHAR separator = (WCHAR)'\\';
    DWORD index = 0;
    size_t ntQualifiedNameLength = 0;
    PWSTR ntName = NULL;
    PWSTR domainName = NULL;
    PWSTR accountName = NULL;

    error = LwWc16sLen(
                 NtQualifiedName,
                 &ntQualifiedNameLength);
    BAIL_ON_SAMDB_ERROR(error);

    for (index = 0; index < (DWORD)ntQualifiedNameLength; index++)
    {
        if (NtQualifiedName[index] == separator)
        {
            error = LwAllocateWc16String(
                           &ntName,
                           NtQualifiedName);
            BAIL_ON_SAMDB_ERROR(error);

            ntName[index] = 0;

            error = LwAllocateWc16String(
                           &domainName,
                           ntName);
            BAIL_ON_SAMDB_ERROR(error);

            index++;

            error = LwAllocateWc16String(
                           &accountName,
                           &(ntName[index]));
            BAIL_ON_SAMDB_ERROR(error);

            goto cleanup;
        }
    }

    error = LwAllocateWc16String(
                   &accountName,
                   NtQualifiedName);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    if (error)
    {
        LW_SAFE_FREE_MEMORY(domainName);
        LW_SAFE_FREE_MEMORY(accountName);
    }

    LW_SAFE_FREE_MEMORY(ntName);

    *pDomainName = domainName;
    *pAccountName = accountName;

    return error;
}


#define SAMDB_SQL_SELECT_OBJECT_PREFIX     "SELECT "
#define SAMDB_SQL_SELECT_OBJECT_FROM       " FROM " SAM_DB_OBJECTS_TABLE " WHERE "


static
DWORD
SamDbBuildSelectObjectStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    SAMDB_QUERY_TYPE QueryType,
    PSAMDB_QUERY Query,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount,
    PSAMDB_ATTRIBUTE* pOutputAttributes,
    PDWORD pOutputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_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;
    PWSTR* ppwszSid = NULL;
    PDWORD objectClass = NULL;

    typedef enum _INPUT_ATTRIBUTE_INDEX {
        ATTR_IDX_OBJECT_TYPE = 0,
        ATTR_IDX_OBJECT_SID,
        ATTR_IDX_DOMAIN,
        ATTR_IDX_SAM_ACCOUNT_NAME,
        ATTR_IDX_UID,
        ATTR_IDX_GID
    } INPUT_ATTRIBUTE_INDEX;

    SAMDB_ATTRIBUTE inputAttrDef[] = {
        {
            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,
            &Query->Name
        },
        {
            SAMDB_ATTR_SAM_ACCOUNT_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            &Query->Name
        },
        {
            SAMDB_ATTR_UID,
            SAMDB_ATTR_TYPE_INT32,
            &Query->Id
        },
        {
            SAMDB_ATTR_GID,
            SAMDB_ATTR_TYPE_INT32,
            &Query->Id
        }
    };

    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_SECURITY_DESCRIPTOR,
            SAMDB_ATTR_TYPE_BLOB,
            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_PWD_PROPERTIES,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_SEQUENCE_NUMBER,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_LIKEWISE_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            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);
    }

    //
    // Append SQL statement (FROM samdbobjects WHERE ...)
    //
    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s",
                    SAMDB_SQL_SELECT_OBJECT_FROM);
    BAIL_ON_SAMDB_ERROR(error);

    //
    // Append SELECT statement condition(s)
    //
    if (QueryType == SAMDB_QUERY_TYPE_SID)
    {
        //
        // Query by object SID
        //
        inputAttributes[inputAttributeIndex] =
            inputAttrDef[ATTR_IDX_OBJECT_SID];

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

        ntStatus = RtlAllocateWC16StringFromSid(
                        ppwszSid,
                        Query->Sid);
        BAIL_ON_NT_STATUS(ntStatus);

        inputAttributes[inputAttributeIndex].Value = ppwszSid;
        inputAttributes[inputAttributeIndex].FreeFn =
            SamDbAttributeFreeSidString;

        ppwszSid = NULL;

        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "%s = ?%u",
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        inputAttributeIndex + 1);
        BAIL_ON_SAMDB_ERROR(error);

        inputAttributeIndex++;
    }
    else if (QueryType == SAMDB_QUERY_TYPE_NT_NAME)
    {
        //
        // Query by NT name:
        // domain name for domains or SAM account name for accounts
        //
        inputAttributes[inputAttributeIndex] =
            inputAttrDef[ATTR_IDX_DOMAIN];
        inputAttributes[inputAttributeIndex + 1] =
            inputAttrDef[ATTR_IDX_SAM_ACCOUNT_NAME];

        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "(%s = ?%u OR %s = ?%u)",
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        inputAttributeIndex + 1,
                        inputAttributes[inputAttributeIndex + 1].DbColumnName,
                        inputAttributeIndex + 2);
        BAIL_ON_SAMDB_ERROR(error);

        inputAttributeIndex += 2;
    }
    else if (QueryType == SAMDB_QUERY_TYPE_UNIX_ID)
    {
        //
        // Query by unix id:
        // uid for users or gid for local groups
        //
        inputAttributes[inputAttributeIndex] =
            inputAttrDef[ATTR_IDX_UID];
        inputAttributes[inputAttributeIndex + 1] =
            inputAttrDef[ATTR_IDX_GID];

        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "(%s = ?%u OR %s = ?%u)",
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        inputAttributeIndex + 1,
                        inputAttributes[inputAttributeIndex + 1].DbColumnName,
                        inputAttributeIndex + 2);
        BAIL_ON_SAMDB_ERROR(error);

        inputAttributeIndex += 2;
    }
    else if (QueryType == SAMDB_QUERY_TYPE_NT_OR_UNIX_NAME)
    {
        //
        // Query by NT or unix name:
        // SAM account name for accounts
        //
        inputAttributes[inputAttributeIndex] =
            inputAttrDef[ATTR_IDX_SAM_ACCOUNT_NAME];

        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        "%s = ?%u",
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        inputAttributeIndex + 1);
        BAIL_ON_SAMDB_ERROR(error);

        inputAttributeIndex++;
    }
    else
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("Unknown query type (%u) requested when looking "
                        "for an object",
                        QueryType);
        goto cleanup;
    }

    //
    // Filter by object type if requested
    //
    if (ObjectClass != SAMDB_OBJECT_CLASS_UNKNOWN)
    {
        error = LwAllocateMemory(
                        sizeof(*objectClass),
                        OUT_PPVOID(&objectClass));
        BAIL_ON_SAMDB_ERROR(error);

        *objectClass = ObjectClass;

        inputAttributes[inputAttributeIndex] =
            inputAttrDef[ATTR_IDX_OBJECT_TYPE];
        inputAttributes[inputAttributeIndex].Value = objectClass;
        inputAttributes[inputAttributeIndex].FreeFn =
            SamDbAttributeFreePointer;
        objectClass = NULL;

        error = SamDbPrintfString(
                        &sqlStatementString,
                        &sqlStatementSize,
                        &sqlStatementLength,
                        maxSqlStatementSize,
                        " AND %s = ?%u;",
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        inputAttributeIndex + 1);
        BAIL_ON_SAMDB_ERROR(error);

        inputAttributeIndex++;
    }
    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);

    inputAttributesCount = inputAttributeIndex;

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

        if (outputAttributes)
        {
            SamDbFreeAttributes(
                     outputAttributes,
                     outputAttributesCount);
            outputAttributes = NULL;
            outputAttributesCount = 0;
        }

        if (sqlStatement)
        {
            sqlite3_finalize(sqlStatement);
            sqlStatement = NULL;
        }

        if (ppwszSid)
        {
            SamDbAttributeFreeSidString(ppwszSid);
        }
    }

    LW_SAFE_FREE_MEMORY(sqlStatementString);

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

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

    return error;
}
