/* -*- 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    2004-2011
 * 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:
 *
 *        accountdb.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Lsa Accounts database
 *
 * Authors: Rafal Szczesniak (rafal@likewise.com)
 */

#include "includes.h"


static
int
LsaSrvSidCompare(
    PCVOID sid1,
    PCVOID sid2
    );

static
size_t
LsaSrvSidHash(
    PCVOID sid
    );

static
VOID
LsaSrvAccountDbEntryFree(
    const LW_HASH_ENTRY *pEntry
    );

static
DWORD
LsaSrvReadAccountEntry(
    IN HANDLE hRegistry,
    IN HKEY hEntry,
    IN PSID AccountSid,
    OUT PLSA_ACCOUNT *ppAccountEntry
    );

static
DWORD
LsaSrvGetPrivilegeValuesFromNames(
    PSTR pszPrivilegeNames,
    DWORD PrivilegeNamesSize,
    DWORD MaxPrivilegeValues,
    PLUID_AND_ATTRIBUTES pPrivilegeValues,
    PDWORD pPrivilegeValuesCount
    );

static
DWORD
LsaSrvSetAccountEntry_inlock(
    IN PLW_HASH_TABLE pAccountsTable,
    IN PSID pAccountSid,
    IN PLSA_ACCOUNT pAccountEntry,
    IN BOOLEAN Overwrite
    );

static
DWORD
LsaSrvSaveAccount_inlock(
    IN PSID pAccountSid,
    IN PWSTR *ppwszPrivilegeNames,
    IN DWORD SystemAccessRights,
    IN PSECURITY_DESCRIPTOR_ABSOLUTE pSecurityDesc,
    IN BOOLEAN Overwrite
    );

static
DWORD
LsaSrvGetPrivilegeNames_inlock(
    IN PLSA_ACCOUNT pAccountEntry,
    OUT PWSTR **ppPrivilegeNames
    );

static
DWORD
LsaSrvDeleteAccount_inlock(
    PSTR pszAccountSid
    );

static
VOID
LsaSrvPrivsUpdateLocalDomainSidSubscription(
    PLW_WORK_ITEM WorkItem,
    PVOID Context
    );


DWORD
LsaSrvCreateAccountsDb(
    IN HANDLE hRegistry,
    IN HKEY hAccountsKey,
    OUT PLW_HASH_TABLE *ppAccounts
    )
{
    DWORD err = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN Locked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PLW_HASH_TABLE pHash = NULL;
    DWORD i = 0;
    DWORD numKeys = 0;
    DWORD maxKeyLen = 0;
    PWSTR pwszAccount = NULL;
    PSTR pszAccount = NULL;
    HKEY hAccount = NULL;
    PSID accountSid = NULL;
    PLSA_ACCOUNT pAccountEntry = NULL;

    LSASRV_PRIVS_WRLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    //
    // Create the database hash table
    //
    err = LwHashCreate(
                 LSA_ACCOUNTS_DB_SIZE,
                 LsaSrvSidCompare,
                 LsaSrvSidHash,
                 LsaSrvAccountDbEntryFree,
                 NULL,
                 &pHash);
    BAIL_ON_LSA_ERROR(err);

    //
    // Prepare for enumerating values under the Privileges key
    //
    err = RegQueryInfoKeyW(
                 hRegistry,
                 hAccountsKey,
                 NULL,
                 NULL,
                 NULL,
                 &numKeys,
                 &maxKeyLen,
                 NULL,
                 NULL,
                 NULL,
                 NULL,
                 NULL,
                 NULL);
    BAIL_ON_LSA_ERROR(err);

    LSA_LOG_VERBOSE("Loading lsa accounts database (%u accounts, "
                    "longest account name = %u)",
                    numKeys, maxKeyLen);

    err = LwAllocateMemory(
                 sizeof(WCHAR) * (maxKeyLen + 1),
                 OUT_PPVOID(&pwszAccount));
    BAIL_ON_LSA_ERROR(err);

    for (i = 0; i < numKeys; i++)
    {
        DWORD accountKeyLen = sizeof(WCHAR) * maxKeyLen;

        err = RegEnumKeyExW(
                     hRegistry,
                     hAccountsKey,
                     i,
                     pwszAccount,
                     &accountKeyLen,
                     NULL,
                     NULL,
                     NULL,
                     NULL);
        BAIL_ON_LSA_ERROR(err);

        err = LwWc16sToMbs(
                     pwszAccount,
                     &pszAccount);
        BAIL_ON_LSA_ERROR(err);

        LSA_LOG_VERBOSE("Loading account %s",
                        LSA_SAFE_LOG_STRING(pszAccount));

        //
        // Each account is a SID
        //
        ntStatus = RtlAllocateSidFromWC16String(
                     &accountSid,
                     pwszAccount);
        if (ntStatus == STATUS_INVALID_SID)
        {
            err = ERROR_INVALID_DATA;
            BAIL_ON_LSA_ERROR(err);
        }
        else if (ntStatus != STATUS_SUCCESS)
        {
            BAIL_ON_NT_STATUS(ntStatus);
        }

        err = RegOpenKeyExW(
                     hRegistry,
                     hAccountsKey,
                     pwszAccount,
                     0,
                     KEY_READ,
                     &hAccount);
        BAIL_ON_LSA_ERROR(err);

        err = LsaSrvReadAccountEntry(
                     hRegistry,
                     hAccount,
                     accountSid,
                     &pAccountEntry);
        BAIL_ON_LSA_ERROR(err);

        err = LsaSrvSetAccountEntry_inlock(
                     pHash,
                     accountSid,
                     pAccountEntry,
                     FALSE);
        BAIL_ON_LSA_ERROR(err);

        accountSid    = NULL;
        pAccountEntry = NULL;

        //
        // Close the account subkey and cleanup the
        // name and the handle
        //
        err = RegCloseKey(
                     hRegistry,
                     hAccount);
        BAIL_ON_LSA_ERROR(err);

        LW_SAFE_FREE_MEMORY(pszAccount);

        memset(pwszAccount, 0, sizeof(WCHAR) * maxKeyLen);
        hAccount = NULL;
    }

    *ppAccounts = pHash;

error:
    if (err || ntStatus)
    {
        if (pAccountEntry)
        {
            LsaSrvPrivsReleaseAccountEntry(pAccountEntry);
        }

        LW_SAFE_FREE_MEMORY(accountSid);

        if (pHash)
        {
            LwHashSafeFree(&pHash);
        }

        *ppAccounts = NULL;
    }

    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    LW_SAFE_FREE_MEMORY(pwszAccount);
    LW_SAFE_FREE_MEMORY(pszAccount);

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

    return err;
}


static
int
LsaSrvSidCompare(
    PCVOID sid1,
    PCVOID sid2
    )
{
    const PSID pSid1 = (PSID)sid1;
    const PSID pSid2 = (PSID)sid2;
    DWORD i = 0;
    DWORD subAuthorityCount = LW_MIN(pSid2->SubAuthorityCount,
                                     pSid2->SubAuthorityCount);

    for (i = 0; i < subAuthorityCount; i++)
    {
        if (pSid1->SubAuthority[i] != pSid2->SubAuthority[i])
        {
            return (int)(pSid1->SubAuthority[i] - pSid2->SubAuthority[i]);
        }
    }

    return 0;
}


