/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright Likewise Software
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.  You should have received a copy of the GNU General
 * Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Copyright (C) Likewise Software. All rights reserved.
 *
 * Module Name:
 *
 *        samdbdel.c
 *
 * Abstract:
 *
 *
 *      Likewise SAM Database Provider
 *
 *      SAM objects delete routines
 *
 * Authors: Krishna Ganugapati (krishnag@likewise.com)
 *          Sriram Nambakam (snambakam@likewise.com)
 *          Rafal Szczesniak (rafal@likewise.com)
 *
 */

#include "includes.h"


static
DWORD
SamDbBuildDeleteObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSID Sid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    );


DWORD
SamDbDeleteObject(
    IN PSID Sid
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_CONTEXT dbContext = NULL;
    BOOLEAN transactionStarted = FALSE;
    SAMDB_OBJECT_CLASS objectClass = SAMDB_OBJECT_CLASS_UNKNOWN;
    SAMDB_QUERY_TYPE queryType = SAMDB_QUERY_TYPE_SID;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO object = NULL;
    DWORD rid = 0;
    DWORD membershipsCount = 0;
    PSID* memberships = NULL;
    PSID localDomainSid = NULL;
    DWORD membershipIndex = 0;
    DWORD membersCount = 0;
    PSID* members = NULL;
    DWORD memberIndex = 0;
    PSAMDB_OBJECT_INFO userObject = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributesCount = 0;

    BAIL_ON_INVALID_POINTER(Sid, error);

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

    SAMDB_BEGIN_TRANSACTION(transactionStarted, dbContext, error);

    query.Sid = Sid;

    error = SamDbFindObjectInternal(
                     dbContext,
                     objectClass,
                     queryType,
                     &query,
                     &object);
    BAIL_ON_SAMDB_ERROR(error);

    if (object->Type != SAMDB_OBJECT_TYPE_USER &&
        object->Type != SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        // Only user or local group can be deleted
        error = ERROR_INVALID_PARAMETER;
        BAIL_ON_SAMDB_ERROR(error);
    }

    rid = Sid->SubAuthority[Sid->SubAuthorityCount - 1];

    if (rid < SAM_DB_MIN_RID)
    {
        // Well-known accounts must not removed
        error = ERROR_SPECIAL_ACCOUNT;
        BAIL_ON_SAMDB_ERROR(error);
    }

    if (object->Type == SAMDB_OBJECT_TYPE_USER)
    {
        //
        // Looks like user account can be removed to let's delete
        // its memberships first
        //
        error = SamDbQueryMemberOf(
                         Sid,
                         &membershipsCount,
                         &memberships);
        BAIL_ON_SAMDB_ERROR(error);

        for (membershipIndex = 0;
             membershipIndex < membershipsCount;
             membershipIndex++)
        {
            error = SamDbRemoveMembersInternal(
                             dbContext,
                             memberships[membershipIndex],
                             1,
                             &Sid);
            BAIL_ON_SAMDB_ERROR(error);
        }
    }
    else // object->Type == SAMDB_OBJECT_TYPE_LOCAL_GROUP
    {
        error = SamDbGetLocalDomainSid(&localDomainSid);
        BAIL_ON_SAMDB_ERROR(error);

        //
        // Remove all members from this local group first
        //
        error = SamDbQueryMembers(
                         Sid,
                         &membersCount,
                         &members);
        BAIL_ON_SAMDB_ERROR(error);

        for (memberIndex = 0;
             memberIndex < membersCount;
             memberIndex++)
        {
            //
            // Check if this group could be a member's primary group.
            //
            query.Sid = members[memberIndex];
            error = SamDbFindObjectInternal(
                             dbContext,
                             SAMDB_OBJECT_CLASS_USER,
                             SAMDB_QUERY_TYPE_SID,
                             &query,
                             &userObject);
            if (error == ERROR_SUCCESS)
            {
                if (userObject->Info.User.PrimaryGroupRid ==
                    Sid->SubAuthority[Sid->SubAuthorityCount - 1] &&
                    RtlIsStrictPrefixSid(localDomainSid, Sid) &&
                    RtlIsStrictPrefixSid(localDomainSid, userObject->Sid))
                {
                    error = ERROR_MEMBERS_PRIMARY_GROUP;
                    BAIL_ON_SAMDB_ERROR(error);
                }

                if (userObject)
                {
                    SamDbFreeObject(userObject);
                    userObject = NULL;
                }
            }
            else if (error == ERROR_FILE_NOT_FOUND)
            {
                // Member being checked is probably a foreign security principal
                // so ignore returned error.
                error = ERROR_SUCCESS;
            }
            else if (error != ERROR_SUCCESS)
            {
                BAIL_ON_SAMDB_ERROR(error);
            }

            error = SamDbRemoveMembersInternal(
                             dbContext,
                             Sid,
                             1,
                             &members[memberIndex]);
            BAIL_ON_SAMDB_ERROR(error);
        }
    }
    //
    // Now, delete the object itself
    //

    error = SamDbBuildDeleteObjectStatement(
                     dbContext,
                     Sid,
                     &sqlStatement,
                     &inputAttributes,
                     &inputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbBindAttributeValues(
                     dbContext,
                     sqlStatement,
                     inputAttributes,
                     inputAttributesCount);
    BAIL_ON_SAMDB_ERROR(error);

    error = sqlite3_step(sqlStatement);
    if (error == SQLITE_DONE)
    {
        error = ERROR_SUCCESS;
    }
    else
    {
        SAMDB_LOG_ERROR("Error code %u returned from sqlite3_step() "
                        "while %u or %u was expected",
                        error,
                        SQLITE_DONE);
        BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, sqlStatement);
    }

    error = SamDbIncrementSequenceNumber(
                     dbContext,
                     Sid);
    BAIL_ON_SAMDB_ERROR(error);

cleanup:
    SAMDB_END_TRANSACTION(transactionStarted, dbContext, error);

    if (error == ERROR_SUCCESS)
    {
        SamDbNotifyCallbacks(Sid);
    }

    if (sqlStatement)
    {
        sqlite3_finalize(sqlStatement);
    }

    if (dbContext)
    {
        SamDbReleaseContext(dbContext);
    }

    if (memberships)
    {
        SamDbFreeSidList(
               membershipsCount,
               memberships);
    }

    if (object)
    {
        SamDbFreeObject(object);
    }

    if (members)
    {
        SamDbFreeSidList(
            membersCount,
            members);
    }

    if (inputAttributes)
    {
        SamDbFreeAttributes(
                 inputAttributes,
                 inputAttributesCount);
    }

    LW_SAFE_FREE_MEMORY(localDomainSid);

    return error;
}


