/* -*- 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:
 *
 *        samdbattr.c
 *
 * Abstract:
 *
 *
 *      Likewise SAM Database Provider
 *
 *      SAM object attribute handling routines
 *
 * Authors: Rafal Szczesniak (rafal@likewise.com)
 *
 */

#include "includes.h"


DWORD
SamDbBindAttributeValues(
    PSAMDB_CONTEXT DbContext,
    sqlite3_stmt* SqlStatement,
    PSAMDB_ATTRIBUTE AttributeValues,
    DWORD AttributeValuesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD index = 0;
    PSTR pszValue = NULL;

    for (index = 0; index < AttributeValuesCount; index++)
    {
        switch (AttributeValues[index].AttributeType)
        {
        case SAMDB_ATTR_TYPE_TEXT:
            {
                PWSTR* ppwszValue = AttributeValues[index].Value;

                if (*ppwszValue)
                {
                    error = LwWc16sToMbs(
                                  *ppwszValue,
                                  &pszValue);
                    BAIL_ON_SAMDB_ERROR(error);

                    error = sqlite3_bind_text(
                                  SqlStatement,
                                  index + 1,
                                  pszValue,
                                  -1,
                                  SQLITE_TRANSIENT);
                    BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, SqlStatement);

                    // SQLITE_TRANSIENT causes sqlite make a local copy
                    // of bound value, so pszValue can be freed.
                    LW_SAFE_FREE_MEMORY(pszValue);
                }
                else
                {
                    error = sqlite3_bind_null(
                                  SqlStatement,
                                  index + 1);
                    BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, SqlStatement);
                }
            }
            break;

        case SAMDB_ATTR_TYPE_INT32:
        case SAMDB_ATTR_TYPE_BOOLEAN:
            {
                PLONG pValue = AttributeValues[index].Value;

                error = sqlite3_bind_int(
                              SqlStatement,
                              index + 1,
                              (int)(*pValue));
                BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, SqlStatement);
            }
            break;

        case SAMDB_ATTR_TYPE_INT64:
        case SAMDB_ATTR_TYPE_DATETIME:
            {
                PLONG64 pValue = AttributeValues[index].Value;

                error = sqlite3_bind_int64(
                              SqlStatement,
                              index + 1,
                              (sqlite3_int64)(*pValue));
                BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, SqlStatement);
            }
            break;

        case SAMDB_ATTR_TYPE_BLOB:
        case SAMDB_ATTR_TYPE_SECURITY_DESCRIPTOR:
            {
                PSAMDB_ATTRIBUTE_VALUE_BLOB pBlob = AttributeValues[index].Value;

                error = sqlite3_bind_blob(
                              SqlStatement,
                              index + 1,
                              pBlob->Value,
                              (int)pBlob->Length,
                              SQLITE_TRANSIENT);
                BAIL_ON_SAMDB_SQLITE_ERROR_STMT(error, SqlStatement);
            }
            break;

        default:
            error = ERROR_INVALID_PARAMETER;
            BAIL_ON_SAMDB_ERROR(error);
        }
    }

cleanup:
    LW_SAFE_FREE_MEMORY(pszValue);

    return error;
}


