/* -*- 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-2008
 * 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:
 *
 *        lpdomain.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Local Authentication Provider
 *
 *        Domain Management
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewisesoftware.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 */

#include "includes.h"


static
VOID
LocalNotifyDomainChanged(
    PVOID Context,
    PSID ObjectSid
    );

static
VOID
LocalUpdateLocalDomainSidSubscription(
    PLW_WORK_ITEM WorkItem,
    PVOID Context
    );


DWORD
LocalSyncDomainInfo(
    PWSTR                    pwszUserDN,
    PWSTR                    pwszCredentials,
    ULONG                    ulMethod,
    PLOCAL_PROVIDER_GLOBALS  pGlobals
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD dwNewMinPasswordAge = LOCAL_MIN_PASSWORD_AGE;
    DWORD dwNewMaxPasswordAge = LOCAL_MAX_PASSWORD_AGE;
    DWORD dwNewMinPasswordLength = LOCAL_MIN_PASSWORD_LENGTH;
    DWORD dwNewPasswordPromptTime = LOCAL_PASSWORD_PROMPT_TIME;
    DWORD dwNewLockoutThreshold = LOCAL_LOCKOUT_THRESHOLD;
    DWORD dwNewLockoutDuration = LOCAL_LOCKOUT_DURATION;
    DWORD dwNewLockoutWindow = LOCAL_LOCKOUT_WINDOW;
    LONG64 llSequenceNumber = 0;
    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;
    PSID localDomainSid = NULL;
    PSTR pszDomainName = NULL;
    PSTR pszNetBIOSName = NULL;
    SAMDB_FIELDS updateFields = 0;
    LONG64 llMinPasswordAge = 0;
    LONG64 llNewMinPasswordAge = 0;
    LONG64 llMaxPasswordAge = 0;
    LONG64 llNewMaxPasswordAge = 0;
    DWORD dwMinPasswordLength = 0;
    LONG64 llPasswordPromptTime = 0;
    LONG64 llNewPasswordPromptTime = 0;
    DWORD dwLockoutThreshold = 0;
    LONG64 llLockoutDuration = 0;
    LONG64 llNewLockoutDuration = 0;
    LONG64 llLockoutWindow = 0;
    LONG64 llNewLockoutWindow = 0;
    PSAMDB_CALLBACK_REGISTRATION registration = NULL;

    LSA_CONFIG SamConfig[] = {
        {
            "MinPasswordAge",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewMinPasswordAge,
            NULL
        },
        {
            "MaxPasswordAge",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewMaxPasswordAge,
            NULL
        },
        {
            "MinPasswordLength",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewMinPasswordLength,
            NULL
        },
        {
            "PasswordPromptTime",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewPasswordPromptTime,
            NULL
        },
        {
            "LockoutThreshold",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewLockoutThreshold,
            NULL
        },
        {
            "LockoutDuration",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewLockoutDuration,
            NULL
        },
        {
            "LockoutWindow",
            TRUE,
            LsaTypeDword,
            0,
            MAXDWORD,
            NULL,
            &dwNewLockoutWindow,
            NULL
        }
    };

    dwError = LsaProcessConfig(
                "Services\\lsass\\Parameters\\SAM",
                "Policy\\Services\\lsass\\Parameters\\SAM",
                SamConfig,
                sizeof(SamConfig)/sizeof(SamConfig[0]));
    BAIL_ON_LSA_ERROR(dwError);

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

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

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

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

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

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

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

    /*
     * Get the basic domain info
     */

    dwError = LwWc16sToMbs(
                    localDomain->Name,
                    &pszDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sToMbs(
                    localDomain->Name,
                    &pszNetBIOSName);
    BAIL_ON_LSA_ERROR(dwError);

    ntStatus = RtlDuplicateSid(
                    &localDomainSid,
                    localDomain->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    llSequenceNumber = localDomain->Info.Domain.SequenceNumber;

    /*
     * Check if we should update any of domain parameters
     */

    /* MinPasswordAge */
    llMinPasswordAge = localDomain->Info.Domain.MinPasswordAge;
    llNewMinPasswordAge = LW_WINTIME_TO_NTTIME_REL(dwNewMinPasswordAge);

    if (llMinPasswordAge != llNewMinPasswordAge)
    {
        localDomain->Info.Domain.MinPasswordAge = llNewMinPasswordAge;
        updateFields |= SAMDB_FIELD_DOMAIN_MIN_PASSWORD_AGE;
    }

    /* MaxPasswordAge */
    llMaxPasswordAge = localDomain->Info.Domain.MaxPasswordAge;
    llNewMaxPasswordAge = LW_WINTIME_TO_NTTIME_REL(dwNewMaxPasswordAge);

    if (llMaxPasswordAge != llNewMaxPasswordAge)
    {
        localDomain->Info.Domain.MaxPasswordAge = llNewMaxPasswordAge;
        updateFields |= SAMDB_FIELD_DOMAIN_MAX_PASSWORD_AGE;
    }

    /* MinPasswordLength */
    dwMinPasswordLength = localDomain->Info.Domain.MinPasswordLength;

    if (dwMinPasswordLength != dwNewMinPasswordLength)
    {
        localDomain->Info.Domain.MinPasswordLength = dwNewMinPasswordLength;
        updateFields |= SAMDB_FIELD_DOMAIN_MIN_PASSWORD_LENGTH;
    }

    /* PasswordPromptTime */
    llPasswordPromptTime = localDomain->Info.Domain.PasswordChangeTime;
    llNewPasswordPromptTime = LW_WINTIME_TO_NTTIME_REL(dwNewPasswordPromptTime);

    if (llPasswordPromptTime != llNewPasswordPromptTime)
    {
        localDomain->Info.Domain.PasswordChangeTime = llNewPasswordPromptTime;
        updateFields |= SAMDB_FIELD_DOMAIN_PASSWORD_CHANGE_TIME;
    }

    /* LockoutThreshold */
    dwLockoutThreshold = localDomain->Info.Domain.LockoutThreshold;

    if (dwLockoutThreshold != dwNewLockoutThreshold)
    {
        localDomain->Info.Domain.LockoutThreshold = dwNewLockoutThreshold;
        updateFields |= SAMDB_FIELD_DOMAIN_LOCKOUT_THRESHOLD;
    }

    /* LockoutDuration */

    llNewLockoutDuration = localDomain->Info.Domain.LockoutDuration;

    if (llLockoutDuration != llNewLockoutDuration)
    {
        localDomain->Info.Domain.LockoutDuration = llNewLockoutDuration;
        updateFields |= SAMDB_FIELD_DOMAIN_LOCKOUT_DURATION;
    }

    /* LockoutWindow */
    llLockoutWindow = localDomain->Info.Domain.LockoutWindow;
    llNewLockoutWindow = LW_WINTIME_TO_NTTIME_REL(dwNewLockoutWindow);

    if (llLockoutWindow != llNewLockoutWindow)
    {
        llLockoutWindow = localDomain->Info.Domain.LockoutWindow;
        updateFields |= SAMDB_FIELD_DOMAIN_LOCKOUT_WINDOW;
    }

    /*
     * Update domain object with current SAM configuration if
     * there was any change found
     */

    if (updateFields)
    {
        dwError = SamDbUpdateObject(
                          localDomain,
                          updateFields);
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = SamDbRegisterCallback(
                           &registration,
                           NULL,
                           localDomainSid,
                           FALSE,
                           LocalNotifyDomainChanged);
    BAIL_ON_LSA_ERROR(dwError);

    pGlobals->LocalDomainChangeCallback = registration;

    pGlobals->pszLocalDomain       = pszDomainName;
    pGlobals->pszNetBIOSName       = pszNetBIOSName;
    pGlobals->pLocalDomainSID      = localDomainSid;
    pGlobals->llMinPwdAge          = llMinPasswordAge;
    pGlobals->llMaxPwdAge          = llMaxPasswordAge;
    pGlobals->dwMinPwdLength       = dwMinPasswordLength;
    pGlobals->llPwdChangeTime      = llPasswordPromptTime;
    pGlobals->dwLockoutThreshold   = dwLockoutThreshold;
    pGlobals->llLockoutDuration    = llLockoutDuration;
    pGlobals->llLockoutWindow      = llLockoutWindow;

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

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

    if (localDomain)
    {
        SamDbFreeObject(localDomain);
    }

    LW_SAFE_FREE_MEMORY(builtinDomainSid);

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

    return dwError;
}


DWORD
LocalGetDomainInfo(
    PSTR*   ppszNetBIOSName,
    PSTR*   ppszDomainName,
    PSID*   ppDomainSID,
    PLONG64 pllMaxPwdAge,
    PLONG64 pllPwdChangeTime
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    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;
    PSID localDomainSid = NULL;
    PSID builtinDomainSid = NULL;
    PSTR pszDomainName = NULL;
    PSTR pszNetBIOSName = NULL;
    LONG64 llMaxPasswordAge = 0;
    LONG64 llPasswordPromptTime = 0;

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

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

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

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

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

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

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

    /*
     * Get the basic domain info
     */

    dwError = LwWc16sToMbs(
                    localDomain->Name,
                    &pszDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sToMbs(
                    localDomain->Name,
                    &pszNetBIOSName);
    BAIL_ON_LSA_ERROR(dwError);

    ntStatus = RtlDuplicateSid(
                    &localDomainSid,
                    localDomain->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    llMaxPasswordAge = localDomain->Info.Domain.MaxPasswordAge;
    llPasswordPromptTime = localDomain->Info.Domain.PasswordChangeTime;

cleanup:
error:
    if (dwError || ntStatus)
    {
        LW_SAFE_FREE_STRING(pszDomainName);
        LW_SAFE_FREE_STRING(pszNetBIOSName);
        RTL_FREE(&localDomainSid);
    }

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

    if (localDomain)
    {
        SamDbFreeObject(localDomain);
    }

    *ppszDomainName = pszDomainName;
    *ppszNetBIOSName = pszNetBIOSName;
    *ppDomainSID = localDomainSid;
    *pllMaxPwdAge = llMaxPasswordAge;
    *pllPwdChangeTime = llPasswordPromptTime;

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

    return dwError;
}


DWORD
LocalDirSetDomainName(
    IN PCSTR  pszNewDomainName
    )
{
    DWORD dwError = ERROR_SUCCESS;
    PWSTR pwszNewDomainName = NULL;

    dwError = LwMbsToWc16s(pszNewDomainName,
                           &pwszNewDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = SamDbChangeDomainNameSid(
                           pwszNewDomainName,
                           NULL);
    BAIL_ON_LSA_ERROR(dwError);

error:
    LW_SAFE_FREE_MEMORY(pwszNewDomainName);

    return dwError;
}


DWORD
LocalDirGetDomainName(
    IN PSTR  *ppszDomainName
    )
{
    DWORD err = ERROR_SUCCESS;
    BOOLEAN locked = FALSE;
    PSTR domainName = NULL;

    if (ppszDomainName == NULL)
    {
        err = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(err);
    }

    LOCAL_RDLOCK_RWLOCK(locked, &gLPGlobals.rwlock);

    err = LwAllocateString(
                       gLPGlobals.pszNetBIOSName,
                       &domainName);
    BAIL_ON_LSA_ERROR(err);

    *ppszDomainName = domainName;

error:
    LOCAL_UNLOCK_RWLOCK(locked, &gLPGlobals.rwlock);

    if (err)
    {
        LW_SAFE_FREE_MEMORY(domainName);
        if (ppszDomainName)
        {
            *ppszDomainName = NULL;
        }
    }

    return err;
}


DWORD
LocalDirSetDomainSid(
    IN PCSTR  pszSid
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN inLock = FALSE;
    PSID pNewDomainSid = NULL;
    PSAMDB_CALLBACK_REGISTRATION registration = NULL;

    ntStatus = RtlAllocateSidFromCString(
                               &pNewDomainSid,
                               pszSid);
    BAIL_ON_NT_STATUS(ntStatus);

    dwError = SamDbChangeDomainNameSid(
                               NULL,
                               pNewDomainSid);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = SamDbRegisterCallback(
                           &registration,
                           NULL,
                           pNewDomainSid,
                           FALSE,
                           LocalNotifyDomainChanged);
    BAIL_ON_LSA_ERROR(dwError);

    LOCAL_WRLOCK_RWLOCK(inLock, &gLPGlobals.rwlock);

    SamDbUnregisterCallback(gLPGlobals.LocalDomainChangeCallback);
    gLPGlobals.LocalDomainChangeCallback = registration;

error:
    LOCAL_UNLOCK_RWLOCK(inLock, &gLPGlobals.rwlock);

    RTL_FREE(&pNewDomainSid);

    return dwError;
}


DWORD
LocalDirGetDomainSid(
    IN PSTR  *ppszDomainSid
    )
{
    DWORD err = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN locked = FALSE;
    PSTR pszDomainSid = NULL;

    if (ppszDomainSid == NULL)
    {
        err = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(err);
    }

    LOCAL_RDLOCK_RWLOCK(locked, &gLPGlobals.rwlock);

    ntStatus = RtlAllocateCStringFromSid(
                       &pszDomainSid,
                       gLPGlobals.pLocalDomainSID);
    BAIL_ON_NT_STATUS(ntStatus);

    *ppszDomainSid = pszDomainSid;

error:
    LOCAL_UNLOCK_RWLOCK(locked, &gLPGlobals.rwlock);

    if (err || ntStatus)
    {
        LW_SAFE_FREE_MEMORY(pszDomainSid);
        if (ppszDomainSid)
        {
            *ppszDomainSid = NULL;
        }
    }

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

    return err;
}


static
VOID
LocalNotifyDomainChanged(
    PVOID Context,
    PSID ObjectSid
    )
{
    BOOLEAN locked = FALSE;
    DWORD dwError = ERROR_SUCCESS;

    LOCAL_WRLOCK_RWLOCK(locked, &gLPGlobals.rwlock);

    //
    // Reload local domain info
    //
    LW_SAFE_FREE_STRING(gLPGlobals.pszLocalDomain);
    LW_SAFE_FREE_STRING(gLPGlobals.pszNetBIOSName);
    LW_SAFE_FREE_MEMORY(gLPGlobals.pLocalDomainSID);

    dwError = LocalGetDomainInfo(
                    &gLPGlobals.pszLocalDomain,
                    &gLPGlobals.pszNetBIOSName,
                    &gLPGlobals.pLocalDomainSID,
                    &gLPGlobals.llMaxPwdAge,
                    &gLPGlobals.llPwdChangeTime);
    if (dwError)
    {
        LSA_LOG_ERROR("Error while reloading local domain info: %s (%u)",
                      LSA_SAFE_LOG_STRING(LwWin32ErrorToName(dwError)),
                      dwError);
    }

    //
    // Check if local domain SID has actually changed before updating
    // subscription.
    //
    if (RtlEqualSid(
                gLPGlobals.pLocalDomainSID,
                ObjectSid))
    {
        goto cleanup;
    }

    dwError = LsaSrvCreateWorkItem(
                    &gLPGlobals.WorkItem,
                    LocalUpdateLocalDomainSidSubscription,
                    NULL);
    if (dwError)
    {
        LSA_LOG_ERROR("Error while creating work item to update local "
                      "domain SID subcription: %s (%u)",
                      LSA_SAFE_LOG_STRING(LwWin32ErrorToName(dwError)),
                      dwError);
    }

    LsaSrvScheduleWorkItem(
                     gLPGlobals.WorkItem,
                     0);

cleanup:
    LOCAL_UNLOCK_RWLOCK(locked, &gLPGlobals.rwlock);
}


static
VOID
LocalUpdateLocalDomainSidSubscription(
    PLW_WORK_ITEM WorkItem,
    PVOID Context
    )
{
    BOOLEAN inLock = FALSE;
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CALLBACK_REGISTRATION callback = NULL;

    LOCAL_WRLOCK_RWLOCK(inLock, &gLPGlobals.rwlock);

    //
    // Register subscription for changes under new SID
    //
    error = SamDbRegisterCallback(
                 &callback,
                 NULL,
                 gLPGlobals.pLocalDomainSID,
                 FALSE,
                 LocalNotifyDomainChanged);
    BAIL_ON_LSA_ERROR(error);

    //
    // Unregister subscription for old SID
    //
    SamDbUnregisterCallback(gLPGlobals.LocalDomainChangeCallback);

    gLPGlobals.LocalDomainChangeCallback = callback;

error:
    LsaSrvFreeWorkItem(&WorkItem);

    LOCAL_UNLOCK_RWLOCK(inLock, &gLPGlobals.rwlock);
}
