/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS system libraries
 * PURPOSE:         Boot Data implementation
 * FILE:            lib/rtl/bootdata.c
 * PROGRAMMERS:
 */

/* INCLUDES *****************************************************************/

#include <rtl.h>
#define NDEBUG
#include <debug.h>

/* FUNCTIONS *****************************************************************/

static SID_IDENTIFIER_AUTHORITY LocalSystemAuthority = {SECURITY_NT_AUTHORITY};

static NTSTATUS
RtlpSysVolCreateSecurityDescriptor(OUT PISECURITY_DESCRIPTOR *SecurityDescriptor,
                                   OUT PSID *SystemSid)
{
    PSECURITY_DESCRIPTOR AbsSD = NULL;
    PSID LocalSystemSid = NULL;
    PACL Dacl = NULL;
    ULONG DaclSize;
    NTSTATUS Status;

    /* create the local SYSTEM SID */
    Status = RtlAllocateAndInitializeSid(&LocalSystemAuthority,
                                         1,
                                         SECURITY_LOCAL_SYSTEM_RID,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         &LocalSystemSid);
    if (!NT_SUCCESS(Status))
    {
        return Status;
    }

    /* allocate and initialize the security descriptor */
    AbsSD = RtlpAllocateMemory(sizeof(SECURITY_DESCRIPTOR),
                               'dSeS');
    if (AbsSD == NULL)
    {
        Status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    Status = RtlCreateSecurityDescriptor(AbsSD,
                                         SECURITY_DESCRIPTOR_REVISION);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* allocate and create the DACL */
    DaclSize = sizeof(ACL) + sizeof(ACE) +
               RtlLengthSid(LocalSystemSid);
    Dacl = RtlpAllocateMemory(DaclSize,
                              'cAeS');
    if (Dacl == NULL)
    {
        Status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    Status = RtlCreateAcl(Dacl,
                          DaclSize,
                          ACL_REVISION);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    Status = RtlAddAccessAllowedAceEx(Dacl,
                                      ACL_REVISION,
                                      OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
                                      STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL,
                                      LocalSystemSid);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* set the DACL in the security descriptor */
    Status = RtlSetDaclSecurityDescriptor(AbsSD,
                                          TRUE,
                                          Dacl,
                                          FALSE);

    /* all done */
    if (NT_SUCCESS(Status))
    {
        *SecurityDescriptor = AbsSD;
        *SystemSid = LocalSystemSid;
    }
    else
    {
Cleanup:
        if (LocalSystemSid != NULL)
        {
            RtlFreeSid(LocalSystemSid);
        }

        if (Dacl != NULL)
        {
            RtlpFreeMemory(Dacl,
                           'cAeS');
        }

        if (AbsSD != NULL)
        {
            RtlpFreeMemory(AbsSD,
                           'dSeS');
        }
    }

    return Status;
}

static NTSTATUS
RtlpSysVolCheckOwnerAndSecurity(IN HANDLE DirectoryHandle,
                                IN PISECURITY_DESCRIPTOR SecurityDescriptor)
{
    PSECURITY_DESCRIPTOR RelSD = NULL;
    PSECURITY_DESCRIPTOR NewRelSD = NULL;
    PSECURITY_DESCRIPTOR AbsSD = NULL;
#ifdef _WIN64
    BOOLEAN AbsSDAllocated = FALSE;
#endif
    PSID AdminSid = NULL;
    PSID LocalSystemSid = NULL;
    ULONG DescriptorSize;
    ULONG AbsSDSize, RelSDSize = 0;
    PACL Dacl;
    BOOLEAN DaclPresent, DaclDefaulted;
    PSID OwnerSid;
    BOOLEAN OwnerDefaulted;
    ULONG AceIndex;
    PACE Ace = NULL;
    NTSTATUS Status;

    /* find out how much memory we need to allocate for the self-relative
       descriptor we're querying */
    Status = ZwQuerySecurityObject(DirectoryHandle,
                                   OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                   NULL,
                                   0,
                                   &DescriptorSize);
    if (Status != STATUS_BUFFER_TOO_SMALL)
    {
        /* looks like the FS doesn't support security... return success */
        Status = STATUS_SUCCESS;
        goto Cleanup;
    }

    /* allocate enough memory for the security descriptor */
    RelSD = RtlpAllocateMemory(DescriptorSize,
                               'dSeS');
    if (RelSD == NULL)
    {
        Status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    /* query the self-relative security descriptor */
    Status = ZwQuerySecurityObject(DirectoryHandle,
                                   OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                   RelSD,
                                   DescriptorSize,
                                   &DescriptorSize);
    if (!NT_SUCCESS(Status))
    {
        /* FIXME - handle the case where someone else modified the owner and/or
                   DACL while we allocated memory. But that should be *very*
                   unlikely.... */
        goto Cleanup;
    }

    /* query the owner and DACL from the descriptor */
    Status = RtlGetOwnerSecurityDescriptor(RelSD,
                                           &OwnerSid,
                                           &OwnerDefaulted);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    Status = RtlGetDaclSecurityDescriptor(RelSD,
                                          &DaclPresent,
                                          &Dacl,
                                          &DaclDefaulted);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* create the Administrators SID */
    Status = RtlAllocateAndInitializeSid(&LocalSystemAuthority,
                                         2,
                                         SECURITY_BUILTIN_DOMAIN_RID,
                                         DOMAIN_ALIAS_RID_ADMINS,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         &AdminSid);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* create the local SYSTEM SID */
    Status = RtlAllocateAndInitializeSid(&LocalSystemAuthority,
                                         1,
                                         SECURITY_LOCAL_SYSTEM_RID,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         &LocalSystemSid);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* check if the Administrators are the owner and at least a not-NULL DACL
       is present */
    if (OwnerSid != NULL &&
        RtlEqualSid(OwnerSid,
                    AdminSid) &&
        DaclPresent && Dacl != NULL)
    {
        /* check the DACL for an Allowed ACE for the SYSTEM account */
        AceIndex = 0;
        do
        {
            Status = RtlGetAce(Dacl,
                               AceIndex++,
                               (PVOID*)&Ace);
            if (!NT_SUCCESS(Status))
            {
                Ace = NULL;
            }
            else if (Ace != NULL && Ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
            {
                /* check if the the ACE is a set of allowed permissions for the
                   local SYSTEM account */
                if (RtlEqualSid((PSID)(Ace + 1),
                                LocalSystemSid))
                {
                    /* check if the ACE is inherited by noncontainer and
                       container objects, if not attempt to change that */
                    if (!(Ace->Header.AceFlags & OBJECT_INHERIT_ACE) ||
                        !(Ace->Header.AceFlags & CONTAINER_INHERIT_ACE))
                    {
                        Ace->Header.AceFlags |= OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
                        Status = ZwSetSecurityObject(DirectoryHandle,
                                                     DACL_SECURITY_INFORMATION,
                                                     RelSD);
                    }
                    else
                    {
                        /* all done, we have access */
                        Status = STATUS_SUCCESS;
                    }

                    goto Cleanup;
                }
            }
        } while (Ace != NULL);
    }

    AbsSDSize = DescriptorSize;

    /* because we need to change any existing data we need to convert it to
       an absolute security descriptor first */
    Status = RtlSelfRelativeToAbsoluteSD2(RelSD,
                                          &AbsSDSize);
#ifdef _WIN64
    if (Status == STATUS_BUFFER_TOO_SMALL)
    {
        /* this error code can only be returned on 64 bit builds because
           the size of an absolute security descriptor is greater than the
           size of a self-relative security descriptor */
        ASSERT(AbsSDSize > DescriptorSize);

        AbsSD = RtlpAllocateMemory(DescriptorSize,
                                   'dSeS');
        if (AbsSD == NULL)
        {
            Status = STATUS_NO_MEMORY;
            goto Cleanup;
        }

        AbsSDAllocated = TRUE;

        /* make a raw copy of the self-relative descriptor */
        RtlCopyMemory(AbsSD,
                      RelSD,
                      DescriptorSize);

        /* finally convert it */
        Status = RtlSelfRelativeToAbsoluteSD2(AbsSD,
                                              &AbsSDSize);
    }
    else
#endif
    {
        AbsSD = RelSD;
    }

    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* set the owner SID */
    Status = RtlSetOwnerSecurityDescriptor(AbsSD,
                                           AdminSid,
                                           FALSE);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* set the DACL in the security descriptor */
    Status = RtlSetDaclSecurityDescriptor(AbsSD,
                                          TRUE,
                                          SecurityDescriptor->Dacl,
                                          FALSE);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* convert it back to a self-relative descriptor, find out how much
       memory we need */
    Status = RtlAbsoluteToSelfRelativeSD(AbsSD,
                                         NULL,
                                         &RelSDSize);
    if (Status != STATUS_BUFFER_TOO_SMALL)
    {
        goto Cleanup;
    }

    /* allocate enough memory for the new self-relative descriptor */
    NewRelSD = RtlpAllocateMemory(RelSDSize,
                                  'dSeS');
    if (NewRelSD == NULL)
    {
        Status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    /* convert the security descriptor to self-relative format */
    Status = RtlAbsoluteToSelfRelativeSD(AbsSD,
                                         NewRelSD,
                                         &RelSDSize);
    if (Status == STATUS_BUFFER_TOO_SMALL)
    {
        goto Cleanup;
    }

    /* finally attempt to change the security information */
    Status = ZwSetSecurityObject(DirectoryHandle,
                                 OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
                                 NewRelSD);

Cleanup:
    if (AdminSid != NULL)
    {
        RtlFreeSid(AdminSid);
    }

    if (LocalSystemSid != NULL)
    {
        RtlFreeSid(LocalSystemSid);
    }

    if (RelSD != NULL)
    {
        RtlpFreeMemory(RelSD,
                       'dSeS');
    }

    if (NewRelSD != NULL)
    {
        RtlpFreeMemory(NewRelSD,
                       'dSeS');
    }

#ifdef _WIN64
    if (AbsSDAllocated)
    {
        RtlpFreeMemory(AbsSD,
                       'dSeS');
    }
#endif

    return Status;
}

_Must_inspect_result_
static
NTSTATUS
RtlpSysVolTakeOwnership(IN PUNICODE_STRING DirectoryPath,
                        IN PSECURITY_DESCRIPTOR SecurityDescriptor)
{
    TOKEN_PRIVILEGES TokenPrivileges;
    OBJECT_ATTRIBUTES ObjectAttributes;
    SECURITY_DESCRIPTOR AbsSD;
    PSID AdminSid = NULL;
    IO_STATUS_BLOCK IoStatusBlock;
    BOOLEAN TokenEnabled = FALSE;
    HANDLE hToken = NULL;
    HANDLE hDirectory = NULL;
    NTSTATUS Status;
    ULONG ReturnLength;

    Status = ZwOpenProcessToken(NtCurrentProcess(),
                                TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
                                &hToken);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* attempt to enable the SE_TAKE_OWNERSHIP_PRIVILEGE privilege */
    TokenPrivileges.PrivilegeCount = 1;
    TokenPrivileges.Privileges[0].Luid.LowPart = SE_TAKE_OWNERSHIP_PRIVILEGE;
    TokenPrivileges.Privileges[0].Luid.HighPart = 0;
    TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    Status = ZwAdjustPrivilegesToken(hToken,
                                     FALSE,
                                     &TokenPrivileges,
                                     sizeof(TokenPrivileges),
                                     &TokenPrivileges,
                                     &ReturnLength);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }
    TokenEnabled = (TokenPrivileges.PrivilegeCount != 0);

    /* open the directory */
    InitializeObjectAttributes(&ObjectAttributes,
                               DirectoryPath,
                               0,
                               NULL,
                               SecurityDescriptor);

    Status = ZwOpenFile(&hDirectory,
                        SYNCHRONIZE | WRITE_OWNER,
                        &ObjectAttributes,
                        &IoStatusBlock,
                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                        FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* create the Administrators SID */
    Status = RtlAllocateAndInitializeSid(&LocalSystemAuthority,
                                         2,
                                         SECURITY_BUILTIN_DOMAIN_RID,
                                         DOMAIN_ALIAS_RID_ADMINS,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         0,
                                         &AdminSid);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* create the security descriptor */
    Status = RtlCreateSecurityDescriptor(&AbsSD,
                                         SECURITY_DESCRIPTOR_REVISION);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    Status = RtlSetOwnerSecurityDescriptor(&AbsSD,
                                           AdminSid,
                                           FALSE);
    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }

    /* attempt to take ownership */
    Status = ZwSetSecurityObject(hDirectory,
                                 OWNER_SECURITY_INFORMATION,
                                 &AbsSD);

Cleanup:
    if (TokenEnabled)
    {
        /* Disable privileges that we had to enable, whetever the result was. */
        NTSTATUS Status2 = ZwAdjustPrivilegesToken(hToken,
                                                   FALSE,
                                                   &TokenPrivileges,
                                                   0,
                                                   NULL,
                                                   NULL);
        /* This must succeed */
        ASSERT(NT_SUCCESS(Status2));
        (void)Status2;
    }

    if (AdminSid != NULL)
    {
        RtlFreeSid(AdminSid);
    }

    if (hDirectory != NULL)
    {
        ZwClose(hDirectory);
    }

    if (hToken != NULL)
    {
        ZwClose(hToken);
    }

    return Status;
}

/*
* @implemented
*/
NTSTATUS
NTAPI
RtlCreateSystemVolumeInformationFolder(IN PUNICODE_STRING VolumeRootPath)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK IoStatusBlock;
    HANDLE hDirectory;
    UNICODE_STRING DirectoryName, NewPath;
    ULONG PathLen;
    PISECURITY_DESCRIPTOR SecurityDescriptor = NULL;
    PSID SystemSid = NULL;
    BOOLEAN AddSep = FALSE;
    NTSTATUS Status;

    PAGED_CODE_RTL();

    RtlInitUnicodeString(&DirectoryName,
                         L"System Volume Information");

    PathLen = VolumeRootPath->Length + DirectoryName.Length;

    /* make sure we don't overflow while appending the strings */
    if (PathLen > 0xFFFC)
    {
        return STATUS_INVALID_PARAMETER;
    }

    if (VolumeRootPath->Buffer[(VolumeRootPath->Length / sizeof(WCHAR)) - 1] != L'\\')
    {
        AddSep = TRUE;
        PathLen += sizeof(WCHAR);
    }

    /* allocate the new string */
    NewPath.MaximumLength = (USHORT)PathLen + sizeof(WCHAR);
    NewPath.Buffer = RtlpAllocateStringMemory(NewPath.MaximumLength,
                                              TAG_USTR);
    if (NewPath.Buffer == NULL)
    {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* create the new path string */
    NewPath.Length = VolumeRootPath->Length;
    RtlCopyMemory(NewPath.Buffer,
                  VolumeRootPath->Buffer,
                  NewPath.Length);
    if (AddSep)
    {
        NewPath.Buffer[NewPath.Length / sizeof(WCHAR)] = L'\\';
        NewPath.Length += sizeof(WCHAR);
    }
    RtlCopyMemory(NewPath.Buffer + (NewPath.Length / sizeof(WCHAR)),
                  DirectoryName.Buffer,
                  DirectoryName.Length);
    NewPath.Length += DirectoryName.Length;
    NewPath.Buffer[NewPath.Length / sizeof(WCHAR)] = L'\0';

    ASSERT(NewPath.Length == PathLen);
    ASSERT(NewPath.Length == NewPath.MaximumLength - sizeof(WCHAR));

    /* create the security descriptor for the new directory */
    Status = RtlpSysVolCreateSecurityDescriptor(&SecurityDescriptor,
                                                &SystemSid);
    if (NT_SUCCESS(Status))
    {
        /* create or open the directory */
        InitializeObjectAttributes(&ObjectAttributes,
                                   &NewPath,
                                   0,
                                   NULL,
                                   SecurityDescriptor);

        Status = ZwCreateFile(&hDirectory,
                              SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL,
                              &ObjectAttributes,
                              &IoStatusBlock,
                              NULL,
                              FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
                              FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                              FILE_OPEN_IF,
                              FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                              NULL,
                              0);
        if (!NT_SUCCESS(Status))
        {
            Status = RtlpSysVolTakeOwnership(&NewPath,
                                             SecurityDescriptor);

            if (NT_SUCCESS(Status))
            {
                /* successfully took ownership, attempt to open it */
                Status = ZwCreateFile(&hDirectory,
                                      SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL,
                                      &ObjectAttributes,
                                      &IoStatusBlock,
                                      NULL,
                                      FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
                                      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                      FILE_OPEN_IF,
                                      FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
                                      NULL,
                                      0);
            }
        }

        if (NT_SUCCESS(Status))
        {
            /* check security now and adjust it if neccessary */
            Status = RtlpSysVolCheckOwnerAndSecurity(hDirectory,
                                                     SecurityDescriptor);
            ZwClose(hDirectory);
        }

        /* free allocated memory */
        ASSERT(SecurityDescriptor != NULL);
        ASSERT(SecurityDescriptor->Dacl != NULL);

        RtlpFreeMemory(SecurityDescriptor->Dacl,
                       'cAeS');
        RtlpFreeMemory(SecurityDescriptor,
                       'dSeS');

        RtlFreeSid(SystemSid);
    }

    RtlpFreeStringMemory(NewPath.Buffer,
                         TAG_USTR);
    return Status;
}

/* EOF */