DWORD
SamDbGetOutputAttributeValues(
    PSAMDB_CONTEXT DbContext,
    sqlite3_stmt* SqlStatement,
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount
    )
{
    DWORD error = ERROR_SUCCESS;
    DWORD attributeIndex = 0;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = NULL;
    PCVOID columnValue = NULL;
    PDWORD pInt32Value = NULL;
    PLONG64 pInt64Value = NULL;
    PWSTR* pStringValue = NULL;
    PCSTR stringValue = NULL;
    size_t stringLength = 0;

    for (attributeIndex = 0;
         attributeIndex < AttributesCount;
         attributeIndex++)
    {
        switch (Attributes[attributeIndex].AttributeType)
        {
        case SAMDB_ATTR_TYPE_INT32:
        {
            error = LwAllocateMemory(
                              sizeof(*pInt32Value),
                              OUT_PPVOID(&pInt32Value));
            BAIL_ON_SAMDB_ERROR(error);

            *pInt32Value = (DWORD)sqlite3_column_int(
                                  SqlStatement,
                                  attributeIndex);
            Attributes[attributeIndex].Value = pInt32Value;
            Attributes[attributeIndex].FreeFn = SamDbAttributeFreePointer;
        }
        break;

        case SAMDB_ATTR_TYPE_INT64:
        {
            error = LwAllocateMemory(
                              sizeof(*pInt64Value),
                              OUT_PPVOID(&pInt64Value));
            BAIL_ON_SAMDB_ERROR(error);

            *pInt64Value = (LONG64)sqlite3_column_int64(
                                  SqlStatement,
                                  attributeIndex);
            Attributes[attributeIndex].Value = pInt64Value;
            Attributes[attributeIndex].FreeFn = SamDbAttributeFreePointer;
        }
        break;

        case SAMDB_ATTR_TYPE_TEXT:
        {
            error = LwAllocateMemory(
                              sizeof(*pStringValue),
                              OUT_PPVOID(&pStringValue));
            BAIL_ON_SAMDB_ERROR(error);

            stringLength = (size_t)sqlite3_column_bytes(
                                      SqlStatement,
                                      attributeIndex);
            if (stringLength)
            {
                stringValue = sqlite3_column_blob(
                                  SqlStatement,
                                  attributeIndex);
                error = LwMbsToWc16s(
                                  stringValue,
                                  pStringValue);
                BAIL_ON_SAMDB_ERROR(error);
            }

            Attributes[attributeIndex].Value = pStringValue;
            Attributes[attributeIndex].FreeFn = SamDbAttributeFreeString;
        }
        break;

        case SAMDB_ATTR_TYPE_SECURITY_DESCRIPTOR:
        case SAMDB_ATTR_TYPE_BLOB:
        {
            error = LwAllocateMemory(
                              sizeof(*blob),
                              OUT_PPVOID(&blob));
            BAIL_ON_SAMDB_ERROR(error);

            blob->Length = (DWORD)sqlite3_column_bytes(
                                      SqlStatement,
                                      attributeIndex);
            if (blob->Length)
            {
                error = LwAllocateMemory(
                                  blob->Length,
                                  OUT_PPVOID(&blob->Value));
                BAIL_ON_SAMDB_ERROR(error);

                columnValue = sqlite3_column_blob(
                                  SqlStatement,
                                  attributeIndex);
                memcpy(blob->Value, columnValue, blob->Length);
            }

            Attributes[attributeIndex].Value = blob;
            Attributes[attributeIndex].FreeFn = SamDbAttributeFreeBlob;
        }
        break;

        default:
            error = ERROR_INVALID_PARAMETER;

            SAMDB_LOG_ERROR("Unknown attribute type requested from SQL "
                            "statement result");
            goto cleanup;
        }
    }

cleanup:
    return error;
}


DWORD
SamDbTransferPointerValue(
    PSAMDB_ATTRIBUTE Source,
    PVOID Destination
    )
{
    DWORD error = ERROR_SUCCESS;
    PWSTR* pString = NULL;
    PLONG pInt32Value = NULL;
    PLONG64 pInt64Value = NULL;

    switch (Source->AttributeType)
    {
    case SAMDB_ATTR_TYPE_INT32:
        pInt32Value = Destination;
        *pInt32Value = *((PLONG)Source->Value);
        break;

    case SAMDB_ATTR_TYPE_INT64:
        pInt64Value = Destination;
        *pInt64Value = *((PLONG64)Source->Value);
        break;

    case SAMDB_ATTR_TYPE_TEXT:
        pString = Destination;
        *pString = *((PWSTR*)Source->Value);

        // Free PWSTR* value (because PWSTR has already been transferred)
        // and NULL out free function so the pointer won't get double-freed
        LW_SAFE_FREE_MEMORY(Source->Value);
        Source->FreeFn = NULL;
        break;

    default:
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Invalid value type requested for transfer");

        goto cleanup;
    }

cleanup:
    return error;
}


DWORD
SamDbTransferSid(
    PSAMDB_ATTRIBUTE Source,
    PVOID Destination
    )
{
    DWORD error = ERROR_SUCCESS;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PWSTR* pString = NULL;
    PSID sid = NULL;
    DWORD sidLength = 0;
    PSID* destinationSid = Destination;

    if (Source->AttributeType != SAMDB_ATTR_TYPE_TEXT)
    {
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Invalid value type while SID was requested "
                        "for transfer");
        goto cleanup;
    }

    pString = Source->Value;

    ntStatus = RtlAllocateSidFromWC16String(
                        &sid,
                        *pString);
    BAIL_ON_NT_STATUS(ntStatus);

    sidLength = RtlLengthSid(sid);
    error = LwAllocateMemory(
                        sidLength,
                        OUT_PPVOID(destinationSid));
    BAIL_ON_SAMDB_ERROR(error);

    ntStatus = RtlCopySid(
                        sidLength,
                        *destinationSid,
                        sid);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:
