/* -*- 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:
 *
 *        lpmarshal.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Local Authentication Provider (Defines)
 *
 *        Marshal from DIRECTORY structures to lsass info levels
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewisesoftware.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 */

#include "includes.h"


static
DWORD
LocalMarshalSamDbLocalGroupObjectToGroupSecurityObject(
    PSAMDB_OBJECT_INFO SamDbObject,
    PSAMDB_OBJECT_INFO DomainObject,
    PLSA_SECURITY_OBJECT LsaObject
    );

static
DWORD
LocalMarshalSamDbUserObjectToUserSecurityObject(
    PSAMDB_OBJECT_INFO SamDbObject,
    PSAMDB_OBJECT_INFO DomainObject,
    PLSA_SECURITY_OBJECT LsaObject
    );


DWORD
LocalMarshalSamDbObjectToSecurityObject(
    PSAMDB_OBJECT_INFO SamDbObject,
    PLSA_SECURITY_OBJECT* pLsaObject
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLSA_SECURITY_OBJECT lsaObject = NULL;
    PSID domainSid = NULL;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* domainObjects = NULL;
    PSAMDB_OBJECT_INFO domainObject = NULL;
    PSTR pszObjectSid = NULL;
    PSTR pszDomainSid = NULL;

    if (SamDbObject->Type != SAMDB_OBJECT_TYPE_USER &&
        SamDbObject->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        dwError = ERROR_INVALID_PARAMETER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    ntStatus = RtlDuplicateSid(
                     &domainSid,
                     SamDbObject->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    domainSid->SubAuthorityCount--;
    query.Sid = domainSid;

    dwError = SamDbFindObjects(
                     SAMDB_OBJECT_TYPE_DOMAIN,
                     SAMDB_QUERY_TYPE_SID,
                     1,
                     &query,
                     &domainObjects);
    BAIL_ON_LSA_ERROR(dwError);

    if (domainObjects[0] == NULL)
    {
        dwError = ERROR_INTERNAL_ERROR;

        LSA_LOG_ERROR("Couldn't find the local domain object.");
        BAIL_ON_LSA_ERROR(dwError);
    }

    domainObject = domainObjects[0];
    domainObjects[0] = NULL;

    dwError = LwAllocateMemory(
                     sizeof(*lsaObject),
                     OUT_PPVOID(&lsaObject));
    BAIL_ON_LSA_ERROR(dwError);

    lsaObject->bIsLocal = TRUE;

    ntStatus = RtlAllocateCStringFromSid(
                     &pszObjectSid,
                     SamDbObject->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    dwError = LwAllocateString(
                     pszObjectSid,
                     &lsaObject->pszObjectSid);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sToMbs(
                     domainObject->Name,
                     &lsaObject->pszNetbiosDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sToMbs(
                     SamDbObject->Name,
                     &lsaObject->pszSamAccountName);
    BAIL_ON_LSA_ERROR(dwError);

    switch (SamDbObject->Type)
    {
    case SAMDB_OBJECT_TYPE_USER:
        lsaObject->type = LSA_OBJECT_TYPE_USER;

        if (SamDbObject->Info.User.LikewiseFlags &
            SAMDB_LIKEWISE_FLAG_UNIX_ENABLED)
        {
            lsaObject->enabled = TRUE;
        }

        dwError = LocalMarshalSamDbUserObjectToUserSecurityObject(
                                       SamDbObject,
                                       domainObject,
                                       lsaObject);
        BAIL_ON_LSA_ERROR(dwError);
        break;

    case SAMDB_OBJECT_TYPE_LOCAL_GROUP:
        lsaObject->type = LSA_OBJECT_TYPE_GROUP;

        if (SamDbObject->Info.LocalGroup.LikewiseFlags &
            SAMDB_LIKEWISE_FLAG_UNIX_ENABLED)
        {
            lsaObject->enabled = TRUE;
        }

        dwError = LocalMarshalSamDbLocalGroupObjectToGroupSecurityObject(
                                       SamDbObject,
                                       domainObject,
                                       lsaObject);
        BAIL_ON_LSA_ERROR(dwError);
        break;

    default:
        break;
    }

error:
    if (dwError || ntStatus)
    {
        if (lsaObject)
        {
            LsaFreeSecurityObject(lsaObject);
            lsaObject = NULL;
        }
    }

    if (domainObjects)
    {
        SamDbFreeObjects(1, domainObjects);
    }

    if (domainObject)
    {
        SamDbFreeObject(domainObject);
    }

    RTL_FREE(&domainSid);
    RTL_FREE(&pszObjectSid);
    RTL_FREE(&pszDomainSid);

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

    *pLsaObject = lsaObject;

    return dwError;
}


static
DWORD
LocalMarshalSamDbUserObjectToUserSecurityObject(
    PSAMDB_OBJECT_INFO SamDbObject,
    PSAMDB_OBJECT_INFO DomainObject,
    PLSA_SECURITY_OBJECT LsaObject
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSID primaryGroupSid = NULL;
    PSTR pszPrimaryGroupSid = NULL;
    PSTR pszDomainName = NULL;
    LONG64 minPasswordAge = 0;
    LONG64 maxPasswordAge = 0;
    LONG64 currentTime = 0;
    LONG64 passwordChangeTime = 0;
    LONG64 passwordAge = 0;
    LONG64 timeToExpiration = 0;
    PSAMDB_OBJECT_INFO* groupObjects = NULL;
    PSAMDB_OBJECT_INFO primaryGroupObject = NULL;

    //
    // Get Primary Group SID from user's domain and PrimaryGroupRid
    //
    ntStatus = RtlDuplicateSid(
                      &primaryGroupSid,
                      SamDbObject->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    primaryGroupSid->SubAuthority[primaryGroupSid->SubAuthorityCount - 1] =
        SamDbObject->Info.User.PrimaryGroupRid;

    ntStatus = RtlAllocateCStringFromSid(
                      &pszPrimaryGroupSid,
                      primaryGroupSid);
    BAIL_ON_NT_STATUS(ntStatus);

    dwError = LwAllocateString(
                      pszPrimaryGroupSid,
                      &LsaObject->userInfo.pszPrimaryGroupSid);
    BAIL_ON_LSA_ERROR(dwError);

    //
    // UPN has to be generated because SamDb is local and it does not
    // store UPN names
    //
    dwError = LwWc16sToMbs(
                      DomainObject->Name,
                      &pszDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwAllocateStringPrintf(
                      &LsaObject->userInfo.pszUPN,
                      "%s@%s",
                      LsaObject->pszSamAccountName,
                      pszDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    LsaObject->userInfo.bIsGeneratedUPN = TRUE;

    //
    // There is no alias name for local accounts
    //
    LsaObject->userInfo.pszAliasName = NULL;

    //
    // Password change/expiration timers
    //
    dwError = LocalCfgGetMinPasswordAge(&minPasswordAge);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LocalCfgGetMaxPasswordAge(&maxPasswordAge);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LocalCfgGetPasswordChangeWarningTime(&passwordChangeTime);
    BAIL_ON_LSA_ERROR(dwError);

    LsaObject->userInfo.qwPwdLastSet     = SamDbObject->Info.User.PasswordLastSet;
    LsaObject->userInfo.qwMaxPwdAge      = maxPasswordAge;
    LsaObject->userInfo.qwPwdExpires
        = LsaObject->userInfo.qwPwdLastSet + maxPasswordAge;
    LsaObject->userInfo.qwAccountExpires = SamDbObject->Info.User.AccountExpires;

    LsaObject->userInfo.bIsAccountInfoKnown = TRUE;

    dwError = LwGetNtTime((PULONG64)&currentTime);
    BAIL_ON_LSA_ERROR(dwError);

    if (SamDbObject->Info.User.UserAccountControl &
        SAMDB_UF_FLAG_DONT_EXPIRE_PASSWD)
    {
        LsaObject->userInfo.bPasswordNeverExpires = TRUE;
    }
    else
    {
        if (currentTime > (LsaObject->userInfo.qwPwdLastSet +
                           LsaObject->userInfo.qwMaxPwdAge))
        {
            LsaObject->userInfo.bPasswordExpired      = TRUE;
            LsaObject->userInfo.bPromptPasswordChange = TRUE;
        }
        else
        {
            LsaObject->userInfo.bPasswordExpired = FALSE;

            passwordAge = currentTime - SamDbObject->Info.User.PasswordLastSet;

            if (minPasswordAge <= passwordAge)
            {
                LsaObject->userInfo.bUserCanChangePassword = TRUE;
            }

            timeToExpiration = maxPasswordAge - passwordAge;
            if (timeToExpiration <= passwordChangeTime)
            {
                LsaObject->userInfo.bPromptPasswordChange = TRUE;
            }
        }
    }

    if (SamDbObject->Info.User.AccountExpires &&
        currentTime > SamDbObject->Info.User.AccountExpires)
    {
        LsaObject->userInfo.bAccountExpired = TRUE;
    }

    if (SamDbObject->Info.User.UserAccountControl &
        SAMDB_UF_FLAG_ACCOUNTDISABLE)
    {
        LsaObject->userInfo.bAccountDisabled = TRUE;
    }

    //
    // Password hashes
    //
    if (SamDbObject->Info.User.LmOwfPassword.IsPresent)
    {
        LsaObject->userInfo.dwLmHashLen =
            sizeof(SamDbObject->Info.User.LmOwfPassword.Data);
        if (LsaObject->userInfo.dwLmHashLen)
        {
            dwError = LwAllocateMemory(
                           LsaObject->userInfo.dwLmHashLen,
                           OUT_PPVOID(&LsaObject->userInfo.pLmHash));
            BAIL_ON_LSA_ERROR(dwError);

            memcpy(LsaObject->userInfo.pLmHash,
                   SamDbObject->Info.User.LmOwfPassword.Data,
                   LsaObject->userInfo.dwLmHashLen);
        }
    }

    if (SamDbObject->Info.User.NtOwfPassword.IsPresent)
    {
        LsaObject->userInfo.dwNtHashLen =
            sizeof(SamDbObject->Info.User.NtOwfPassword.Data);
        if (LsaObject->userInfo.dwNtHashLen)
        {
            dwError = LwAllocateMemory(
                           LsaObject->userInfo.dwNtHashLen,
                           OUT_PPVOID(&LsaObject->userInfo.pNtHash));
            BAIL_ON_LSA_ERROR(dwError);

            memcpy(LsaObject->userInfo.pNtHash,
                   SamDbObject->Info.User.NtOwfPassword.Data,
                   LsaObject->userInfo.dwNtHashLen);
        }
    }

    //
    // Unix fields
    //
    if (LsaObject->enabled)
    {
        SAMDB_QUERY query = {0};

        LsaObject->userInfo.uid = SamDbObject->Info.User.UnixInfo->Uid;

        query.Sid = primaryGroupSid;

        dwError = SamDbFindObjects(
                         SAMDB_OBJECT_TYPE_LOCAL_GROUP,
                         SAMDB_QUERY_TYPE_SID,
                         1,
                         &query,
                         &groupObjects);
        BAIL_ON_LSA_ERROR(dwError);

        if (groupObjects[0] == NULL)
        {
            dwError = ERROR_INTERNAL_ERROR;

            LSA_LOG_ERROR("Couldn't find the primary group of user %s.",
                          LsaObject->pszSamAccountName);
            BAIL_ON_LSA_ERROR(dwError);
        }

        primaryGroupObject = groupObjects[0];
        groupObjects[0] = NULL;

        LsaObject->userInfo.gid =
            primaryGroupObject->Info.LocalGroup.UnixInfo->Gid;

        dwError = LwWc16sToMbs(
                       SamDbObject->Name,
                       &LsaObject->userInfo.pszUnixName);
        BAIL_ON_LSA_ERROR(dwError);

        LsaObject->userInfo.pszPasswd = NULL;

        if (SamDbObject->Info.User.UnixInfo->Shell)
        {
            dwError = LwWc16sToMbs(
                           SamDbObject->Info.User.UnixInfo->Shell,
                           &LsaObject->userInfo.pszShell);
            BAIL_ON_LSA_ERROR(dwError);
        }

        if (SamDbObject->Info.User.UnixInfo->Gecos)
        {
            dwError = LwWc16sToMbs(
                           SamDbObject->Info.User.UnixInfo->Gecos,
                           &LsaObject->userInfo.pszGecos);
            BAIL_ON_LSA_ERROR(dwError);
        }

        if (SamDbObject->Info.User.HomeDirectory)
        {
            dwError = LwWc16sToMbs(
                           SamDbObject->Info.User.HomeDirectory,
                           &LsaObject->userInfo.pszHomedir);
            BAIL_ON_LSA_ERROR(dwError);
        }
    }

error:
    if (groupObjects)
    {
        SamDbFreeObjects(1, groupObjects);
    }

    if (primaryGroupObject)
    {
        SamDbFreeObject(primaryGroupObject);
    }

    RTL_FREE(&primaryGroupSid);
    RTL_FREE(&pszPrimaryGroupSid);
    LW_SAFE_FREE_MEMORY(pszDomainName);

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


    return dwError;
}


static
DWORD
LocalMarshalSamDbLocalGroupObjectToGroupSecurityObject(
    PSAMDB_OBJECT_INFO SamDbObject,
    PSAMDB_OBJECT_INFO DomainObject,
    PLSA_SECURITY_OBJECT LsaObject
    )
{
    DWORD dwError = ERROR_SUCCESS;

    dwError = LwWc16sToMbs(
                   SamDbObject->Name,
                   &LsaObject->groupInfo.pszUnixName);
    BAIL_ON_LSA_ERROR(dwError);

    if (LsaObject->enabled)
    {
        LsaObject->groupInfo.gid = SamDbObject->Info.LocalGroup.UnixInfo->Gid;
    }

    LsaObject->groupInfo.pszAliasName = NULL;
    LsaObject->groupInfo.pszPasswd = NULL;

error:
    return dwError;
}