static
size_t
LsaSrvSidHash(
    PCVOID sid
    )
{
    const PSID pSid = (PSID)sid;
    DWORD i = 0;
    size_t hash = 0;

    for (i = 0; i < pSid->SubAuthorityCount; i++)
    {
        hash += pSid->SubAuthority[i];
    }

    return hash;
}


static
VOID
LsaSrvAccountDbEntryFree(
    const LW_HASH_ENTRY *pEntry
    )
{
    LwFreeMemory(pEntry->pKey);
    LsaSrvPrivsReleaseAccountEntry(pEntry->pValue);
}


VOID
LsaSrvPrivsReleaseAccountEntry(
    PLSA_ACCOUNT pEntry
    )
{
    BOOLEAN Locked = FALSE;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSID pOwnerSid = NULL;
    PSID pGroupSid = NULL;
    PACL pDacl = NULL;
    PACL pSacl = NULL;
    BOOLEAN defaulted = FALSE;
    BOOLEAN present = FALSE;

    InterlockedDecrement(&pEntry->Refcount);
    if (pEntry->Refcount)
    {
        return;
    }

    LSASRV_PRIVS_WRLOCK_RWLOCK(Locked, &pEntry->accountRwLock);

    LW_SAFE_FREE_MEMORY(pEntry->pSid);

    ntStatus = RtlGetOwnerSecurityDescriptor(
                        pEntry->pSecurityDesc,
                        &pOwnerSid,
                        &defaulted);
    if (ntStatus == STATUS_SUCCESS && pOwnerSid)
    {
        LW_SAFE_FREE_MEMORY(pOwnerSid);
    }

    ntStatus = RtlGetGroupSecurityDescriptor(
                        pEntry->pSecurityDesc,
                        &pGroupSid,
                        &defaulted);
    if (ntStatus == STATUS_SUCCESS && pGroupSid)
    {
        LW_SAFE_FREE_MEMORY(pGroupSid);
    }

    ntStatus = RtlGetDaclSecurityDescriptor(
                        pEntry->pSecurityDesc,
                        &present,
                        &pDacl,
                        &defaulted);
    if (ntStatus == STATUS_SUCCESS && pDacl)
    {
        LW_SAFE_FREE_MEMORY(pDacl);
    }

    ntStatus = RtlGetSaclSecurityDescriptor(
                        pEntry->pSecurityDesc,
                        &present,
                        &pSacl,
                        &defaulted);
    if (ntStatus == STATUS_SUCCESS && pSacl)
    {
        LW_SAFE_FREE_MEMORY(pSacl);
    }

    LW_SAFE_FREE_MEMORY(pEntry->pSecurityDesc);

    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pEntry->accountRwLock);
    pthread_rwlock_destroy(&pEntry->accountRwLock);

    LW_SAFE_FREE_MEMORY(pEntry);
}


VOID
LsaSrvPrivsAcquireAccountEntry(
    PLSA_ACCOUNT pEntry
    )
{
    InterlockedIncrement(&pEntry->Refcount);
}


static
DWORD
LsaSrvReadAccountEntry(
    IN HANDLE hRegistry,
    IN HKEY hEntry,
    IN PSID AccountSid,
    OUT PLSA_ACCOUNT *ppAccountEntry
    )
{
    DWORD err = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PSTR privilegesValueName = LSA_ACCOUNT_PRIVILEGES_NAME;
    DWORD privilegesValueSize = 0;
    PSTR privilegesValue = NULL;
    PSTR sysAccountRightsValueName = LSA_ACCOUNT_SYS_ACCESS_RIGHTS_NAME;
    DWORD systemAccess = 0;
    DWORD systemAccessSize = 0;
    PSTR securityDescValueName = LSA_PRIVILEGE_SECURITY_DESC_NAME;
    PSECURITY_DESCRIPTOR_RELATIVE pSecurityDescRelative = NULL;
    DWORD securityDescRelativeSize = 0;
    BOOLEAN defaultSecurityDesc = FALSE;
    PLSA_ACCOUNT pAccountEntry = NULL;
    DWORD accountSidSize = 0;
    PSTR pszAccountSid = NULL;
    PWSTR *pPrivilegeNames = NULL;

    //
    // Get privileges value
    //
    err = RegGetValueA(
                 hRegistry,
                 hEntry,
                 NULL,
                 privilegesValueName,
                 RRF_RT_REG_MULTI_SZ,
                 NULL,
                 NULL,
                 &privilegesValueSize);
    BAIL_ON_LSA_ERROR(err);

    err = LwAllocateMemory(
                 privilegesValueSize,
                 OUT_PPVOID(&privilegesValue));
    BAIL_ON_LSA_ERROR(err);

    err = RegGetValueA(
                 hRegistry,
                 hEntry,
                 NULL,
                 privilegesValueName,
                 RRF_RT_REG_MULTI_SZ,
                 NULL,
                 (PBYTE)privilegesValue,
                 &privilegesValueSize);
    BAIL_ON_LSA_ERROR(err);

    //
    // Get system access rights value
    //
    systemAccessSize = sizeof(systemAccess);
    err = RegGetValueA(
                 hRegistry,
                 hEntry,
                 NULL,
                 sysAccountRightsValueName,
                 RRF_RT_REG_DWORD,
                 NULL,
                 (PBYTE)&systemAccess,
                 &systemAccessSize);
    BAIL_ON_LSA_ERROR(err);

    //
    // Get account security descriptor
    //
    err = RegGetValueA(
                 hRegistry,
                 hEntry,
                 NULL,
                 securityDescValueName,
                 RRF_RT_REG_BINARY,
                 NULL,
                 NULL,
                 &securityDescRelativeSize);
    if (err == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        //
        // This is probably the first lsassd start so there is no
        // security descriptor value assigned.
        //
        defaultSecurityDesc = TRUE;
        securityDescRelativeSize = pGlobals->accountsSecDescRelativeSize;
    }
    else if (err != STATUS_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(err);
    }

    err = LwAllocateMemory(
                 securityDescRelativeSize,
                 OUT_PPVOID(&pSecurityDescRelative));
    BAIL_ON_LSA_ERROR(err);

    if (defaultSecurityDesc)
    {
        memcpy(pSecurityDescRelative,
               pGlobals->pAccountsSecDescRelative,
               securityDescRelativeSize);
    }
    else
    {
        err = RegGetValueA(
                     hRegistry,
                     hEntry,
                     NULL,
                     securityDescValueName,
                     RRF_RT_REG_BINARY,
                     NULL,
                     (PBYTE)pSecurityDescRelative,
                     &securityDescRelativeSize);
        BAIL_ON_LSA_ERROR(err);
    }

    //
    // Create LSA_ACCOUNT entry
    //
    err = LwAllocateMemory(
                 sizeof(*pAccountEntry),
                 OUT_PPVOID(&pAccountEntry));
    BAIL_ON_LSA_ERROR(err);

    pthread_rwlock_init(&pAccountEntry->accountRwLock, NULL);
    pAccountEntry->Refcount = 1;

    accountSidSize = RtlLengthSid(AccountSid);
    err = LwAllocateMemory(
                 accountSidSize,
                 OUT_PPVOID(&pAccountEntry->pSid));
    BAIL_ON_LSA_ERROR(err);

    ntStatus = RtlCopySid(
                 accountSidSize,
                 pAccountEntry->pSid,
                 AccountSid);
    BAIL_ON_NT_STATUS(ntStatus);

    err = LsaSrvGetPrivilegeValuesFromNames(
                 privilegesValue,
                 privilegesValueSize,
                 sizeof(pAccountEntry->Privileges)/
                 sizeof(pAccountEntry->Privileges[0]),
                 pAccountEntry->Privileges,
                 &pAccountEntry->NumPrivileges);
    if (err == ERROR_INCORRECT_SIZE)
    {
        ntStatus = RtlAllocateCStringFromSid(
                     &pszAccountSid,
                     AccountSid);
        BAIL_ON_NT_STATUS(ntStatus);

        LSA_LOG_ERROR("Incorrect size of the privileges list assigned to %s",
                      pszAccountSid);

        err = ERROR_INVALID_DATA;
        BAIL_ON_LSA_ERROR(err);
    }
    else if (err != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(err);
    }

    pAccountEntry->SystemAccessRights = systemAccess;

    err = LsaSrvAllocateAbsoluteFromSelfRelativeSD(
                 pSecurityDescRelative,
                 &pAccountEntry->pSecurityDesc);
    BAIL_ON_LSA_ERROR(err);

    if (defaultSecurityDesc)
    {
        BOOLEAN overwrite = TRUE;

        err = LsaSrvGetPrivilegeNames_inlock(
                          pAccountEntry,
                          &pPrivilegeNames);
        BAIL_ON_LSA_ERROR(err);

        //
        // Update account because it has just got its default
        // security descriptor
        //
        err = LsaSrvSaveAccount_inlock(
                     AccountSid,
                     pPrivilegeNames,
                     pAccountEntry->SystemAccessRights,
                     pAccountEntry->pSecurityDesc,
                     overwrite);
        BAIL_ON_LSA_ERROR(err);
    }

    *ppAccountEntry = pAccountEntry;

error:
    if (err || ntStatus)
    {
        if (pAccountEntry)
        {
            LsaSrvPrivsReleaseAccountEntry(pAccountEntry);
        }

        *ppAccountEntry = NULL;
    }

    LW_SAFE_FREE_MEMORY(privilegesValue);

    if (pPrivilegeNames)
    {
        DWORD i = 0;

        for (i = 0; pPrivilegeNames[i]; i++)
        {
            LW_SAFE_FREE_MEMORY(pPrivilegeNames[i]);
        }
        LW_SAFE_FREE_MEMORY(pPrivilegeNames);
    }

    LW_SAFE_FREE_MEMORY(pSecurityDescRelative);

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

    return err;
}