error:
    if (error || ntStatus)
    {
        LW_SAFE_FREE_MEMORY(*destinationSid);
    }

    RTL_FREE(&sid);

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

    return error;
}


DWORD
SamDbTransferWorkstationsArray(
    PSAMDB_ATTRIBUTE Source,
    PVOID Destination
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_STRING_ARRAY pWorkstations = Destination;

    if (Source->AttributeType != SAMDB_ATTR_TYPE_BLOB)
    {
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Invalid value type while workstations blob was "
                        "requested for transfer");
        goto cleanup;
    }

    pWorkstations->Count = 0;
    pWorkstations->String = NULL;

cleanup:
    return error;
}


DWORD
SamDbTransferOwfPassword(
    PSAMDB_ATTRIBUTE Source,
    PVOID Destination
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = Source->Value;
    PSAMDB_OWF_PASSWORD owfPassword = Destination;

    if (Source->AttributeType != SAMDB_ATTR_TYPE_BLOB)
    {
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Invalid value type while password blob was requested "
                        "for transfer");
        goto cleanup;
    }

    if (blob->Length == 0)
    {
        owfPassword->IsPresent = FALSE;
    }
    else if (blob->Length == sizeof(owfPassword->Data))
    {
        memcpy(owfPassword->Data,
               blob->Value,
               blob->Length);
        owfPassword->IsPresent = TRUE;
    }
    else
    {
        error = ERROR_INTERNAL_ERROR;

        SAMDB_LOG_ERROR("Invalid blob length = %u found in password "
                        "hash attribute",
                        blob->Length);
        goto cleanup;
    }

cleanup:
    if (error)
    {
        memset(owfPassword->Data,
               0,
               sizeof(owfPassword->Data));
        owfPassword->IsPresent = FALSE;
    }

    return error;
}


DWORD
SamDbTransferLogonHoursArray(
    PSAMDB_ATTRIBUTE Source,
    PVOID Destination
    )
{
    DWORD error = ERROR_SUCCESS;
    PSAMDB_LOGON_HOURS pLogonHours = Destination;
    size_t arraySize = 0;
    DWORD unitIndex = 0;

    if (Source->AttributeType != SAMDB_ATTR_TYPE_BLOB)
    {
        error = ERROR_INVALID_PARAMETER;
        SAMDB_LOG_ERROR("Invalid value type while logon hours blob was "
                        "requested for transfer");
        goto cleanup;
    }

    pLogonHours->UnitsPerWeek = 7 * 24;
    arraySize = sizeof(pLogonHours->LogonHours[0]) * pLogonHours->UnitsPerWeek;

    error = LwAllocateMemory(
                    arraySize,
                    OUT_PPVOID(&pLogonHours->LogonHours));
    BAIL_ON_SAMDB_ERROR(error);

    for (unitIndex = 0;
         unitIndex < pLogonHours->UnitsPerWeek;
         unitIndex++)
    {
        pLogonHours->LogonHours[unitIndex] = TRUE;
    }

cleanup:
    return error;
}


VOID
SamDbFreeAttributesContents(
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount
    )
{
    DWORD attributeIndex = 0;

    for (attributeIndex = 0;
         attributeIndex < AttributesCount;
         attributeIndex++)
    {
        if (Attributes[attributeIndex].FreeFn)
        {
            Attributes[attributeIndex].FreeFn(
                     Attributes[attributeIndex].Value);
            Attributes[attributeIndex].FreeFn = NULL;
            Attributes[attributeIndex].Value = NULL;
        }
    }
}


VOID
SamDbFreeAttributes(
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount
    )
{
    SamDbFreeAttributesContents(
        Attributes,
        AttributesCount);

    LW_SAFE_FREE_MEMORY(Attributes);
}


VOID
SamDbAttributeFreeSidString(
    PVOID Pointer
    )
{
    PWSTR* ppwszSid = Pointer;

    //
    // SID saved to the database is in text format ("S-1-5-21-...")
    //
    RTL_FREE(ppwszSid);
    LW_SAFE_FREE_MEMORY(ppwszSid);
}


VOID
SamDbAttributeFreePointer(
    PVOID Pointer
    )
{
    //
    // This is a simple pointer (i.e. doesn't require special freeing)
    // that could have been allocated by LONG/LONG64 value generator
    //
    LW_SAFE_FREE_MEMORY(Pointer);
}


