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

#include "includes.h"


//
// Attribute value validator types. They are used when certain attribute
// values have to be validated against the database contents e.g. to look
// for potential conflicts.
//
typedef DWORD (*PFN_SAMDB_ATTRIBUTE_VALIDATOR)(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

typedef struct _SAMDB_ATTRIBUTE_VALIDATOR
{
    PSTR DbColumnName;
    PFN_SAMDB_ATTRIBUTE_VALIDATOR pfnAttrValidator;

} SAMDB_ATTRIBUTE_VALIDATOR, *PSAMDB_ATTRIBUTE_VALIDATOR;


//
// Attribute value generator types. They're used when the value
// is missing which means the default should be generated.
//
typedef DWORD (*PFN_SAMDB_ADD_VALUE_GENERATOR)(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

typedef struct _SAMDB_ADD_VALUE_GENERATOR
{
    PSTR DbColumnName;
    PFN_SAMDB_ADD_VALUE_GENERATOR pfnValueGenerator;

} SAMDB_ADD_VALUE_GENERATOR, *PSAMDB_ADD_VALUE_GENERATOR;


static
DWORD
SamDbBuildAddObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pAttributes,
    PDWORD pAttributesCount
    );

static
DWORD
SamDbGenerateObjectSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID* pObjectSid
    );

static
DWORD
SamDbBuildAddForeignSidStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    PSID ObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    );


DWORD
SamDbAddObject(
    IN PSAMDB_OBJECT_INFO Object,
    IN OPTIONAL PSID ObjectSid
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    BOOLEAN transactionStarted = FALSE;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO user = NULL;
    PSID localDomainSid = NULL;
    PSID likewiseUsersSid = NULL;

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

    SAMDB_BEGIN_TRANSACTION(transactionStarted, dbContext, error);

    error = SamDbAddObjectInternal(
                  dbContext,
                  Object,
                  ObjectSid);
    BAIL_ON_SAMDB_ERROR(error);

    query.Name = Object->Name;

    error = SamDbFindObjectInternal(
                  dbContext,
                  (SAMDB_OBJECT_CLASS)Object->Type,
                  SAMDB_QUERY_TYPE_NT_NAME,
                  &query,
                  &user);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbIncrementSequenceNumber(
                  dbContext,
                  user->Sid);
    BAIL_ON_SAMDB_ERROR(error);

    //
    // If it's a user then add it to Likewise Users local group
    // which is the default primary group
    //
    if (Object->Type == SAMDB_OBJECT_TYPE_USER)
    {
        error = SamDbGetLocalDomainSid(
                      &localDomainSid);
        BAIL_ON_SAMDB_ERROR(error);

        error = LwAllocateWellKnownSid(
                      WinAccountLikewiseUsersSid,
                      localDomainSid,
                      &likewiseUsersSid,
                      NULL);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbAddMembersInternal(
                      dbContext,
                      likewiseUsersSid,
                      1,
                      &user->Sid);
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    SAMDB_END_TRANSACTION(transactionStarted, dbContext, error);

    if (error == ERROR_SUCCESS)
    {
        SamDbNotifyCallbacks(user->Sid);
    }

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }

    if (user)
    {
        SamDbFreeObject(user);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);
    LW_SAFE_FREE_MEMORY(likewiseUsersSid);

    return error;
}