static
DWORD
LsaSrvGetPrivilegeValuesFromNames(
    PSTR pszPrivilegeNames,
    DWORD PrivilegeNamesSize,
    DWORD MaxPrivilegeValues,
    PLUID_AND_ATTRIBUTES pPrivilegeValues,
    PDWORD pPrivilegeValuesCount
    )
{
    DWORD err = ERROR_SUCCESS;
    PSTR pszPrivilegeName = NULL;
    PLSA_PRIVILEGE pPrivilegeEntry = NULL;
    DWORD iValue = 0;

    pszPrivilegeName = pszPrivilegeNames;

    while (pszPrivilegeName && *pszPrivilegeName != '\0')
    {
        err = LsaSrvGetPrivilegeEntryByName(
                                  pszPrivilegeName,
                                  &pPrivilegeEntry);
        if (err == ERROR_NO_SUCH_PRIVILEGE)
        {
            LSA_LOG_ERROR("%s privilege is assigned but not supported",
                          pszPrivilegeName);

            err = ERROR_INVALID_DATA;
            BAIL_ON_LSA_ERROR(err);
        }
        else if (err != ERROR_SUCCESS)
        {
            BAIL_ON_LSA_ERROR(err);
        }

        if (iValue < MaxPrivilegeValues)
        {
            pPrivilegeValues[iValue].Luid = pPrivilegeEntry->Luid;
            if (pPrivilegeEntry->EnabledByDefault)
            {
                pPrivilegeValues[iValue].Attributes
                    |= SE_PRIVILEGE_ENABLED_BY_DEFAULT;
            }

            iValue++;
        }
        else
        {
            LSA_LOG_ERROR("Too many privileges assigned (max number = %d)",
                          MaxPrivilegeValues);

            err = ERROR_INCORRECT_SIZE;
            BAIL_ON_LSA_ERROR(err);
        }

        pszPrivilegeName += strlen(pszPrivilegeName) + 1;
    }

    *pPrivilegeValuesCount = iValue;

error:
    return err;
}


DWORD
LsaSrvGetAccountEntry(
    IN PSID pAccountSid,
    OUT PLSA_ACCOUNT *ppAccountEntry
    )
{
    DWORD err = ERROR_SUCCESS;
    BOOLEAN Locked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PLSA_ACCOUNT pAccountEntry = NULL;

    LSASRV_PRIVS_RDLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    err = LwHashGetValue(gLsaPrivilegeGlobals.pAccounts,
                         pAccountSid,
                         (PVOID)&pAccountEntry);
    if (err == ERROR_NOT_FOUND)
    {
        // Maps to STATUS_OBJECT_NAME_NOT_FOUND
        err = ERROR_FILE_NOT_FOUND;
        BAIL_ON_LSA_ERROR(err);
    }
    else if (err != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(err);
    }

    LsaSrvPrivsAcquireAccountEntry(pAccountEntry);

    *ppAccountEntry = pAccountEntry;

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    if (err)
    {
        if (ppAccountEntry)
        {
            *ppAccountEntry = NULL;
        }
    }

    return err;
}


DWORD
LsaSrvSetAccountEntry(
    IN PSID pAccountSid,
    IN PLSA_ACCOUNT pAccountEntry,
    IN BOOLEAN Overwrite
    )
{
    DWORD err = ERROR_SUCCESS;
    BOOLEAN Locked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;

    LSASRV_PRIVS_WRLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);
    err = LsaSrvSetAccountEntry_inlock(
                      pGlobals->pAccounts,
                      pAccountSid,
                      pAccountEntry,
                      Overwrite);
    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    return err;
}


