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

#include "includes.h"


static
DWORD
SamDbInitBuiltinMemberships(
    PSAMDB_GROUP_MEMBERSHIP* Memberships,
    PDWORD MembershipsCount
    );

static
VOID
SamDbFreeBuiltinMemberships(
    PSAMDB_GROUP_MEMBERSHIP Memberships,
    DWORD MembershipsCount
    );

static
DWORD
SamDbCreateSchema(
    PSAMDB_CONTEXT DbContext
    );

static
DWORD
SamDbInitConfig(
    PSAMDB_CONTEXT DbContext
    );

static
DWORD
SamDbProvisionObjects(
    PSAMDB_CONTEXT DbContext
    );

static
DWORD
SamDbCreateNetBiosName(
    PSTR* pNetBiosName
    );

static
DWORD
SamDbProvisionBuiltinDomain(
    PSAMDB_CONTEXT DbContext
    );

static
DWORD
SamDbProvisionLocalDomain(
    PSAMDB_CONTEXT DbContext,
    PSTR DomainName,
    PSID* pDomainSid
    );

static
DWORD
SamDbGenerateMachineSid(
    PSID* pSid
    );

static
DWORD
SamDbSetupLocalGroupMemberships(
    PSAMDB_CONTEXT DbContext,
    PSID localDomainSid
    );

static
DWORD
SamDbBackupDatabase(
    PCSTR DatabasePath,
    PSTR* BackupDatabasePath
    );


DWORD
SamDbInitContext(
    PSAMDB_CONTEXT* pDbContext
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    PCSTR  databaseDirPath = SAMDB_DIRECTORY;
    PCSTR databasePath = SAMDB_FILE;
    PCSTR databaseTempPath = SAMDB_TEMP_FILE;
    BOOLEAN exists = FALSE;
    DWORD currentVersion = 0;
    PSTR backupDatabasePath = NULL;
    int dirFd = -1;

    error = LsaCheckFileExists(
                    databasePath,
                    &exists);
    BAIL_ON_SAMDB_ERROR(error);

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

    if (exists)
    {
        //
        // We don't call SamDbAcquireContext because we would have
        // ended up calling it recursively
        //
        error = sqlite3_open(
                        databasePath,
                        &dbContext->DbHandle);
        BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, dbContext->DbHandle);

        error = SamDbCheckDatabaseVersion(
                        dbContext,
                        &currentVersion);
        BAIL_ON_SAMDB_ERROR(error);

        if (currentVersion < SAM_DB_SCHEMA_VERSION)
        {
            SAMDB_LOG_WARNING("Old database version (%u) detected. "
                              "Starting upgrade.",
                              currentVersion);

            error = SamDbBackupDatabase(
                            databasePath,
                            &backupDatabasePath);
            BAIL_ON_SAMDB_ERROR(error);

            SAMDB_LOG_WARNING("Original database file copied to %s",
                              backupDatabasePath);

            error = SamDbUpgradeDatabase(
                            dbContext,
                            currentVersion);
            BAIL_ON_SAMDB_ERROR(error);
        }
        else if (currentVersion > SAM_DB_SCHEMA_VERSION)
        {
            error = ERROR_NOT_SUPPORTED;

            SAMDB_LOG_ERROR("Database version (%u) is higher than supported (%u)",
                            currentVersion,
                            SAM_DB_SCHEMA_VERSION);
            goto cleanup;
        }
    }
    else
    {
        mode_t othersMode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;
        mode_t ownerMode = S_IRWXU;

        //
        // Make sure the necessary directory exists
        // before creating database
        //
        error = LsaCheckDirectoryExists(
                        databaseDirPath,
                        &exists);
        BAIL_ON_SAMDB_ERROR(error);

        if (!exists)
        {

            // Allow go+rx to the base folder
            error = LsaCreateDirectory(
                            databaseDirPath,
                            othersMode);
            BAIL_ON_SAMDB_ERROR(error);

            // restrict access to u+rwx to the db folder
            error = LsaChangeOwnerAndPermissions(
                            databaseDirPath,
                            0,
                            0,
                            ownerMode);
            BAIL_ON_SAMDB_ERROR(error);
        }

        error = LsaRemoveFile(databaseTempPath);
        if (error == ERROR_FILE_NOT_FOUND)
        {
            error = ERROR_SUCCESS;
        }
        BAIL_ON_SAMDB_ERROR(error);

        //
        // We don't call SamDbAcquireContext because we would have
        // ended up calling it recursively
        //
        error = sqlite3_open(
                        databaseTempPath,
                        &dbContext->DbHandle);
        BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, dbContext->DbHandle);

        error = SamDbCreateSchema(dbContext);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbInitConfig(dbContext);
        BAIL_ON_SAMDB_ERROR(error);

        error = SamDbProvisionObjects(dbContext);
        BAIL_ON_SAMDB_ERROR(error);

        error = LsaChangeOwnerAndPermissions(
                        databaseTempPath,
                        0,
                        0,
                        ownerMode);
        BAIL_ON_SAMDB_ERROR(error);

        if ((dirFd = open(databaseDirPath, O_RDONLY)) < 0)
        {
            error = LwErrnoToWin32Error(errno);
            BAIL_ON_SAMDB_ERROR(error);
        }

        sqlite3_close(dbContext->DbHandle);
        dbContext->DbHandle = NULL;

        error = LsaMoveFile(databaseTempPath, databasePath);
        BAIL_ON_SAMDB_ERROR(error);

        if (fsync(dirFd) < 0)
        {
            LSA_LOG_WARNING("Could not fsync database directory: %s (%i)", LSA_SAFE_LOG_STRING(LwErrnoToName(errno)), errno);
        }

        error = sqlite3_open(
            databasePath,
            &dbContext->DbHandle);
        BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, dbContext->DbHandle);
    }