DWORD
SamDbAddObjectInternal(
    IN PSAMDB_CONTEXT DbContext,
    IN PSAMDB_OBJECT_INFO Object,
    IN OPTIONAL PSID ObjectSid
    )
{
    DWORD error = ERROR_SUCCESS;
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO object = NULL;

    //
    // Make sure no object with such name already exists in the database
    //
    query.Name = Object->Name;

    error = SamDbFindObjectInternal(
                  DbContext,
                  SAMDB_OBJECT_CLASS_UNKNOWN,
                  SAMDB_QUERY_TYPE_NT_NAME,
                  &query,
                  &object);
    if (error == ERROR_FILE_NOT_FOUND)
    {
        error = ERROR_SUCCESS;
    }
    else if (error == ERROR_SUCCESS)
    {
        if (object->Type == SAMDB_OBJECT_TYPE_USER)
        {
            error = ERROR_USER_EXISTS;
        }
        else if (object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
        {
            error = ERROR_ALIAS_EXISTS;
        }
        else if (object->Type == SAMDB_OBJECT_TYPE_DOMAIN)
        {
            error = ERROR_DOMAIN_EXISTS;
        }

        BAIL_ON_SAMDB_ERROR(error);
    }
    else if (error != ERROR_SUCCESS)
    {
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = SamDbBuildAddObjectStatement(
                  DbContext,
                  Object,
                  ObjectSid,
                  &sqlStatement,
                  &inputAttributes,
                  &inputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

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

    error = sqlite3_step(sqlStatement);
    if (error == SQLITE_DONE)
    {
        error = ERROR_SUCCESS;
    }
    else
    {
        SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                        "while %u was expected",
                        error,
                        SQLITE_DONE);
        BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);
    }

cleanup:
    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

    if (object)
    {
        SamDbFreeObject(object);
    }

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

    return error;
}


static
DWORD
SamDbValidateAccountName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidatePrimaryGroupRid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateLocalAccountSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidatePasswordLastSet(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateDomainSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidatePasswordHash(
    PSAMDB_OWF_PASSWORD OwfPassword,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateLmHash(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateNtHash(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateAccountFlags(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateShell(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateGecos(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );

static
DWORD
SamDbValidateUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    );


static SAMDB_ATTRIBUTE_VALIDATOR gSamDbUnknownObjectValidators[] =
{
    { NULL,                     NULL }
};

static SAMDB_ATTRIBUTE_VALIDATOR gSamDbDomainObjectValidators[] =
{
    { SAM_DB_COL_OBJECT_SID,    SamDbValidateDomainSid },
    { NULL,                     NULL }
};

static SAMDB_ATTRIBUTE_VALIDATOR gSamDbUserObjectValidators[] =
{
    { SAM_DB_COL_SAM_ACCOUNT_NAME,    SamDbValidateAccountName },
    { SAM_DB_COL_UID,                 SamDbValidateUid },
    { SAM_DB_COL_PRIMARY_GROUP,       SamDbValidatePrimaryGroupRid },
    { SAM_DB_COL_OBJECT_SID,          SamDbValidateLocalAccountSid },
    { SAM_DB_COL_PASSWORD_LAST_SET,   SamDbValidatePasswordLastSet },
    { SAM_DB_COL_LM_HASH,             SamDbValidateLmHash },
    { SAM_DB_COL_NT_HASH,             SamDbValidateNtHash },
    { SAM_DB_COL_ACCOUNT_FLAGS,       SamDbValidateAccountFlags },
    { SAM_DB_COL_SHELL,               SamDbValidateShell },
    { SAM_DB_COL_UNIX_HOME_DIR,       SamDbValidateUnixHomeDirectory },
    { SAM_DB_COL_GECOS,               SamDbValidateGecos },
    { NULL,                           NULL }
};

static SAMDB_ATTRIBUTE_VALIDATOR gSamDbLocalGroupObjectValidators[] =
{
    { SAM_DB_COL_SAM_ACCOUNT_NAME,    SamDbValidateAccountName },
    { SAM_DB_COL_GID,                 SamDbValidateGid },
    { SAM_DB_COL_OBJECT_SID,          SamDbValidateLocalAccountSid },
    { NULL,                           NULL }
};

static PSAMDB_ATTRIBUTE_VALIDATOR gSamDbObjectTypeValidators[] =
{
    /* SAMDB_OBJECT_TYPE_UNKNOWN */
    &gSamDbUnknownObjectValidators[0],

    /* SAMDB_OBJECT_TYPE_DOMAIN */
    &gSamDbDomainObjectValidators[0],

    /* SAMDB_OBJECT_TYPE_USER */
    &gSamDbUserObjectValidators[0],

    /* SAMDB_OBJECT_TYPE_LOCAL_GROUP */
    &gSamDbLocalGroupObjectValidators[0],
};


static
DWORD
SamDbAddGenerateUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateDomainSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateUserSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateLocalGroupSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateWorkstationsBlob(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateLogonHoursBlob(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGeneratePasswordLastSet(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );

static
DWORD
SamDbAddGenerateUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    );


static SAMDB_ADD_VALUE_GENERATOR gSamDbUnknownObjectValueGenerators[] =
{
    { NULL,                             NULL },
};

static SAMDB_ADD_VALUE_GENERATOR gSamDbDomainObjectValueGenerators[] =
{
    { SAM_DB_COL_SECURITY_DESCRIPTOR,   SamDbAddGenerateDomainSecurityDescriptor },
    { NULL,                             NULL },
};

static SAMDB_ADD_VALUE_GENERATOR gSamDbUserObjectValueGenerators[] =
{
    { SAM_DB_COL_SECURITY_DESCRIPTOR,   SamDbAddGenerateUserSecurityDescriptor },
    { SAM_DB_COL_UID,                   SamDbAddGenerateUid },
    { SAM_DB_COL_WORKSTATIONS,          SamDbAddGenerateWorkstationsBlob },
    { SAM_DB_COL_LOGON_HOURS,           SamDbAddGenerateLogonHoursBlob },
    { SAM_DB_COL_PASSWORD_LAST_SET,     SamDbAddGeneratePasswordLastSet },
    { SAM_DB_COL_UNIX_HOME_DIR,         SamDbAddGenerateUnixHomeDirectory },
    { NULL,                             NULL },
};

static SAMDB_ADD_VALUE_GENERATOR gSamDbLocalGroupObjectValueGenerators[] =
{
    { SAM_DB_COL_SECURITY_DESCRIPTOR,   SamDbAddGenerateLocalGroupSecurityDescriptor },
    { SAM_DB_COL_GID,                   SamDbAddGenerateGid },
    { NULL,                             NULL },
};

static PSAMDB_ADD_VALUE_GENERATOR gSamDbObjectValueGenerators[] =
{
    /* SAMDB_OBJECT_TYPE_UNKNOWN */
    &gSamDbUnknownObjectValueGenerators[0],

    /* SAMDB_OBJECT_TYPE_DOMAIN */
    &gSamDbDomainObjectValueGenerators[0],

    /* SAMDB_OBJECT_TYPE_USER */
    &gSamDbUserObjectValueGenerators[0],

    /* SAMDB_OBJECT_TYPE_LOCAL_GROUP */
    &gSamDbLocalGroupObjectValueGenerators[0]
};


#define SAMDB_SQL_INSERT_OBJECT_PREFIX     "INSERT INTO " SAM_DB_OBJECTS_TABLE " ("
#define SAMDB_SQL_INSERT_OBJECT_VALUES     "VALUES ("


static
DWORD
SamDbBuildAddObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSAMDB_ATTRIBUTE objectAttributes = NULL;
    PSAMDB_ATTRIBUTE_VALIDATOR attributeValidators = NULL;
    PSAMDB_ADD_VALUE_GENERATOR valueGenerators = NULL;
    DWORD objectAttributesCount = 0;
    DWORD attribIndex = 0;
    DWORD inputAttributeIndex = 0;
    DWORD validatorIndex = 0;
    DWORD generatorIndex = 0;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;
    PSID objectSid = NULL;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 4096;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;

    SAMDB_ATTRIBUTE domainAttributes[] = {
        {
            SAMDB_ATTR_OBJECT_CLASS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Type
        },
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_DOMAIN,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Name
        },
        {
            SAMDB_ATTR_SECURITY_DESCRIPTOR,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_MIN_PWD_AGE,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.MinPasswordAge
        },
        {
            SAMDB_ATTR_MAX_PWD_AGE,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.MaxPasswordAge
        },
        {
            SAMDB_ATTR_PWD_PROMPT_TIME,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.PasswordChangeTime
        },
        {
            SAMDB_ATTR_MIN_PWD_LENGTH,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.Domain.MinPasswordLength
        },
        {
            SAMDB_ATTR_SEQUENCE_NUMBER,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.SequenceNumber
        },
        {
            SAMDB_ATTR_LOCKOUT_THRESHOLD,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.Domain.LockoutThreshold
        },
        {
            SAMDB_ATTR_LOCKOUT_DURATION,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.LockoutDuration
        },
        {
            SAMDB_ATTR_LOCKOUT_WINDOW,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.LockoutWindow
        },
        {
            SAMDB_ATTR_PWD_PROPERTIES,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.Domain.PasswordProperties
        }
    };

    SAMDB_ATTRIBUTE userAttributes[] = {
        {
            SAMDB_ATTR_OBJECT_CLASS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Type
        },
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_SAM_ACCOUNT_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Name
        },
        {
            SAMDB_ATTR_SECURITY_DESCRIPTOR,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_PASSWORD_LAST_SET,
            SAMDB_ATTR_TYPE_INT64,
            NULL
        },
        {
            SAMDB_ATTR_ACCOUNT_EXPIRY,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.User.AccountExpires
        },
        {
            SAMDB_ATTR_FULL_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.FullName
        },
        {
            SAMDB_ATTR_HOME_DIR,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.HomeDirectory
        },
        {
            SAMDB_ATTR_HOME_DRIVE,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.HomeDirectoryDrive
        },
        {
            SAMDB_ATTR_LOGON_SCRIPT,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.ScriptPath
        },
        {
            SAMDB_ATTR_PROFILE_PATH,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.ProfilePath
        },
        {
            SAMDB_ATTR_DESCRIPTION,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.Description
        },
        {
            SAMDB_ATTR_WORKSTATIONS,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_COMMENT,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.User.UserComment
        },
        {
            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,
            &Object->Info.User.PrimaryGroupRid
        },
        {
            SAMDB_ATTR_ACCOUNT_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.UserAccountControl
        },
        {
            SAMDB_ATTR_LOGON_HOURS,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_COUNTRY_CODE,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.CountryCode
        },
        {
            SAMDB_ATTR_CODE_PAGE,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.CodePage
        },
        {
            SAMDB_ATTR_UID,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_SHELL,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_GECOS,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_UNIX_HOME_DIR,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_LIKEWISE_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.LikewiseFlags
        }
    };

    SAMDB_ATTRIBUTE localGroupAttributes[] = {
        {
            SAMDB_ATTR_OBJECT_CLASS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Type
        },
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_SAM_ACCOUNT_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Name
        },
        {
            SAMDB_ATTR_SECURITY_DESCRIPTOR,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_DESCRIPTION,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Info.LocalGroup.Description
        },
        {
            SAMDB_ATTR_GID,
            SAMDB_ATTR_TYPE_INT32,
            NULL
        },
        {
            SAMDB_ATTR_LIKEWISE_FLAGS,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.LocalGroup.LikewiseFlags
        },
    };

    //
    // Select object attributes to be considered
    //
    switch (Object->Type)
    {
    case SAMDB_OBJECT_TYPE_DOMAIN:
        objectAttributes      = domainAttributes;
        objectAttributesCount = sizeof(domainAttributes)/sizeof(domainAttributes[0]);
        break;

    case SAMDB_OBJECT_TYPE_USER:
        objectAttributes      = userAttributes;
        objectAttributesCount = sizeof(userAttributes)/sizeof(userAttributes[0]);
        break;

    case SAMDB_OBJECT_TYPE_LOCAL_GROUP:
        objectAttributes      = localGroupAttributes;
        objectAttributesCount = (sizeof(localGroupAttributes)
                                 /sizeof(localGroupAttributes[0]));
        break;

    default:
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("Invalid object type passed (%u)",
                        (unsigned int)Object->Type);
        goto cleanup;
    }

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

    //
    // Make a copy of ObjectSid if it's non-NULL.
    // Otherwise objectSid is going to be initialised with autogenerated SID.
    //
    if (ObjectSid)
    {
        DWORD objectSidLength = RtlLengthSid(ObjectSid);

        error = LwAllocateMemory(
                          objectSidLength,
                          OUT_PPVOID(&objectSid));
        BAIL_ON_SAMDB_ERROR(error);

        ntStatus = RtlCopySid(
                          objectSidLength,
                          objectSid,
                          ObjectSid);
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else
    {
        error = SamDbGenerateObjectSid(
                          DbContext,
                          Object,
                          &objectSid);
        BAIL_ON_SAMDB_ERROR(error);
    }

    //
    // Validate attributes and create the list of attributes to be added
    // to the database
    //

    attributeValidators = gSamDbObjectTypeValidators[Object->Type];

    for (attribIndex = 0;
         attribIndex < objectAttributesCount;
         attribIndex++)
    {
        BOOLEAN ignoreAttribute = FALSE;

        validatorIndex = 0;

        //
        // Loop through the array of object's attribute validators
        // to check if there's anything to validate
        //
        while (attributeValidators[validatorIndex].DbColumnName)
        {
            SAMDB_ATTRIBUTE_VALIDATOR attribValidator =
                attributeValidators[validatorIndex];

            if (RtlCStringIsEqual(
                        attribValidator.DbColumnName,
                        objectAttributes[attribIndex].DbColumnName,
                        TRUE))
            {
                error = attribValidator.pfnAttrValidator(
                                     DbContext,
                                     Object,
                                     objectSid,
                                     &objectAttributes[attribIndex],
                                     &ignoreAttribute);
                BAIL_ON_SAMDB_ERROR(error);

                break;
            }

            validatorIndex++;
        }

        if (!ignoreAttribute)
        {
            //
            // Copy the attribute (along with its value) to the array
            // of attributes that will be saved in the database
            //
            inputAttributes[inputAttributeIndex++] =
                objectAttributes[attribIndex];
        }
    }

    inputAttributesCount = inputAttributeIndex;

    //
    // Look for value generator if there's been no value assigned
    // to an attribute and the attribute hasn't been ignored
    //

    valueGenerators = gSamDbObjectValueGenerators[Object->Type];

    for (inputAttributeIndex = 0;
         inputAttributeIndex < inputAttributesCount;
         inputAttributeIndex++)
    {
        if (inputAttributes[inputAttributeIndex].Value != NULL)
        {
            continue;
        }

        generatorIndex = 0;

        while (valueGenerators[generatorIndex].DbColumnName)
        {
            SAMDB_ADD_VALUE_GENERATOR valueGenerator =
                valueGenerators[generatorIndex];

            if (RtlCStringIsEqual(
                        valueGenerator.DbColumnName,
                        inputAttributes[inputAttributeIndex].DbColumnName,
                        TRUE))
            {
                error = valueGenerator.pfnValueGenerator(
                                      DbContext,
                                      Object,
                                      objectSid,
                                      &inputAttributes[inputAttributeIndex]);
                BAIL_ON_SAMDB_ERROR(error);

                break;
            }

            generatorIndex++;
        }
    }

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

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

    for (inputAttributeIndex = 0;
         inputAttributeIndex < inputAttributesCount;
         inputAttributeIndex++)
    {
        //
        // Append SQL column names separated with "," and ended with ") "
        //
        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      (inputAttributeIndex < inputAttributesCount - 1) ? "%s," : "%s) ",
                      inputAttributes[inputAttributeIndex].DbColumnName);
        BAIL_ON_SAMDB_ERROR(error);
    };

    //
    // Append placeholders for the actual values
    //
    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s",
                    SAMDB_SQL_INSERT_OBJECT_VALUES);
    BAIL_ON_SAMDB_ERROR(error);

    for (inputAttributeIndex = 1;
         inputAttributeIndex <= inputAttributesCount;
         inputAttributeIndex++)
    {
        //
        // Append sqlite value placeholders "?{1,2,...}" separated with ","
        // and ended with ");"
        //
        error = SamDbPrintfString(
                  &sqlStatementString,
                  &sqlStatementSize,
                  &sqlStatementLength,
                  maxSqlStatementSize,
                  (inputAttributeIndex < inputAttributesCount) ?"?%u," : "?%u);",
                  inputAttributeIndex);
        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:
error:
    if (error)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }

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

    LW_SAFE_FREE_MEMORY(objectSid);
    LW_SAFE_FREE_MEMORY(sqlStatementString);

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

    *pSqlStatement = sqlStatement;
    *pInputAttributes = inputAttributes;
    *pInputAttributesCount = inputAttributesCount;

    return error;
}


static
DWORD
SamDbGenerateObjectSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID* pObjectSid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD rid = 0;
    PSID localDomainSid = NULL;
    DWORD newAccountSidSize = 0;
    PSID newAccountSid = NULL;

    if (Object->Type != SAMDB_OBJECT_TYPE_USER &&
        Object->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

    //
    // Prepare new account SID from local domain SID
    //
    error = SamDbGetLocalDomainSid(
                    &localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    newAccountSidSize = RtlLengthRequiredSid(
                               localDomainSid->SubAuthorityCount + 1);
    error = LwAllocateMemory(
                    newAccountSidSize,
                    OUT_PPVOID(&newAccountSid));
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlCopySid(
                    newAccountSidSize,
                    newAccountSid,
                    localDomainSid);
    BAIL_ON_NT_STATUS(ntStatus);

    //
    // Get next available RID and append it to new account SID
    //
    error = SamDbGetNextAvailableRid(
                    DbContext,
                    &rid);
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlAppendRidSid(
                    newAccountSidSize,
                    newAccountSid,
                    rid);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:
error:
    if (error || ntStatus)
    {
        LW_SAFE_FREE_MEMORY(newAccountSid);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);

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

    *pObjectSid = newAccountSid;

    return error;
}


static
DWORD
SamDbValidateAccountName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN nameIsValid = TRUE;

    if (Object->Type == SAMDB_OBJECT_TYPE_USER)
    {
        nameIsValid = SamDbIsValidUserName(Object->Name);
    }
    else if (Object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        nameIsValid = SamDbIsValidLocalGroupName(Object->Name);
    }
    else
    {
        // There's something seriously wrong if we end up here
        LSA_LOG_ERROR("Invalid object type requested (%u)",
                      (unsigned int)Object->Type);
        LSA_ASSERT(FALSE);
    }

    if (!nameIsValid)
    {
        error = ERROR_INVALID_ACCOUNT_NAME;
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


static
DWORD
SamDbValidateUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;
    DWORD uid = 0;

    if (!Object->Info.User.UnixInfo)
    {
        goto cleanup;
    }

    uid = Object->Info.User.UnixInfo->Uid;

    error = SamDbCheckAvailableUid(
                         DbContext,
                         uid);
    BAIL_ON_SAMDB_ERROR(error);

    Object->Info.User.LikewiseFlags |= SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;

    Attribute->Value = &Object->Info.User.UnixInfo->Uid;

cleanup:
    *pIgnore = ignore;

    return error;
}


static
DWORD
SamDbValidatePrimaryGroupRid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD groupRid = 0;
    PSID groupSid = NULL;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO groupObject = NULL;

    PDWORD pGroupRid = (PDWORD)Attribute->Value;

    // Default primary group for new user account is always "Likewise Users"
    // Newly created user cannot have its primary group set to some other group
    // because it's not a member of any other yet.
    groupRid = DOMAIN_ALIAS_RID_LW_USERS;

    //
    // If user account is to be unix-enabled then its primary group has
    // to be unix-enabled too (to provide a valid gid). Otherwise,
    // nothing else needs to be checked.
    //
    if (!(Object->Info.User.LikewiseFlags &
          SAMDB_LIKEWISE_FLAG_UNIX_ENABLED))
    {
        goto cleanup;
    }

    ntStatus = RtlDuplicateSid(
                      &groupSid,
                      ObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

    groupSid->SubAuthority[groupSid->SubAuthorityCount - 1] = groupRid;

    query.Sid = groupSid;

    error = SamDbFindObjectInternal(
                      DbContext,
                      SAMDB_OBJECT_CLASS_LOCAL_GROUP,
                      SAMDB_QUERY_TYPE_SID,
                      &query,
                      &groupObject);
    if (error == ERROR_FILE_NOT_FOUND)
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("No \"Likewise Users\" group found in the database");
        goto cleanup;
    }
    else if (error != ERROR_SUCCESS)
    {
        BAIL_ON_SAMDB_ERROR(error);
    }

    if (!(groupObject->Info.LocalGroup.LikewiseFlags &
          SAMDB_LIKEWISE_FLAG_UNIX_ENABLED))
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("User account cannot have "
                        "SAMDB_LIKEWISE_FLAG_UNIX_ENABLED flag set because "
                        "its primary group is not enabled too.");
        goto cleanup;
    }

cleanup:
error:
    if (groupObject)
    {
        SamDbFreeObject(groupObject);
    }

    RTL_FREE(&groupSid);

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

    *pGroupRid = groupRid;

    return error;
}


static
DWORD
SamDbValidateGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;
    DWORD gid = 0;

    if (!Object->Info.LocalGroup.UnixInfo)
    {
        goto cleanup;
    }

    gid = Object->Info.LocalGroup.UnixInfo->Gid;

    error = SamDbCheckAvailableGid(
                      DbContext,
                      gid);
    BAIL_ON_SAMDB_ERROR(error);

    Object->Info.LocalGroup.LikewiseFlags |= SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;

    Attribute->Value = &Object->Info.LocalGroup.UnixInfo->Gid;

cleanup:
    *pIgnore = ignore;

    return error;
}