VOID
SamDbAttributeFreeString(
    PVOID Pointer
    )
{
    PWSTR* pString = Pointer;

    LW_SAFE_FREE_MEMORY(*pString);
    LW_SAFE_FREE_MEMORY(pString);
}


VOID
SamDbAttributeFreeBlob(
    PVOID Pointer
    )
{
    PSAMDB_ATTRIBUTE_VALUE_BLOB blob = Pointer;

    LW_SAFE_FREE_MEMORY(blob->Value);
    LW_SAFE_FREE_MEMORY(blob);
}


DWORD
SamDbMarshalDomainObject(
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount,
    PSAMDB_OBJECT_INFO* pObject
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_OBJECT_INFO object = {0};
    PSAMDB_OBJECT_INFO domainObject = NULL;
    DWORD attributeIndex = 0;
    DWORD transferIndex = 0;
    CHAR objectSidAttrName[] = SAMDB_ATTR_OBJECT_SID;
    CHAR domainAttrName[] = SAMDB_ATTR_DOMAIN;
    CHAR sequenceNumberAttrName[] = SAMDB_ATTR_SEQUENCE_NUMBER;
    CHAR minPasswordLengthAttrName[] = SAMDB_ATTR_MIN_PWD_LENGTH;
    CHAR passwordHistoryLengthAttrName[] = SAMDB_ATTR_PWD_HISTORY_LENGTH;
    CHAR minPasswordAgeAttrName[] = SAMDB_ATTR_MIN_PWD_AGE;
    CHAR maxPasswordAgeAttrName[] = SAMDB_ATTR_MAX_PWD_AGE;
    CHAR commentAttrName[] = SAMDB_ATTR_COMMENT;
    CHAR passwordPropertiesAttrName[] = SAMDB_ATTR_PWD_PROPERTIES;

    SAMDB_ATTRIBUTE_TRANSFER domainAttributes[] = {
        {
            objectSidAttrName,
            &object.Sid,
            SamDbTransferSid,
        },
        {
            domainAttrName,
            &object.Name,
            SamDbTransferPointerValue,
        },
        {
            sequenceNumberAttrName,
            &object.Info.Domain.SequenceNumber,
            SamDbTransferPointerValue,
        },
        {
            minPasswordLengthAttrName,
            &object.Info.Domain.MinPasswordLength,
            SamDbTransferPointerValue,
        },
        {
            passwordHistoryLengthAttrName,
            &object.Info.Domain.PasswordHistoryLength,
            SamDbTransferPointerValue,
        },
        {
            minPasswordAgeAttrName,
            &object.Info.Domain.MinPasswordAge,
            SamDbTransferPointerValue,
        },
        {
            maxPasswordAgeAttrName,
            &object.Info.Domain.MaxPasswordAge,
            SamDbTransferPointerValue,
        },
        {
            commentAttrName,
            &object.Info.Domain.Comment,
            SamDbTransferPointerValue,
        },
        {
            passwordPropertiesAttrName,
            &object.Info.Domain.PasswordProperties,
            SamDbTransferPointerValue,
        }
    };

    object.Type = SAMDB_OBJECT_TYPE_DOMAIN;

    for (attributeIndex = 0;
         attributeIndex < AttributesCount;
         attributeIndex++)
    {
        for (transferIndex = 0;
             transferIndex < sizeof(domainAttributes)/sizeof(domainAttributes[0]);
             transferIndex++)
        {
            if (RtlCStringIsEqual(
                       Attributes[attributeIndex].DbColumnName,
                       domainAttributes[transferIndex].AttributeName,
                       TRUE))
            {
                error = domainAttributes[transferIndex].TransferFn(
                           &(Attributes[attributeIndex]),
                           domainAttributes[transferIndex].Destination);
                BAIL_ON_SAMDB_ERROR(error);
                break;
            }
        }
    }

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

    *domainObject = object;
    memset(&object, 0 , sizeof(object));

cleanup:
    if (error)
    {
        SamDbFreeObject(domainObject);
        domainObject = NULL;
    }

    *pObject = domainObject;

    return error;
}


