/* -*- 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:
 *
 *        lsassdb.c
 *
 * Abstract:
 *
 *        Likewise Security and Authentication Subsystem (LSASS)
 *
 *        Local Authentication Provider
 *
 *        Directory User Management API
 *
 * Authors: Krishna Ganugapati (krishnag@likewisesoftware.com)
 *          Sriram Nambakam (snambakam@likewisesoftware.com)
 */
#include "includes.h"

static
DWORD
LocalDirValidateUID(
    uid_t uid
    );

static
DWORD
LocalDirGetBuiltinAdministratorsSid(
    HANDLE hProvider,
    PSTR* ppszSid
    );

DWORD
LocalDirGetUserInfoFlags(
    HANDLE hProvider,
    PSID   pSid,
    PDWORD pdwUserInfoFlags
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* userObjects = NULL;
    PSAMDB_OBJECT_INFO userObject = NULL;
    DWORD userInfoFlags = 0;

    query.Sid = pSid;

    dwError = SamDbFindObjects(
                       SAMDB_OBJECT_TYPE_USER,
                       SAMDB_QUERY_TYPE_SID,
                       1,
                       &query,
                       &userObjects);
    BAIL_ON_LSA_ERROR(dwError);

    if (userObjects[0] == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    userObject = userObjects[0];
    userInfoFlags = SamDbMapDatabaseToSamrFlags(
                        userObject->Info.User.UserAccountControl,
                        NULL);

error:
    if (dwError)
    {
        userInfoFlags = 0;
    }

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

    *pdwUserInfoFlags = userInfoFlags;

    return dwError;
}


DWORD
LocalDirAddUser(
    HANDLE           hProvider,
    PLSA_USER_ADD_INFO pUserInfo
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bEventlogEnabled = FALSE;
    PLSA_LOGIN_NAME_INFO pLoginInfo = NULL;
    PSID pSid = NULL;
    PSID pGroupSid = NULL;
    PSTR pszShell  = NULL;
    PSTR pszHomedir = NULL;
    PWSTR pwszPassword = NULL;
    SAMDB_OBJECT_INFO userObject = {0};
    PSAMDB_USER_INFO userInfo = NULL;
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    SAMDB_QUERY Query = {0};
    SAMDB_OBJECT_INFO userUpdateInfo = {0};
    SAMDB_FIELDS updateFields = 0;
    BOOLEAN bLocked = FALSE;


    BAIL_ON_INVALID_STRING(pUserInfo->pszName);

    if (pUserInfo->uid)
    {
        dwError = LocalDirValidateUID(pUserInfo->uid);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwAllocateMemory(
                        sizeof(*userObject.Info.User.UnixInfo),
                        OUT_PPVOID(&userObject.Info.User.UnixInfo));
        BAIL_ON_LSA_ERROR(dwError);

        userObject.Info.User.UnixInfo->Uid = pUserInfo->uid;
    }

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

    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);
    }

    userObject.Type = SAMDB_OBJECT_TYPE_USER;
    userObject.Info.User.LikewiseFlags |= SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;

    userInfo = &userObject.Info.User;

    dwError = LwMbsToWc16s(
                    pLoginInfo->pszName,
                    &userObject.Name);
    BAIL_ON_LSA_ERROR(dwError);

    userInfo->UserAccountControl = SAMDB_UF_FLAG_NORMAL_ACCOUNT;

    if (LW_IS_NULL_OR_EMPTY_STR(pUserInfo->pszPassword))
    {
        userInfo->UserAccountControl |= SAMDB_UF_FLAG_ACCOUNTDISABLE;
    }

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

    if (pUserInfo->uid)
    {
        dwError = LwAllocateMemory(
                         sizeof(*userObject.Info.User.UnixInfo),
                         OUT_PPVOID(&userObject.Info.User.UnixInfo));
        BAIL_ON_LSA_ERROR(dwError);

        userObject.Info.User.UnixInfo->Uid = pUserInfo->uid;
    }

    /*
     * Set user password
     */
    if (pUserInfo->pszPassword)
    {
        dwError = LwMbsToWc16s(
                    pUserInfo->pszPassword,
                    &pwszPassword);
        BAIL_ON_LSA_ERROR(dwError);

        ntStatus = SamrCreateNtOwfPassword(
                    pwszPassword,
                    userInfo->NtOwfPassword.Data,
                    sizeof(userInfo->NtOwfPassword.Data));
        BAIL_ON_NT_STATUS(ntStatus);
    }

    dwError = SamDbAddObject(&userObject, pSid);
    BAIL_ON_LSA_ERROR(dwError);

    Query.Name = userObject.Name;

    dwError = SamDbFindObjects(SAMDB_OBJECT_TYPE_USER,
                               SAMDB_QUERY_TYPE_NT_NAME,
                               1,
                               &Query,
                               &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

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

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

    userUpdateInfo.Type = SAMDB_OBJECT_TYPE_USER;
    userUpdateInfo.Sid = ppObjects[0]->Sid;
    ppObjects[0]->Sid = NULL;

    /*
     * Add user to primary group
     */
    if (pUserInfo->pszPrimaryGroupSid)
    {
        dwError = RtlAllocateSidFromCString(&pGroupSid,
                                            pUserInfo->pszPrimaryGroupSid);
        BAIL_ON_LSA_ERROR(dwError);

        if (!RtlIsPrefixSid(gLPGlobals.pLocalDomainSID, pGroupSid))
        {
            dwError = ERROR_INVALID_PARAMETER;
            BAIL_ON_LSA_ERROR(dwError);
        }

        dwError = SamDbUpdateMembers(pGroupSid,
                                     1,
                                     &userUpdateInfo.Sid,
                                     0,
                                     NULL);
        BAIL_ON_LSA_ERROR(dwError);

        ntStatus = RtlGetRidSid(&userUpdateInfo.Info.User.PrimaryGroupRid,
                                pGroupSid);
        BAIL_ON_NT_STATUS(ntStatus);

        updateFields |= SAMDB_FIELD_USER_PRIMARY_GROUP_RID;
    }

    dwError = LwAllocateMemory(
                     sizeof(*userUpdateInfo.Info.User.UnixInfo),
                     OUT_PPVOID(&userUpdateInfo.Info.User.UnixInfo));
    BAIL_ON_LSA_ERROR(dwError);

    if (!LW_IS_NULL_OR_EMPTY_STR(pUserInfo->pszGecos))
    {
        dwError = LwMbsToWc16s(
                    pUserInfo->pszGecos,
                    &userUpdateInfo.Info.User.UnixInfo->Gecos);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_UNIX_GECOS;
    }

    if (!LW_IS_NULL_OR_EMPTY_STR(pUserInfo->pszHomedir))
    {
        dwError = LwMbsToWc16s(
                    pUserInfo->pszHomedir,
                    &userUpdateInfo.Info.User.HomeDirectory);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_HOME_DIRECTORY;
    }
    else
    {
        dwError = LocalBuildHomeDirPathFromTemplate(
                    pLoginInfo->pszName,
                    pLoginInfo->pszDomain,
                    &pszHomedir);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwMbsToWc16s(
                    pszHomedir,
                    &userUpdateInfo.Info.User.HomeDirectory);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_HOME_DIRECTORY;
    }

    if (!LW_IS_NULL_OR_EMPTY_STR(pUserInfo->pszShell))
    {
        dwError = LwMbsToWc16s(
                    pUserInfo->pszShell,
                    &userUpdateInfo.Info.User.UnixInfo->Shell);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_UNIX_SHELL;
    }
    else
    {
        dwError = LocalCfgGetDefaultShell(&pszShell);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwMbsToWc16s(
                    pszShell,
                    &userUpdateInfo.Info.User.UnixInfo->Shell);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_UNIX_SHELL;
    }

    dwError = SamDbUpdateObject(
                    &userUpdateInfo,
                    updateFields);
    BAIL_ON_LSA_ERROR(dwError);

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

    if (bEventlogEnabled)
    {
        LocalEventLogUserAdd(pLoginInfo->pszName,
                             pUserInfo->uid);
    }

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

    if (pLoginInfo)
    {
        LsaSrvFreeNameInfo(pLoginInfo);
    }

    LW_SAFE_FREE_STRING(pszShell);
    LW_SAFE_FREE_STRING(pszHomedir);
    LW_SECURE_FREE_WSTRING(pwszPassword);
    RTL_FREE(&pGroupSid);
    RTL_FREE(&pSid);

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

    SamDbReleaseObject(&userObject);
    SamDbReleaseObject(&userUpdateInfo);

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

    return dwError;
}