#define SAMDB_SQL_DELETE_OBJECT \
    "DELETE FROM " SAM_DB_OBJECTS_TABLE " WHERE "


static
DWORD
SamDbBuildDeleteObjectStatement(
    PSAMDB_CONTEXT DbContext,
    PSID Sid,
    sqlite3_stmt** pSqlStatement,
    PSAMDB_ATTRIBUTE* pInputAttributes,
    PDWORD pInputAttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD inputAttributesCount = 0;
    PSAMDB_ATTRIBUTE inputAttributes = NULL;
    DWORD inputAttributeIndex = 0;
    size_t sqlStatementSize = 128;
    size_t sqlStatementLength = 0;
    size_t maxSqlStatementSize = 1024;
    PSTR sqlStatementString = NULL;
    sqlite3_stmt* sqlStatement = NULL;
    PWSTR* ppwszSid = NULL;

    typedef enum _INPUT_ATTRIBUTE_INDEX {
        ATTR_IDX_OBJECT_SID = 0
    } INPUT_ATTRIBUTE_INDEX;

    SAMDB_ATTRIBUTE inputAttrDef[] = {
        {
            SAMDB_ATTR_OBJECT_SID,
            SAMDB_ATTR_TYPE_TEXT,
            NULL
        },
    };

    error = LwAllocateMemory(
                    sizeof(inputAttrDef),
                    OUT_PPVOID(&inputAttributes));
    BAIL_ON_SAMDB_ERROR(error);

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

    ntStatus = RtlAllocateWC16StringFromSid(
                    ppwszSid,
                    Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    inputAttributes[inputAttributeIndex] = inputAttrDef[ATTR_IDX_OBJECT_SID];

    inputAttributes[inputAttributeIndex].Value = ppwszSid;
    inputAttributes[inputAttributeIndex].FreeFn = SamDbAttributeFreeSidString;

    //
    // Create SQL statement string (DELETE FROM samdbobjects ... )
    //
    error = LwAllocateMemory(
                    sizeof(sqlStatementString[0]) * sqlStatementSize,
                    OUT_PPVOID(&sqlStatementString));
    BAIL_ON_SAMDB_ERROR(error);

    error = SamDbPrintfString(
                    &sqlStatementString,
                    &sqlStatementSize,
                    &sqlStatementLength,
                    maxSqlStatementSize,
                    "%s%s = ?%u;",
                    SAMDB_SQL_DELETE_OBJECT,
                    inputAttributes[inputAttributeIndex].DbColumnName,
                    inputAttributeIndex + 1);
    BAIL_ON_SAMDB_ERROR(error);

    inputAttributesCount = ++inputAttributeIndex;

    error = sqlite3_prepare_v2(
                      DbContext->DbHandle,
                      sqlStatementString,
                      -1,
                      &sqlStatement,
                      NULL);
    if (error)
    {
        SAMDB_LOG_DEBUG("Error when preparing SQL statement:\n%s",
                        sqlStatementString);
    }
    BAIL_ON_SAMDB_SQLITE_ERROR_DB(error, DbContext->DbHandle);

cleanup:
error:
    if (error)
    {
        if (inputAttributes)
        {
            SamDbFreeAttributes(
                     inputAttributes,
                     inputAttributesCount);
            inputAttributes = NULL;
            inputAttributesCount = 0;
        }

        if (sqlStatement)
        {
            sqlite3_finalize(sqlStatement);
            sqlStatement = NULL;
        }
    }

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

    LW_SAFE_FREE_MEMORY(sqlStatementString);

    *pSqlStatement = sqlStatement;
    *pInputAttributes = inputAttributes;
    *pInputAttributesCount = inputAttributesCount;

    return error;
}