static
DWORD
SamDbValidateLocalAccountSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSTR pszObjectSid = NULL;
    PSID builtinDomainSid = NULL;
    PSID localDomainSid = NULL;
    PWSTR* ppwszSid = NULL;

    //
    // Specifying local account SID is optional (typically
    // it is autogenerated)
    //
    if (ObjectSid == NULL)
    {
        goto cleanup;
    }

    ntStatus = RtlAllocateCStringFromSid(
                      &pszObjectSid,
                      ObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

    error = LwAllocateWellKnownSid(
                      WinBuiltinDomainSid,
                      NULL,
                      &builtinDomainSid,
                      NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbGetLocalDomainSidInternal(
                      DbContext,
                      &localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    if (!RtlIsStrictPrefixSid(
                      localDomainSid,
                      ObjectSid) &&
        !RtlIsStrictPrefixSid(
                      builtinDomainSid,
                      ObjectSid))
    {
        error = ERROR_INVALID_SID;

        SAMDB_LOG_ERROR("SID %s requested for new account does not belong "
                        "to local domain nor builtin domain.",
                        pszObjectSid);
        goto cleanup;
    }

    error = SamDbCheckAvailableSid(
                      DbContext,
                      pszObjectSid);
    BAIL_ON_SAMDB_ERROR(error);

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

    ntStatus = RtlAllocateWC16StringFromSid(
                            ppwszSid,
                            ObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:
error:
    if (error)
    {
        if (ppwszSid)
        {
            SamDbAttributeFreeSidString(ppwszSid);
        }
    }

    LW_SAFE_FREE_MEMORY(builtinDomainSid);
    LW_SAFE_FREE_MEMORY(localDomainSid);
    RTL_FREE(&pszObjectSid);

    Attribute->Value = ppwszSid;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeSidString;
    }

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

    return error;
}


static
DWORD
SamDbValidatePasswordLastSet(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;

    //
    // If NT password hash has not been set then this attribute
    // should be ignored (it will be zeroed by the database).
    // Otherwise make sure it is NULL because it's going to be
    // autogenerated further on.
    //
    if (!Object->Info.User.NtOwfPassword.IsPresent)
    {
        ignore = TRUE;
    }
    else
    {
        Attribute->Value = NULL;
        Attribute->FreeFn = NULL;
    }

    *pIgnore = ignore;

    return error;
}


static
DWORD
SamDbValidateDomainSid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR* ppwszSid = NULL;

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

    ntStatus = RtlAllocateWC16StringFromSid(
                            ppwszSid,
                            ObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:
error:
    if (error)
    {
        SamDbAttributeFreeSidString(ppwszSid);
    }

    Attribute->Value = ppwszSid;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeSidString;
    }

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

    return error;
}


static
DWORD
SamDbValidatePasswordHash(
    PSAMDB_OWF_PASSWORD OwfPassword,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;
    BOOLEAN ignore = FALSE;

    if (!OwfPassword ||
        !OwfPassword->IsPresent)
    {
        ignore = TRUE;
        goto cleanup;
    }

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

    blob->Length = sizeof(OwfPassword->Data);
    error = LwAllocateMemory(
                      blob->Length,
                      OUT_PPVOID(&blob->Value));
    BAIL_ON_SAMDB_ERROR(error);

    memcpy(blob->Value,
           OwfPassword->Data,
           blob->Length);

    Attribute->Value = blob;
    Attribute->FreeFn = SamDbAttributeFreeBlob;

cleanup:
    if (error)
    {
        SamDbAttributeFreeBlob(blob);
        Attribute->Value = NULL;
        Attribute->FreeFn = NULL;
    }

    *pIgnore = ignore;

    return error;
}

static
DWORD
SamDbValidateLmHash(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;

    error = SamDbValidatePasswordHash(
                      &Object->Info.User.LmOwfPassword,
                      Attribute,
                      pIgnore);
    return error;
}

static
DWORD
SamDbValidateNtHash(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;

    error = SamDbValidatePasswordHash(
                      &Object->Info.User.NtOwfPassword,
                      Attribute,
                      pIgnore);
    return error;
}

static
DWORD
SamDbValidateAccountFlags(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_UF_FLAGS flags = Object->Info.User.UserAccountControl;

    if (flags & SAMDB_UF_FLAG_INTERDOMAIN_TRUST_ACCOUNT ||
        flags & SAMDB_UF_FLAG_WORKSTATION_TRUST_ACCOUNT ||
        flags & SAMDB_UF_FLAG_SERVER_TRUST_ACCOUNT)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("Only normal user accounts (SAMDB_UF_FLAG_NORMAL_ACCOUNT) "
                        "are allowed to be created. Requested UserAccountControl "
                        "flags = 0x%0x",
                        flags);
        goto cleanup;
    }

    //
    // Ensure the created account is a normal account type
    //
    Object->Info.User.UserAccountControl |= SAMDB_UF_FLAG_NORMAL_ACCOUNT;

    //
    // Disable accounts with no password set
    //
    if (!Object->Info.User.NtOwfPassword.IsPresent)
    {
        Object->Info.User.UserAccountControl |=
            SAMDB_UF_FLAG_ACCOUNTDISABLE;
    }

cleanup:
    return error;
}