static
DWORD
LocalDirValidateUID(
    uid_t uid
    )
{
    DWORD dwError = 0;

    /* Check whether account uid is within permitted range */
    if (uid < LOWEST_UID) {
        BAIL_WITH_LSA_ERROR(LW_ERROR_UID_TOO_LOW);
    }

cleanup:
    return dwError;

error:
    goto cleanup;
}


DWORD
LocalDirModifyUser(
    HANDLE             hProvider,
    PLSA_USER_MOD_INFO_2 pUserModInfo
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR pwszPassword      = NULL;
    SAMDB_QUERY Query = {0};
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_OBJECT_INFO *ppGroupObjects = NULL;
    PSAMDB_OBJECT_INFO pGroupObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;
    SAMDB_FIELDS updateFields = 0;
    PSID pSid = NULL;
    PSID pGroupSid = NULL;
    PSID *ppGroupSids = NULL;
    PWSTR pwszGroupName = NULL;
    DWORD i = 0;
    SAMDB_UF_FLAGS userAccountFlags = 0;
    SAMDB_UF_FLAGS oldUserAccountFlags = 0;
    DWORD dwGroupCount = 0;

    dwError = RtlAllocateSidFromCString(&pSid, pUserModInfo->pszSid);
    BAIL_ON_LSA_ERROR(dwError);

    Query.Sid = pSid;
    ntStatus = SamDbFindObjects(SAMDB_OBJECT_TYPE_USER,
                                SAMDB_QUERY_TYPE_SID,
                                1,
                                &Query,
                                &ppObjects);
    BAIL_ON_NT_STATUS(ntStatus);

    if (ppObjects[0] == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    pObject = ppObjects[0];
    pUserObject = &pObject->Info.User;

    oldUserAccountFlags = pUserObject->UserAccountControl;
    userAccountFlags = oldUserAccountFlags;

    if (pUserModInfo->actions.bEnableUser)
    {
        userAccountFlags &= ~SAMDB_UF_FLAG_ACCOUNTDISABLE;
    }
    else if (pUserModInfo->actions.bDisableUser)
    {
        userAccountFlags |= SAMDB_UF_FLAG_ACCOUNTDISABLE;
    }

    if (pUserModInfo->actions.bSetPasswordMustExpire)
    {
        userAccountFlags &= ~SAMDB_UF_FLAG_DONT_EXPIRE_PASSWD;
    }
    else if (pUserModInfo->actions.bSetPasswordNeverExpires)
    {
        userAccountFlags |= SAMDB_UF_FLAG_DONT_EXPIRE_PASSWD;
    }

    if (pUserModInfo->actions.bSetChangePasswordOnNextLogon)
    {
        pUserObject->PasswordLastSet = 0;
        updateFields |= SAMDB_FIELD_USER_PASSWORD_LAST_SET;
    }

    if (oldUserAccountFlags != userAccountFlags)
    {
        pUserObject->UserAccountControl = userAccountFlags;
        updateFields |= SAMDB_FIELD_USER_ACCOUNT_CONTROL;
    }

    if (pUserModInfo->actions.bSetAccountExpiryDate)
    {
        struct tm tmbuf = {0};

        if (strptime(pUserModInfo->pszExpiryDate, "%Y-%m-%d", &tmbuf) == NULL)
        {
            dwError = LwMapErrnoToLwError(errno);
            BAIL_ON_LSA_ERROR(dwError);
        }

        pUserObject->AccountExpires = LocalGetNTTime(mktime(&tmbuf));
        updateFields |= SAMDB_FIELD_USER_ACCOUNT_EXPIRES;
    }

    if (pUserModInfo->actions.bSetNtPasswordHash)
    {
        RtlCopyMemory(pUserObject->NtOwfPassword.Data,
                      pUserModInfo->pNtPasswordHash->pData,
                      pUserModInfo->pNtPasswordHash->dwLen);
        pUserObject->NtOwfPassword.IsPresent = TRUE;

        updateFields |= SAMDB_FIELD_USER_NT_OWF_PASSWORD;
    }

    if (pUserModInfo->actions.bSetLmPasswordHash)
    {
        RtlCopyMemory(pUserObject->LmOwfPassword.Data,
                      pUserModInfo->pLmPasswordHash->pData,
                      pUserModInfo->pLmPasswordHash->dwLen);
        pUserObject->LmOwfPassword.IsPresent = TRUE;

        updateFields |= SAMDB_FIELD_USER_LM_OWF_PASSWORD;
    }

    if (pUserModInfo->actions.bSetPrimaryGroup)
    {
        BOOLEAN isMember = FALSE;

        ntStatus = RtlAllocateSidFromCString(
                           &pGroupSid,
                           pUserModInfo->pszPrimaryGroupSid);
        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);

        /*
         * Check if the user is member of this group before making
         * it their primary group
         */
        dwError = SamDbQueryMemberOf(
                            pObject->Sid,
                            &dwGroupCount,
                            &ppGroupSids);
        BAIL_ON_NT_STATUS(ntStatus);

        for (i = 0; i < dwGroupCount; i++)
        {
            if(RtlEqualSid(ppGroupSids[i], pGroupSid))
            {
                isMember = TRUE;
                break;
            }
        }

        if (!isMember)
        {
            dwError = ERROR_MEMBER_NOT_IN_ALIAS;
            BAIL_ON_LSA_ERROR(dwError);
        }

        ntStatus = RtlGetRidSid(&pUserObject->PrimaryGroupRid, pGroupSid);
        BAIL_ON_NT_STATUS(ntStatus);

        updateFields |= SAMDB_FIELD_USER_PRIMARY_GROUP_RID;
    }

    if (pUserModInfo->actions.bSetShell)
    {
        if (pUserObject->UnixInfo)
        {
            LW_SAFE_FREE_MEMORY(pUserObject->UnixInfo->Shell);
            pUserObject->UnixInfo->Shell = NULL;
        }
        else
        {
            dwError = LwAllocateMemory(sizeof(*pUserObject->UnixInfo),
                                       OUT_PPVOID(&pUserObject->UnixInfo));
            BAIL_ON_LSA_ERROR(dwError);
        }

        dwError = LwMbsToWc16s(pUserModInfo->pszShell,
                               &pUserObject->UnixInfo->Shell);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_UNIX_SHELL;
    }

    if (pUserModInfo->actions.bSetGecos)
    {
        if (pUserObject->UnixInfo)
        {
            LW_SAFE_FREE_MEMORY(pUserObject->UnixInfo->Gecos);
            pUserObject->UnixInfo->Gecos = NULL;
        }
        else
        {
            dwError = LwAllocateMemory(sizeof(*pUserObject->UnixInfo),
                                       OUT_PPVOID(&pUserObject->UnixInfo));
            BAIL_ON_LSA_ERROR(dwError);
        }

        dwError = LwMbsToWc16s(pUserModInfo->pszGecos,
                               &pUserObject->UnixInfo->Gecos);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_UNIX_GECOS;
    }

    if (pUserModInfo->actions.bSetHomedir)
    {
        LW_SAFE_FREE_MEMORY(pUserObject->HomeDirectory);

        dwError = LwMbsToWc16s(pUserModInfo->pszHomedir,
                               &pUserObject->HomeDirectory);
        BAIL_ON_LSA_ERROR(dwError);

        updateFields |= SAMDB_FIELD_USER_HOME_DIRECTORY;
    }

    if (pUserModInfo->actions.bSetPassword)
    {
        dwError = LwMbsToWc16s(
                        pUserModInfo->pszPassword,
                        &pwszPassword);
        BAIL_ON_LSA_ERROR(dwError);

        ntStatus = SamrCreateNtOwfPassword(
                        pwszPassword,
                        pUserObject->NtOwfPassword.Data,
                        sizeof(pUserObject->NtOwfPassword.Data));
        BAIL_ON_NT_STATUS(ntStatus);

        pUserObject->NtOwfPassword.IsPresent = TRUE;

        updateFields |= SAMDB_FIELD_USER_NT_OWF_PASSWORD;

        // Don't set LmOwfPassword because it's deprecated
    }

    if (updateFields != 0)
    {
        dwError = SamDbUpdateObject(pObject, updateFields);
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (pUserModInfo->actions.bRemoveFromGroups)
    {
        dwError = LwMbsToWc16s(pUserModInfo->pszRemoveFromGroups,
                               &pwszGroupName);
        BAIL_ON_LSA_ERROR(dwError);

        Query.Name = pwszGroupName;

        dwError = SamDbFindObjects(
                           SAMDB_OBJECT_TYPE_LOCAL_GROUP,
                           SAMDB_QUERY_TYPE_NT_OR_UNIX_NAME,
                           1,
                           &Query,
                           &ppGroupObjects);
        BAIL_ON_LSA_ERROR(dwError);

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

        dwError = SamDbUpdateMembers(
                           pGroupObject->Sid,
                           0,
                           NULL,
                           1,
                           &pObject->Sid);
        BAIL_ON_LSA_ERROR(dwError);

        SamDbFreeObjects(1, ppGroupObjects);
        ppGroupObjects = NULL;
    }

    if (pUserModInfo->actions.bAddToGroups)
    {
        dwError = LwMbsToWc16s(pUserModInfo->pszAddToGroups,
                               &pwszGroupName);
        BAIL_ON_LSA_ERROR(dwError);

        Query.Name = pwszGroupName;

        dwError = SamDbFindObjects(
                           SAMDB_OBJECT_TYPE_LOCAL_GROUP,
                           SAMDB_QUERY_TYPE_NT_OR_UNIX_NAME,
                           1,
                           &Query,
                           &ppGroupObjects);
        BAIL_ON_NT_STATUS(ntStatus);

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

        dwError = SamDbUpdateMembers(
                           pGroupObject->Sid,
                           1,
                           &pObject->Sid,
                           0,
                           NULL);
        BAIL_ON_LSA_ERROR(dwError);

        LW_SAFE_FREE_MEMORY(pwszGroupName);

        SamDbFreeObjects(1, ppGroupObjects);
        ppGroupObjects = NULL;
    }

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

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

    RTL_FREE(&pSid);

    LW_SAFE_FREE_MEMORY(pwszGroupName);
    LW_SECURE_FREE_WSTRING(pwszPassword);

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

    return dwError;
}


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

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

error:
    return dwError;
}