DWORD
SamDbMarshalUserObject(
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount,
    PSAMDB_OBJECT_INFO* pObject
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_OBJECT_INFO object = {0};
    SAMDB_UNIX_USER_INFO unixInfo = {0};
    PSAMDB_OBJECT_INFO userObject = NULL;
    DWORD attributeIndex = 0;
    DWORD transferIndex = 0;
    CHAR objectSidAttrName[] = SAMDB_ATTR_OBJECT_SID;
    CHAR samAccountAttrName[] = SAMDB_ATTR_SAM_ACCOUNT_NAME;
    CHAR passwordLastSetAttrName[] = SAMDB_ATTR_PASSWORD_LAST_SET;
    CHAR accountExpiryAttrName[] = SAMDB_ATTR_ACCOUNT_EXPIRY;
    CHAR fullNameAttrName[] = SAMDB_ATTR_FULL_NAME;
    CHAR homeDirectoryAttrName[] = SAMDB_ATTR_HOME_DIR;
    CHAR homeDriveAttrName[] = SAMDB_ATTR_HOME_DRIVE;
    CHAR homeLogonScriptAttrName[] = SAMDB_ATTR_LOGON_SCRIPT;
    CHAR profilePathAttrName[] = SAMDB_ATTR_PROFILE_PATH;
    CHAR descriptionAttrName[] = SAMDB_ATTR_DESCRIPTION;
    CHAR workstationsAttrName[] = SAMDB_ATTR_WORKSTATIONS;
    CHAR commentAttrName[] = SAMDB_ATTR_COMMENT;
    CHAR lmHashAttrName[] = SAMDB_ATTR_LM_HASH;
    CHAR ntHashAttrName[] = SAMDB_ATTR_NT_HASH;
    CHAR primaryGroupAttrName[] = SAMDB_ATTR_PRIMARY_GROUP;
    CHAR accountFlagsAttrName[] = SAMDB_ATTR_ACCOUNT_FLAGS;
    CHAR likewiseFlagsAttrName[] = SAMDB_ATTR_LIKEWISE_FLAGS;
    CHAR logonHoursAttrName[] = SAMDB_ATTR_LOGON_HOURS;
    CHAR countryCodeAttrName[] = SAMDB_ATTR_COUNTRY_CODE;
    CHAR codePageAttrName[] = SAMDB_ATTR_CODE_PAGE;
    CHAR uidAttrName[] = SAMDB_ATTR_UID;
    CHAR shellAttrName[] = SAMDB_ATTR_SHELL;
    CHAR gecosAttrName[] = SAMDB_ATTR_GECOS;

    SAMDB_ATTRIBUTE_TRANSFER userAttributes[] = {
        {
            objectSidAttrName,
            &object.Sid,
            SamDbTransferSid,
        },
        {
            samAccountAttrName,
            &object.Name,
            SamDbTransferPointerValue,
        },
        {
            passwordLastSetAttrName,
            &object.Info.User.PasswordLastSet,
            SamDbTransferPointerValue,
        },
        {
            accountExpiryAttrName,
            &object.Info.User.AccountExpires,
            SamDbTransferPointerValue,
        },
        {
            fullNameAttrName,
            &object.Info.User.FullName,
            SamDbTransferPointerValue,
        },
        {
            homeDirectoryAttrName,
            &object.Info.User.HomeDirectory,
            SamDbTransferPointerValue,
        },
        {
            homeDriveAttrName,
            &object.Info.User.HomeDirectoryDrive,
            SamDbTransferPointerValue,
        },
        {
            homeLogonScriptAttrName,
            &object.Info.User.ScriptPath,
            SamDbTransferPointerValue,
        },
        {
            profilePathAttrName,
            &object.Info.User.ProfilePath,
            SamDbTransferPointerValue,
        },
        {
            descriptionAttrName,
            &object.Info.User.Description,
            SamDbTransferPointerValue,
        },
        {
            workstationsAttrName,
            &object.Info.User.Workstations,
            SamDbTransferWorkstationsArray,
        },
        {
            commentAttrName,
            &object.Info.User.UserComment,
            SamDbTransferPointerValue,
        },
        {
            lmHashAttrName,
            &object.Info.User.LmOwfPassword,
            SamDbTransferOwfPassword,
        },
        {
            ntHashAttrName,
            &object.Info.User.NtOwfPassword,
            SamDbTransferOwfPassword,
        },
        {
            primaryGroupAttrName,
            &object.Info.User.PrimaryGroupRid,
            SamDbTransferPointerValue,
        },
        {
            passwordLastSetAttrName,
            &object.Info.User.PasswordLastSet,
            SamDbTransferPointerValue,
        },
        {
            accountFlagsAttrName,
            &object.Info.User.UserAccountControl,
            SamDbTransferPointerValue,
        },
        {
            likewiseFlagsAttrName,
            &object.Info.User.LikewiseFlags,
            SamDbTransferPointerValue,
        },
        {
            logonHoursAttrName,
            &object.Info.User.LogonHours,
            SamDbTransferLogonHoursArray,
        },
        {
            countryCodeAttrName,
            &object.Info.User.CountryCode,
            SamDbTransferPointerValue,
        },
        {
            codePageAttrName,
            &object.Info.User.CodePage,
            SamDbTransferPointerValue,
        },
        {
            uidAttrName,
            &unixInfo.Uid,
            SamDbTransferPointerValue,
        },
        {
            shellAttrName,
            &unixInfo.Shell,
            SamDbTransferPointerValue,
        },
        {
            gecosAttrName,
            &unixInfo.Gecos,
            SamDbTransferPointerValue,
        },
    };

    object.Type = SAMDB_OBJECT_TYPE_USER;

    for (attributeIndex = 0;
         attributeIndex < AttributesCount;
         attributeIndex++)
    {
        for (transferIndex = 0;
             transferIndex < sizeof(userAttributes)/sizeof(userAttributes[0]);
             transferIndex++)
        {
            if (RtlCStringIsEqual(
                       Attributes[attributeIndex].DbColumnName,
                       userAttributes[transferIndex].AttributeName,
                       TRUE))
            {
                error = userAttributes[transferIndex].TransferFn(
                           &(Attributes[attributeIndex]),
                           userAttributes[transferIndex].Destination);
                BAIL_ON_SAMDB_ERROR(error);
                break;
            }
        }
    }

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

    if (object.Info.User.LikewiseFlags & SAMDB_LIKEWISE_FLAG_UNIX_ENABLED)
    {
        error = LwAllocateMemory(
                         sizeof(unixInfo),
                         OUT_PPVOID(&object.Info.User.UnixInfo));
        BAIL_ON_SAMDB_ERROR(error);

        *object.Info.User.UnixInfo = unixInfo;
        memset(&unixInfo, 0, sizeof(unixInfo));
    }
    else
    {
        object.Info.User.UnixInfo = NULL;
    }

    *userObject = object;
    memset(&object, 0 , sizeof(object));

cleanup:
    if (error)
    {
        SamDbFreeObject(userObject);
        userObject = NULL;
    }

    *pObject = userObject;

    return error;
}


