#include "includes.h"


static
DWORD
SamrSrvBuildHomedirPath(
    PCWSTR  pwszSamAccountName,
    PCWSTR  pwszDomainName,
    PWSTR  *ppHomedirPath
    );


NTSTATUS
SamrSrvCreateAccount(
    IN  handle_t          hBinding,
    IN  DOMAIN_HANDLE     hDomain,
    IN  PUNICODE_STRING   AccountName,
    IN  SAMDB_OBJECT_TYPE ObjectType,
    IN  DWORD             dwAccountFlags,
    IN  DWORD             dwAccessMask,
    OUT ACCOUNT_HANDLE   *phAccount,
    OUT PDWORD            pdwAccessGranted,
    OUT PDWORD            pdwRid
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    DWORD dwError = ERROR_SUCCESS;
    PDOMAIN_CONTEXT pDomCtx = NULL;
    PCONNECT_CONTEXT pConnCtx = NULL;
    PACCOUNT_CONTEXT pAccCtx = NULL;
    PWSTR pwszAccountName = NULL;
    PSTR pszAccountName = NULL;
    PSTR pszShell = NULL;
    PWSTR pwszShell = NULL;
    PWSTR pwszHomedirPath = NULL;
    DWORD accountRid = 0;
    DWORD dwAccountType = 0;
    SECURITY_INFORMATION securityInformation = 0;
    PSECURITY_DESCRIPTOR_ABSOLUTE securityDescriptor = NULL;
    PSECURITY_DESCRIPTOR_RELATIVE securityDescriptorRelative = NULL;
    GENERIC_MAPPING GenericMapping = {0};
    DWORD dwAccessGranted = 0;
    SAMDB_QUERY query = {0};
    PSAMDB_OBJECT_INFO* objects = NULL;
    PSAMDB_OBJECT_INFO newAccountObject = NULL;
    SAMDB_OBJECT_INFO accountObject = {0};
    PSID accountSid = NULL;
    SAMDB_OBJECT_INFO updateObject = {0};
    SAMDB_FIELDS updateFields = 0;

    pDomCtx  = (PDOMAIN_CONTEXT)hDomain;
    pConnCtx = pDomCtx->pConnCtx;

    if (pDomCtx == NULL || pDomCtx->Type != SamrContextDomain)
    {
        ntStatus = STATUS_INVALID_HANDLE;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    switch (ObjectType)
    {
    case SAMDB_OBJECT_TYPE_USER:
        dwAccountType = SID_TYPE_USER;
        break;

    case SAMDB_OBJECT_TYPE_LOCAL_GROUP:
        dwAccountType = SID_TYPE_ALIAS;
        break;

    default:
        ntStatus = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntStatus);
        break;
    }

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

    dwError = LwAllocateWc16StringFromUnicodeString(
                            &pwszAccountName,
                            AccountName);
    BAIL_ON_LSA_ERROR(dwError);

    /*
     * Check if such account name already exists.
     */
    query.Name = pwszAccountName;

    dwError = SamDbFindObjects(
                       ObjectType,
                       SAMDB_QUERY_TYPE_NT_NAME,
                       1,
                       &query,
                       &objects);
    if (dwError == ERROR_SUCCESS && objects[0] != NULL)
    {
        /* Account already exists - return error code */
        switch (ObjectType)
        {
        case SAMDB_OBJECT_TYPE_USER:
            ntStatus = STATUS_USER_EXISTS;
            break;

        case SAMDB_OBJECT_TYPE_LOCAL_GROUP:
            ntStatus = STATUS_ALIAS_EXISTS;
            break;

        default:
            ntStatus = STATUS_INTERNAL_ERROR;

            LSA_LOG_ERROR("Invalid object type passed");
            goto error;
        }
        BAIL_ON_NT_STATUS(ntStatus);
    }
    else if (dwError == ERROR_SUCCESS && objects[0] == NULL)
    {
        /* Account doesn't exist - proceed with creating it */
        ntStatus = STATUS_SUCCESS;
    }
    else if (dwError != ERROR_SUCCESS)
    {
        BAIL_ON_LSA_ERROR(dwError);
    }

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

    accountObject.Type = ObjectType;
    accountObject.Name = pwszAccountName;

    if (ObjectType == SAMDB_OBJECT_TYPE_USER)
    {
        accountObject.Info.User.LikewiseFlags =
            SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;
    }
    else if (ObjectType == SAMDB_OBJECT_TYPE_LOCAL_GROUP)
    {
        accountObject.Info.LocalGroup.LikewiseFlags =
            SAMDB_LIKEWISE_FLAG_UNIX_ENABLED;
    }

    dwError = SamDbAddObject(&accountObject, NULL);
    BAIL_ON_LSA_ERROR(dwError);

    query.Name = pwszAccountName;
    pwszAccountName = NULL;

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

    newAccountObject = objects[0];
    if (newAccountObject == NULL)
    {
        ntStatus = STATUS_INTERNAL_ERROR;

        dwError = LwWc16sToMbs(
                       pwszAccountName,
                       &pszAccountName);
        BAIL_ON_LSA_ERROR(dwError);

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

    //
    // Copy account SID and RID
    //
    ntStatus = RtlDuplicateSid(
                         &accountSid,
                         newAccountObject->Sid);
    BAIL_ON_NT_STATUS(ntStatus);

    accountRid = accountSid->SubAuthority[accountSid->SubAuthorityCount - 1];

    //
    // Set user specific fields (shell, homedir)
    //
    if (ObjectType == SAMDB_OBJECT_TYPE_USER)
    {
        PSAMDB_USER_INFO userInfo = &updateObject.Info.User;

        updateObject.Type = ObjectType;
        updateObject.Sid = newAccountObject->Sid;
        newAccountObject->Sid = NULL;

        dwError = LwAllocateMemory(
                               sizeof(*userInfo->UnixInfo),
                               OUT_PPVOID(&userInfo->UnixInfo));
        BAIL_ON_LSA_ERROR(dwError);

        // Set Shell
        dwError = SamrSrvConfigGetDefaultLoginShell(&pszShell);
        BAIL_ON_LSA_ERROR(dwError);

        dwError = LwMbsToWc16s(pszShell, &pwszShell);
        BAIL_ON_LSA_ERROR(dwError);

        userInfo->UnixInfo->Shell = pwszShell;
        pwszShell = NULL;

        updateFields |= SAMDB_FIELD_USER_UNIX_SHELL;

        // Set HomeDirectory
        dwError = SamrSrvBuildHomedirPath(
                               newAccountObject->Name,
                               pDomCtx->pwszDomainName,
                               &pwszHomedirPath);
        BAIL_ON_LSA_ERROR(dwError);

        userInfo->HomeDirectory = pwszHomedirPath;
        pwszHomedirPath = NULL;

        updateFields |= SAMDB_FIELD_USER_HOME_DIRECTORY;

        updateObject.Info.User.UserAccountControl =
            newAccountObject->Info.User.UserAccountControl;
        userInfo->UserAccountControl |= SAMDB_UF_FLAG_ACCOUNTDISABLE;

        updateFields |= SAMDB_FIELD_USER_ACCOUNT_CONTROL;

        dwError = SamDbUpdateObject(
                              &updateObject,
                              updateFields);
        BAIL_ON_LSA_ERROR(dwError);
    }

    securityInformation =
        OWNER_SECURITY_INFORMATION |
        GROUP_SECURITY_INFORMATION |
        DACL_SECURITY_INFORMATION;

    dwError = SamDbQuerySecurityDescriptor(
                              accountSid,
                              securityInformation,
                              &securityDescriptorRelative);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = SamrSrvAllocateAbsoluteFromSelfRelativeSD(
                              securityDescriptorRelative,
                              &securityDescriptor);
    BAIL_ON_LSA_ERROR(dwError);

    if (!RtlAccessCheck(securityDescriptor,
                        pConnCtx->pUserToken,
                        dwAccessMask,
                        0,
                        &GenericMapping,
                        &dwAccessGranted,
                        &ntStatus))
    {
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pAccCtx->Type            = SamrContextAccount;
    pAccCtx->refcount        = 1;
    pAccCtx->dwAccessGranted = dwAccessGranted;
    pAccCtx->pwszName        = pwszAccountName;
    pAccCtx->pSid            = accountSid;
    pAccCtx->dwRid           = accountRid;
    pAccCtx->dwAccountType   = dwAccountType;
    pAccCtx->ObjectType      = ObjectType;

    pAccCtx->pDomCtx         = pDomCtx;
    InterlockedIncrement(&pDomCtx->refcount);

    *phAccount        = (ACCOUNT_HANDLE)pAccCtx;
    *pdwRid           = accountRid;
    *pdwAccessGranted = dwAccessGranted;

error:
    if (ntStatus || dwError)
    {
        if (pAccCtx)
        {
            SamrSrvAccountContextFree(pAccCtx);
        }

        *phAccount        = NULL;
        *pdwAccessGranted = 0;
        *pdwRid           = 0;
    }

    LW_SAFE_FREE_MEMORY(pwszAccountName);

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

    SamDbReleaseObject(&accountObject);
    SamDbReleaseObject(&updateObject);

    if (securityDescriptor)
    {
        SamrSrvFreeSecurityDescriptor(&securityDescriptor);
    }

    LW_SAFE_FREE_MEMORY(securityDescriptorRelative);

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

    return ntStatus;
}


static
DWORD
SamrSrvBuildHomedirPath(
    PCWSTR pwszSamAccountName,
    PCWSTR pwszDomainName,
    PWSTR *ppwszHomedirPath
    )
{
    DWORD dwError = ERROR_SUCCESS;
    PWSTR pwszSamAccount = NULL;
    PWSTR pwszDomain = NULL;
    PSTR pszHomedirTemplate = NULL;
    PWSTR pwszHomedirTemplate = NULL;
    size_t sHomedirTemplateLen = 0;
    PWSTR pwszTemplateCursor = NULL;
    PSTR pszHomedirPrefix = NULL;
    PWSTR pwszHomedirPrefix = NULL;
    size_t sHomedirPrefixLen = 0;
    PSTR pszHostName = NULL;
    PWSTR pwszHostName = NULL;
    size_t sHostNameLen = 0;
    size_t sSamAccountNameLen = 0;
    size_t sDomainNameLen = 0;
    PWSTR pwszHomedirPath = NULL;
    DWORD dwHomedirPathLenAllowed = 0;
    DWORD dwOffset = 0;
    DWORD dwLenRemaining = 0;
    PWSTR pwszInsert = NULL;
    size_t sInsertLen = 0;

    dwError = LwAllocateWc16String(&pwszSamAccount,
                                   pwszSamAccountName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwAllocateWc16String(&pwszDomain,
                                   pwszDomainName);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = SamrSrvConfigGetHomedirTemplate(&pszHomedirTemplate);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwMbsToWc16s(pszHomedirTemplate,
                           &pwszHomedirTemplate);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sLen(pwszHomedirTemplate,
                         &sHomedirTemplateLen);
    BAIL_ON_LSA_ERROR(dwError);

    if (strstr(pszHomedirTemplate, "%H"))
    {
        dwError = SamrSrvConfigGetHomedirPrefix(&pszHomedirPrefix);
        BAIL_ON_LSA_ERROR(dwError);

        if (pszHomedirPrefix)
        {
            dwError = LwMbsToWc16s(pszHomedirPrefix,
                                   &pwszHomedirPrefix);
            BAIL_ON_LSA_ERROR(dwError);

            dwError = LwWc16sLen(pwszHomedirPrefix,
                                 &sHomedirPrefixLen);
            BAIL_ON_LSA_ERROR(dwError);
        }
    }

    if (strstr(pszHomedirTemplate, "%L"))
    {
        dwError = LsaDnsGetHostInfo(&pszHostName);
        BAIL_ON_LSA_ERROR(dwError);

        if (pszHostName)
        {
            dwError = LwMbsToWc16s(pszHostName,
                                   &pwszHostName);
            BAIL_ON_LSA_ERROR(dwError);

            dwError = LwWc16sLen(pwszHostName,
                                 &sHostNameLen);
            BAIL_ON_LSA_ERROR(dwError);
        }
    }

    dwError = LwWc16sLen(pwszSamAccount,
                         &sSamAccountNameLen);
    BAIL_ON_LSA_ERROR(dwError);

    dwError = LwWc16sLen(pwszDomain,
                         &sDomainNameLen);
    BAIL_ON_LSA_ERROR(dwError);

    dwHomedirPathLenAllowed = (sHomedirTemplateLen +
                               sHomedirPrefixLen +
                               sHostNameLen +
                               sSamAccountNameLen +
                               sDomainNameLen +
                               1);

    dwError = LwAllocateMemory(sizeof(WCHAR) * dwHomedirPathLenAllowed,
                               OUT_PPVOID(&pwszHomedirPath));
    BAIL_ON_LSA_ERROR(dwError);

    pwszTemplateCursor = pwszHomedirTemplate;
    dwLenRemaining = dwHomedirPathLenAllowed - dwOffset - 1;

    while (pwszTemplateCursor[0] &&
           dwLenRemaining > 0)
    {
        if (pwszTemplateCursor[0] == (WCHAR)('%'))
        {
            switch (pwszTemplateCursor[1])
            {
            case (WCHAR)('D'):
                pwszInsert = pwszDomain;
                sInsertLen = sDomainNameLen;

                dwError = LwWc16sToUpper(pwszInsert);
                BAIL_ON_LSA_ERROR(dwError);
                break;

            case (WCHAR)('U'):
                pwszInsert = pwszSamAccount;
                sInsertLen = sSamAccountNameLen;

                dwError = LwWc16sToLower(pwszInsert);
                BAIL_ON_LSA_ERROR(dwError);
                break;

            case (WCHAR)('H'):
                pwszInsert = pwszHomedirPrefix;
                sInsertLen = sHomedirPrefixLen;
                break;

            case (WCHAR)('L'):
                pwszInsert = pwszHostName;
                sInsertLen = sHostNameLen;

            default:
                dwError = LW_ERROR_INVALID_HOMEDIR_TEMPLATE;
                BAIL_ON_LSA_ERROR(dwError);
            }

            pwszTemplateCursor += 2;
        }
        else
        {
            PCWSTR pwszEnd = pwszTemplateCursor;
            while (pwszEnd[0] &&
                   pwszEnd[0] != (WCHAR)('%'))
            {
                pwszEnd++;
            }

            if (!pwszEnd)
            {
                dwError = LwWc16sLen(pwszTemplateCursor,
                                     &sInsertLen);
                BAIL_ON_LSA_ERROR(dwError);
            }
            else
            {
                sInsertLen = pwszEnd - pwszTemplateCursor;
            }

            pwszInsert = pwszTemplateCursor;
            pwszTemplateCursor += sInsertLen;
        }

        memcpy(pwszHomedirPath + dwOffset,
               pwszInsert,
               sizeof(WCHAR) * sInsertLen);

        dwOffset += sInsertLen;
        dwLenRemaining = dwHomedirPathLenAllowed - dwOffset - 1;
    }

    LSA_ASSERT(dwOffset < dwHomedirPathLenAllowed);

    pwszHomedirPath[dwOffset] = 0;
    dwOffset++;

    *ppwszHomedirPath = pwszHomedirPath;

cleanup:
    LW_SAFE_FREE_MEMORY(pwszSamAccount);
    LW_SAFE_FREE_MEMORY(pwszDomain);
    LW_SAFE_FREE_MEMORY(pszHomedirTemplate);
    LW_SAFE_FREE_MEMORY(pwszHomedirTemplate);
    LW_SAFE_FREE_MEMORY(pszHomedirPrefix);
    LW_SAFE_FREE_MEMORY(pwszHomedirPrefix);
    LW_SAFE_FREE_MEMORY(pszHostName);
    LW_SAFE_FREE_MEMORY(pwszHostName);

    return dwError;

error:
    LW_SAFE_FREE_MEMORY(pwszHomedirPath);

    *ppwszHomedirPath = NULL;
    goto cleanup;
}


/*
local variables:
mode: c
c-basic-offset: 4
indent-tabs-mode: nil
tab-width: 4
end:
*/