static
DWORD
LsaSrvSetAccountEntry_inlock(
    IN PLW_HASH_TABLE pAccountsTable,
    IN PSID pAccountSid,
    IN PLSA_ACCOUNT pAccountEntry,
    IN BOOLEAN Overwrite
    )
{
    DWORD err = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLSA_ACCOUNT pExistingEntry = NULL;
    PSTR pszSid = NULL;

    //
    // Check if a value already exists under this key and
    // overwrite it if requested
    //
    err = LwHashGetValue(pAccountsTable,
                         pAccountSid,
                         (PVOID)&pExistingEntry);
    if (err == ERROR_SUCCESS && !Overwrite)
    {
        ntStatus = RtlAllocateCStringFromSid(
                         &pszSid,
                         pAccountSid);
        BAIL_ON_NT_STATUS(ntStatus);

        LSA_LOG_WARNING("Duplicate %s account entry found", pszSid);

        err = ERROR_ALREADY_EXISTS;
        BAIL_ON_LSA_ERROR(err);
    }
    else if (err == ERROR_NOT_FOUND)
    {
        err = ERROR_SUCCESS;
    }
    else
    {
        BAIL_ON_LSA_ERROR(err);
    }

    err = LwHashSetValue(pAccountsTable,
                         pAccountSid,
                         pAccountEntry);

    BAIL_ON_LSA_ERROR(err);

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

    RTL_FREE(&pszSid);

    return err;
}


DWORD
LsaSrvAddAccount_inlock(
    IN PSID pAccountSid,
    IN PLSA_ACCOUNT pAccountEntry
    )
{
    DWORD err = ERROR_SUCCESS;
    BOOLEAN Locked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PWSTR *pPrivilegeNames = NULL;
    BOOLEAN overwrite = FALSE;

    err = LsaSrvGetPrivilegeNames_inlock(
                      pAccountEntry,
                      &pPrivilegeNames);
    BAIL_ON_LSA_ERROR(err);

    LSASRV_PRIVS_WRLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);
    err = LsaSrvSetAccountEntry_inlock(
                      pGlobals->pAccounts,
                      pAccountSid,
                      pAccountEntry,
                      overwrite);
    BAIL_ON_LSA_ERROR(err);

    err = LsaSrvSaveAccount_inlock(
                      pAccountSid,
                      pPrivilegeNames,
                      pAccountEntry->SystemAccessRights,
                      pAccountEntry->pSecurityDesc,
                      overwrite);
    BAIL_ON_LSA_ERROR(err);

    //
    // LsaSrvAddAccount_inlock is called by LsaSrvPrivsCreateAccount so
    // new account has to be acquired before releasing accounts database
    // lock and making the account available for other threads.
    //
    LsaSrvPrivsAcquireAccountEntry(pAccountEntry);

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    if (pPrivilegeNames)
    {
        DWORD i = 0;

        for (i = 0; pPrivilegeNames[i]; i++)
        {
            LW_SAFE_FREE_MEMORY(pPrivilegeNames[i]);
        }
        LW_SAFE_FREE_MEMORY(pPrivilegeNames);
    }

    return err;
}


static
DWORD
LsaSrvSaveAccount_inlock(
    IN PSID pAccountSid,
    IN PWSTR *ppwszPrivilegeNames,
    IN DWORD SystemAccessRights,
    IN PSECURITY_DESCRIPTOR_ABSOLUTE pSecurityDesc,
    IN BOOLEAN Overwrite
    )
{
    DWORD err = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    HANDLE hRegistry = NULL;
    HKEY hAccountsKey = NULL;
    PWSTR account = NULL;
    HKEY hAccount = NULL;
    WCHAR wszPrivilegeValueName[] = LSA_ACCOUNT_PRIVILEGES_NAME_W;
    WCHAR wszSysAccessRightsValueName[] = LSA_ACCOUNT_SYS_ACCESS_RIGHTS_NAME_W;
    WCHAR wszSecurityDescriptorValueName[] = LSA_PRIVILEGE_SECURITY_DESC_NAME_W;
    PBYTE privilegeNamesBlob = NULL;
    SSIZE_T privilegeNamesBlobSize = 0;
    PSECURITY_DESCRIPTOR_RELATIVE pSecurityDescRelative = NULL;
    DWORD securityDescRelativeSize = 0;

    err = RegOpenServer(&hRegistry);
    BAIL_ON_LSA_ERROR(err);

    err = RegOpenKeyExA(
                 hRegistry,
                 NULL,
                 LSA_ACCOUNTS_REG_KEY,
                 0,
                 KEY_WRITE,
                 &hAccountsKey);
    BAIL_ON_LSA_ERROR(err);

    ntStatus = RtlAllocateWC16StringFromSid(
                 &account,
                 pAccountSid);
    BAIL_ON_NT_STATUS(ntStatus);

    //
    // Open the account subkey or create it if not existing
    //
    err = RegOpenKeyExW(
                 hRegistry,
                 hAccountsKey,
                 account,
                 0,
                 KEY_WRITE,
                 &hAccount);
    if (err == ERROR_SUCCESS && !Overwrite)
    {
        err = ERROR_ALREADY_EXISTS;
        BAIL_ON_LSA_ERROR(err);
    }
    else if (err == LWREG_ERROR_NO_SUCH_KEY_OR_VALUE)
    {
        err = RegCreateKeyExW(
                     hRegistry,
                     hAccountsKey,
                     account,
                     0,
                     NULL,
                     0,
                     KEY_WRITE,
                     NULL,
                     &hAccount,
                     NULL);
        BAIL_ON_LSA_ERROR(err);
    }
    else if (err != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(err);
    }

    //
    // Save Privileges value
    //

    // Convert privilege names array to blob and save it
    err = RegMultiStrsToByteArrayW(
                 ppwszPrivilegeNames,
                 &privilegeNamesBlob,
                 &privilegeNamesBlobSize);
    BAIL_ON_LSA_ERROR(err);

    err = RegSetValueExW(
                 hRegistry,
                 hAccount,
                 wszPrivilegeValueName,
                 0,
                 REG_MULTI_SZ,
                 privilegeNamesBlob,
                 privilegeNamesBlobSize);
    BAIL_ON_LSA_ERROR(err);

    //
    // Save system access rights
    //

    err = RegSetValueExW(
                 hRegistry,
                 hAccount,
                 wszSysAccessRightsValueName,
                 0,
                 REG_DWORD,
                 (PBYTE)&SystemAccessRights,
                 sizeof(SystemAccessRights));
    BAIL_ON_LSA_ERROR(err);

    //
    // Save account security descriptor
    //

    ntStatus = RtlAbsoluteToSelfRelativeSD(
                 pSecurityDesc,
                 pSecurityDescRelative,
                 &securityDescRelativeSize);
    if (ntStatus == STATUS_BUFFER_TOO_SMALL)
    {
        ntStatus = STATUS_SUCCESS;
    }
    else if (ntStatus != STATUS_SUCCESS)
    {
        BAIL_ON_NT_STATUS(ntStatus);
    }

    err = LwAllocateMemory(
                 securityDescRelativeSize,
                 OUT_PPVOID(&pSecurityDescRelative));
    BAIL_ON_LSA_ERROR(err);

    ntStatus = RtlAbsoluteToSelfRelativeSD(
                 pSecurityDesc,
                 pSecurityDescRelative,
                 &securityDescRelativeSize);
    BAIL_ON_NT_STATUS(ntStatus);

    err = RegSetValueExW(
                 hRegistry,
                 hAccount,
                 wszSecurityDescriptorValueName,
                 0,
                 REG_BINARY,
                 (PBYTE)pSecurityDescRelative,
                 securityDescRelativeSize);
    BAIL_ON_LSA_ERROR(err);

error:
    if (hAccount)
    {
        RegCloseKey(hRegistry, hAccount);
    }

    if (hAccountsKey)
    {
        RegCloseKey(hRegistry, hAccountsKey);
    }

    if (hRegistry)
    {
        RegCloseServer(hRegistry);
    }

    RTL_FREE(&account);
    LW_SAFE_FREE_MEMORY(privilegeNamesBlob);
    LW_SAFE_FREE_MEMORY(pSecurityDescRelative);

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

    return err;
}


