/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"

/*
 * ntsec.c
 *
 * Implement the POSIX-style mode bits (access permissions) for
 * files and other securable objects in Windows NT using Windows
 * NT's security descriptors with appropriate discretionary
 * access-control lists.
 */

/*
 * The security identifiers (SIDs) for owner, primary group,
 * and the Everyone (World) group.
 *
 * These SIDs are looked up during NSPR initialization and
 * saved in this global structure (see _PR_NT_InitSids) so
 * that _PR_NT_MakeSecurityDescriptorACL doesn't need to
 * look them up every time.
 */
static struct {
  PSID owner;
  PSID group;
  PSID everyone;
} _pr_nt_sids;

/*
 * Initialize the SIDs for owner, primary group, and the Everyone
 * group in the _pr_nt_sids structure.
 *
 * This function needs to be called by NSPR initialization.
 */
void _PR_NT_InitSids(void) {
#ifdef WINCE /* not supported */
  return;
#else
  SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
  HANDLE hToken = NULL;                 /* initialized to an arbitrary value to
                                         * silence a Purify UMR warning */
  PSID infoBuffer[1024 / sizeof(PSID)]; /* defined as an array of PSIDs
                                         * to force proper alignment */
  PTOKEN_OWNER pTokenOwner = (PTOKEN_OWNER)infoBuffer;
  PTOKEN_PRIMARY_GROUP pTokenPrimaryGroup = (PTOKEN_PRIMARY_GROUP)infoBuffer;
  DWORD dwLength;
  BOOL rv;

  /*
   * Look up and make a copy of the owner and primary group
   * SIDs in the access token of the calling process.
   */
  rv = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
  if (rv == 0) {
    /*
     * On non-NT systems, this function is not implemented
     * (error code ERROR_CALL_NOT_IMPLEMENTED), and neither are
     * the other security functions.  There is no point in
     * going further.
     *
     * A process with insufficient access permissions may fail
     * with the error code ERROR_ACCESS_DENIED.
     */
    PR_LOG(_pr_io_lm, PR_LOG_DEBUG,
           ("_PR_NT_InitSids: OpenProcessToken() failed. Error: %d",
            GetLastError()));
    return;
  }

  rv = GetTokenInformation(hToken, TokenOwner, infoBuffer, sizeof(infoBuffer),
                           &dwLength);
  PR_ASSERT(rv != 0);
  dwLength = GetLengthSid(pTokenOwner->Owner);
  _pr_nt_sids.owner = (PSID)PR_Malloc(dwLength);
  PR_ASSERT(_pr_nt_sids.owner != NULL);
  rv = CopySid(dwLength, _pr_nt_sids.owner, pTokenOwner->Owner);
  PR_ASSERT(rv != 0);

  rv = GetTokenInformation(hToken, TokenPrimaryGroup, infoBuffer,
                           sizeof(infoBuffer), &dwLength);
  PR_ASSERT(rv != 0);
  dwLength = GetLengthSid(pTokenPrimaryGroup->PrimaryGroup);
  _pr_nt_sids.group = (PSID)PR_Malloc(dwLength);
  PR_ASSERT(_pr_nt_sids.group != NULL);
  rv = CopySid(dwLength, _pr_nt_sids.group, pTokenPrimaryGroup->PrimaryGroup);
  PR_ASSERT(rv != 0);

  rv = CloseHandle(hToken);
  PR_ASSERT(rv != 0);

  /* Create a well-known SID for the Everyone group. */
  rv = AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0,
                                0, 0, 0, 0, &_pr_nt_sids.everyone);
  PR_ASSERT(rv != 0);
#endif
}

/*
 * Free the SIDs for owner, primary group, and the Everyone group
 * in the _pr_nt_sids structure.
 *
 * This function needs to be called by NSPR cleanup.
 */