static
DWORD
SamDbValidateShell(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;

    if (!Object->Info.User.UnixInfo)
    {
        ignore = TRUE;
        goto cleanup;
    }

    Attribute->Value = &Object->Info.User.UnixInfo->Shell;

    // Someone else owns the value pointer so there must not be
    // a destructor set
    Attribute->FreeFn = NULL;

cleanup:
    *pIgnore = ignore;

    return error;
}

static
DWORD
SamDbValidateGecos(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;

    if (!Object->Info.User.UnixInfo)
    {
        ignore = TRUE;
        goto cleanup;
    }

    Attribute->Value = &Object->Info.User.UnixInfo->Gecos;

    // Someone else owns the value pointer so there must not be
    // a destructor set
    Attribute->FreeFn = NULL;

cleanup:
    *pIgnore = ignore;

    return error;
}

static
DWORD
SamDbValidateUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute,
    PBOOLEAN pIgnore
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN ignore = FALSE;

    if (!Object->Info.User.UnixInfo ||
        !Object->Info.User.UnixInfo->HomeDirectory)
    {
        //
        // If windows home directory is NULL (there's nothing to copy
        // to unix home directory as a default value) then ignore
        // unix home directory attribute when creating account
        //
        if (!Object->Info.User.HomeDirectory)
        {
            ignore = TRUE;
        }

        goto cleanup;
    }

    Attribute->Value = &Object->Info.User.UnixInfo->HomeDirectory;

    // Someone else owns the value pointer so there must not be
    // a destructor set
    Attribute->FreeFn = NULL;