static
DWORD
LsaSrvGetPrivilegeNames_inlock(
    IN PLSA_ACCOUNT pAccountEntry,
    OUT PWSTR **ppPrivilegeNames
    )
{
    DWORD err = ERROR_SUCCESS;
    PWSTR *pPrivilegeNames = NULL;
    DWORD i = 0;

    err = LwAllocateMemory(
                 sizeof(PWSTR) * (pAccountEntry->NumPrivileges + 1),
                 OUT_PPVOID(&pPrivilegeNames));
    BAIL_ON_LSA_ERROR(err);

    for (i = 0; i < pAccountEntry->NumPrivileges; i++)
    {
        PLSA_PRIVILEGE pPrivilege = NULL;

        err = LsaSrvGetPrivilegeEntryByValue(
                     &pAccountEntry->Privileges[i].Luid,
                     &pPrivilege);
        BAIL_ON_LSA_ERROR(err);

        err = LwMbsToWc16s(
                     pPrivilege->pszName,
                     &pPrivilegeNames[i]);
        BAIL_ON_LSA_ERROR(err);
    }

    *ppPrivilegeNames = pPrivilegeNames;

error:
    if (err)
    {
        for (i = 0; pPrivilegeNames && i < pAccountEntry->NumPrivileges; i++)
        {
            LW_SAFE_FREE_MEMORY(pPrivilegeNames[i]);
        }
        LW_SAFE_FREE_MEMORY(pPrivilegeNames);

        *ppPrivilegeNames = NULL;
    }

    return err;
}


DWORD
LsaSrvUpdateAccount_inlock(
    IN PSID pAccountSid,
    IN PLSA_ACCOUNT pAccountEntry
    )
{
    DWORD err = ERROR_SUCCESS;
    BOOLEAN Locked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PWSTR *pPrivilegeNames = NULL;
    BOOLEAN Overwrite = TRUE;

    err = LsaSrvGetPrivilegeNames_inlock(
                      pAccountEntry,
                      &pPrivilegeNames);
    BAIL_ON_LSA_ERROR(err);

    LSASRV_PRIVS_WRLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);
    err = LsaSrvSaveAccount_inlock(
                      pAccountSid,
                      pPrivilegeNames,
                      pAccountEntry->SystemAccessRights,
                      pAccountEntry->pSecurityDesc,
                      Overwrite);
    BAIL_ON_LSA_ERROR(err);

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(Locked, &pGlobals->accountsRwLock);

    if (pPrivilegeNames)
    {
        DWORD i = 0;

        for (i = 0; pPrivilegeNames[i]; i++)
        {
            LW_SAFE_FREE_MEMORY(pPrivilegeNames[i]);
        }
        LW_SAFE_FREE_MEMORY(pPrivilegeNames);
    }

    return err;
}


DWORD
LsaSrvPrivsGetAccountEntries(
    IN OUT PDWORD pResume,
    IN DWORD PreferredMaxSize,
    OUT PLSA_ACCOUNT **pppAccounts,
    OUT PDWORD pCount
    )
{
    DWORD err = ERROR_SUCCESS;
    DWORD enumerationStatus = ERROR_SUCCESS;
    BOOLEAN accountsLocked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    DWORD totalSize = 0;
    DWORD entrySize = 0;
    DWORD count = 0;
    DWORD index = 0;
    PLSA_ACCOUNT *ppAccounts = NULL;
    LW_HASH_ITERATOR iterator = {0};
    LW_HASH_ENTRY* pEntry = NULL;
    PLSA_ACCOUNT pAccountEntry = NULL;
    DWORD i = 0;

    LSASRV_PRIVS_RDLOCK_RWLOCK(accountsLocked, &pGlobals->accountsRwLock);

    err = LwHashGetIterator(
                 pGlobals->pAccounts,
                 &iterator);
    BAIL_ON_LSA_ERROR(err);

    //
    // First, calculate how many privileges are we going to return
    // (at least one)
    //
    if (PreferredMaxSize != (DWORD)(-1))
    {
        pEntry = LwHashNext(&iterator);

        do
        {
            if (index++ < (*pResume))
            {
                pEntry = LwHashNext(&iterator);
                continue;
            }
            else if (!pEntry)
            {
                break;
            }

            pAccountEntry = (PLSA_ACCOUNT)pEntry->pValue;
            totalSize += entrySize;

            // LSA_ACCOUNT_INFO includes pointer to account SID
            entrySize += sizeof(LSA_ACCOUNT_INFO);
            entrySize += RtlLengthSid(pAccountEntry->pSid);

            count++;
            pEntry = LwHashNext(&iterator);

        } while (pEntry && (totalSize + entrySize < PreferredMaxSize));

        err = LwHashGetIterator(
                     pGlobals->pAccounts,
                     &iterator);
        BAIL_ON_LSA_ERROR(err);

        index     = 0;
        totalSize = 0;
        entrySize = 0;
    }
    else
    {
        count = LwHashGetKeyCount(pGlobals->pAccounts);
    }

    //
    // Allocate array of LSA_PRIVILEGE pointers to return
    //
    err = LwAllocateMemory(
                 sizeof(ppAccounts[0]) * count,
                 OUT_PPVOID(&ppAccounts));
    BAIL_ON_LSA_ERROR(err);

    count = 0;
    pEntry = LwHashNext(&iterator);

    do
    {
        if (index++ < (*pResume))
        {
            pEntry = LwHashNext(&iterator);
            continue;
        }
        else if (!pEntry)
        {
            break;
        }

        pAccountEntry = (PLSA_ACCOUNT)pEntry->pValue;
        LsaSrvPrivsAcquireAccountEntry(pAccountEntry);

        totalSize += entrySize;

        // LSA_ACCOUNT_INFO includes pointer to account SID
        entrySize += sizeof(LSA_ACCOUNT_INFO);
        entrySize += RtlLengthSid(pAccountEntry->pSid);

        ppAccounts[count++] = pAccountEntry;
        pEntry = LwHashNext(&iterator);

    } while (pEntry && (totalSize + entrySize < PreferredMaxSize));

    if (pEntry && count > 0)
    {
        enumerationStatus = ERROR_MORE_DATA;
    }
    else if (!pEntry && count == 0)
    {
        enumerationStatus = ERROR_NO_MORE_ITEMS;
    }

    *pResume      = index;
    *pppAccounts  = ppAccounts;
    *pCount       = count;

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(accountsLocked, &pGlobals->accountsRwLock);

    if (err)
    {
        for (i = 0; i < count; i++)
        {
            LsaSrvPrivsReleaseAccountEntry(ppAccounts[i]);
        }
        LW_SAFE_FREE_MEMORY(ppAccounts);

        if (pppAccounts)
        {
            *pppAccounts = NULL;
        }

        if (pCount)
        {
            *pCount = 0;
        }

        if (pResume)
        {
            *pResume = 0;
        }
    }

    if (err == ERROR_SUCCESS &&
        enumerationStatus != ERROR_SUCCESS)
    {
        err = enumerationStatus;
    }

    return err;
}


