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

#include "includes.h"


//
// Attribute value modifier types. They are used either when certain attribute
// changes trigger more changes (e.g. updating password causes PasswordLastSet
// update) or when validation of new value is needed.
//
typedef DWORD (*PFN_SAMDB_ATTRIBUTE_MODIFIER)(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

typedef struct _SAMDB_ATTRIBUTE_VALIDATOR
{
    SAMDB_FIELDS Field;
    PFN_SAMDB_ATTRIBUTE_MODIFIER pfnAttrModifier;

} SAMDB_ATTRIBUTE_MODIFIER, *PSAMDB_ATTRIBUTE_MODIFIER;


static
DWORD
SamDbModifyDomainName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyAccountName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyWorkstations(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyLmOwfPassword(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyNtOwfPassword(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyPrimaryGroupRid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyAccountFlags(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyLogonHours(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyUnixUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyUnixShell(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyUnixGecos(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );

static
DWORD
SamDbModifyUnixGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    );


static SAMDB_ATTRIBUTE_MODIFIER gSamDbUnknownObjectModifiers[] =
{
    { 0,                        NULL }
};

static SAMDB_ATTRIBUTE_MODIFIER gSamDbDomainObjectModifiers[] =
{
    { SAMDB_FIELD_OBJECT_NAME,  SamDbModifyDomainName },
    { 0,                        NULL }
};

static SAMDB_ATTRIBUTE_MODIFIER gSamDbUserObjectModifiers[] =
{
    { SAMDB_FIELD_OBJECT_NAME,              SamDbModifyAccountName },
    { SAMDB_FIELD_USER_WORKSTATIONS,        SamDbModifyWorkstations },
    { SAMDB_FIELD_USER_LM_OWF_PASSWORD,     SamDbModifyLmOwfPassword },
    { SAMDB_FIELD_USER_NT_OWF_PASSWORD,     SamDbModifyNtOwfPassword },
    { SAMDB_FIELD_USER_PRIMARY_GROUP_RID,   SamDbModifyPrimaryGroupRid },
    { SAMDB_FIELD_USER_ACCOUNT_CONTROL,     SamDbModifyAccountFlags },
    { SAMDB_FIELD_USER_LOGON_HOURS,         SamDbModifyLogonHours },
    { SAMDB_FIELD_USER_UNIX_UID,            SamDbModifyUnixUid },
    { SAMDB_FIELD_USER_UNIX_SHELL,          SamDbModifyUnixShell },
    { SAMDB_FIELD_USER_UNIX_GECOS,          SamDbModifyUnixGecos },
    { SAMDB_FIELD_USER_UNIX_HOME_DIRECTORY, SamDbModifyUnixHomeDirectory },
    { 0,                                    NULL }
};

static SAMDB_ATTRIBUTE_MODIFIER gSamDbLocalGroupObjectModifiers[] =
{
    { SAMDB_FIELD_OBJECT_NAME,           SamDbModifyAccountName },
    { SAMDB_FIELD_LOCAL_GROUP_UNIX_GID,  SamDbModifyUnixGid },
    { 0,                                 NULL }
};

static PSAMDB_ATTRIBUTE_MODIFIER gSamDbObjectTypeModifier[] =
{
    /* SAMDB_OBJECT_TYPE_UNKNOWN */
    &gSamDbUnknownObjectModifiers[0],

    /* SAMDB_OBJECT_TYPE_DOMAIN */
    &gSamDbDomainObjectModifiers[0],

    /* SAMDB_OBJECT_TYPE_USER */
    &gSamDbUserObjectModifiers[0],

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


typedef struct _SAMDB_FIELD_ATTRIBUTE_MAP
{
    SAMDB_FIELDS Field;
    DWORD Index;
} SAMDB_FIELD_ATTRIBUTE_MAP, *PSAMDB_FIELD_ATTRIBUTE_MAP;


static
DWORD
SamDbUpdateObjectInternal(
    PSAMDB_CONTEXT DbContext,
    PSID ObjectSid,
    PSAMDB_OBJECT_INFO Object,
    SAMDB_FIELDS UpdateFields
    );

static
DWORD
SamDbBuildUpdateObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSID ObjectSid,
    PSAMDB_OBJECT_INFO Object,
    SAMDB_FIELDS UpdateFields,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    );

static
DWORD
SamDbChangeDomainName(
    PSAMDB_CONTEXT DbContext,
    PWSTR DomainName
    );

static
DWORD
SamDbBuildUpdateDomainNameStatement(
    PSAMDB_CONTEXT DbContext,
    PWSTR DomainName,
    PSID DomainSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    );

static
DWORD
SamDbChangeDomainSid(
    PSAMDB_CONTEXT DbContext,
    PSID DomainSid
    );

static
DWORD
SamDbBuildUpdateObjectSidStatement(
    PSAMDB_CONTEXT DbContext,
    PSID OldObjectSid,
    PSID NewObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    );


DWORD
SamDbUpdateObject(
    IN PSAMDB_OBJECT_INFO Object,
    IN SAMDB_FIELDS UpdateFields
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    BOOLEAN transactionStarted = FALSE;
    PSAMDB_OBJECT_INFO object = NULL;
    PSAMDB_ATTRIBUTE_MODIFIER attributeModifiers = NULL;
    DWORD modifierIndex = 0;

    BAIL_ON_INVALID_POINTER(Object, error);

    if ((Object->Type != SAMDB_OBJECT_TYPE_DOMAIN &&
         Object->Type != SAMDB_OBJECT_TYPE_USER &&
         Object->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP) ||
        UpdateFields == 0)
    {
        error = ERROR_INVALID_PARAMETER;
        goto cleanup;
    }

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

    SAMDB_BEGIN_TRANSACTION(transactionStarted, dbContext, error);

    //
    // Make a copy of Object in case more changes to other fields
    // were necessary
    //
    error = LwAllocateMemory(
                  sizeof(*object),
                  OUT_PPVOID(&object));
    BAIL_ON_SAMDB_ERROR(error);

    *object = *Object;

    attributeModifiers = gSamDbObjectTypeModifier[Object->Type];

    while (attributeModifiers[modifierIndex].Field)
    {
        if (attributeModifiers[modifierIndex].Field & UpdateFields)
        {
            error = attributeModifiers[modifierIndex].pfnAttrModifier(
                                       dbContext,
                                       object,
                                       &UpdateFields);
            BAIL_ON_SAMDB_ERROR(error);
        }

        modifierIndex++;
    }

    error = SamDbUpdateObjectInternal(
                  dbContext,
                  object->Sid,
                  object,
                  UpdateFields);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    SAMDB_END_TRANSACTION(transactionStarted, dbContext, error);

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

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }

    // SamDbFreeObject can't be called here because pointers in object's
    // fields are owned by Object
    LW_SAFE_FREE_MEMORY(object);

    return error;
}


static
DWORD
SamDbUpdateObjectInternal(
    PSAMDB_CONTEXT DbContext,
    PSID ObjectSid,
    PSAMDB_OBJECT_INFO Object,
    SAMDB_FIELDS UpdateFields
    )
{
    DWORD error = ERROR_SUCCESS;
    sqlite3_stmt *sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;

    error = SamDbBuildUpdateObjectStatement(
                      DbContext,
                      ObjectSid,
                      Object,
                      UpdateFields,
                      &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_UPDATE_OBJECT_PREFIX     "UPDATE " SAM_DB_OBJECTS_TABLE " SET "
#define SAMDB_SQL_UPDATE_OBJECT_WHERE      "WHERE "


static
DWORD
SamDbBuildUpdateObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSID ObjectSid,
    PSAMDB_OBJECT_INFO Object,
    SAMDB_FIELDS UpdateFields,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributeIndex = 0;
    DWORD inputAttributesCount = 0;
    DWORD inputAttrDefIndex = 0;
    DWORD inputAttrDefCount = 0;
    DWORD attributeIndex = 0;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PWSTR* ppwszSid = NULL;
    PSAMDB_FIELD_ATTRIBUTE_MAP fieldAttributes = NULL;

    enum _INPUT_ATTR_INDEX {
        ATTR_IDX_OBJECT_SID = 0,
        ATTR_IDX_DOMAIN,
        ATTR_IDX_SAM_ACCOUNT_NAME,
        ATTR_IDX_MIN_PWD_AGE,
        ATTR_IDX_MAX_PWD_AGE,
        ATTR_IDX_MIN_PWD_LENGTH,
        ATTR_IDX_PWD_HISTORY_LENGTH,
        ATTR_IDX_PWD_CHANGE_TIME,
        ATTR_IDX_LAST_LOGON,
        ATTR_IDX_LAST_LOGOFF,
        ATTR_IDX_PWD_LAST_SET,
        ATTR_IDX_ACCOUNT_EXPIRY,
        ATTR_IDX_FULL_NAME,
        ATTR_IDX_HOME_DIR,
        ATTR_IDX_HOME_DRIVE,
        ATTR_IDX_LOGON_SCRIPT,
        ATTR_IDX_PROFILE_PATH,
        ATTR_IDX_DESCRIPTION,
        ATTR_IDX_WORKSTATIONS,
        ATTR_IDX_COMMENT,
        ATTR_IDX_LM_HASH,
        ATTR_IDX_NT_HASH,
        ATTR_IDX_PRIMARY_GROUP,
        ATTR_IDX_ACCOUNT_FLAGS,
        ATTR_IDX_LOGON_HOURS,
        ATTR_IDX_BAD_PASSWORD_COUNT,
        ATTR_IDX_LOGON_COUNT,
        ATTR_IDX_COUNTRY_CODE,
        ATTR_IDX_CODE_PAGE,
        ATTR_IDX_UID,
        ATTR_IDX_SHELL,
        ATTR_IDX_GECOS,
        ATTR_IDX_UNIX_HOME_DIR,
        ATTR_IDX_GID,
        ATTR_IDX_LIKEWISE_FLAGS
    };

    SAMDB_ATTRIBUTE inputAttrDef[] = {
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
        {
            SAMDB_ATTR_DOMAIN,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Name
        },
        {
            SAMDB_ATTR_SAM_ACCOUNT_NAME,
            SAMDB_ATTR_TYPE_TEXT,
            &Object->Name
        },
        {
            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_MIN_PWD_LENGTH,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.Domain.MinPasswordLength
        },
        {
            SAMDB_ATTR_PWD_HISTORY_LENGTH,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.Domain.PasswordHistoryLength
        },
        {
            SAMDB_ATTR_PWD_PROMPT_TIME,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.Domain.PasswordChangeTime
        },
        {
            SAMDB_ATTR_LAST_LOGON,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.User.LastLogon
        },
        {
            SAMDB_ATTR_LAST_LOGOFF,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.User.LastLogoff
        },
        {
            SAMDB_ATTR_PASSWORD_LAST_SET,
            SAMDB_ATTR_TYPE_INT64,
            &Object->Info.User.PasswordLastSet
        },
        {
            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,
            // NULL because it can be either user or local group description
            // and these are fields in different locations
            NULL
        },
        {
            SAMDB_ATTR_WORKSTATIONS,
            SAMDB_ATTR_TYPE_BLOB,
            NULL
        },
        {
            SAMDB_ATTR_COMMENT,
            SAMDB_ATTR_TYPE_TEXT,
            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,
            &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_BAD_PASSWORD_COUNT,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.BadPasswordCount
        },
        {
            SAMDB_ATTR_LOGON_COUNT,
            SAMDB_ATTR_TYPE_INT32,
            &Object->Info.User.LogonCount
        },
        {
            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_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,
            NULL
        }
    };

    SAMDB_FIELD_ATTRIBUTE_MAP unknownObjectFields[] = {
        { 0, 0 }
    };

    SAMDB_FIELD_ATTRIBUTE_MAP domainObjectFields[] = {
        {
            SAMDB_FIELD_OBJECT_NAME,
            ATTR_IDX_DOMAIN
        },
        {
            SAMDB_FIELD_DOMAIN_MIN_PASSWORD_LENGTH,
            ATTR_IDX_MIN_PWD_LENGTH
        },
        {
            SAMDB_FIELD_DOMAIN_PASSWORD_HISTORY_LENGTH,
            ATTR_IDX_PWD_HISTORY_LENGTH
        },
        {
            SAMDB_FIELD_DOMAIN_MIN_PASSWORD_AGE,
            ATTR_IDX_MIN_PWD_AGE
        },
        {
            SAMDB_FIELD_DOMAIN_MAX_PASSWORD_AGE,
            ATTR_IDX_MAX_PWD_AGE
        },
        {
            SAMDB_FIELD_DOMAIN_PASSWORD_CHANGE_TIME,
            ATTR_IDX_PWD_CHANGE_TIME
        },
        {
            SAMDB_ATTR_TYPE_TEXT,
            ATTR_IDX_COMMENT
        },
        {
            0,
            0
        }
    };

    SAMDB_FIELD_ATTRIBUTE_MAP userObjectFields[] = {
        {
            SAMDB_FIELD_OBJECT_NAME,
            ATTR_IDX_SAM_ACCOUNT_NAME,
        },
        {
            SAMDB_FIELD_USER_LAST_LOGON,
            ATTR_IDX_LAST_LOGON
        },
        {
            SAMDB_FIELD_USER_LAST_LOGOFF,
            ATTR_IDX_LAST_LOGOFF
        },
        {
            SAMDB_FIELD_USER_PASSWORD_LAST_SET,
            ATTR_IDX_PWD_LAST_SET
        },
        {
            SAMDB_FIELD_USER_ACCOUNT_EXPIRES,
            ATTR_IDX_ACCOUNT_EXPIRY
        },
        {
            SAMDB_FIELD_USER_FULL_NAME,
            ATTR_IDX_FULL_NAME
        },
        {
            SAMDB_FIELD_USER_HOME_DIRECTORY,
            ATTR_IDX_HOME_DIR
        },
        {
            SAMDB_FIELD_USER_HOME_DIRECTORY_DRIVE,
            ATTR_IDX_HOME_DRIVE
        },
        {
            SAMDB_FIELD_USER_SCRIPT_PATH,
            ATTR_IDX_LOGON_SCRIPT
        },
        {
            SAMDB_FIELD_USER_PROFILE_PATH,
            ATTR_IDX_PROFILE_PATH
        },
        {
            SAMDB_FIELD_USER_DESCRIPTION,
            ATTR_IDX_DESCRIPTION
        },
        {
            SAMDB_FIELD_USER_WORKSTATIONS,
            ATTR_IDX_WORKSTATIONS
        },
        {
            SAMDB_FIELD_USER_USER_COMMENT,
            ATTR_IDX_COMMENT
        },
        {
            SAMDB_FIELD_USER_LM_OWF_PASSWORD,
            ATTR_IDX_LM_HASH
        },
        {
            SAMDB_FIELD_USER_NT_OWF_PASSWORD,
            ATTR_IDX_NT_HASH
        },
        {
            SAMDB_FIELD_USER_PRIMARY_GROUP_RID,
            ATTR_IDX_PRIMARY_GROUP
        },
        {
            SAMDB_FIELD_USER_ACCOUNT_CONTROL,
            ATTR_IDX_ACCOUNT_FLAGS
        },
        {
            SAMDB_FIELD_USER_LOGON_HOURS,
            ATTR_IDX_LOGON_HOURS
        },
        {
            SAMDB_FIELD_USER_BAD_PASSWORD_COUNT,
            ATTR_IDX_BAD_PASSWORD_COUNT
        },
        {
            SAMDB_FIELD_USER_LOGON_COUNT,
            ATTR_IDX_LOGON_COUNT
        },
        {
            SAMDB_FIELD_USER_COUNTRY_CODE,
            ATTR_IDX_COUNTRY_CODE
        },
        {
            SAMDB_FIELD_USER_CODE_PAGE,
            ATTR_IDX_CODE_PAGE
        },
        {
            SAMDB_FIELD_USER_UNIX_UID,
            ATTR_IDX_UID
        },
        {
            SAMDB_FIELD_USER_UNIX_SHELL,
            ATTR_IDX_SHELL
        },
        {
            SAMDB_FIELD_USER_UNIX_GECOS,
            ATTR_IDX_GECOS
        },
        {
            SAMDB_FIELD_USER_LIKEWISE_FLAGS,
            ATTR_IDX_LIKEWISE_FLAGS
        },
        {
            0,
            0
        }
    };

    SAMDB_FIELD_ATTRIBUTE_MAP localGroupObjectFields[] = {
        {
            SAMDB_FIELD_OBJECT_NAME,
            ATTR_IDX_SAM_ACCOUNT_NAME,
        },
        {
            SAMDB_FIELD_LOCAL_GROUP_DESCRIPTION,
            ATTR_IDX_DESCRIPTION
        },
        {
            SAMDB_FIELD_LOCAL_GROUP_UNIX_GID,
            ATTR_IDX_GID
        },
        {
            SAMDB_FIELD_LOCAL_GROUP_LIKEWISE_FLAGS,
            ATTR_IDX_LIKEWISE_FLAGS
        },
        {
            0,
            0
        }
    };

    PSAMDB_FIELD_ATTRIBUTE_MAP objectFieldMaps[] = {
        /* SAMDB_OBJECT_TYPE_UNKNOWN */
        &unknownObjectFields[0],

        /* SAMDB_OBJECT_TYPE_DOMAIN */
        &domainObjectFields[0],

        /* SAMDB_OBJECT_TYPE_USER */
        &userObjectFields[0],

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

    inputAttrDefCount = sizeof(inputAttrDef)/sizeof(inputAttrDef[0]) + 1;
    error = LwAllocateMemory(
                    sizeof(*inputAttributes) * inputAttrDefCount,
                    OUT_PPVOID(&inputAttributes));
    BAIL_ON_SAMDB_ERROR(error);

    fieldAttributes = objectFieldMaps[Object->Type];

    for (attributeIndex = 0;
         fieldAttributes[attributeIndex].Field != 0;
         attributeIndex++)
    {
        if (!(fieldAttributes[attributeIndex].Field & UpdateFields))
        {
            continue;
        }

        inputAttrDefIndex = fieldAttributes[attributeIndex].Index;
        inputAttributes[inputAttributeIndex] = inputAttrDef[inputAttrDefIndex];

        if (!inputAttributes[inputAttributeIndex].Value)
        {
            PSAMDB_ATTRIBUTE inputAttribute =
                &(inputAttributes[inputAttributeIndex]);

            //
            // This attribute value requires special treatment
            //
            switch (inputAttrDefIndex)
            {
            case ATTR_IDX_DESCRIPTION:
                if (Object->Type == SAMDB_OBJECT_TYPE_USER)
                {
                    inputAttribute->Value =
                        &Object->Info.User.Description;
                }
                else if (Object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
                {
                    inputAttribute->Value =
                        &Object->Info.LocalGroup.Description;
                }

                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_COMMENT:
                if (Object->Type == SAMDB_OBJECT_TYPE_USER)
                {
                    inputAttribute->Value =
                        &Object->Info.User.UserComment;
                }
                else if (Object->Type == SAMDB_OBJECT_TYPE_DOMAIN)
                {
                    inputAttribute->Value =
                        &Object->Info.Domain.Comment;
                }

                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_LIKEWISE_FLAGS:
                if (Object->Type == SAMDB_OBJECT_TYPE_USER)
                {
                    inputAttribute->Value =
                        &Object->Info.User.LikewiseFlags;
                }
                else if (Object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
                {
                    inputAttribute->Value =
                        &Object->Info.LocalGroup.LikewiseFlags;
                }

                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_WORKSTATIONS:
            case ATTR_IDX_LOGON_HOURS:
                error = ERROR_INVALID_PARAMETER;
                goto cleanup;

            case ATTR_IDX_LM_HASH:
                {
                    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

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

                    blob->Length = sizeof(Object->Info.User.LmOwfPassword.Data);
                    error = LwAllocateMemory(
                                blob->Length,
                                OUT_PPVOID(&blob->Value));
                    BAIL_ON_SAMDB_ERROR(error);

                    memcpy(blob->Value,
                           Object->Info.User.LmOwfPassword.Data,
                           blob->Length);

                    inputAttribute->Value = blob;
                    inputAttribute->FreeFn = SamDbAttributeFreeBlob;
                }
                break;

            case ATTR_IDX_NT_HASH:
                {
                    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;

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

                    blob->Length = sizeof(Object->Info.User.NtOwfPassword.Data);
                    error = LwAllocateMemory(
                                blob->Length,
                                OUT_PPVOID(&blob->Value));
                    BAIL_ON_SAMDB_ERROR(error);

                    memcpy(blob->Value,
                           Object->Info.User.NtOwfPassword.Data,
                           blob->Length);

                    inputAttribute->Value = blob;
                    inputAttribute->FreeFn = SamDbAttributeFreeBlob;
                }
                break;

            case ATTR_IDX_UID:
                inputAttribute->Value = &Object->Info.User.UnixInfo->Uid;
                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_SHELL:
                inputAttribute->Value = &Object->Info.User.UnixInfo->Shell;
                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_GECOS:
                inputAttribute->Value = &Object->Info.User.UnixInfo->Gecos;
                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_UNIX_HOME_DIR:
                inputAttribute->Value = &Object->Info.User.UnixInfo->HomeDirectory;
                inputAttribute->FreeFn = NULL;
                break;

            case ATTR_IDX_GID:
                inputAttribute->Value = &Object->Info.LocalGroup.UnixInfo->Gid;
                inputAttribute->FreeFn = NULL;
                break;
            }
        }

        inputAttributeIndex++;
    }

    //
    // Prepare WHERE condition argument
    //
    error = LwAllocateMemory(
                    sizeof(*ppwszSid),
                    OUT_PPVOID(&ppwszSid));
    BAIL_ON_SAMDB_ERROR(error);

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

    inputAttributes[inputAttributeIndex] = inputAttrDef[ATTR_IDX_OBJECT_SID];

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

    inputAttributesCount = inputAttributeIndex;

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

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

    //
    // Skip attribute index = 0 because it's the SID that will
    // be part of WHERE condition
    //
    for (inputAttributeIndex = 0;
         inputAttributeIndex < (inputAttributesCount - 1);
         inputAttributeIndex++)
    {
        //
        // Append SQL column names separated with "," and ended with ") "
        //
        error = SamDbPrintfString(
                      &sqlStatementString,
                      &sqlStatementSize,
                      &sqlStatementLength,
                      maxSqlStatementSize,
                      (inputAttributeIndex < inputAttributesCount - 2) ? "%s = ?%u," : "%s = ?%u ",
                      inputAttributes[inputAttributeIndex].DbColumnName,
                      inputAttributeIndex + 1);
        BAIL_ON_SAMDB_ERROR(error);
    };

    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s%s = ?%u;",
                    SAMDB_SQL_UPDATE_OBJECT_WHERE,
                    inputAttributes[inputAttributeIndex].DbColumnName,
                    inputAttributeIndex + 1);
    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);

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

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

    return error;
}


static
DWORD
SamDbModifyDomainName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_INVALID_PARAMETER;

    // SamDbUpdateObject cannot be used to change local domain name
    return error;
}


static
DWORD
SamDbModifyAccountName(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN nameIsValid = TRUE;
    SAMDB_QUERY_TYPE queryType = SAMDB_QUERY_TYPE_NT_NAME;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO object = NULL;

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

    query.Name = Object->Name;

    error = SamDbFindObjectInternal(
                       DbContext,
                       (SAMDB_OBJECT_CLASS)Object->Type,
                       queryType,
                       &query,
                       &object);
    if (error == ERROR_FILE_NOT_FOUND)
    {
        // No such account name found, so there is no collision
        error = ERROR_SUCCESS;
    }
    else if (error == ERROR_SUCCESS)
    {
        // User or local group with such a name already exists
        switch (Object->Type)
        {
        case SAMDB_OBJECT_TYPE_USER:
            error = ERROR_USER_EXISTS;
            break;

        case SAMDB_OBJECT_TYPE_LOCAL_GROUP:
            error = ERROR_ALIAS_EXISTS;
            break;

        default:
            error = ERROR_INTERNAL_ERROR;

            SAMDB_LOG_ERROR("Invalid object type found (%u) when trying "
                            "to update user or local group",
                            Object->Type);
            goto cleanup;
        }
    }
    else
    {
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    if (object)
    {
        SamDbFreeObject(object);
    }

    return error;
}


static
DWORD
SamDbModifyWorkstations(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_INVALID_PARAMETER;

    SAMDB_LOG_ERROR("Updating workstations array is not supported");

    return error;
}


static
DWORD
SamDbModifyLmOwfPassword(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_FIELDS fields = *Fields;
    LONG64 currentTime = SamDbGetNTTime(time(NULL));

    if (!Object->Info.User.LmOwfPassword.IsPresent)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("LmOwfPassword update requested while the password "
                        "blob is not present");
        goto cleanup;
    }

    Object->Info.User.PasswordLastSet = currentTime;
    fields |= SAMDB_FIELD_USER_PASSWORD_LAST_SET;

cleanup:
    if (fields != *Fields)
    {
        *Fields = fields;
    }

    return error;
}


static
DWORD
SamDbModifyNtOwfPassword(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_FIELDS fields = *Fields;
    LONG64 currentTime = SamDbGetNTTime(time(NULL));

    if (!Object->Info.User.NtOwfPassword.IsPresent)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("NtOwfPassword update requested while the password "
                        "blob is not present");
        goto cleanup;
    }

    Object->Info.User.PasswordLastSet = currentTime;
    fields |= SAMDB_FIELD_USER_PASSWORD_LAST_SET;

cleanup:
    if (fields != *Fields)
    {
        *Fields = fields;
    }

    return error;
}


static
DWORD
SamDbModifyPrimaryGroupRid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD membersCount = 0;
    PSID* members = NULL;
    DWORD memberIndex = 0;
    DWORD sidLength = 0;
    PSID primaryGroupSid = NULL;
    BOOLEAN isMember = FALSE;

    sidLength = RtlLengthSid(Object->Sid);
    error = LwAllocateMemory(
                      sidLength,
                      OUT_PPVOID(&primaryGroupSid));
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlCopySid(
                      sidLength,
                      primaryGroupSid,
                      Object->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    primaryGroupSid->SubAuthorityCount -= 1;

    ntStatus = RtlAppendRidSid(
                      sidLength,
                      primaryGroupSid,
                      Object->Info.User.PrimaryGroupRid);
    BAIL_ON_NT_STATUS(ntStatus);

    error = SamDbQueryMemberOf(
                      Object->Sid,
                      &membersCount,
                      &members);
    BAIL_ON_SAMDB_ERROR(error);

    for (memberIndex = 0; memberIndex < membersCount; memberIndex++)
    {
        if (RtlEqualSid(
                  primaryGroupSid,
                  members[memberIndex]))
        {
            isMember = TRUE;
            break;
        }
    }

    if (!isMember)
    {
        error = ERROR_INVALID_PRIMARY_GROUP;
    }

cleanup:
error:
    if (members)
    {
        for (memberIndex = 0;
             memberIndex < membersCount;
             memberIndex++)
        {
            LW_SAFE_FREE_MEMORY(members[memberIndex]);
        }
        LW_SAFE_FREE_MEMORY(members);
    }

    LW_SAFE_FREE_MEMORY(primaryGroupSid);

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

    return error;
}


static
DWORD
SamDbModifyAccountFlags(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_UF_FLAGS flags = Object->Info.User.UserAccountControl;
    SAMDB_UF_FLAGS accountTypeFlags = 0;

    accountTypeFlags = (flags &
                        (SAMDB_UF_FLAG_TEMP_DUPLICATE_ACCOUNT |
                         SAMDB_UF_FLAG_NORMAL_ACCOUNT |
                         SAMDB_UF_FLAG_INTERDOMAIN_TRUST_ACCOUNT |
                         SAMDB_UF_FLAG_WORKSTATION_TRUST_ACCOUNT |
                         SAMDB_UF_FLAG_SERVER_TRUST_ACCOUNT));

    //
    // Type of account cannot be changed
    //
    if (accountTypeFlags != SAMDB_UF_FLAG_NORMAL_ACCOUNT)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


static
DWORD
SamDbModifyLogonHours(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_INVALID_PARAMETER;

    SAMDB_LOG_ERROR("Updating logon hours is not supported");

    return error;
}


static
DWORD
SamDbModifyUnixUid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;

    if (!Object->Info.User.UnixInfo)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("No UnixInfo available while user uid "
                        "change requested");
        goto cleanup;
    }

cleanup:
    return error;
}


static
DWORD
SamDbModifyUnixShell(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;

    if (!Object->Info.User.UnixInfo)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("No UnixInfo available while user unix shell "
                        "change requested");
        goto cleanup;
    }

    if (!Object->Info.User.UnixInfo->Shell)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


static
DWORD
SamDbModifyUnixGecos(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;

    if (!Object->Info.User.UnixInfo)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("No UnixInfo available while user unix gecos "
                        "change requested");
        goto cleanup;
    }

    if (!Object->Info.User.UnixInfo->Gecos)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


static
DWORD
SamDbModifyUnixHomeDirectory(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;

    if (!Object->Info.User.UnixInfo)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("No UnixInfo available while user unix home directory"
                        "change requested");
        goto cleanup;
    }

    if (!Object->Info.User.UnixInfo->HomeDirectory)
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    return error;
}


static
DWORD
SamDbModifyUnixGid(
    PSAMDB_CONTEXT DbContext,
    PSAMDB_OBJECT_INFO Object,
    PSAMDB_FIELDS Fields
    )
{
    DWORD error = ERROR_SUCCESS;

    if (!Object->Info.LocalGroup.UnixInfo)
    {
        error = ERROR_INVALID_PARAMETER;

        SAMDB_LOG_ERROR("No UnixInfo available while group gid "
                        "change requested");
        goto cleanup;
    }

cleanup:
    return error;
}


DWORD
SamDbChangeDomainNameSid(
    IN OPTIONAL PWSTR DomainName,
    IN OPTIONAL PSID DomainSid
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    PSID localDomainSid = NULL;
    BOOLEAN inLock = FALSE;
    BOOLEAN inRwLock = FALSE;
    BOOLEAN transactionStarted = FALSE;

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

    error = SamDbGetLocalDomainSid(&localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    //
    // Freeze the database contents to prevent from adding
    // new accounts while the local domain SID is being changed
    //
    SAMDB_LOCK_MUTEX(inLock, gSamDbGlobals.pMutex);
    SAMDB_BEGIN_TRANSACTION(transactionStarted, dbContext, error);

    if (DomainName)
    {
        error = SamDbChangeDomainName(
                      dbContext,
                      DomainName);
        BAIL_ON_SAMDB_ERROR(error);
    }

    if (DomainSid)
    {
        error = SamDbChangeDomainSid(
                      dbContext,
                      DomainSid);
        BAIL_ON_SAMDB_ERROR(error);
    }

cleanup:
    SAMDB_END_TRANSACTION(transactionStarted, dbContext, error);

    if (DomainSid &&
        error == ERROR_SUCCESS)
    {
        //
        // Free cached local domain SID because it is no longer valid.
        // It will be reloaded on nearest SamDbGetLocalDomainSid() call.
        //
        SAMDB_EXCLUSIVE_LOCK_RWLOCK(inRwLock, gSamDbGlobals.pRwLock);
        LW_SAFE_FREE_MEMORY(gSamDbGlobals.LocalDomainSid);
        SAMDB_UNLOCK_RWLOCK(inRwLock, gSamDbGlobals.pRwLock);
    }

    SAMDB_UNLOCK_MUTEX(inLock, gSamDbGlobals.pMutex);

    if (error == ERROR_SUCCESS)
    {
        SamDbNotifyCallbacks(localDomainSid);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }

    return error;
}


static
DWORD
SamDbChangeDomainName(
    PSAMDB_CONTEXT DbContext,
    PWSTR DomainName
    )
{
    DWORD error = ERROR_SUCCESS;
    PSID localDomainSid = NULL;
    sqlite3_stmt *sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;

    if (!SamDbIsValidNetBiosDomainName(DomainName))
    {
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = SamDbGetLocalDomainSid(&localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbBuildUpdateDomainNameStatement(
                    DbContext,
                    DomainName,
                    localDomainSid,
                    &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);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);

    return error;
}


#define SAMDB_SQL_UPDATE_DOMAIN_NAME_PREFIX   "UPDATE " SAM_DB_OBJECTS_TABLE " SET "
#define SAMDB_SQL_UPDATE_DOMAIN_NAME_WHERE    "WHERE "


static
DWORD
SamDbBuildUpdateDomainNameStatement(
    PSAMDB_CONTEXT DbContext,
    PWSTR DomainName,
    PSID DomainSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR* pDomainName = NULL;
    PWSTR* ppwszDomainSid = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributeIndex = 0;
    DWORD inputAttributesCount = 0;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;

    enum _INPUT_ATTR_INDEX {
        ATTR_IDX_DOMAIN = 0,
        ATTR_IDX_OBJECT_SID,
    };

    SAMDB_ATTRIBUTE inputAttrDef[] = {
        {
            SAMDB_ATTR_DOMAIN,
            SAMDB_ATTR_TYPE_TEXT,
            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(*pDomainName),
                    OUT_PPVOID(&pDomainName));
    BAIL_ON_SAMDB_ERROR(error);

    error = LwAllocateWc16String(
                    pDomainName,
                    DomainName);
    BAIL_ON_SAMDB_ERROR(error);

    inputAttributes[inputAttributeIndex] = inputAttrDef[ATTR_IDX_DOMAIN];
    inputAttributes[inputAttributeIndex].Value = pDomainName;
    inputAttributes[inputAttributeIndex].FreeFn = SamDbAttributeFreeString;
    inputAttributeIndex++;

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

    ntStatus = RtlAllocateWC16StringFromSid(
                    ppwszDomainSid,
                    DomainSid);
    BAIL_ON_NT_STATUS(ntStatus);

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

    inputAttributesCount = inputAttributeIndex;
    inputAttributeIndex = 0;

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

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

    inputAttributeIndex += 1;

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

    inputAttributeIndex += 1;

    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 || ntStatus)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }

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

    LW_SAFE_FREE_MEMORY(sqlStatementString);

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

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

    return error;
}


static
DWORD
SamDbChangeDomainSid(
    PSAMDB_CONTEXT DbContext,
    PSID DomainSid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    SAMDB_ENUM_HANDLE enumHandle = NULL;
    DWORD maxCount = (DWORD)-1;
    DWORD objectsCount = 0;
    PSAMDB_OBJECT_INFO* objects = NULL;
    DWORD objectIndex = 0;
    PSAMDB_OBJECT_INFO object = NULL;
    PSID oldObjectSid = NULL;
    DWORD newObjectSidLength = 0;
    PSID newObjectSid = NULL;
    DWORD objectRid = 0;
    PSID builtinDomainSid = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;

    error = SamDbOpenEnumObjectsInternal(
                         DbContext,
                         NULL,
                         SAMDB_OBJECT_TYPE_UNKNOWN,
                         SAMDB_ENUM_SORT_ORDER_NONE,
                         &enumHandle);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbEnumObjects(
                         enumHandle,
                         maxCount,
                         &objectsCount,
                         &objects);
    BAIL_ON_SAMDB_ERROR(error);

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

    for (objectIndex = 0; objectIndex < objectsCount; objectIndex++)
    {
        object = objects[objectIndex];
        oldObjectSid = objects[objectIndex]->Sid;

        if (RtlIsPrefixSid(
                    builtinDomainSid,
                    oldObjectSid) ||
            RtlEqualSid(
                    builtinDomainSid,
                    oldObjectSid))
        {
            //
            // We're only interested in objects from local domain
            //
            continue;
        }

        newObjectSidLength = RtlLengthSid(oldObjectSid);
        error = LwAllocateMemory(
                         newObjectSidLength,
                         OUT_PPVOID(&newObjectSid));
        BAIL_ON_SAMDB_ERROR(error);

        ntStatus = RtlCopySid(
                         newObjectSidLength,
                         newObjectSid,
                         DomainSid);
        BAIL_ON_NT_STATUS(ntStatus);

        if (object->Type == SAMDB_OBJECT_TYPE_USER ||
            object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
        {
            ntStatus = RtlGetRidSid(
                             &objectRid,
                             oldObjectSid);
            BAIL_ON_NT_STATUS(ntStatus);

            ntStatus = RtlAppendRidSid(
                             newObjectSidLength,
                             newObjectSid,
                             objectRid);
            BAIL_ON_NT_STATUS(ntStatus);
        }
        else if (object->Type != SAMDB_OBJECT_TYPE_DOMAIN)
        {
            error = ERROR_INTERNAL_ERROR;

            SAMDB_LOG_ERROR("Unknown object type found in the database");
            goto error;
        }

        error = SamDbBuildUpdateObjectSidStatement(
                         DbContext,
                         oldObjectSid,
                         newObjectSid,
                         &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);
        }

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

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

        LW_SAFE_FREE_MEMORY(newObjectSid);
    }

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

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

    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

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

    LW_SAFE_FREE_MEMORY(builtinDomainSid);
    LW_SAFE_FREE_MEMORY(newObjectSid);

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

    return error;
}


#define SAMDB_SQL_UPDATE_OBJECT_SID_PREFIX   "UPDATE " SAM_DB_OBJECTS_TABLE " SET "
#define SAMDB_SQL_UPDATE_OBJECT_SID_WHERE    "WHERE "


static
DWORD
SamDbBuildUpdateObjectSidStatement(
    PSAMDB_CONTEXT DbContext,
    PSID OldObjectSid,
    PSID NewObjectSid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR* ppwszOldSid = NULL;
    PWSTR* ppwszNewSid = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributeIndex = 0;
    DWORD inputAttributesCount = 0;
    size_t sqlStatementSize = 256;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;

    SAMDB_ATTRIBUTE inputAttrDef = {
        SAMDB_ATTR_OBJECT_SID,
        SAMDB_ATTR_TYPE_TEXT,
        NULL
    };

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

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

    ntStatus = RtlAllocateWC16StringFromSid(
                    ppwszNewSid,
                    NewObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

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

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

    ntStatus = RtlAllocateWC16StringFromSid(
                    ppwszOldSid,
                    OldObjectSid);
    BAIL_ON_NT_STATUS(ntStatus);

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

    inputAttributesCount = inputAttributeIndex;
    inputAttributeIndex = 0;

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

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

    inputAttributeIndex++;

    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s%s = ?%u;",
                    SAMDB_SQL_UPDATE_OBJECT_SID_WHERE,
                    inputAttributes[inputAttributeIndex].DbColumnName,
                    inputAttributeIndex + 1);
    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 || ntStatus)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }

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

    LW_SAFE_FREE_MEMORY(sqlStatementString);

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

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

    return error;
}