DWORD
SamDbMarshalLocalGroupObject(
    PSAMDB_ATTRIBUTE Attributes,
    DWORD AttributesCount,
    PSAMDB_OBJECT_INFO* pObject
    )
{
    DWORD error = ERROR_SUCCESS;
    SAMDB_OBJECT_INFO object = {0};
    SAMDB_UNIX_GROUP_INFO unixInfo = {0};
    PSAMDB_OBJECT_INFO localGroupObject = NULL;
    DWORD attributeIndex = 0;
    DWORD transferIndex = 0;
    CHAR objectSidAttrName[] = SAMDB_ATTR_OBJECT_SID;
    CHAR samAccountAttrName[] = SAMDB_ATTR_SAM_ACCOUNT_NAME;
    CHAR descriptionAttrName[] = SAMDB_ATTR_DESCRIPTION;
    CHAR likewiseFlagsAttrName[] = SAMDB_ATTR_LIKEWISE_FLAGS;
    CHAR gidAttrName[] = SAMDB_ATTR_GID;

    SAMDB_ATTRIBUTE_TRANSFER localGroupAttributes[] = {
        {
            objectSidAttrName,
            &object.Sid,
            SamDbTransferSid,
        },
        {
            samAccountAttrName,
            &object.Name,
            SamDbTransferPointerValue,
        },
        {
            descriptionAttrName,
            &object.Info.LocalGroup.Description,
            SamDbTransferPointerValue,
        },
        {
            likewiseFlagsAttrName,
            &object.Info.LocalGroup.LikewiseFlags,
            SamDbTransferPointerValue,
        },
        {
            gidAttrName,
            &unixInfo.Gid,
            SamDbTransferPointerValue,
        },
    };

    object.Type = SAMDB_OBJECT_TYPE_LOCAL_GROUP;

    for (attributeIndex = 0;
         attributeIndex < AttributesCount;
         attributeIndex++)
    {
        for (transferIndex = 0;
             transferIndex < (sizeof(localGroupAttributes)
                              /sizeof(localGroupAttributes[0]));
             transferIndex++)
        {
            if (RtlCStringIsEqual(
                       Attributes[attributeIndex].DbColumnName,
                       localGroupAttributes[transferIndex].AttributeName,
                       TRUE))
            {
                error = localGroupAttributes[transferIndex].TransferFn(
                           &(Attributes[attributeIndex]),
                           localGroupAttributes[transferIndex].Destination);
                BAIL_ON_SAMDB_ERROR(error);
                break;
            }
        }
    }

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

    if (object.Info.LocalGroup.LikewiseFlags &
        SAMDB_LIKEWISE_FLAG_UNIX_ENABLED)
    {
        error = LwAllocateMemory(
                       sizeof(unixInfo),
                       OUT_PPVOID(&object.Info.LocalGroup.UnixInfo));
        BAIL_ON_SAMDB_ERROR(error);

        *object.Info.LocalGroup.UnixInfo = unixInfo;
        memset(&unixInfo, 0, sizeof(unixInfo));
    }
    else
    {
        object.Info.LocalGroup.UnixInfo = NULL;
    }

    *localGroupObject = object;
    memset(&object, 0, sizeof(object));

cleanup:
    if (error)
    {
        SamDbFreeObject(localGroupObject);
        localGroupObject = NULL;
    }

    *pObject = localGroupObject;

    return error;
}