VOID
LsaSrvPrivsCheckIfDeleteAccount(
    PLSA_ACCOUNT pAccount
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD err = ERROR_SUCCESS;
    BOOLEAN accountsLocked = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PSID accountSid = NULL;
    PSTR pszAccountSid = NULL;

    LSASRV_PRIVS_WRLOCK_RWLOCK(accountsLocked, &pGlobals->accountsRwLock);

    if (!pAccount->Delete || pAccount->Refcount > 1)
    {
        goto error;
    }

    accountSid = pAccount->pSid;
    ntStatus = RtlAllocateCStringFromSid(
                        &pszAccountSid,
                        accountSid);
    BAIL_ON_NT_STATUS(ntStatus);

    err = LwHashRemoveKey(
                        pGlobals->pAccounts,
                        accountSid);
    BAIL_ON_LSA_ERROR(err);

    err = LsaSrvDeleteAccount_inlock(pszAccountSid);
    BAIL_ON_LSA_ERROR(err);

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(accountsLocked, &pGlobals->accountsRwLock);

    RTL_FREE(&pszAccountSid);
}


static
DWORD
LsaSrvDeleteAccount_inlock(
    PSTR pszAccountSid
    )
{
    DWORD err = ERROR_SUCCESS;
    HANDLE hRegistry = NULL;
    HKEY hAccounts = NULL;

    err = RegOpenServer(&hRegistry);
    BAIL_ON_LSA_ERROR(err);

    err = RegOpenKeyExA(
                 hRegistry,
                 NULL,
                 LSA_ACCOUNTS_REG_KEY,
                 0,
                 KEY_WRITE | DELETE,
                 &hAccounts);
    BAIL_ON_LSA_ERROR(err);

    err = RegDeleteKeyA(
                 hRegistry,
                 hAccounts,
                 pszAccountSid);
    BAIL_ON_LSA_ERROR(err);

error:
    if (hAccounts)
    {
        RegCloseKey(hRegistry, hAccounts);
    }

    if (hRegistry)
    {
        RegCloseServer(hRegistry);
    }

    return err;
}


DWORD
LsaSrvPrivsUpdateAccountsSids(
    IN PSID OldPrefixSid,
    IN PSID NewPrefixSid
    )
{
    DWORD error = ERROR_SUCCESS;
    BOOLEAN inLock = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;

    LSASRV_PRIVS_WRLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);

    error = LsaSrvPrivsUpdateAccountsSids_inlock(
                     OldPrefixSid,
                     NewPrefixSid);
    BAIL_ON_LSA_ERROR(error);

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);

    return error;
}