cleanup:
    *pIgnore = ignore;

    return error;
}

static
DWORD
SamDbAddGenerateUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD uid = 0;
    PDWORD pUid = NULL;

    if (Object->Type != SAMDB_OBJECT_TYPE_USER)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

    if (!(Object->Info.User.LikewiseFlags &
          SAMDB_LIKEWISE_FLAG_UNIX_ENABLED))
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("Autogenerated uid requested but "
                        "SAMDB_LIKEWISE_FLAG_UNIX_ENABLED flag is not set");
        goto cleanup;
    }

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

    error = SamDbGetNextAvailableUid(
                    DbContext,
                    &uid);
    BAIL_ON_SAMDB_ERROR(error);

    *pUid = uid;

cleanup:
    if (error)
    {
        SamDbAttributeFreePointer(pUid);
    }

    Attribute->Value = pUid;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreePointer;
    }

    return error;
}


static
DWORD
SamDbAddGenerateGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD gid = 0;
    PDWORD pGid = NULL;

    if (Object->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

    if (!(Object->Info.LocalGroup.LikewiseFlags &
          SAMDB_LIKEWISE_FLAG_UNIX_ENABLED))
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("Autogenerated gid requested but "
                        "SAMDB_LIKEWISE_FLAG_UNIX_ENABLED flag is not set");
        goto cleanup;
    }

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

    error = SamDbGetNextAvailableGid(
                    DbContext,
                    &gid);
    BAIL_ON_SAMDB_ERROR(error);

    *pGid = gid;