static
inline
DWORD
SamDbMapFlags(
    IN DWORD Flags,
    IN BOOLEAN IsSamrToDatabase,
    OUT OPTIONAL PDWORD Remaining
    )
{
    DWORD result = 0;
    DWORD remaining = Flags;
    // See [MS-SAMR] 3.1.5.14.2
    static struct {
        ULONG SamrFlag;
        ULONG DatabaseFlag;
    } Mappings[] = {
        { SAMDB_USER_FLAG_ACCOUNT_DISABLED,                SAMDB_UF_FLAG_ACCOUNTDISABLE },
        { SAMDB_USER_FLAG_HOME_DIRECTORY_REQUIRED,         SAMDB_UF_FLAG_HOMEDIR_REQUIRED },
        { SAMDB_USER_FLAG_PASSWORD_NOT_REQUIRED,           SAMDB_UF_FLAG_PASSWD_NOTREQD },
        { SAMDB_USER_FLAG_TEMP_DUPLICATE_ACCOUNT,          SAMDB_UF_FLAG_TEMP_DUPLICATE_ACCOUNT },
        { SAMDB_USER_FLAG_ENCRYPTED_TEXT_PASSWORD_ALLOWED, SAMDB_UF_FLAG_ENCRYPTED_TEXT_PASSWORD_ALLOWED },
        { SAMDB_USER_FLAG_NORMAL_ACCOUNT,                  SAMDB_UF_FLAG_NORMAL_ACCOUNT },
        { SAMDB_USER_FLAG_INTERDOMAIN_TRUST_ACCOUNT,       SAMDB_UF_FLAG_INTERDOMAIN_TRUST_ACCOUNT },
        { SAMDB_USER_FLAG_WORKSTATION_TRUST_ACCOUNT,       SAMDB_UF_FLAG_WORKSTATION_TRUST_ACCOUNT },
        { SAMDB_USER_FLAG_SERVER_TRUST_ACCOUNT,            SAMDB_UF_FLAG_SERVER_TRUST_ACCOUNT },
        { SAMDB_USER_FLAG_DONT_EXPIRE_PASSWORD,            SAMDB_UF_FLAG_DONT_EXPIRE_PASSWD },
        { SAMDB_USER_FLAG_MNS_LOGON_ACCOUNT,               SAMDB_UF_FLAG_MNS_LOGON_ACCOUNT },
        { SAMDB_USER_FLAG_SMARTCARD_REQUIRED,              SAMDB_UF_FLAG_SMARTCARD_REQUIRED },
        { SAMDB_USER_FLAG_TRUSTED_FOR_DELEGATION,          SAMDB_UF_FLAG_TRUSTED_FOR_DELEGATION },
        { SAMDB_USER_FLAG_NOT_DELEGATED,                   SAMDB_UF_FLAG_NOT_DELEGATED },
        { SAMDB_USER_FLAG_USE_DES_KEY_ONLY,                SAMDB_UF_FLAG_USE_DES_KEY_ONLY },
        { SAMDB_USER_FLAG_DONT_REQUIRE_PREAUTH,            SAMDB_UF_FLAG_DONT_REQUIRE_PREAUTH },
        { SAMDB_USER_FLAG_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, SAMDB_UF_FLAG_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION },
        { SAMDB_USER_FLAG_NO_AUTH_DATA_REQUIRED,           SAMDB_UF_FLAG_NO_AUTH_DATA_REQUIRED },
        { SAMDB_USER_FLAG_ACCOUNT_AUTO_LOCKED,             SAMDB_UF_FLAG_LOCKOUT },
        { SAMDB_USER_FLAG_PASSWORD_EXPIRED,                SAMDB_UF_FLAG_PASSWORD_EXPIRED },
        { SAMDB_USER_FLAG_PARTIAL_SECRETS_ACCOUNT,         SAMDB_UF_FLAG_PARTIAL_SECRETS_ACCOUNT },
        { SAMDB_USER_FLAG_USE_AES_KEYS,                    SAMDB_UF_FLAG_USE_AES_KEYS },
    };

    DWORD i = 0;

    for (i = 0; i < sizeof(Mappings)/sizeof(Mappings[0]); i++)
    {
        DWORD from = IsSamrToDatabase ? Mappings[i].SamrFlag : Mappings[i].DatabaseFlag;
        DWORD to = IsSamrToDatabase ? Mappings[i].DatabaseFlag : Mappings[i].SamrFlag;

        if (IsSetFlag(Flags, from))
        {
            SetFlag(result, to);
            ClearFlag(remaining, from);
        }
    }

    if (remaining)
    {
        SAMDB_LOG_DEBUG("Unexpected remaining flags 0x%08x "
                        "(Flags = 0x%08x, IsSamrToDatabase = %c)",
                        remaining, Flags, IsSamrToDatabase ? 'T' : 'F');
    }

    if (Remaining)
    {
        *Remaining = remaining;
    }

    return result;
}