cleanup:

    if (dirFd >= 0)
    {
        close(dirFd);
    }

    if (error)
    {
        if (dbContext)
        {
            if (dbContext->DbHandle)
            {
                sqlite3_close(dbContext->DbHandle);
            }

            LW_SAFE_FREE_MEMORY(dbContext);
        }
    }

    LW_SAFE_FREE_MEMORY(backupDatabasePath);

    *pDbContext = dbContext;

    return error;
}


DWORD
SamDbInitGlobals(
    PSAMDB_GLOBALS pGlobals
    )
{
    DWORD error = ERROR_SUCCESS;

    error = pthread_mutex_init(&pGlobals->Mutex, NULL);
    if (error)
    {
        error = LwErrnoToWin32Error(error);
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = pthread_rwlock_init(&pGlobals->RwLock, NULL);
    if (error)
    {
        error = LwErrnoToWin32Error(error);
        BAIL_ON_SAMDB_ERROR(error);
    }

    pGlobals->ReferenceCount   = 1;
    pGlobals->pRwLock          = &pGlobals->RwLock;
    pGlobals->pMutex           = &pGlobals->Mutex;
    pGlobals->DbContextList    = NULL;
    pGlobals->NumDbContexts    = 0;
    pGlobals->MaxNumDbContexts = SAM_DB_CONTEXT_POOL_MAX_ENTRIES;

    pGlobals->LocalDomainSid   = NULL;

    error = SamDbInitBuiltinMemberships(
                     &pGlobals->BuiltinMemberships,
                     &pGlobals->BuiltinMembershipsCount);
    BAIL_ON_SAMDB_ERROR(error);

    LwListInit(&pGlobals->NotifyCallbacks);

cleanup:
    return error;
}


static
DWORD
SamDbInitBuiltinMemberships(
    PSAMDB_GROUP_MEMBERSHIP* Memberships,
    PDWORD MembershipsCount
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_GROUP_MEMBERSHIP memberships = NULL;
    DWORD count = 0;
    DWORD membershipIndex = 0;
    PSID builtinAdministratorsSid = NULL;
    DWORD administratorRid = 0;

    error = LwAllocateWellKnownSid(
                      WinBuiltinAdministratorsSid,
                      NULL,
                      &builtinAdministratorsSid,
                      NULL);
    BAIL_ON_SAMDB_ERROR(error);

    administratorRid = DOMAIN_USER_RID_ADMIN;

    count++;

    error = LwAllocateMemory(
                      sizeof(memberships[0]) * count,
                      OUT_PPVOID(&memberships));
    BAIL_ON_SAMDB_ERROR(error);

    memberships[membershipIndex].LocalGroupSid = builtinAdministratorsSid;
    memberships[membershipIndex].MemberRid     = administratorRid;

    builtinAdministratorsSid = NULL;

cleanup:
    if (error)
    {
        SamDbFreeBuiltinMemberships(memberships, count);

        memberships = NULL;
        count       = 0;
    }

    LW_SAFE_FREE_MEMORY(builtinAdministratorsSid);

    *Memberships      = memberships;
    *MembershipsCount = count;

    return error;
}


VOID
SamDbCleanupLibrary(
    VOID
    )
{
    InterlockedDecrement(&pgSamDbGlobals->ReferenceCount);

    if (pgSamDbGlobals->ReferenceCount > 0)
    {
        return;
    }

    SamDbCleanupLibraryInternal();
}


VOID
SamDbCleanupLibraryInternal(
    VOID
    )
{
    BOOLEAN bInLock = FALSE;

    SAMDB_LOCK_MUTEX(bInLock, pgSamDbGlobals->pMutex);

    if (pgSamDbGlobals->NumDbContexts)
    {
        SamDbFreeContextList();
    }

    SAMDB_UNLOCK_MUTEX(bInLock, pgSamDbGlobals->pMutex);

    if (pgSamDbGlobals->pMutex)
    {
        pthread_mutex_destroy(&pgSamDbGlobals->Mutex);
        pgSamDbGlobals->pMutex = NULL;
    }

    SAMDB_EXCLUSIVE_LOCK_RWLOCK(bInLock, pgSamDbGlobals->pRwLock);

    SamDbFreeCallbackRegistrations();

    LW_SAFE_FREE_MEMORY(pgSamDbGlobals->LocalDomainSid);

    SamDbFreeBuiltinMemberships(
                    pgSamDbGlobals->BuiltinMemberships,
                    pgSamDbGlobals->BuiltinMembershipsCount);

    SAMDB_UNLOCK_RWLOCK(bInLock, pgSamDbGlobals->pRwLock);

    if (pgSamDbGlobals->pRwLock)
    {
        pthread_rwlock_destroy(&pgSamDbGlobals->RwLock);
        pgSamDbGlobals->pRwLock = NULL;
    }
}


static
VOID
SamDbFreeBuiltinMemberships(
    PSAMDB_GROUP_MEMBERSHIP Memberships,
    DWORD MembershipsCount
    )
{
    DWORD membershipIndex = 0;

    for (membershipIndex = 0;
         membershipIndex < MembershipsCount;
         membershipIndex++)
    {
        LW_SAFE_FREE_MEMORY(Memberships[membershipIndex].LocalGroupSid);
    }

    LW_SAFE_FREE_MEMORY(Memberships);
}


static
DWORD
SamDbCreateSchema(
    PSAMDB_CONTEXT DbContext
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR errorMessage = NULL;
    PCSTR createConfigTableStatement = SAMDB_QUERY_CREATE_CONFIG_TABLE;
    PSTR createObjectsTableStatement = NULL;
    PSTR createMembersTableStatement = NULL;

    //
    // Create configuration table (samdbconfig)
    //
    SAMDB_LOG_DEBUG("Statement used to create config table:\n%s",
                    createConfigTableStatement);

    error = sqlite3_exec(
                    DbContext->DbHandle,
                    createConfigTableStatement,
                    NULL,
                    NULL,
                    &errorMessage);
    if (error)
    {
        SAMDB_LOG_VERBOSE("Error when executing SQL statement:\n%s",
                          createConfigTableStatement);
        BAIL_ON_SAMDB_SQLITE_ERROR(error, errorMessage);
    }

    //
    // Create objects table (samdbobjects)
    //
    error = LwAllocateStringPrintf(
                    &createObjectsTableStatement,
                    SAMDB_QUERY_CREATE_OBJECTS_TABLE,
                    SAM_DB_OBJECTS_TABLE);
    BAIL_ON_SAMDB_ERROR(error);

    SAMDB_LOG_DEBUG("Statement used to create objects table:\n%s",
                    createObjectsTableStatement);

    error = sqlite3_exec(
                    DbContext->DbHandle,
                    createObjectsTableStatement,
                    NULL,
                    NULL,
                    &errorMessage);
    if (error)
    {
        SAMDB_LOG_VERBOSE("Error when executing SQL statement:\n%s",
                          createObjectsTableStatement);
        BAIL_ON_SAMDB_SQLITE_ERROR(error, errorMessage);
    }

    //
    // Create local group membership table (samdbmembers)
    //
    error = LwAllocateStringPrintf(
                    &createMembersTableStatement,
                    SAMDB_QUERY_CREATE_MEMBERS_TABLE,
                    SAM_DB_MEMBERS_TABLE);
    BAIL_ON_SAMDB_ERROR(error);

    SAMDB_LOG_DEBUG("Statement used to create members table:\n%s",
                    createMembersTableStatement);

    error = sqlite3_exec(
                    DbContext->DbHandle,
                    createMembersTableStatement,
                    NULL,
                    NULL,
                    &errorMessage);
    if (error)
    {
        SAMDB_LOG_VERBOSE("Error when executing SQL statement:\n%s",
                          createMembersTableStatement);
        BAIL_ON_SAMDB_SQLITE_ERROR(error, errorMessage);
    }

cleanup:
    if (error)
    {
        if (errorMessage)
        {
            sqlite3_free(errorMessage);
        }
    }

    LW_SAFE_FREE_MEMORY(createObjectsTableStatement);
    LW_SAFE_FREE_MEMORY(createMembersTableStatement);

    return error;
}


static
DWORD
SamDbInitConfig(
    PSAMDB_CONTEXT DbContext
    )
{
    DWORD error = 0;
    PCSTR sqlStatementTemplate =
            "INSERT INTO " SAM_DB_CONFIG_TABLE \
            " (UIDCounter,"     \
            "  GIDCounter,"     \
            "  RIDCounter,"     \
            "  Version"         \
            " )"                \
            " VALUES ("         \
            "  %u,"             \
            "  %u,"             \
            "  %u,"             \
            "  %u"              \
            " )";
    PSTR sqlStatement = NULL;
    PSTR errorMessage = NULL;

    sqlStatement = sqlite3_mprintf(
                       sqlStatementTemplate,
                       SAM_DB_MIN_UID,
                       SAM_DB_MIN_GID,
                       SAM_DB_MIN_RID,
                       SAM_DB_SCHEMA_VERSION);
    if (sqlStatement == NULL)
    {
        error = ERROR_OUTOFMEMORY;
        BAIL_ON_SAMDB_ERROR(error);
    }

    error = sqlite3_exec(
                    DbContext->DbHandle,
                    sqlStatement,
                    NULL,
                    NULL,
                    &errorMessage);
    if (error)
    {
        SAMDB_LOG_VERBOSE("Error when executing SQL statement:\n%s",
                          sqlStatement);
        BAIL_ON_SAMDB_SQLITE_ERROR(error, errorMessage);
    }

cleanup:
    if (error)
    {
        if (errorMessage)
        {
            sqlite3_free(errorMessage);
        }
    }

    if (sqlStatement)
    {
        sqlite3_free(sqlStatement);
    }

    return error;
}


static
DWORD
SamDbProvisionObjects(
    PSAMDB_CONTEXT DbContext
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR domainName = NULL;
    PWSTR pwszDomainName = NULL;
    PSID machineSid = NULL;

    error = SamDbCreateNetBiosName(
                    &domainName);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwMbsToWc16s(
                    domainName,
                    &pwszDomainName);
    BAIL_ON_SAMDB_ERROR(error);

    if (!SamDbIsValidNetBiosDomainName(pwszDomainName))
    {
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Generated local domain name %s is invalid",
                        LSA_SAFE_LOG_STRING(domainName));
        goto cleanup;
    }

    error = SamDbProvisionLocalDomain(
                    DbContext,
                    domainName,
                    &machineSid);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbProvisionBuiltinDomain(
                    DbContext);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbSetupLocalGroupMemberships(
                    DbContext,
                    machineSid);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    LW_SAFE_FREE_MEMORY(domainName);
    LW_SAFE_FREE_MEMORY(pwszDomainName);
    LW_SAFE_FREE_MEMORY(machineSid);

    return error;
}