void _PR_NT_FreeSids(void) {
#ifdef WINCE
  return;
#else
  if (_pr_nt_sids.owner) {
    PR_Free(_pr_nt_sids.owner);
  }
  if (_pr_nt_sids.group) {
    PR_Free(_pr_nt_sids.group);
  }
  if (_pr_nt_sids.everyone) {
    FreeSid(_pr_nt_sids.everyone);
  }
#endif
}

/*
 * Construct a security descriptor whose discretionary access-control
 * list implements the specified mode bits.  The SIDs for owner, group,
 * and everyone are obtained from the global _pr_nt_sids structure.
 * Both the security descriptor and access-control list are returned
 * and should be freed by a _PR_NT_FreeSecurityDescriptorACL call.
 *
 * The accessTable array maps NSPR's read, write, and execute access
 * rights to the corresponding NT access rights for the securable
 * object.
 */
PRStatus _PR_NT_MakeSecurityDescriptorACL(PRIntn mode, DWORD accessTable[],
                                          PSECURITY_DESCRIPTOR* resultSD,
                                          PACL* resultACL) {
#ifdef WINCE
  PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
  return PR_FAILURE;
#else
  PSECURITY_DESCRIPTOR pSD = NULL;
  PACL pACL = NULL;
  DWORD cbACL; /* size of ACL */
  DWORD accessMask;

  if (_pr_nt_sids.owner == NULL) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
  }

  pSD = (PSECURITY_DESCRIPTOR)PR_Malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
  if (pSD == NULL) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  if (!SetSecurityDescriptorOwner(pSD, _pr_nt_sids.owner, FALSE)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  if (!SetSecurityDescriptorGroup(pSD, _pr_nt_sids.group, FALSE)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }

  /*
   * Construct a discretionary access-control list with three
   * access-control entries, one each for owner, primary group,
   * and Everyone.
   */

  cbACL = sizeof(ACL) + 3 * (sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)) +
          GetLengthSid(_pr_nt_sids.owner) + GetLengthSid(_pr_nt_sids.group) +
          GetLengthSid(_pr_nt_sids.everyone);
  pACL = (PACL)PR_Malloc(cbACL);
  if (pACL == NULL) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  accessMask = 0;
  if (mode & 00400) {
    accessMask |= accessTable[0];
  }
  if (mode & 00200) {
    accessMask |= accessTable[1];
  }
  if (mode & 00100) {
    accessMask |= accessTable[2];
  }
  if (accessMask &&
      !AddAccessAllowedAce(pACL, ACL_REVISION, accessMask, _pr_nt_sids.owner)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  accessMask = 0;
  if (mode & 00040) {
    accessMask |= accessTable[0];
  }
  if (mode & 00020) {
    accessMask |= accessTable[1];
  }
  if (mode & 00010) {
    accessMask |= accessTable[2];
  }
  if (accessMask &&
      !AddAccessAllowedAce(pACL, ACL_REVISION, accessMask, _pr_nt_sids.group)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }
  accessMask = 0;
  if (mode & 00004) {
    accessMask |= accessTable[0];
  }
  if (mode & 00002) {
    accessMask |= accessTable[1];
  }
  if (mode & 00001) {
    accessMask |= accessTable[2];
  }
  if (accessMask && !AddAccessAllowedAce(pACL, ACL_REVISION, accessMask,
                                         _pr_nt_sids.everyone)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }

  if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    goto failed;
  }

  *resultSD = pSD;
  *resultACL = pACL;
  return PR_SUCCESS;

failed:
  if (pSD) {
    PR_Free(pSD);
  }
  if (pACL) {
    PR_Free(pACL);
  }
  return PR_FAILURE;
#endif
}

/*
 * Free the specified security descriptor and access-control list
 * previously created by _PR_NT_MakeSecurityDescriptorACL.
 */
void _PR_NT_FreeSecurityDescriptorACL(PSECURITY_DESCRIPTOR pSD, PACL pACL) {
  if (pSD) {
    PR_Free(pSD);
  }
  if (pACL) {
    PR_Free(pACL);
  }
}