cleanup:
    if (error)
    {
        SamDbAttributeFreePointer(pGid);
    }

    Attribute->Value = pGid;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreePointer;
    }

    return error;
}


static
DWORD
SamDbAddGenerateDomainSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    PSECURITY_DESCRIPTOR_RELATIVE securityDescriptor = NULL;
    DWORD securityDescriptorLength = 0;
    PSID builtinSid = NULL;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

    error = LwAllocateWellKnownSid(
                   WinBuiltinDomainSid,
                   NULL,
                   &builtinSid,
                   NULL);
    BAIL_ON_SAMDB_ERROR(error);

    if (RtlEqualSid(ObjectSid, builtinSid))
    {
        error = SamDbCreateBuiltinDomainSecDesc(
                           ObjectSid,
                           &securityDescriptor,
                           &securityDescriptorLength);
    }
    else
    {
        error = SamDbCreateLocalDomainSecDesc(
                           ObjectSid,
                           &securityDescriptor,
                           &securityDescriptorLength);
    }
    BAIL_ON_SAMDB_ERROR(error);

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

    blob->Length = securityDescriptorLength;
    blob->Value  = securityDescriptor;

cleanup:
    if (error)
    {
        SamDbAttributeFreeBlob(blob);
        blob = NULL;
    }

    Attribute->Value = blob;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeBlob;
    }

    LW_SAFE_FREE_MEMORY(builtinSid);

    return error;
}