static
DWORD
SamDbCreateNetBiosName(
    PSTR* pNetBiosName
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR hostnameUc = NULL;      // uppercased hostname
    size_t hostnameLen = 0;
    PSTR hostnameLc = NULL;      // lowercased hostname
    DWORD hostnameHash = 0;
    PSTR hostnameHashStr = NULL;
    size_t hashStrLen = 0;
    const size_t maxNetBiosNameLen = 15;
    PSTR netBiosName = NULL;

    error = LsaDnsGetHostInfo(&hostnameUc);
    BAIL_ON_SAMDB_ERROR(error);

    LwStrToUpper(hostnameUc);
    hostnameLen = strlen(hostnameUc);

    error = LwAllocateMemory(
                  sizeof(netBiosName[0]) * maxNetBiosNameLen,
                  OUT_PPVOID(&netBiosName));
    BAIL_ON_SAMDB_ERROR(error);

    if (hostnameLen <= maxNetBiosNameLen)
    {
        strncpy(netBiosName, hostnameUc, hostnameLen);
    }
    else
    {
        //
        // The hostname is too long for NetBIOS max size
        // so format the machine domain name based on the hostname
        // and its hash (separated with '-').
        //
        error = LwAllocateString(
                       hostnameUc,
                       &hostnameLc);
        BAIL_ON_SAMDB_ERROR(error);

        LwStrToLower(hostnameLc);

        error = LsaStrHash(hostnameLc, &hostnameHash);
        BAIL_ON_SAMDB_ERROR(error);

        error = LsaHashToStr(hostnameHash, &hostnameHashStr);
        BAIL_ON_SAMDB_ERROR(error);

        hashStrLen = strlen(hostnameHashStr);

        strncpy(netBiosName,
                hostnameUc,
                maxNetBiosNameLen - hashStrLen - 1);
        netBiosName[maxNetBiosNameLen - hashStrLen - 2] = '-';
        strncpy(&netBiosName[maxNetBiosNameLen - hashStrLen - 1],
                hostnameHashStr,
                hashStrLen);
    }

cleanup:
    if (error)
    {
        LW_SAFE_FREE_MEMORY(netBiosName);
    }

    LW_SAFE_FREE_MEMORY(hostnameUc);
    LW_SAFE_FREE_MEMORY(hostnameLc);

    *pNetBiosName = netBiosName;

    return error;
}