SAMDB_UF_FLAGS
SamDbMapSamrToDatabaseFlags(
    IN SAMDB_USER_FLAGS Flags,
    OUT OPTIONAL PSAMDB_USER_FLAGS Remaining
    )
{
    return SamDbMapFlags(Flags, TRUE, Remaining);
}


SAMDB_USER_FLAGS
SamDbMapDatabaseToSamrFlags(
    IN SAMDB_UF_FLAGS Flags,
    OUT OPTIONAL PSAMDB_UF_FLAGS Remaining
    )
{
    return SamDbMapFlags(Flags, FALSE, Remaining);
}


static
inline
DWORD
SamDbMapObjectType(
    IN DWORD Input,
    IN BOOLEAN IsObjectTypeToClass
    )
{
    DWORD result = 0;
    // See [MS-SAMR] 3.1.5.14.2
    static struct {
        SAMDB_OBJECT_TYPE Type;
        SAMDB_OBJECT_CLASS Class;
    } Mappings[] = {
        { SAMDB_OBJECT_TYPE_UNKNOWN,      SAMDB_OBJECT_CLASS_UNKNOWN },
        { SAMDB_OBJECT_TYPE_DOMAIN,       SAMDB_OBJECT_CLASS_DOMAIN },
        { SAMDB_OBJECT_TYPE_USER,         SAMDB_OBJECT_CLASS_USER },
        { SAMDB_OBJECT_TYPE_LOCAL_GROUP,  SAMDB_OBJECT_CLASS_LOCAL_GROUP },
        // Foreign Security Principal is internal to samdb
    };

    DWORD index = 0;

    for (index = 0; index < sizeof(Mappings)/sizeof(Mappings[0]); index++)
    {
        if (IsObjectTypeToClass &&
            Input == Mappings[index].Type)
        {
            result = Mappings[index].Class;
            break;
        }
        else if (!IsObjectTypeToClass &&
                 Input == Mappings[index].Class)
        {
            result = Mappings[index].Class;
            break;
        }
    }

    if (index >= sizeof(Mappings)/sizeof(Mappings[0]))
    {
        SAMDB_LOG_DEBUG("Unknown %s attempted to map to %s.",
                        IsObjectTypeToClass ? "object type" : "object class",
                        IsObjectTypeToClass ? "object class" : "object type");
        result = 0;
    }

    return result;
}


SAMDB_OBJECT_CLASS
SamDbMapObjectTypeToObjectClass(
    IN SAMDB_OBJECT_TYPE Type
    )
{
    return SamDbMapObjectType(Type, TRUE);
}


SAMDB_OBJECT_TYPE
SamDbMapObjectClassToObjectType(
    IN SAMDB_OBJECT_CLASS Class
    )
{
    return SamDbMapObjectType(Class, FALSE);
}