static
DWORD
SamDbAddGenerateUserSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    PSECURITY_DESCRIPTOR_RELATIVE securityDescriptor = NULL;
    DWORD securityDescriptorLength = 0;
    PSID builtinSid = NULL;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

    error = LwAllocateWellKnownSid(
                   WinBuiltinDomainSid,
                   NULL,
                   &builtinSid,
                   NULL);
    BAIL_ON_SAMDB_ERROR(error);

    if (RtlEqualSid(ObjectSid, builtinSid))
    {
        // Yes, both local group and user account can have security
        // descriptor created using the same function here.
        // At the moment there are no builtin user accounts, though.
        error = SamDbCreateBuiltinGroupSecDesc(
                           ObjectSid,
                           &securityDescriptor,
                           &securityDescriptorLength);
        BAIL_ON_SAMDB_ERROR(error);
    }
    else
    {
        DWORD rid = ObjectSid->SubAuthority[ObjectSid->SubAuthorityCount - 1];

        if (rid < SAM_DB_MIN_RID)
        {
            error = SamDbCreateLocalWellKnownUserSecDesc(
                               ObjectSid,
                               &securityDescriptor,
                               &securityDescriptorLength);
        }
        else
        {
            error = SamDbCreateNewLocalUserSecDesc(
                               ObjectSid,
                               &securityDescriptor,
                               &securityDescriptorLength);
        }
        BAIL_ON_SAMDB_ERROR(error);
    }

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

    blob->Length = securityDescriptorLength;
    blob->Value  = securityDescriptor;

cleanup:
    if (error)
    {
        SamDbAttributeFreeBlob(blob);
        blob = NULL;
    }

    Attribute->Value = blob;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeBlob;
    }

    LW_SAFE_FREE_MEMORY(builtinSid);

    return error;
}


static
DWORD
SamDbAddGenerateLocalGroupSecurityDescriptor(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    PSECURITY_DESCRIPTOR_RELATIVE securityDescriptor = NULL;
    DWORD securityDescriptorLength = 0;
    PSID builtinSid = NULL;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

    error = LwAllocateWellKnownSid(
                   WinBuiltinDomainSid,
                   NULL,
                   &builtinSid,
                   NULL);
    BAIL_ON_SAMDB_ERROR(error);

    if (RtlEqualSid(ObjectSid, builtinSid))
    {
        error = SamDbCreateBuiltinGroupSecDesc(
                           ObjectSid,
                           &securityDescriptor,
                           &securityDescriptorLength);
        BAIL_ON_SAMDB_ERROR(error);
    }
    else
    {
        DWORD rid = ObjectSid->SubAuthority[ObjectSid->SubAuthorityCount - 1];

        if (rid < SAM_DB_MIN_RID)
        {
            error = SamDbCreateLocalWellKnownGroupSecDesc(
                               ObjectSid,
                               &securityDescriptor,
                               &securityDescriptorLength);
        }
        else
        {
            error = SamDbCreateNewLocalGroupSecDesc(
                               ObjectSid,
                               &securityDescriptor,
                               &securityDescriptorLength);
        }
        BAIL_ON_SAMDB_ERROR(error);
    }

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

    blob->Length = securityDescriptorLength;
    blob->Value  = securityDescriptor;

cleanup:
    if (error)
    {
        SamDbAttributeFreeBlob(blob);
        blob = NULL;
    }

    Attribute->Value = blob;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeBlob;
    }

    LW_SAFE_FREE_MEMORY(builtinSid);

    return error;
}


static
DWORD
SamDbAddGenerateWorkstationsBlob(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

    if (Object->Type != SAMDB_OBJECT_TYPE_USER)
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Allowed workstations blob requested for a non-user object");
        goto cleanup;
    }

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

    Attribute->Value  = blob;
    Attribute->FreeFn = SamDbAttributeFreeBlob;