DWORD
LsaSrvPrivsUpdateAccountsSids_inlock(
    IN PSID OldPrefixSid,
    IN PSID NewPrefixSid
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD error = ERROR_SUCCESS;
    BOOLEAN inAccountLock = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    LW_HASH_ITERATOR iterator = {0};
    LW_HASH_ENTRY* entry = NULL;
    PLSA_ACCOUNT account = NULL;
    PSID oldAccountSid = NULL;
    DWORD newAccountSidSize = 0;
    PSID newAccountSid = NULL;
    PLSA_ACCOUNT newAccount = NULL;
    DWORD securityDescriptorRelativeSize = 0;
    PSECURITY_DESCRIPTOR_RELATIVE securityDescriptorRelative = NULL;
    PWSTR* privilegeNames = NULL;
    PSTR pszAccountSid = NULL;
    BOOLEAN dirty = FALSE;
    DWORD i = 0;

    error = LwHashGetIterator(
                 pGlobals->pAccounts,
                 &iterator);
    BAIL_ON_LSA_ERROR(error);

    while ((entry = LwHashNext(&iterator)) != NULL)
    {
        account = (PLSA_ACCOUNT)entry->pValue;

        LSASRV_PRIVS_WRLOCK_RWLOCK(inAccountLock, &account->accountRwLock);

        oldAccountSid = account->pSid;

        if (RtlIsPrefixSid(
                       OldPrefixSid,
                       oldAccountSid))
        {
            //
            // Create new SID for account (new local domain SID + account RID)
            //
            newAccountSidSize = RtlLengthSid(oldAccountSid);
            error = LwAllocateMemory(
                           newAccountSidSize,
                           OUT_PPVOID(&newAccountSid));
            BAIL_ON_LSA_ERROR(error);

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

            while (RtlLengthSid(newAccountSid) < newAccountSidSize)
            {
                ntStatus = RtlAppendRidSid(
                                newAccountSidSize,
                                newAccountSid,
                                oldAccountSid->SubAuthority[newAccountSid->SubAuthorityCount]);
                BAIL_ON_NT_STATUS(ntStatus);
            }

            //
            // Allocate new account object
            //
            error = LwAllocateMemory(
                           sizeof(*newAccount),
                           OUT_PPVOID(&newAccount));
            BAIL_ON_LSA_ERROR(error);

            newAccount->Refcount = 1;

            //
            // New SID
            //
            error = LwAllocateMemory(
                           newAccountSidSize,
                           OUT_PPVOID(&newAccount->pSid));
            BAIL_ON_LSA_ERROR(error);

            ntStatus = RtlCopySid(
                           newAccountSidSize,
                           newAccount->pSid,
                           newAccountSid);
            BAIL_ON_NT_STATUS(ntStatus);

            //
            // Privileges
            //
            newAccount->NumPrivileges = account->NumPrivileges;
            RtlCopyMemory(
                 newAccount->Privileges,
                 account->Privileges,
                 sizeof(newAccount->Privileges[0]) * newAccount->NumPrivileges);

            //
            // System Access Rights
            //
            newAccount->SystemAccessRights = account->SystemAccessRights;

            //
            // Copy and update security descriptor
            //
            ntStatus = RtlAbsoluteToSelfRelativeSD(
                                  account->pSecurityDesc,
                                  securityDescriptorRelative,
                                  &securityDescriptorRelativeSize);
            if (ntStatus == STATUS_BUFFER_TOO_SMALL)
            {
                ntStatus = STATUS_SUCCESS;
            }
            else
            {
                BAIL_ON_NT_STATUS(ntStatus);
            }

            error = LwAllocateMemory(
                            securityDescriptorRelativeSize,
                            OUT_PPVOID(&securityDescriptorRelative));
            BAIL_ON_LSA_ERROR(error);

            ntStatus = RtlAbsoluteToSelfRelativeSD(
                            account->pSecurityDesc,
                            securityDescriptorRelative,
                            &securityDescriptorRelativeSize);
            BAIL_ON_NT_STATUS(ntStatus);

            error = LsaSrvAllocateAbsoluteFromSelfRelativeSD(
                            securityDescriptorRelative,
                            &newAccount->pSecurityDesc);
            BAIL_ON_LSA_ERROR(error);

            error = LsaSrvUpdateAccountSecurityDescriptor(
                            account->pSecurityDesc,
                            OldPrefixSid,
                            NewPrefixSid,
                            NULL);
            BAIL_ON_LSA_ERROR(error);

            //
            // Account SID has changed so this is creating new account
            // and removing old one
            //
            error = LsaSrvGetPrivilegeNames_inlock(
                           newAccount,
                           &privilegeNames);
            BAIL_ON_LSA_ERROR(error);

            error = LsaSrvSetAccountEntry_inlock(
                           pGlobals->pAccounts,
                           newAccountSid,
                           newAccount,
                           FALSE);
            BAIL_ON_LSA_ERROR(error);

            newAccountSid = NULL;

            error = LsaSrvSaveAccount_inlock(
                           newAccount->pSid,
                           privilegeNames,
                           newAccount->SystemAccessRights,
                           newAccount->pSecurityDesc,
                           FALSE);
            BAIL_ON_LSA_ERROR(error);

            newAccount = NULL;

            ntStatus = RtlAllocateCStringFromSid(
                           &pszAccountSid,
                           oldAccountSid);
            BAIL_ON_NT_STATUS(ntStatus);

            //
            // account has to be unlocked here because removing it from
            // the hash table destroys rwlock
            //
            LSASRV_PRIVS_UNLOCK_RWLOCK(inAccountLock, &account->accountRwLock);

            error = LwHashRemoveKey(
                           pGlobals->pAccounts,
                           oldAccountSid);
            BAIL_ON_LSA_ERROR(error);

            error = LsaSrvDeleteAccount_inlock(pszAccountSid);
            BAIL_ON_LSA_ERROR(error);
        }
        else
        {
            error = LsaSrvUpdateAccountSecurityDescriptor(
                           account->pSecurityDesc,
                           OldPrefixSid,
                           NewPrefixSid,
                           &dirty);
            BAIL_ON_LSA_ERROR(error);

            if (dirty)
            {
                //
                // Account SID hasn't changed so this is just updating
                //
                error = LsaSrvGetPrivilegeNames_inlock(
                               account,
                               &privilegeNames);
                BAIL_ON_LSA_ERROR(error);

                error = LsaSrvSaveAccount_inlock(
                               account->pSid,
                               privilegeNames,
                               account->SystemAccessRights,
                               account->pSecurityDesc,
                               TRUE);
                BAIL_ON_LSA_ERROR(error);
            }
        }

        LSASRV_PRIVS_UNLOCK_RWLOCK(inAccountLock, &account->accountRwLock);

        if (privilegeNames)
        {
            for (i = 0; privilegeNames[i]; i++)
            {
                LW_SAFE_FREE_MEMORY(privilegeNames[i]);
            }
            LW_SAFE_FREE_MEMORY(privilegeNames);
        }

        RTL_FREE(&pszAccountSid);
        LW_SAFE_FREE_MEMORY(securityDescriptorRelative);
        securityDescriptorRelativeSize = 0;

        dirty = FALSE;
    }

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(inAccountLock, &account->accountRwLock);

    if (privilegeNames)
    {
        for (i = 0; privilegeNames[i]; i++)
        {
            LW_SAFE_FREE_MEMORY(privilegeNames[i]);
        }
        LW_SAFE_FREE_MEMORY(privilegeNames);
    }

    if (newAccount)
    {
        LsaSrvPrivsReleaseAccountEntry(newAccount);
    }

    LW_SAFE_FREE_MEMORY(newAccountSid);
    RTL_FREE(&pszAccountSid);
    LW_SAFE_FREE_MEMORY(securityDescriptorRelative);

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

    return error;
}


VOID
LsaSrvPrivsNotifyLocalDomainChanged(
    PVOID Context,
    PSID Sid
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN inLock = FALSE;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PSID builtinDomainSid = NULL;
    SAMDB_ENUM_HANDLE enumHandle = NULL;
    DWORD maxCount = (DWORD)-1;
    DWORD domainsCount = 0;
    PSAMDB_OBJECT_INFO* domains = NULL;
    DWORD domainIndex = 0;
    PSAMDB_OBJECT_INFO localDomain = NULL;
    DWORD newLocalDomainSidSize = 0;
    PSID newLocalDomainSid = NULL;

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

    error = SamDbOpenEnumObjects(
                    NULL,
                    SAMDB_OBJECT_TYPE_DOMAIN,
                    SAMDB_ENUM_SORT_ORDER_NONE,
                    &enumHandle);
    BAIL_ON_LSA_ERROR(error);

    error = SamDbEnumObjects(
                    enumHandle,
                    maxCount,
                    &domainsCount,
                    &domains);
    BAIL_ON_LSA_ERROR(error);

    for (domainIndex = 0; domainIndex < domainsCount; domainIndex++)
    {
        if (RtlEqualSid(
                  domains[domainIndex]->Sid,
                  builtinDomainSid))
        {
            // This is BUILTIN domain, skip it
            continue;
        }

        localDomain = domains[domainIndex];
        domains[domainIndex] = NULL;
        break;
    }

    if (localDomain == NULL)
    {
        error = ERROR_INTERNAL_ERROR;

        LSA_LOG_ERROR("No local domain object found.");
        goto error;
    }

    LSASRV_PRIVS_WRLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);

    //
    // Check if local domain SID has actually changed before doing
    // anything.
    //
    if (RtlEqualSid(
                localDomain->Sid,
                pGlobals->LocalDomainSid))
    {
        goto error;
    }

    newLocalDomainSidSize = RtlLengthSid(localDomain->Sid);
    error = LwAllocateMemory(
                      newLocalDomainSidSize,
                      OUT_PPVOID(&newLocalDomainSid));
    BAIL_ON_LSA_ERROR(error);

    ntStatus = RtlCopySid(
                      newLocalDomainSidSize,
                      newLocalDomainSid,
                      localDomain->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    error = LsaSrvPrivsUpdateAccountsSids_inlock(
                     pGlobals->LocalDomainSid,
                     newLocalDomainSid);
    BAIL_ON_LSA_ERROR(error);

    error = LsaSrvCreateWorkItem(
                     &pGlobals->WorkItem,
                     LsaSrvPrivsUpdateLocalDomainSidSubscription,
                     newLocalDomainSid);
    BAIL_ON_LSA_ERROR(error);

    LsaSrvScheduleWorkItem(
                     pGlobals->WorkItem,
                     0);

error:
    LSASRV_PRIVS_UNLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);

    if (enumHandle)
    {
        SamDbCloseEnum(enumHandle);
    }

    if (domains)
    {
        SamDbFreeObjects(
                 domainsCount,
                 domains);
    }

    if (localDomain)
    {
        SamDbFreeObject(localDomain);
    }

    LW_SAFE_FREE_MEMORY(builtinDomainSid);

    if (error)
    {
        LSA_LOG_ERROR("Error when notifying privileges server about local "
                      "domain SID change: %s (%u)",
                      LSA_SAFE_LOG_STRING(LwWin32ErrorToName(error)),
                      error);
    }

    if (ntStatus)
    {
        LSA_LOG_ERROR("Error when notifying privileges server about local "
                      "domain SID change: %s (0x%08x)",
                      LSA_SAFE_LOG_STRING(LwNtStatusToName(ntStatus)),
                      ntStatus);
    }
}


