/* -*- 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:
 *
 *        lpgroup.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Local Authentication Provider
 *
 *        User/Group Database Interface
 *
 * Authors: Krishna Ganugapati (krishnag@likewise.com)
 *          Sriram Nambakam (snambakam@likewise.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 */
#include "includes.h"

static
DWORD
LocalDirValidateGID(
    gid_t gid
    );

static
DWORD
LocalAddMembersToGroup(
    PLOCAL_PROVIDER_CONTEXT pContext,
    PSID pGroupSid,
    DWORD dwMemberCount,
    PSTR* ppszMemberSids
    );

DWORD
LocalDirAddGroup(
    HANDLE            hProvider,
    PLSA_GROUP_ADD_INFO pGroupInfo
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PLOCAL_PROVIDER_CONTEXT pContext = (PLOCAL_PROVIDER_CONTEXT)hProvider;
    BOOLEAN bEventlogEnabled = FALSE;
    PLSA_LOGIN_NAME_INFO pLoginInfo = NULL;
    SAMDB_OBJECT_INFO group = {0};
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* objects = NULL;
    PSID specifiedSid = NULL;
    PSID groupSid = NULL;
    BOOLEAN bLocked = FALSE;

    BAIL_ON_INVALID_STRING(pGroupInfo->pszName);

    if (pGroupInfo->gid)
    {
        dwError = LocalDirValidateGID(pGroupInfo->gid);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwAllocateMemory(sizeof(group.Info.LocalGroup.UnixInfo),
                                   OUT_PPVOID(&group.Info.LocalGroup.UnixInfo));
        BAIL_ON_LSA_ERROR(dwError);

        group.Info.LocalGroup.UnixInfo->Gid = pGroupInfo->gid;
    }

    dwError = LsaSrvCrackDomainQualifiedName(
                    pGroupInfo->pszName,
                    &pLoginInfo);
    BAIL_ON_LSA_ERROR(dwError);

    if (!LW_IS_NULL_OR_EMPTY_STR(pGroupInfo->pszSid))
    {
        ntStatus = RtlAllocateSidFromCString(&specifiedSid,
                                             pGroupInfo->pszSid);
        BAIL_ON_NT_STATUS(ntStatus);
    }

    LOCAL_RDLOCK_RWLOCK(bLocked, &gLPGlobals.rwlock);

    if (!pLoginInfo->pszDomain)
    {
        dwError = LwAllocateString(
                        gLPGlobals.pszNetBIOSName,
                        &pLoginInfo->pszDomain);
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (!LocalServicesDomainInternal(pLoginInfo->pszDomain))
    {
        dwError = LW_ERROR_NOT_HANDLED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwMbsToWc16s(
                        pGroupInfo->pszName,
                        &group.Name);
    BAIL_ON_LSA_ERROR(dwError);

    group.Type = SAMDB_OBJECT_TYPE_LOCAL_GROUP;
    group.Info.LocalGroup.LikewiseFlags = SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;

    dwError = SamDbAddObject(&group, specifiedSid);
    BAIL_ON_LSA_ERROR(dwError);

    query.Name = group.Name;

    dwError = SamDbFindObjects(
                        SAMDB_OBJECT_TYPE_LOCAL_GROUP,
                        SAMDB_QUERY_TYPE_NT_NAME,
                        1,
                        &query,
                        &objects);
    BAIL_ON_LSA_ERROR(dwError);

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

        LSA_LOG_ERROR("Couldn't find local group account which has just been created");
        goto error;
    }

    groupSid = objects[0]->Sid;

    if (pGroupInfo->dwMemberCount)
    {
        dwError = LocalAddMembersToGroup(
                        pContext,
                        groupSid,
                        pGroupInfo->dwMemberCount,
                        pGroupInfo->ppszMemberSids);
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LocalCfgIsEventlogEnabled(&bEventlogEnabled);
    BAIL_ON_LSA_ERROR(dwError);

    if (bEventlogEnabled)
    {
        LocalEventLogGroupAdd(pLoginInfo->pszName,
                             ((PLSA_GROUP_INFO_0)pGroupInfo)->gid);
    }

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

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

    if (pLoginInfo)
    {
        LsaSrvFreeNameInfo(pLoginInfo);
    }

    if (specifiedSid)
    {
        RTL_FREE(&specifiedSid);
    }

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

    LW_SAFE_FREE_MEMORY(group.Name);

    return dwError;
}


static
DWORD
LocalDirValidateGID(
    gid_t gid
    )
{
    DWORD dwError = 0;

    /* Check whether group gid is within permitted range */
    if (gid < LOWEST_GID) {
        BAIL_WITH_LSA_ERROR(LW_ERROR_GID_TOO_LOW);
    }

cleanup:
    return dwError;

error:
    goto cleanup;
}


DWORD
LocalDirModifyGroup(
    HANDLE hProvider,
    PLSA_GROUP_MOD_INFO_2 pGroupModInfo
    )
{
    DWORD dwError = 0;
    NTSTATUS ntStatus = 0;
    PSID pGroupSid = NULL;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO *ppGroupObjects = NULL;
    PSAMDB_OBJECT_INFO *ppUserObjects = NULL;
    DWORD i = 0;
    PSTR pszSid = NULL;
    PSID *ppGroupMemberSids = NULL;
    PSID pMemberSid = NULL;
    DWORD dwMembersCount = 0;
    PSID *ppMembersToAdd = NULL;
    PSID *ppMembersToRemove = NULL;
    DWORD dwNumMembersToAdd = 0;
    DWORD dwNumMembersToRemove = 0;

    ntStatus = RtlAllocateSidFromCString(&pGroupSid, pGroupModInfo->pszSid);
    BAIL_ON_NT_STATUS(ntStatus);

    query.Sid = pGroupSid;

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

    if (ppGroupObjects[0] == NULL)
    {
        dwError = ERROR_NO_SUCH_ALIAS;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = SamDbQueryMembers(
                        pGroupSid,
                        &dwMembersCount,
                        &ppGroupMemberSids);
    BAIL_ON_LSA_ERROR(dwError);

    if (pGroupModInfo->actions.bAddMembers)
    {
        dwError = LwAllocateMemory(
                    pGroupModInfo->dwAddMembersNum * sizeof(*ppMembersToAdd),
                    OUT_PPVOID(&ppMembersToAdd));
        BAIL_ON_LSA_ERROR(dwError);

        for (i = 0; i < pGroupModInfo->dwAddMembersNum; i++)
        {
            pszSid = pGroupModInfo->ppszAddMembers[i];

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

            ppMembersToAdd[i] = pMemberSid;
            pMemberSid = NULL;
            dwNumMembersToAdd++;
        }
    }
    else if (pGroupModInfo->actions.bRemoveMembers)
    {
        dwError = LwAllocateMemory(
                 pGroupModInfo->dwRemoveMembersNum * sizeof(*ppMembersToRemove),
                 OUT_PPVOID(&ppMembersToRemove));
        BAIL_ON_LSA_ERROR(dwError);

        for (i = 0; i < pGroupModInfo->dwRemoveMembersNum; i++)
        {
            pszSid = pGroupModInfo->ppszRemoveMembers[i];

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

            ppMembersToRemove[i] = pMemberSid;
            pMemberSid = NULL;
            dwNumMembersToRemove++;
        }
    }

    dwError = SamDbUpdateMembers(pGroupSid,
                                 dwNumMembersToAdd,
                                 ppMembersToAdd,
                                 dwNumMembersToRemove,
                                 ppMembersToRemove);
    BAIL_ON_LSA_ERROR(dwError);

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

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

    if (ppGroupMemberSids)
    {
        SamDbFreeSidList(dwMembersCount, ppGroupMemberSids);
    }

    RTL_FREE(&pMemberSid);
    RTL_FREE(&pGroupSid);

    if (ppMembersToAdd)
    {
        SamDbFreeSidList(dwNumMembersToAdd, ppMembersToAdd);
    }

    if (ppMembersToRemove)
    {
        SamDbFreeSidList(dwNumMembersToRemove, ppMembersToRemove);
    }

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

    return dwError;
}

static
DWORD
LocalAddMembersToGroup(
    PLOCAL_PROVIDER_CONTEXT pContext,
    PSID GroupSid,
    DWORD dwMemberCount,
    PSTR* ppszMemberSids
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    LSA_GROUP_MOD_INFO_2 groupModInfo = {0};
    PSTR pszGroupSid = NULL;

    ntStatus = RtlAllocateCStringFromSid(
                          &pszGroupSid,
                          GroupSid);
    BAIL_ON_NT_STATUS(ntStatus);

    groupModInfo.actions.bAddMembers = TRUE;
    groupModInfo.ppszAddMembers = ppszMemberSids;
    groupModInfo.pszSid = pszGroupSid;

    dwError = LocalDirModifyGroup(
        pContext,
        &groupModInfo);
    BAIL_ON_LSA_ERROR(dwError);

cleanup:
    LW_SAFE_FREE_STRING(pszGroupSid);

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

    return dwError;

error:

    goto cleanup;
}

DWORD
LocalDirDeleteGroup(
    HANDLE hProvider,
    PSID   pSid
    )
{
    DWORD dwError = ERROR_SUCCESS;

    dwError = SamDbDeleteObject(pSid);
    BAIL_ON_LSA_ERROR(dwError);

error:
    return dwError;
}

VOID
LocalDirFreeGroupMemberList(
    PLOCAL_PROVIDER_GROUP_MEMBER* ppMemberList,
    DWORD                         dwNumMembers
    )
{
    DWORD iMember = 0;

    for (; iMember < dwNumMembers; iMember++)
    {
        if (ppMemberList[iMember])
        {
            LocalDirFreeGroupMember(ppMemberList[iMember]);
        }
    }

    LwFreeMemory(ppMemberList);
}

VOID
LocalDirFreeGroupMember(
    PLOCAL_PROVIDER_GROUP_MEMBER pMember
    )
{
    LW_SAFE_FREE_STRING(pMember->pszNetbiosDomain);
    LW_SAFE_FREE_STRING(pMember->pszSamAccountName);
    LW_SAFE_FREE_STRING(pMember->pszSID);

    LwFreeMemory(pMember);
}