static
DWORD
SamDbProvisionBuiltinDomain(
    PSAMDB_CONTEXT DbContext
    )
{
    DWORD error = ERROR_SUCCESS;
    PWSTR builtinDomainName = NULL;
    PSID builtinDomainSid = NULL;
    DWORD accountIndex = 0;
    SAMDB_UNIX_GROUP_INFO unixInfo = {0};
    SAMDB_OBJECT_INFO accountObject = {0};
    PSID accountSid = NULL;

    SAMDB_OBJECT_INFO builtinDomain = {
        .Type = SAMDB_OBJECT_TYPE_DOMAIN,
        .Sid  = NULL,
        .Name = NULL
    };

    SAMDB_DOMAIN_INFO builtinDomainInfo = {
        .MinPasswordLength = SAMDB_MIN_PASSWORD_LENGTH,
        .PasswordHistoryLength = 0,
        .MinPasswordAge = (LONG64)(SAMDB_MIN_PWD_AGE * SAMBD_100NS_CYCLES_IN_SECOND),
        .MaxPasswordAge = (LONG64)(SAMDB_MAX_PWD_AGE * SAMBD_100NS_CYCLES_IN_SECOND),
        .PasswordChangeTime = (LONG64)(SAMDB_PWD_PROMPT_TIME * SAMBD_100NS_CYCLES_IN_SECOND),
    };

    struct _BUILTIN_ACCOUNT {
        SAMDB_OBJECT_TYPE Type;
        PCSTR Name;
        DWORD SidType;
        DWORD Gid;
        PCSTR Description;
        PCSTR DomainName;
        PCSTR NetBiosDomain;
    } builtinAccounts[] = {
        {
            .Type           = SAMDB_OBJECT_TYPE_LOCAL_GROUP,
            .Name           = "Administrators",
            .SidType        = WinBuiltinAdministratorsSid,
            .Gid            = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_ADMINS),
            .Description    = "Administrators have complete and unrestricted "
                              "access to the computer/domain",
            .DomainName     = "BUILTIN",
            .NetBiosDomain  = "BUILTIN",
        },
        {
            .Type           = SAMDB_OBJECT_TYPE_LOCAL_GROUP,
            .Name           = "Users",
            .SidType        = WinBuiltinUsersSid,
            .Gid            = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_USERS),
            .Description    = "Users are prevented from making accidental "
                              "or intentional system-wide changes. Thus, "
                              "users can run certified applications, but not "
                              "most legacy applications",
            .DomainName     = "BUILTIN",
            .NetBiosDomain  = "BUILTIN",
        },
        {
            .Type           = SAMDB_OBJECT_TYPE_LOCAL_GROUP,
            .Name           = "Guests",
            .SidType        = WinBuiltinGuestsSid,
            .Gid            = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_GUESTS),
            .Description    = "Guests have the same access as members of the "
                              "Users group by default, except for the Guest "
                              "account which is further restricted",
            .DomainName     = "BUILTIN",
            .NetBiosDomain  = "BUILTIN",
        },
        {
            .Type           = SAMDB_OBJECT_TYPE_LOCAL_GROUP,
            .Name           = "Backup Operators",
            .SidType        = WinBuiltinBackupOperatorsSid,
            .Gid            = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_BACKUP_OPS),
            .Description    = "Backup Operators have the privilege to do "
                              "backup/restore operations",
            .DomainName     = "BUILTIN",
            .NetBiosDomain  = "BUILTIN",
        },
    };

    //
    // Create BUILTIN domain object
    //
    error = LwAllocateWellKnownSid(
                     WinBuiltinDomainSid,
                     NULL,
                     &builtinDomainSid,
                     NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwMbsToWc16s(
                     SAMDB_BUILTIN_DOMAIN_NAME,
                     &builtinDomainName);
    BAIL_ON_SAMDB_ERROR(error);

    builtinDomain.Sid = builtinDomainSid;
    builtinDomain.Name = builtinDomainName;
    builtinDomain.Info.Domain = builtinDomainInfo;

    error = SamDbAddObjectInternal(
                     DbContext,
                     &builtinDomain,
                     builtinDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    for (accountIndex = 0;
         accountIndex < sizeof(builtinAccounts)/sizeof(builtinAccounts[0]);
         accountIndex++)
    {
        struct _BUILTIN_ACCOUNT* account = &builtinAccounts[accountIndex];

        accountObject.Type = account->Type;

        error = LwMbsToWc16s(
                       account->Name,
                       &accountObject.Name);
        BAIL_ON_SAMDB_ERROR(error);

        error = LwAllocateWellKnownSid(
                       account->SidType,
                       NULL,
                       &accountSid,
                       NULL);
        BAIL_ON_SAMDB_ERROR(error);

        error = LwMbsToWc16s(
                       account->Description,
                       &accountObject.Info.LocalGroup.Description);
        BAIL_ON_SAMDB_ERROR(error);

        unixInfo.Gid = account->Gid;
        accountObject.Info.LocalGroup.UnixInfo = &unixInfo;

        error = SamDbAddObjectInternal(
                       DbContext,
                       &accountObject,
                       accountSid);
        BAIL_ON_SAMDB_ERROR(error);

        LW_SAFE_FREE_MEMORY(accountObject.Name);
        LW_SAFE_FREE_MEMORY(accountObject.Info.LocalGroup.Description);
        LW_SAFE_FREE_MEMORY(accountSid);
    }

cleanup:
    LW_SAFE_FREE_MEMORY(builtinDomainSid);
    LW_SAFE_FREE_MEMORY(builtinDomainName);
    LW_SAFE_FREE_MEMORY(accountObject.Name);
    LW_SAFE_FREE_MEMORY(accountObject.Info.LocalGroup.Description);
    LW_SAFE_FREE_MEMORY(accountSid);

    return error;
}


static
DWORD
SamDbProvisionLocalDomain(
    PSAMDB_CONTEXT DbContext,
    PSTR DomainName,
    PSID* pDomainSid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR localDomainName = NULL;
    PSID localDomainSid = NULL;
    DWORD accountIndex = 0;
    SAMDB_UNIX_GROUP_INFO unixGroupInfo = {0};
    SAMDB_UNIX_USER_INFO unixUserInfo = {0};
    SAMDB_OBJECT_INFO accountObject = {0};
    SAMDB_LOCAL_GROUP_INFO localGroupInfo = {0};
    SAMDB_USER_INFO userInfo = {0};
    DWORD sidLength = 0;
    PSID accountSid = NULL;

    SAMDB_OBJECT_INFO localDomain = {
        .Type = SAMDB_OBJECT_TYPE_DOMAIN,
        .Sid  = NULL,
        .Name = NULL
    };

    SAMDB_DOMAIN_INFO localDomainInfo = {
        .MinPasswordLength = SAMDB_MIN_PASSWORD_LENGTH,
        .PasswordHistoryLength = 0,
        .MinPasswordAge = (LONG64)(SAMDB_MIN_PWD_AGE * SAMBD_100NS_CYCLES_IN_SECOND),
        .MaxPasswordAge = (LONG64)(SAMDB_MAX_PWD_AGE * SAMBD_100NS_CYCLES_IN_SECOND),
        .PasswordChangeTime = (LONG64)(SAMDB_PWD_PROMPT_TIME * SAMBD_100NS_CYCLES_IN_SECOND),
    };

    struct _LOCAL_GROUP {
        SAMDB_OBJECT_TYPE Type;
        PCSTR Name;
        DWORD Rid;
        DWORD Gid;
        PCSTR Description;
        PCSTR DomainName;
        PCSTR NetBiosDomain;
    } localGroups[] = {
        {
            .Type           = SAMDB_OBJECT_TYPE_LOCAL_GROUP,
            .Name           = "Likewise Users",
            .Rid            = DOMAIN_ALIAS_RID_LW_USERS,
            .Gid            = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_LW_USERS),
            .Description    = "Built-in group of Likewise Users",
            .DomainName     = DomainName,
            .NetBiosDomain  = DomainName,
        }
    };

    struct _LOCAL_USER {
        SAMDB_OBJECT_TYPE Type;
        PCSTR Name;
        DWORD Rid;
        DWORD Uid;
        DWORD PrimaryGroupRid;
        PCSTR Description;
        PCSTR DomainName;
        PCSTR NetBiosDomain;
        PCSTR Shell;
        PCSTR HomeDirectory;
        SAMDB_UF_FLAGS Flags;
    } localUsers[] = {
        {
            .Type            = SAMDB_OBJECT_TYPE_USER,
            .Name            = "Administrator",
            .Rid             = DOMAIN_USER_RID_ADMIN,
            .Uid             = SAM_DB_UID_FROM_RID(DOMAIN_USER_RID_ADMIN),
            .PrimaryGroupRid = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_LW_USERS),
            .Description     = "Built-in account for administering the "
                               "computer/domain",
            .DomainName      = DomainName,
            .NetBiosDomain   = DomainName,
            .Shell           = SAM_DB_DEFAULT_ADMINISTRATOR_SHELL,
            .HomeDirectory   = SAM_DB_DEFAULT_ADMINISTRATOR_HOMEDIR,
            .Flags           = SAMDB_UF_FLAG_NORMAL_ACCOUNT |
                               SAMDB_UF_FLAG_ACCOUNTDISABLE,
        },
        {
            .Type            = SAMDB_OBJECT_TYPE_USER,
            .Name            = "Guest",
            .Rid             = DOMAIN_USER_RID_GUEST,
            .Uid             = SAM_DB_UID_FROM_RID(DOMAIN_USER_RID_GUEST),
            .PrimaryGroupRid = SAM_DB_GID_FROM_RID(DOMAIN_ALIAS_RID_LW_USERS),
            .Description     = "Built-in account for guest access to the "
                               "computer/domain",
            .DomainName      = DomainName,
            .NetBiosDomain   = DomainName,
            .Shell           = SAM_DB_DEFAULT_GUEST_SHELL,
            .HomeDirectory   = SAM_DB_DEFAULT_GUEST_HOMEDIR,
            .Flags           = SAMDB_UF_FLAG_NORMAL_ACCOUNT |
                               SAMDB_UF_FLAG_ACCOUNTDISABLE,
        }
    };

    //
    // Create local domain object
    //
    error = SamDbGenerateMachineSid(&localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwMbsToWc16s(
                     DomainName,
                     &localDomainName);
    BAIL_ON_SAMDB_ERROR(error);

    localDomain.Sid = localDomainSid;
    localDomain.Name = localDomainName;
    localDomain.Info.Domain = localDomainInfo;

    //
    // Allocate SID buffer for local accounts (it takes the same
    // space for each account)
    //
    error = SamDbAddObjectInternal(
                     DbContext,
                     &localDomain,
                     localDomainSid);
    BAIL_ON_SAMDB_ERROR(error);

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

    //
    // Create local groups
    //
    for (accountIndex = 0;
         accountIndex < sizeof(localGroups)/sizeof(localGroups[0]);
         accountIndex++)
    {
        struct _LOCAL_GROUP* localGroup = &localGroups[accountIndex];

        accountObject.Type = localGroup->Type;

        error = LwMbsToWc16s(
                       localGroup->Name,
                       &accountObject.Name);
        BAIL_ON_SAMDB_ERROR(error);

        ntStatus = RtlCopySid(
                       sidLength,
                       accountSid,
                       localDomainSid);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = RtlAppendRidSid(
                       sidLength,
                       accountSid,
                       localGroup->Rid);
        BAIL_ON_NT_STATUS(ntStatus);

        error = LwMbsToWc16s(
                       localGroup->Description,
                       &localGroupInfo.Description);
        BAIL_ON_SAMDB_ERROR(error);

        unixGroupInfo.Gid = localGroup->Gid;
        localGroupInfo.UnixInfo = &unixGroupInfo;

        accountObject.Info.LocalGroup = localGroupInfo;

        error = SamDbAddObjectInternal(
                       DbContext,
                       &accountObject,
                       accountSid);
        BAIL_ON_SAMDB_ERROR(error);

        LW_SAFE_FREE_MEMORY(accountObject.Name);
        LW_SAFE_FREE_MEMORY(localGroupInfo.Description);
    }

    //
    // Create local users
    //
    for (accountIndex = 0;
         accountIndex < sizeof(localUsers)/sizeof(localUsers[0]);
         accountIndex++)
    {
        struct _LOCAL_USER* localUser = &localUsers[accountIndex];

        accountObject.Type = localUsers->Type;

        error = LwMbsToWc16s(
                       localUser->Name,
                       &accountObject.Name);
        BAIL_ON_SAMDB_ERROR(error);

        ntStatus = RtlCopySid(
                       sidLength,
                       accountSid,
                       localDomainSid);
        BAIL_ON_NT_STATUS(ntStatus);

        ntStatus = RtlAppendRidSid(
                       sidLength,
                       accountSid,
                       localUser->Rid);
        BAIL_ON_NT_STATUS(ntStatus);

        error = LwMbsToWc16s(
                       localUser->Description,
                       &userInfo.Description);
        BAIL_ON_SAMDB_ERROR(error);

        error = LwMbsToWc16s(
                       localUser->HomeDirectory,
                       &userInfo.HomeDirectory);
        BAIL_ON_SAMDB_ERROR(error);

        userInfo.UserAccountControl = localUser->Flags;

        unixUserInfo.Uid = localUser->Uid;

        unixUserInfo.HomeDirectory = NULL;  // means it is the same as windows homedir

        error = LwMbsToWc16s(
                       localUser->Shell,
                       &unixUserInfo.Shell);
        BAIL_ON_SAMDB_ERROR(error);

        userInfo.UnixInfo = &unixUserInfo;

        accountObject.Info.User = userInfo;

        error = SamDbAddObjectInternal(
                       DbContext,
                       &accountObject,
                       accountSid);
        BAIL_ON_SAMDB_ERROR(error);

        LW_SAFE_FREE_MEMORY(accountObject.Name);
        LW_SAFE_FREE_MEMORY(userInfo.Description);
        LW_SAFE_FREE_MEMORY(userInfo.HomeDirectory);
        LW_SAFE_FREE_MEMORY(unixUserInfo.Shell);
    }

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

    LW_SAFE_FREE_MEMORY(localDomainName);
    LW_SAFE_FREE_MEMORY(accountObject.Name);
    LW_SAFE_FREE_MEMORY(localGroupInfo.Description);
    LW_SAFE_FREE_MEMORY(userInfo.Description);
    LW_SAFE_FREE_MEMORY(userInfo.HomeDirectory);
    LW_SAFE_FREE_MEMORY(unixUserInfo.Shell);
    LW_SAFE_FREE_MEMORY(accountSid);

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

    *pDomainSid = localDomainSid;

    return error;
}