static
VOID
LsaSrvPrivsUpdateLocalDomainSidSubscription(
    PLW_WORK_ITEM WorkItem,
    PVOID Context
    )
{
    DWORD error = ERROR_SUCCESS;
    PSID newLocalDomainSid = (PSID)Context;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    BOOLEAN inLock = FALSE;
    PSAMDB_CALLBACK_REGISTRATION callback = NULL;

    //
    // Protecting local domain SID and callback subscription with
    // accountsRwLock is enough because no other call changes it.
    //

    LSASRV_PRIVS_WRLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);

    RTL_FREE(&pGlobals->LocalDomainSid);

    //
    // Register subscription for changes under new SID
    //
    pGlobals->LocalDomainSid = newLocalDomainSid;
    error = SamDbRegisterCallback(
                 &callback,
                 NULL,
                 pGlobals->LocalDomainSid,
                 FALSE,
                 LsaSrvPrivsNotifyLocalDomainChanged);
    BAIL_ON_LSA_ERROR(error);

    //
    // Unregister subscription for old SID
    //
    SamDbUnregisterCallback(pGlobals->SamDbCallback);

    pGlobals->SamDbCallback = callback;

error:
    LsaSrvFreeWorkItem(&WorkItem);

    LSASRV_PRIVS_UNLOCK_RWLOCK(inLock, &pGlobals->accountsRwLock);
}


DWORD
LsaSrvProvisionDefaultAccounts(
    VOID
    )
{
    DWORD err = ERROR_SUCCESS;

    //
    // This is an example showing how a default account can be provisioned.
    // It provisions "Likewise Users" (RID=800) account with:
    //   Privileges: SE_BACKUP_NAME and SE_SHUTDOWN_NAME,
    //   System access rights: POLICY_MODE_INTERACTIVE and POLICY_MODE_NETWORK
    //   Security descriptor: default for lsa accounts
    //
    // This kind of account cannot be provisioned using .reg file because
    // it's an account from local domain and therefore its SID is not known
    // until sam database is created i.e. lsass is started for the first time.
    //
    // The function code is disabled by default because no such account
    // needs to be provisioned in default lsass configuration.
    //
#if PROVISIONING_DEFAULT_ACCOUNTS_ENABLED
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLSASRV_PRIVILEGE_GLOBALS pGlobals = &gLsaPrivilegeGlobals;
    PSID localDomainSid = NULL;
    HANDLE hConnection = NULL;
    PLSASTATUS pLocalStatus = NULL;
    DWORD sidSize = 0;
    PSID likewiseUsersSid = NULL;
    WCHAR backupPrivilege[] = SE_BACKUP_NAME;
    WCHAR shutdownPrivilege[] = SE_SHUTDOWN_NAME;
    PWSTR likewiseUsersPrivileges[] = {
        backupPrivilege,
        shutdownPrivilege,
        NULL
    };
    DWORD likewiseUsersSar =
        POLICY_MODE_INTERACTIVE |
        POLICY_MODE_NETWORK;
    PSECURITY_DESCRIPTOR_ABSOLUTE likewiseUsersSecurityDescriptor = NULL;

    err = LsaSrvOpenServer(0, 0, getpid(), &hConnection);
    BAIL_ON_LSA_ERROR(err);

    //
    // Get local domain SID
    //
    err = LsaSrvGetStatus(
                    hConnection,
                    LSA_PROVIDER_TAG_LOCAL,
                    &pLocalStatus);
    BAIL_ON_LSA_ERROR(err);

    LSA_ASSERT(pLocalStatus->dwCount == 1);

    ntStatus = RtlAllocateSidFromCString(
                    &localDomainSid,
                    pLocalStatus->pAuthProviderStatusList->pszDomainSid);
    BAIL_ON_NT_STATUS(err);

    //
    // Allocate "Likewise Users" well-known SID
    //
    sidSize = RtlLengthRequiredSid(
                    localDomainSid->SubAuthorityCount + 1);
    err = LwAllocateMemory(
                    sidSize,
                    OUT_PPVOID(&likewiseUsersSid));
    BAIL_ON_LSA_ERROR(err);

    ntStatus = RtlCopySid(
                    sidSize,
                    likewiseUsersSid,
                    localDomainSid);
    BAIL_ON_NT_STATUS(ntStatus);

    ntStatus = RtlAppendRidSid(
                    sidSize,
                    likewiseUsersSid,
                    DOMAIN_ALIAS_RID_LW_USERS);
    BAIL_ON_NT_STATUS(ntStatus);

    //
    // Get defaut security descriptor
    //
    err = LsaSrvAllocateAbsoluteFromSelfRelativeSD(
                    pGlobals->pAccountsSecDescRelative,
                    &likewiseUsersSecurityDescriptor);
    BAIL_ON_LSA_ERROR(err);

    //
    // Try to save account in the registry if doesn't exist yet.
    //
    err = LsaSrvSaveAccount_inlock(
                    likewiseUsersSid,
                    likewiseUsersPrivileges,
                    likewiseUsersSar,
                    likewiseUsersSecurityDescriptor,
                    FALSE);
    if (err == ERROR_ALREADY_EXISTS)
    {
        err = ERROR_SUCCESS;
    }
    else if (err != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(err);
    }

error:
    if (hConnection)
    {
        LsaSrvCloseServer(hConnection);
    }

    if (pLocalStatus)
    {
        LsaFreeStatus(pLocalStatus);
    }

    RTL_FREE(&localDomainSid);

    RTL_FREE(&likewiseUsersSid);
    if (likewiseUsersSecurityDescriptor)
    {
        LsaSrvFreeSecurityDescriptor(likewiseUsersSecurityDescriptor);
    }

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

#endif // ENABLED_PROVISIONING_DEFAULT_ACCOUNTS
    return err;
}