cleanup:
    return error;
}


static
DWORD
SamDbAddGenerateLogonHoursBlob(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;
    DWORD hoursPerWeek = 7 * 24;
    PBYTE hoursBlob = NULL;
    // Each bit (not byte) represents a single hour in a week
    size_t hoursBlobSize = hoursPerWeek / 8 * sizeof(hoursBlob[0]);

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

    blob->Length = hoursBlobSize;
    error = LwAllocateMemory(
                      blob->Length,
                      OUT_PPVOID(&blob->Value));
    BAIL_ON_SAMDB_ERROR(error);

    memset(blob->Value, 1, blob->Length);

cleanup:
    Attribute->Value = blob;

    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreeBlob;
    }

    return error;
}


static
DWORD
SamDbAddGeneratePasswordLastSet(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;
    LONG64 datetimeNow = 0;
    PLONG64 pValue = NULL;

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

    if (Object->Info.User.NtOwfPassword.IsPresent)
    {
        datetimeNow = SamDbGetNTTime(time(NULL));
    }

    *pValue = datetimeNow;

cleanup:
    Attribute->Value = pValue;
    if (Attribute->Value)
    {
        Attribute->FreeFn = SamDbAttributeFreePointer;
    }

    return error;
}


static
DWORD
SamDbAddGenerateUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSID ObjectSid,
    PSAMDB_ATTRIBUTE Attribute
    )
{
    DWORD error = ERROR_SUCCESS;

    //
    // If no unix home directory was specified then set it to
    // the same value as windows home directory by default
    //
    if (Object->Info.User.HomeDirectory)
    {
        Attribute->Value = &Object->Info.User.HomeDirectory;
        Attribute->FreeFn = NULL;
    }

    return error;
}


DWORD
SamDbAddForeignSecurityPrincipalObject(
    PSAMDB_CONTEXT DbContext,
    PSID ForeignSid
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_OBJECT_CLASS objectClass =
        SAMDB_OBJECT_CLASS_FOREIGN_SECURITY_PRINCIPAL;
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;

    error = SamDbBuildAddForeignSidStatement(
                  DbContext,
                  objectClass,
                  ForeignSid,
                  &sqlStatement,
                  &inputAttributes,
                  &inputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

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

    error = sqlite3_step(sqlStatement);
    if (error == SQLITE_DONE)
    {
        error = ERROR_SUCCESS;
    }
    else
    {
        SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                        "while %u was expected",
                        error,
                        SQLITE_DONE);
        BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);
    }

cleanup:
    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

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

    return error;
}


#define SAMDB_SQL_INSERT_FOREIGN_SID_PREFIX   "INSERT INTO " SAM_DB_OBJECTS_TABLE " ("
#define SAMDB_SQL_INSERT_FOREIGN_SID_VALUES   "VALUES ("


static
DWORD
SamDbBuildAddForeignSidStatement(
    PSAMDB_CONTEXT DbContext,
    SAMDB_OBJECT_CLASS ObjectClass,
    PSID ObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD inputAttributeIndex = 0;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;
    PDWORD pObjectClass = NULL;
    PWSTR* ppwszObjectSid = NULL;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 4096;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;

    typedef enum _INPUT_ATTRIBUTE_INDEX {
        ATTR_IDX_OBJECT_CLASS = 0,
        ATTR_IDX_OBJECT_SID,
    } INPUT_ATTRIBUTE_INDEX;

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

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

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

    *pObjectClass = SAMDB_OBJECT_CLASS_FOREIGN_SECURITY_PRINCIPAL;

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

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

    ntStatus = RtlAllocateWC16StringFromSid(
                    ppwszObjectSid,
                    ObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

    inputAttributes[inputAttributeIndex] = inputAttrDef[ATTR_IDX_OBJECT_SID];
    inputAttributes[inputAttributeIndex].Value = ppwszObjectSid;
    inputAttributes[inputAttributeIndex].FreeFn = SamDbAttributeFreeSidString;
    inputAttributeIndex++;

    inputAttributesCount = inputAttributeIndex;

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

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

    for (inputAttributeIndex = 0;
         inputAttributeIndex < inputAttributesCount;
         inputAttributeIndex++)
    {
        //
        // Append SQL column names separated with "," and ended with ") "
        //
        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      (inputAttributeIndex < inputAttributesCount - 1) ? "%s," : "%s) ",
                      inputAttributes[inputAttributeIndex].DbColumnName);
        BAIL_ON_SAMDB_ERROR(error);
    };

    //
    // Append placeholders for the actual values
    //
    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s",
                    SAMDB_SQL_INSERT_FOREIGN_SID_VALUES);
    BAIL_ON_SAMDB_ERROR(error);

    for (inputAttributeIndex = 1;
         inputAttributeIndex <= inputAttributesCount;
         inputAttributeIndex++)
    {
        //
        // Append sqlite value placeholders "?{1,2,...}" separated with ","
        // and ended with ");"
        //
        error = SamDbPrintfString(
                  &sqlStatementString,
                  &sqlStatementSize,
                  &sqlStatementLength,
                  maxSqlStatementSize,
                  (inputAttributeIndex < inputAttributesCount) ?"?%u," : "?%u);",
                  inputAttributeIndex);
        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:
error:
    if (error)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }

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

    LW_SAFE_FREE_MEMORY(sqlStatementString);

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

    *pSqlStatement = sqlStatement;
    *pInputAttributes = inputAttributes;
    *pInputAttributesCount = inputAttributesCount;

    return error;
}