static
DWORD
SamDbGenerateMachineSid(
    PSID* pSid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    uuid_t guid = {0};
    ULONG subAuth[3] = {0};
    PBYTE pSubAuth = NULL;
    DWORD subAuthorityCount = 4;
    DWORD sidLength = 0;
    SID_IDENTIFIER_AUTHORITY AuthId = { SECURITY_NT_AUTHORITY };
    PSID machineSid = NULL;

    uuid_generate(guid);

    pSubAuth = (PBYTE)guid;
    subAuth[0] = *((PULONG)pSubAuth);
    pSubAuth += sizeof(ULONG);
    subAuth[1] = *((PULONG)pSubAuth);
    pSubAuth += sizeof(ULONG);
    subAuth[2] = *((PULONG)pSubAuth);

    sidLength = RtlLengthRequiredSid(subAuthorityCount);

    error = LwAllocateMemory(
                     sidLength,
                     OUT_PPVOID(&machineSid));
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlInitializeSid(
                     machineSid,
                     &AuthId,
                     subAuthorityCount);
    if (ntStatus)
    {
        SAMDB_LOG_ERROR("Error when initialising machine SID: %s (0x%08x)",
                        LSA_SAFE_LOG_STRING(LwNtStatusToName(ntStatus)),
                        ntStatus);
        goto cleanup;
    }

    machineSid->SubAuthority[0] = SECURITY_NT_NON_UNIQUE;
    machineSid->SubAuthority[1] = subAuth[0];
    machineSid->SubAuthority[2] = subAuth[1];
    machineSid->SubAuthority[3] = subAuth[2];

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

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

    *pSid = machineSid;

    return error;
}