DWORD
LocalDirChangePassword(
    HANDLE hProvider,
    PSID   pSid,
    PWSTR  OldPassword,
    PWSTR  NewPassword
    )
{
    DWORD dwError = 0;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;
    SECURITY_INFORMATION securityInfo = 0;
    SAMDB_QUERY query = {0};
    DWORD dwUid = 0;
    DWORD dwGid = 0;
    PSECURITY_DESCRIPTOR_ABSOLUTE pSecurityDescriptorAbsolute = NULL;
    PSECURITY_DESCRIPTOR_RELATIVE pSecurityDescriptorRelative = NULL;
    GENERIC_MAPPING GenericMapping = {0};
    BOOLEAN bLocked = FALSE;
    PACCESS_TOKEN pUserToken = NULL;
    DWORD dwAccessGranted = 0;
    SAMDB_OWF_PASSWORD oldNtOwfPassword = {0};
    SAMDB_FIELDS updateFields = 0;

    query.Sid = pSid;
    dwError = SamDbFindObjects(SAMDB_OBJECT_TYPE_USER,
                               SAMDB_QUERY_TYPE_SID,
                               1,
                               &query,
                               &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    pObject = ppObjects[0];
    pUserObject = &pObject->Info.User;

    dwUid = pUserObject->UnixInfo->Uid;
    dwGid = pUserObject->PrimaryGroupRid;

    securityInfo = OWNER_SECURITY_INFORMATION |
                   GROUP_SECURITY_INFORMATION |
                   DACL_SECURITY_INFORMATION;
    dwError = SamDbQuerySecurityDescriptor(pSid,
                                           securityInfo,
                                           &pSecurityDescriptorRelative);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LocalDirAllocateAbsoluteFromSelfRelativeSD(
                pSecurityDescriptorRelative,
                &pSecurityDescriptorAbsolute);
    BAIL_ON_LSA_ERROR(dwError);

    LOCAL_RDLOCK_RWLOCK(bLocked, &gLPGlobals.rwlock);

    /*
     * Check if user has right to change the password first
     */
    ntStatus = LwMapSecurityCreateAccessTokenFromWC16StringUsername(
                    gLPGlobals.pSecCtx,
                    &pUserToken,
                    pObject->Name);
    BAIL_ON_NT_STATUS(ntStatus);

    if (!RtlAccessCheck(pSecurityDescriptorAbsolute,
                        pUserToken,
                        USER_ACCESS_CHANGE_PASSWORD,
                        0,
                        &GenericMapping,
                        &dwAccessGranted,
                        &ntStatus))
    {
        BAIL_ON_NT_STATUS(ntStatus);
    }

    //
    // Verify old password (it should yield the same hash as returned
    // in NtOwfPassword field).
    //
    ntStatus = SamrCreateNtOwfPassword(
                        OldPassword,
                        oldNtOwfPassword.Data,
                        sizeof(oldNtOwfPassword.Data));
    BAIL_ON_NT_STATUS(ntStatus);

    if (!RtlEqualMemory(pUserObject->NtOwfPassword.Data,
                        oldNtOwfPassword.Data,
                        sizeof(oldNtOwfPassword.Data)))
    {
        dwError = ERROR_INVALID_PASSWORD;
        BAIL_ON_LSA_ERROR(dwError);
    }

    //
    // Set new password hash
    //
    ntStatus = SamrCreateNtOwfPassword(
                        NewPassword,
                        pUserObject->NtOwfPassword.Data,
                        sizeof(pUserObject->NtOwfPassword.Data));
    BAIL_ON_NT_STATUS(ntStatus);

    pUserObject->NtOwfPassword.IsPresent = TRUE;

    updateFields |= SAMDB_FIELD_USER_NT_OWF_PASSWORD;

    dwError = SamDbUpdateObject(
                        pObject,
                        updateFields);
    BAIL_ON_LSA_ERROR(dwError);

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

    if (pSecurityDescriptorAbsolute)
    {
        LocalDirFreeSecurityDescriptor(&pSecurityDescriptorAbsolute);
    }

    if (pSecurityDescriptorRelative)
    {
        SamDbFree(pSecurityDescriptorRelative);
    }

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

    RtlReleaseAccessToken(&pUserToken);

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

    return dwError;
}


DWORD
LocalDirSetPassword(
    HANDLE hProvider,
    PSID pSid,
    PWSTR NewPassword
    )
{
    DWORD dwError = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bIsAdmin = FALSE;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* userObjects = NULL;
    PSAMDB_OBJECT_INFO userObject = NULL;
    SAMDB_FIELDS updateFields = 0;

    dwError = LocalCheckIsAdministrator(
                    hProvider,
                    &bIsAdmin);
    BAIL_ON_LSA_ERROR(dwError);

    if (!bIsAdmin)
    {
        dwError = ERROR_ACCESS_DENIED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    query.Sid = pSid;

    dwError = SamDbFindObjects(
                       SAMDB_OBJECT_TYPE_USER,
                       SAMDB_QUERY_TYPE_SID,
                       1,
                       &query,
                       &userObjects);
    BAIL_ON_LSA_ERROR(dwError);

    userObject = userObjects[0];
    if (userObject == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    //
    // Set new password hash
    //
    ntStatus = SamrCreateNtOwfPassword(
                        NewPassword,
                        userObject->Info.User.NtOwfPassword.Data,
                        sizeof(userObject->Info.User.NtOwfPassword.Data));
    BAIL_ON_NT_STATUS(ntStatus);

    userObject->Info.User.NtOwfPassword.IsPresent = TRUE;

    updateFields |= SAMDB_FIELD_USER_NT_OWF_PASSWORD;

    dwError = SamDbUpdateObject(
                        userObject,
                        updateFields);
    BAIL_ON_LSA_ERROR(dwError);

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

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

    return dwError;
}


DWORD
LocalCreateHomeDirectory(
    PLSA_SECURITY_OBJECT pObject
    )
{
    DWORD dwError = 0;
    BOOLEAN bExists = FALSE;
    mode_t  umask = LOCAL_CFG_DEFAULT_HOMEDIR_UMASK;
    mode_t  perms = (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
    BOOLEAN bRemoveDir = FALSE;

    if (LW_IS_NULL_OR_EMPTY_STR(pObject->userInfo.pszHomedir))
    {
       LSA_LOG_ERROR("The user's [Uid:%ld] home directory is not defined",
                     (long)pObject->userInfo.uid);
       dwError = LW_ERROR_FAILED_CREATE_HOMEDIR;
       BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LsaCheckDirectoryExists(
                    pObject->userInfo.pszHomedir,
                    &bExists);
    BAIL_ON_LSA_ERROR(dwError);

    if (!bExists)
    {
        dwError = LocalCfgGetHomedirUmask(&umask);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LsaCreateDirectory(
                    pObject->userInfo.pszHomedir,
                    perms & (~umask));
        BAIL_ON_LSA_ERROR(dwError);

        bRemoveDir = TRUE;

        dwError = LsaChangeOwner(
                    pObject->userInfo.pszHomedir,
                    pObject->userInfo.uid,
                    pObject->userInfo.gid);
        BAIL_ON_LSA_ERROR(dwError);

        bRemoveDir = FALSE;

        dwError = LocalProvisionHomeDir(
                       pObject->userInfo.uid,
                       pObject->userInfo.gid,
                       pObject->userInfo.pszHomedir);
        BAIL_ON_LSA_ERROR(dwError);
    }

cleanup:

    return dwError;

error:

    if (bRemoveDir) {
       LsaRemoveDirectory(pObject->userInfo.pszHomedir);
    }

    goto cleanup;
}

DWORD
LocalProvisionHomeDir(
    uid_t ownerUid,
    gid_t ownerGid,
    PCSTR pszHomedirPath
    )
{
    DWORD dwError = 0;
    BOOLEAN bExists = FALSE;
    PSTR pszSkelPaths = NULL;
    PSTR pszSkelPath = NULL;
    PSTR pszIter = NULL;
    size_t stLen = 0;

    dwError = LocalCfgGetSkeletonDirs(&pszSkelPaths);
    BAIL_ON_LSA_ERROR(dwError);

    if (LW_IS_NULL_OR_EMPTY_STR(pszSkelPaths))
    {
        goto cleanup;
    }

    pszIter = pszSkelPaths;
    while ((stLen = strcspn(pszIter, ",")) != 0)
    {
        dwError = LwStrndup(
                      pszIter,
                      stLen,
                      &pszSkelPath);
        BAIL_ON_LSA_ERROR(dwError);

        LwStripWhitespace(pszSkelPath, TRUE, TRUE);

        if (LW_IS_NULL_OR_EMPTY_STR(pszSkelPath))
        {
            LW_SAFE_FREE_STRING(pszSkelPath);
            continue;
        }

        dwError = LsaCheckDirectoryExists(
                        pszSkelPath,
                        &bExists);
        BAIL_ON_LSA_ERROR(dwError);

        if (bExists)
        {
            dwError = LsaCopyDirectory(
                        pszSkelPath,
                        ownerUid,
                        ownerGid,
                        pszHomedirPath);
            BAIL_ON_LSA_ERROR(dwError);
        }

        LW_SAFE_FREE_STRING(pszSkelPath);

        pszIter += stLen;
        stLen = strspn(pszIter, ",");
        pszIter += stLen;
    }

cleanup:

    LW_SAFE_FREE_STRING(pszSkelPath);
    LW_SAFE_FREE_STRING(pszSkelPaths);

    return dwError;

error:

    goto cleanup;
}

DWORD
LocalCheckIsGuest(
    PLSA_SECURITY_OBJECT pObject,
    PBOOLEAN pbUserIsGuest
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;
    PSID pUserSid = NULL;
    ULONG ulRid = 0;
    BOOLEAN bUserIsGuest = FALSE;

    dwError = LwNtStatusToWin32Error(
                  RtlAllocateSidFromCString(
                      &pUserSid,
                      pObject->pszObjectSid));
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwNtStatusToWin32Error(
                  RtlGetRidSid(
                      &ulRid,
                      pUserSid));
    BAIL_ON_LSA_ERROR(dwError);

    if (ulRid == DOMAIN_USER_RID_GUEST)
    {
        bUserIsGuest = TRUE;
    }

cleanup:

    if (pUserSid)
    {
        LW_RTL_FREE(&pUserSid);
    }

    *pbUserIsGuest = bUserIsGuest;

    return dwError;

error:

    goto cleanup;
}

DWORD
LocalCheckAccountFlags(
    PLSA_SECURITY_OBJECT pObject
    )
{
    DWORD dwError = LW_ERROR_SUCCESS;

    BAIL_ON_INVALID_POINTER(pObject);

    if (pObject->userInfo.bAccountDisabled)
    {
        dwError = LW_ERROR_ACCOUNT_DISABLED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (pObject->userInfo.bAccountLocked)
    {
        dwError = LW_ERROR_ACCOUNT_LOCKED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (pObject->userInfo.bAccountExpired)
    {
        dwError = LW_ERROR_ACCOUNT_EXPIRED;
        BAIL_ON_LSA_ERROR(dwError);
    }

    if (pObject->userInfo.bPasswordExpired)
    {
        dwError = LW_ERROR_PASSWORD_EXPIRED;
        BAIL_ON_LSA_ERROR(dwError);
    }

error:

    return dwError;
}

DWORD
LocalCheckPasswordPolicy(
    PLSA_SECURITY_OBJECT pObject,
    PCSTR                pszPassword
    )
{
    DWORD dwError = 0;
    DWORD dwMinPasswordLen = 0;
    size_t sPasswordLen = 0;

    BAIL_ON_INVALID_POINTER(pObject);
    BAIL_ON_INVALID_POINTER(pszPassword);

    if (!pObject->userInfo.bUserCanChangePassword)
    {
        dwError = LW_ERROR_PASSWORD_RESTRICTION;
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LocalCfgGetMinPwdLength(&dwMinPasswordLen);
    BAIL_ON_LSA_ERROR(dwError);

    sPasswordLen = strlen(pszPassword);
    if (dwMinPasswordLen > sPasswordLen)
    {
        dwError = LW_ERROR_PASSWORD_RESTRICTION;
        BAIL_ON_LSA_ERROR(dwError);
    }

error:
    return dwError;
}

DWORD
LocalGetUserLogonInfo(
    HANDLE   hProvider,
    PSID     pSid,
    PDWORD   pdwLogonCount,
    PDWORD   pdwBadPasswordCount
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_QUERY Query = {0};
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;

    Query.Sid = pSid;

    dwError = SamDbFindObjects(
                        SAMDB_OBJECT_TYPE_USER,
                        SAMDB_QUERY_TYPE_SID,
                        1,
                        &Query,
                        &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    pObject = ppObjects[0];
    if (pObject == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    pUserObject = &pObject->Info.User;

    if (pdwLogonCount)
    {
        *pdwLogonCount = pUserObject->LogonCount;
    }

    if (pdwBadPasswordCount)
    {
        *pdwBadPasswordCount = pUserObject->BadPasswordCount;
    }

error:
    if (dwError)
    {
        if (pdwLogonCount)
        {
            *pdwLogonCount = 0;
        }

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

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

    return dwError;
}


DWORD
LocalSetUserLogonInfo(
    HANDLE  hProvider,
    PSID    pSid,
    PDWORD  pdwLogonCount,
    PDWORD  pdwBadPasswordCount,
    PLONG64 pllLastLogonTime,
    PLONG64 pllLastLogoffTime
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_QUERY Query = {0};
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;
    SAMDB_FIELDS updateFields = 0;

    Query.Sid = pSid;

    dwError = SamDbFindObjects(SAMDB_OBJECT_TYPE_USER,
                               SAMDB_QUERY_TYPE_SID,
                               1,
                               &Query,
                               &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    pObject = ppObjects[0];
    if (pObject == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    pUserObject = &pObject->Info.User;

    if (pdwLogonCount)
    {
        pUserObject->LogonCount = *pdwLogonCount;
        updateFields |= SAMDB_FIELD_USER_LOGON_COUNT;
    }

    if (pdwBadPasswordCount)
    {
        pUserObject->BadPasswordCount = *pdwBadPasswordCount;
        updateFields |= SAMDB_FIELD_USER_BAD_PASSWORD_COUNT;
    }

    if (pllLastLogonTime)
    {
        pUserObject->LastLogon = *pllLastLogonTime;
        updateFields |= SAMDB_FIELD_USER_LAST_LOGON;
    }

    if (pllLastLogoffTime)
    {
        pUserObject->LastLogoff = *pllLastLogoffTime;
        updateFields |= SAMDB_FIELD_USER_LAST_LOGOFF;
    }

    if (updateFields != 0)
    {
        dwError = SamDbUpdateObject(pObject, updateFields);
        BAIL_ON_LSA_ERROR(dwError);
    }

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

    return dwError;
}


DWORD
LocalUpdateUserLoginTime(
    HANDLE hProvider,
    PSID pSid
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_QUERY Query = {0};
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;
    SAMDB_FIELDS updateFields = 0;

    Query.Sid = pSid;

    dwError = SamDbFindObjects(SAMDB_OBJECT_TYPE_USER,
                               SAMDB_QUERY_TYPE_SID,
                               1,
                               &Query,
                               &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    pObject = ppObjects[0];
    if (pObject == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    pUserObject = &pObject->Info.User;

    pUserObject->LastLogon = LocalGetNTTime(time(NULL));
    updateFields |= SAMDB_FIELD_USER_LAST_LOGON;

    dwError = SamDbUpdateObject(pObject, updateFields);
    BAIL_ON_LSA_ERROR(dwError);

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

    return dwError;
}


DWORD
LocalUpdateUserLogoffTime(
    HANDLE hProvider,
    PSID pSid
    )
{
    DWORD dwError = ERROR_SUCCESS;
    SAMDB_QUERY Query = {0};
    PSAMDB_OBJECT_INFO *ppObjects = NULL;
    PSAMDB_OBJECT_INFO pObject = NULL;
    PSAMDB_USER_INFO pUserObject = NULL;
    SAMDB_FIELDS updateFields = 0;

    Query.Sid = pSid;

    dwError = SamDbFindObjects(
                       SAMDB_OBJECT_TYPE_USER,
                       SAMDB_QUERY_TYPE_SID,
                       1,
                       &Query,
                       &ppObjects);
    BAIL_ON_LSA_ERROR(dwError);

    pObject = ppObjects[0];
    if (pObject == NULL)
    {
        dwError = ERROR_NO_SUCH_USER;
        BAIL_ON_LSA_ERROR(dwError);
    }

    pUserObject = &pObject->Info.User;

    pUserObject->LastLogoff = LocalGetNTTime(time(NULL));
    updateFields |= SAMDB_FIELD_USER_LAST_LOGOFF;

    dwError = SamDbUpdateObject(
                       pObject,
                       updateFields);
    BAIL_ON_LSA_ERROR(dwError);

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

    return dwError;
}


DWORD
LocalDirCheckIfAdministrator(
    HANDLE   hProvider,
    uid_t    uid,
    PBOOLEAN pbIsAdmin
    )
{
    DWORD dwError = 0;
    LSA_QUERY_LIST QueryList;
    PLSA_SECURITY_OBJECT* ppObjects = NULL;
    PSTR* ppszGroupSids = NULL;
    DWORD dwNumGroups = 0;
    BOOLEAN bIsAdmin = FALSE;
    DWORD dwUid = (DWORD) uid;
    PSTR pszBuiltinAdminSid = NULL;
    DWORD iGroup = 0;

    if (uid == 0)
    {
        bIsAdmin = TRUE;
    }
    else
    {
        QueryList.pdwIds = &dwUid;

        dwError = LocalDirFindObjects(
            hProvider,
            0,
            LSA_OBJECT_TYPE_USER,
            LSA_QUERY_TYPE_BY_UNIX_ID,
            1,
            QueryList,
            &ppObjects);
        BAIL_ON_LSA_ERROR(dwError);

        if (ppObjects[0])
        {
            dwError = LocalQueryMemberOf(
                hProvider,
                0,
                1,
                &ppObjects[0]->pszObjectSid,
                &dwNumGroups,
                &ppszGroupSids);
            BAIL_ON_LSA_ERROR(dwError);

            if (dwNumGroups)
            {
                dwError = LocalDirGetBuiltinAdministratorsSid(
                    hProvider,
                    &pszBuiltinAdminSid);
                BAIL_ON_LSA_ERROR(dwError);

                for (; iGroup < dwNumGroups; iGroup++)
                {
                    if (!strcmp(ppszGroupSids[iGroup], pszBuiltinAdminSid))
                    {
                        bIsAdmin = TRUE;
                        break;
                    }
                }
            }
        }
    }

    *pbIsAdmin = bIsAdmin;

cleanup:

    LwFreeStringArray(ppszGroupSids, dwNumGroups);
    LW_SAFE_FREE_STRING(pszBuiltinAdminSid);

    return dwError;

error:

    *pbIsAdmin = FALSE;

    goto cleanup;
}

static
DWORD
LocalDirGetBuiltinAdministratorsSid(
    HANDLE hProvider,
    PSTR* ppszSid
    )
{
    DWORD dwError = 0;
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    static PSTR pszBuiltinAdminSid = 0;
    LSA_QUERY_LIST QueryList;
    PLSA_SECURITY_OBJECT* ppObjects = NULL;
    PCSTR pszGroupName = "BUILTIN\\Administrators";

    pthread_mutex_lock(&mutex);

    if (!pszBuiltinAdminSid)
    {
        QueryList.ppszStrings = &pszGroupName;

        dwError = LocalDirFindObjects(
            hProvider,
            0,
            LSA_OBJECT_TYPE_GROUP,
            LSA_QUERY_TYPE_BY_NT4,
            1,
            QueryList,
            &ppObjects);
        BAIL_ON_LSA_ERROR(dwError);

        if (ppObjects[0] == NULL)
        {
            dwError = LW_ERROR_NO_SUCH_GROUP;
            BAIL_ON_LSA_ERROR(dwError);
        }

        dwError = LwAllocateString(ppObjects[0]->pszObjectSid, &pszBuiltinAdminSid);
        BAIL_ON_LSA_ERROR(dwError);
    }

    dwError = LwAllocateString(pszBuiltinAdminSid, ppszSid);
    BAIL_ON_LSA_ERROR(dwError);

cleanup:

    LsaUtilFreeSecurityObjectList(1, ppObjects);

    pthread_mutex_unlock(&mutex);

    return dwError;

error:

    *ppszSid = NULL;

    goto cleanup;
}