static
DWORD
SamDbSetupLocalGroupMemberships(
    PSAMDB_CONTEXT DbContext,
    PSID localDomainSid
    )
{
    DWORD error = ERROR_SUCCESS;
    PSID builtinAdministratorsSid = NULL;
    PSID builtinUsersSid = NULL;
    PSID builtinGuestsSid = NULL;
    PSID localAdministratorSid = NULL;
    PSID localGuestSid = NULL;
    PSID likewiseUsersSid = NULL;
    DWORD localGroupIndex = 0;
    DWORD memberIndex = 0;
    PSID* addMembers = NULL;

    PSID* builtinAdministratorsMembers[] = {
        &localAdministratorSid
    };

    PSID* builtinUsersMembers[] = {
        &localAdministratorSid
    };

    PSID* builtinGuestsMembers[] = {
        &localGuestSid
    };

    PSID* likewiseUsersMembers[] = {
        &localAdministratorSid,
        &localGuestSid
    };

    //
    // Create local group memberships information
    //
    struct _LOCAL_GROUP_MEMBERSHIP
    {
        PSID* LocalGroupSid;
        PSID** MembersSid;
        DWORD MembersCount;
    } memberships[] = {
        {
            &builtinAdministratorsSid,
            &builtinAdministratorsMembers[0],
            (sizeof(builtinAdministratorsMembers)
             /sizeof(builtinAdministratorsMembers[0]))
        },
        {
            &builtinUsersSid,
            &builtinUsersMembers[0],
            (sizeof(builtinUsersMembers)
             /sizeof(builtinUsersMembers[0]))
        },
        {
            &builtinGuestsSid,
            &builtinGuestsMembers[0],
            (sizeof(builtinGuestsMembers)
             /sizeof(builtinGuestsMembers[0]))
        },
        {
            &likewiseUsersSid,
            &likewiseUsersMembers[0],
            (sizeof(likewiseUsersMembers)
             /sizeof(likewiseUsersMembers[0]))
        }
    };

    //
    // Allocate well-known SIDs
    //
    error = LwAllocateWellKnownSid(
                    WinBuiltinAdministratorsSid,
                    NULL,
                    &builtinAdministratorsSid,
                    NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwAllocateWellKnownSid(
                    WinBuiltinUsersSid,
                    NULL,
                    &builtinUsersSid,
                    NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwAllocateWellKnownSid(
                    WinBuiltinGuestsSid,
                    NULL,
                    &builtinGuestsSid,
                    NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwAllocateWellKnownSid(
                    WinAccountAdministratorSid,
                    localDomainSid,
                    &localAdministratorSid,
                    NULL);
    BAIL_ON_SAMDB_ERROR(error);

    error = LwAllocateWellKnownSid(
                    WinAccountGuestSid,
                    localDomainSid,
                    &localGuestSid,
                    NULL);
    BAIL_ON_SAMDB_ERROR(error);

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

    for (localGroupIndex = 0;
         localGroupIndex < sizeof(memberships)/sizeof(memberships[0]);
         localGroupIndex++)
    {
        DWORD membersCount = memberships[localGroupIndex].MembersCount;

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

        for (memberIndex = 0;
             memberIndex < membersCount;
             memberIndex++)
        {
            addMembers[memberIndex] =
                *(memberships[localGroupIndex].MembersSid[memberIndex]);
        }

        error = SamDbAddMembersInternal(
                        DbContext,
                        *memberships[localGroupIndex].LocalGroupSid,
                        membersCount,
                        addMembers);
        BAIL_ON_SAMDB_ERROR(error);

        LW_SAFE_FREE_MEMORY(addMembers);
    }

cleanup:
    LW_SAFE_FREE_MEMORY(addMembers);
    LW_SAFE_FREE_MEMORY(builtinAdministratorsSid);
    LW_SAFE_FREE_MEMORY(builtinUsersSid);
    LW_SAFE_FREE_MEMORY(builtinGuestsSid);
    LW_SAFE_FREE_MEMORY(localAdministratorSid);
    LW_SAFE_FREE_MEMORY(localGuestSid);
    LW_SAFE_FREE_MEMORY(likewiseUsersSid);

    return error;
}


DWORD
SamDbInitializeLibrary(
    VOID
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;

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

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

    return error;
}


static
DWORD
SamDbBackupDatabase(
    PCSTR DatabasePath,
    PSTR* BackupDatabasePath
    )
{
    DWORD error = ERROR_SUCCESS;
    PSTR backupPath = NULL;
    PCSTR backupPathFormat = "%s.orig";

    BAIL_ON_INVALID_POINTER(DatabasePath, error);
    BAIL_ON_INVALID_POINTER(BackupDatabasePath, error);

    error = LwAllocateStringPrintf(
                       &backupPath,
                       backupPathFormat,
                       DatabasePath);
    BAIL_ON_SAMDB_ERROR(error);

    error = LsaCopyFileWithOriginalPerms(
                       DatabasePath,
                       backupPath);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    if (error)
    {
        LW_SAFE_FREE_MEMORY(backupPath);
    }

    *BackupDatabasePath = backupPath;

    return error;
}
