/* sec/acl.cc: Solaris compatible ACL functions.

   Written by Corinna Vinschen <corinna@vinschen.de>

This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include <stdlib.h>
#include <ctype.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "cygheap.h"
#include "ntdll.h"
#include "tls_pbuf.h"
#include "sec_posixacl.h"

/* How does a correctly constructed new-style Windows ACL claiming to be a
   POSIX ACL look like?

   - NULL deny ACE (special bits, CLASS_OBJ).

   - USER_OBJ deny.  If the user has less permissions than the sum of CLASS_OBJ
     (or GROUP_OBJ if CLASS_OBJ doesn't exist) and OTHER_OBJ, deny the excess
     permissions so that group and other perms don't spill into the owner perms.

       USER_OBJ deny ACE   == ~USER_OBJ & (CLASS_OBJ | OTHER_OBJ)
     or
       USER_OBJ deny ACE   == ~USER_OBJ & (GROUP_OBJ | OTHER_OBJ)

   - USER deny.  If a user has more permissions than CLASS_OBJ, or if the
     user has less permissions than OTHER_OBJ, deny the excess permissions.

       USER deny ACE       == (USER & ~CLASS_OBJ) | (~USER & OTHER_OBJ)

   - USER_OBJ  allow ACE
   - USER      allow ACEs

   The POSIX permissions returned for a USER entry are the allow bits alone!

   - GROUP{_OBJ} deny.  If a group has more permissions than CLASS_OBJ,
     or less permissions than OTHER_OBJ, deny the excess permissions.

       GROUP{_OBJ} deny ACEs  == (GROUP & ~CLASS_OBJ)

   - GROUP_OBJ	allow ACE
   - GROUP	allow ACEs

   The POSIX permissions returned for a GROUP entry are the allow bits alone!

   - 2. GROUP{_OBJ} deny.  If a group has less permissions than OTHER_OBJ,
     deny the excess permissions.

       2. GROUP{_OBJ} deny ACEs  == (~GROUP & OTHER_OBJ)

   - OTHER_OBJ	allow ACE

   Rinse and repeat for default ACEs with INHERIT flags set.

   - Default NULL deny ACE (S_ISGID, CLASS_OBJ). */

						/* POSIX <-> Win32 */

/* Historically, these bits are stored in a NULL allow SID ACE.  To distinguish
   the new ACL style from the old one, we're using an access denied ACE, plus
   setting an as yet unused bit in the access mask.  The new ACEs can exist
   twice in an ACL, the "normal one" containing CLASS_OBJ and special bits
   and the one with INHERIT bit set to pass the DEF_CLASS_OBJ bits and the
   S_ISGID bit on. */
#define CYG_ACE_ISVTX		0x001		/* 0x200 <-> 0x001 */
#define CYG_ACE_ISGID		0x002		/* 0x400 <-> 0x002 */
#define CYG_ACE_ISUID		0x004		/* 0x800 <-> 0x004 */
#define CYG_ACE_ISBITS_TO_POSIX(val)	\
				(((val) & 0x007) << 9)
#define CYG_ACE_ISBITS_TO_WIN(val) \
				(((val) & (S_ISVTX | S_ISUID | S_ISGID)) >> 9)

#define CYG_ACE_MASK_X		0x008		/* 0x001 <-> 0x008 */
#define CYG_ACE_MASK_W		0x010		/* 0x002 <-> 0x010 */
#define CYG_ACE_MASK_R		0x020		/* 0x004 <-> 0x020 */
#define CYG_ACE_MASK_RWX	0x038
#define CYG_ACE_MASK_VALID	0x040		/* has mask if set */
#define CYG_ACE_MASK_TO_POSIX(val)	\
				(((val) & CYG_ACE_MASK_RWX) >> 3)
#define CYG_ACE_MASK_TO_WIN(val)	\
				((((val) & S_IRWXO) << 3) \
				 | CYG_ACE_MASK_VALID)
#define CYG_ACE_NEW_STYLE	READ_CONTROL	/* New style if set. */

/* Define own bit masks rather than using the GENERIC masks.  The latter
   also contain standard rights, which we don't need here. */
#define FILE_ALLOW_READ		(FILE_READ_DATA | FILE_READ_ATTRIBUTES | \
				 FILE_READ_EA)
#define FILE_DENY_READ		(FILE_READ_DATA | FILE_READ_EA)
#define FILE_ALLOW_WRITE	(FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | \
				 FILE_WRITE_EA | FILE_APPEND_DATA)
#define FILE_DENY_WRITE		FILE_ALLOW_WRITE | FILE_DELETE_CHILD
#define FILE_DENY_WRITE_OWNER	(FILE_WRITE_DATA | FILE_WRITE_EA | \
				 FILE_APPEND_DATA | FILE_DELETE_CHILD)
#define FILE_ALLOW_EXEC		(FILE_EXECUTE)
#define FILE_DENY_EXEC		FILE_ALLOW_EXEC

#define STD_RIGHTS_OTHER	(STANDARD_RIGHTS_READ | SYNCHRONIZE)
#define STD_RIGHTS_OWNER	(STANDARD_RIGHTS_ALL | SYNCHRONIZE)

int
searchace (aclent_t *aclp, int nentries, int type, uid_t id)
{
  int i;

  for (i = 0; i < nentries; ++i)
    if ((aclp[i].a_type == type
	 && (id == ACL_UNDEFINED_ID || aclp[i].a_id == id))
	|| !aclp[i].a_type)
      return i;
  return -1;
}

/* From the attributes and the POSIX ACL list, compute a new-style Cygwin
   security descriptor.  The function returns a pointer to the
   SECURITY_DESCRIPTOR in sd_ret, or NULL if the function fails.

   This function *requires* a verified and sorted acl list! */
PSECURITY_DESCRIPTOR
set_posix_access (mode_t attr, uid_t uid, gid_t gid,
		  aclent_t *aclbufp, int nentries,
		  security_descriptor &sd_ret,
		  bool is_samba)
{
  SECURITY_DESCRIPTOR sd;
  cyg_ldap cldap;
  PSID owner = NULL, group = NULL;
  cygsid smb_owner, smb_group;
  NTSTATUS status;
  tmp_pathbuf tp;
  cygpsid *aclsid;
  PACL acl;
  size_t acl_len = sizeof (ACL);
  mode_t user_obj, group_obj, other_obj, deny;
  mode_t class_obj = 0;
  DWORD access;
  int idx, start_idx, tmp_idx;
  bool owner_eq_group = false;
  bool dev_has_admins = false;
  bool has_class_obj;

  /* Initialize local security descriptor. */
  RtlCreateSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);

  /* As in alloc_sd, set SE_DACL_PROTECTED to prevent the DACL from being
     modified by inheritable ACEs. */
  RtlSetControlSecurityDescriptor (&sd, SE_DACL_PROTECTED, SE_DACL_PROTECTED);

  /* Fetch owner and group and set in security descriptor.

     For Samba we check if there's an RFC2307 mapping in place, otherwise
     we're trying to create an ACL with the wrong Windows SIDs rather than
     the correct Unix SIDs.  Same happens below for mapping other USER and
     GROUP SIDs. */
  if (is_samba)
    {
      uint32_t smb_uid, smb_gid;

      smb_uid = cygheap->ugid_cache.reverse_get_uid (uid);
      if (smb_uid != ILLEGAL_UID)
	owner = smb_owner.create (22, 2, 1, smb_uid);
      smb_gid = cygheap->ugid_cache.reverse_get_gid (gid);
      if (smb_gid != ILLEGAL_GID)
	group = smb_group.create (22, 2, 2, smb_gid);
    }
  if (!owner)
    owner = sidfromuid (uid, &cldap);
  if (!group)
    group = sidfromgid (gid, &cldap);
  if (!owner || !group)
    {
      set_errno (EINVAL);
      return NULL;
    }
  status = RtlSetOwnerSecurityDescriptor (&sd, owner, FALSE);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return NULL;
    }
  status = RtlSetGroupSecurityDescriptor (&sd, group, FALSE);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return NULL;
    }
  owner_eq_group = RtlEqualSid (owner, group);
  if (S_ISCHR (attr))
    dev_has_admins = well_known_admins_sid == owner
		     || well_known_admins_sid == group;

  /* No POSIX ACL?  Use attr to generate one from scratch. */
  if (!aclbufp)
    {
      aclbufp = (aclent_t *) tp.c_get ();
      aclbufp[0].a_type = USER_OBJ;
      aclbufp[0].a_id = ACL_UNDEFINED_ID;
      aclbufp[0].a_perm = (attr >> 6) & S_IRWXO;
      aclbufp[1].a_type = GROUP_OBJ;
      aclbufp[1].a_id = ACL_UNDEFINED_ID;
      aclbufp[1].a_perm = (attr >> 3) & S_IRWXO;
      aclbufp[2].a_type = OTHER_OBJ;
      aclbufp[2].a_id = ACL_UNDEFINED_ID;
      aclbufp[2].a_perm = attr & S_IRWXO;
      nentries = MIN_ACL_ENTRIES;
      if (S_ISDIR (attr))
	{
	  aclbufp[3].a_type = DEF_USER_OBJ;
	  aclbufp[3].a_id = ACL_UNDEFINED_ID;
	  aclbufp[3].a_perm = (attr >> 6) & S_IRWXO;
	  aclbufp[4].a_type = GROUP_OBJ;
	  aclbufp[4].a_id = ACL_UNDEFINED_ID;
	  aclbufp[4].a_perm = (attr >> 3) & S_IRWXO;
	  aclbufp[5].a_type = OTHER_OBJ;
	  aclbufp[5].a_id = ACL_UNDEFINED_ID;
	  aclbufp[5].a_perm = attr & S_IRWXO;
	  nentries += MIN_ACL_ENTRIES;
	}
    }

  /* Collect SIDs of all entries in aclbufp. */
  aclsid = (cygpsid *) tp.w_get ();
  for (idx = 0; idx < nentries; ++idx)
    switch (aclbufp[idx].a_type)
      {
      case USER_OBJ:
	aclsid[idx] = owner;
	break;
      case DEF_USER_OBJ:
	aclsid[idx] = well_known_creator_owner_sid;
	break;
      case USER:
      case DEF_USER:
	aclsid[idx] = NO_SID;
	if (is_samba)
	  {
	    uint32_t smb_uid;
	    cygsid *smb_sid;

	    smb_uid = cygheap->ugid_cache.reverse_get_uid (aclbufp[idx].a_id);
	    if (smb_uid != ILLEGAL_UID)
	      {
		smb_sid = (cygsid *) alloca (sizeof (cygsid));
		aclsid[idx] = smb_sid->create (22, 2, 1, smb_uid);
	      }
	  }
	if (!aclsid[idx])
	  aclsid[idx] = sidfromuid (aclbufp[idx].a_id, &cldap);
	break;
      case GROUP_OBJ:
	aclsid[idx] = group;
	break;
      case DEF_GROUP_OBJ:
	aclsid[idx] = !(attr & S_ISGID) ? (PSID) well_known_creator_group_sid
					: group;
	break;
      case GROUP:
      case DEF_GROUP:
	aclsid[idx] = NO_SID;
	if (is_samba)
	  {
	    uint32_t smb_gid;
	    cygsid *smb_sid;

	    smb_gid = cygheap->ugid_cache.reverse_get_gid (aclbufp[idx].a_id);
	    if (smb_gid != ILLEGAL_GID)
	      {
		smb_sid = (cygsid *) alloca (sizeof (cygsid));
		aclsid[idx] = smb_sid->create (22, 2, 2, smb_gid);
	      }
	  }
	if (!aclsid[idx])
	  aclsid[idx] = sidfromgid (aclbufp[idx].a_id, &cldap);
	break;
      case CLASS_OBJ:
      case DEF_CLASS_OBJ:
	aclsid[idx] = well_known_null_sid;
	break;
      case OTHER_OBJ:
      case DEF_OTHER_OBJ:
	aclsid[idx] = well_known_world_sid;
	break;
      }

  /* Initialize ACL. */
  acl = (PACL) tp.w_get ();
  RtlCreateAcl (acl, ACL_MAXIMUM_SIZE, ACL_REVISION);

  /* This loop has two runs, the first handling the actual permission,
     the second handling the default permissions. */
  idx = 0;
  for (int def = 0; def <= ACL_DEFAULT; def += ACL_DEFAULT)
    {
      DWORD inherit = def ? SUB_CONTAINERS_AND_OBJECTS_INHERIT | INHERIT_ONLY
			  : NO_INHERITANCE;

      /* No default ACEs on files. */
      if (def && !S_ISDIR (attr))
	{
	  /* Trying to set default ACEs on a non-directory is an error.
	     The underlying functions on Linux return EACCES. */
	  if (idx < nentries && aclbufp[idx].a_type & ACL_DEFAULT)
	    {
	      set_errno (EACCES);
	      return NULL;
	    }
	  break;
	}

      /* To check if the NULL SID deny ACE is required we need user_obj.  */
      tmp_idx = searchace (aclbufp, nentries, def | USER_OBJ);
      /* No default entries present? */
      if (tmp_idx < 0)
	break;
      user_obj = aclbufp[tmp_idx].a_perm;
      /* To compute deny access masks, we need group_obj, other_obj and... */
      tmp_idx = searchace (aclbufp, nentries, def | GROUP_OBJ);
      group_obj = aclbufp[tmp_idx].a_perm;
      tmp_idx = searchace (aclbufp, nentries, def | OTHER_OBJ);
      other_obj = aclbufp[tmp_idx].a_perm;

      /* ... class_obj.  Create NULL deny ACE.  Only the S_ISGID attribute gets
	 inherited.  For directories check if we are also going to generate
	 default entries.  If not we have a problem.  We can't generate only a
	 single, inheritable NULL SID ACE because that leads to (fixable, TODO)
	 access problems when trying to create the matching child permissions.
	 Therefore we remove the S_ISGID bit on the directory because having it
	 set would be misleading. */
      if (!def && S_ISDIR (attr) && (attr & S_ISGID))
	{
	  /* Check for a required entry per POSIX. */
	  tmp_idx = searchace (aclbufp, nentries, DEF_USER_OBJ);
	  if (tmp_idx < 0)
	    attr &= ~S_ISGID;
	}
      access = CYG_ACE_ISBITS_TO_WIN (def ? attr & S_ISGID : attr)
	       | CYG_ACE_NEW_STYLE;
      tmp_idx = searchace (aclbufp, nentries, def | CLASS_OBJ);
      if (tmp_idx >= 0)
	{
	  has_class_obj = true;
	  class_obj = aclbufp[tmp_idx].a_perm;
	  access |= CYG_ACE_MASK_TO_WIN (class_obj);
	}
      else
	{
	  /* Setting class_obj to group_obj allows to write below code without
	     additional checks for existence of a CLASS_OBJ. */
	  has_class_obj = false;
	  class_obj = group_obj;
	}
      /* Note that Windows filters the ACE Mask value so it only reflects
	 the bit values supported by the object type.  The result is that
	 we can't set a CLASS_OBJ value for ptys.  The get_posix_access
	 function has to workaround that.

	 We also don't write the NULL SID ACE in case we have a simple POSIX
	 permission ACL with the user perms >= group perms >= other perms and
	 no special bits set.  In all other cases we either need the NULL SID
	 ACE or we write it to avoid calls to AuthZ from get_posix_access. */
      if (!S_ISCHR (attr)
	  && (has_class_obj
	      || access != CYG_ACE_NEW_STYLE
	      || ((user_obj | group_obj | other_obj) != user_obj
		  || (group_obj | other_obj) != group_obj))
	  && !add_access_denied_ace (acl, access, well_known_null_sid, acl_len,
				     inherit))
	return NULL;

      /* Do we potentially chmod a file with owner SID == group SID?  If so,
	 make sure the owner perms are always >= group perms. */
      if (!def && owner_eq_group)
	  aclbufp[0].a_perm |= group_obj & class_obj;

      /* This loop has two runs, the first w/ check_types == (USER_OBJ | USER),
	 the second w/ check_types == (GROUP_OBJ | GROUP).  Each run creates
	 first the deny, then the allow ACEs for the current types. */
      for (int check_types = USER_OBJ | USER;
	   check_types < CLASS_OBJ;
	   check_types <<= 2)
	{
	  /* Create deny ACEs for users, then 1st run for groups.  For groups,
	     only take CLASS_OBJ permissions into account.  Class permissions
	     are handled in the 2nd deny loop below. */
	  for (start_idx = idx;
	       idx < nentries && aclbufp[idx].a_type & check_types;
	       ++idx)
	    {
	      /* Avoid creating DENY ACEs for the second occurrence of
		 accounts which show up twice, as USER_OBJ and USER, or
		 GROUP_OBJ and GROUP. */
	      if ((aclbufp[idx].a_type & USER && aclsid[idx] == owner)
		  || (aclbufp[idx].a_type & GROUP && aclsid[idx] == group))
		continue;
	      /* For the rules how to construct the deny access mask, see the
		 comment right at the start of this file. */
	      if (aclbufp[idx].a_type & USER_OBJ)
		deny = ~aclbufp[idx].a_perm & (class_obj | other_obj);
	      else if (aclbufp[idx].a_type & USER)
		deny = (aclbufp[idx].a_perm & ~class_obj)
		       | (~aclbufp[idx].a_perm & other_obj);
	      /* Accommodate Windows: Only generate deny masks for SYSTEM
		 and the Administrators group in terms of the execute bit,
		 if they are not the primary group. */
	      else if (aclbufp[idx].a_type & GROUP
		       && (aclsid[idx] == well_known_system_sid
			   || aclsid[idx] == well_known_admins_sid))
		deny = aclbufp[idx].a_perm & ~(class_obj | S_IROTH | S_IWOTH);
	      else
		deny = (aclbufp[idx].a_perm & ~class_obj);
	      if (!deny)
		continue;
	      access = 0;
	      if (deny & S_IROTH)
		access |= FILE_DENY_READ;
	      if (deny & S_IWOTH)
		access |= (aclbufp[idx].a_type & USER_OBJ)
			  ? FILE_DENY_WRITE_OWNER : FILE_DENY_WRITE;
	      if (deny & S_IXOTH)
		access |= FILE_DENY_EXEC;
	      if (!add_access_denied_ace (acl, access, aclsid[idx], acl_len,
					  inherit))
		return NULL;
	    }
	  /* Create allow ACEs for users, then groups. */
	  for (idx = start_idx;
	       idx < nentries && aclbufp[idx].a_type & check_types;
	       ++idx)
	    {
	      /* Don't set FILE_READ/WRITE_ATTRIBUTES unconditionally on Samba,
		 otherwise it enforces read permissions. */
	      access = STD_RIGHTS_OTHER | (is_samba ? 0 : FILE_READ_ATTRIBUTES);
	      if (aclbufp[idx].a_type & USER_OBJ)
		{
		  access |= STD_RIGHTS_OWNER;
		  if (!is_samba)
		    access |= FILE_WRITE_ATTRIBUTES;
		  /* Set FILE_DELETE_CHILD on files with "rwx" perms for the
		     owner so that the owner gets "full control" (Duh). */
		  if (aclbufp[idx].a_perm == S_IRWXO)
		    access |= FILE_DELETE_CHILD;
		}
	      if (aclbufp[idx].a_perm & S_IROTH)
		access |= FILE_ALLOW_READ;
	      if (aclbufp[idx].a_perm & S_IWOTH)
		access |= FILE_ALLOW_WRITE;
	      if (aclbufp[idx].a_perm & S_IXOTH)
		access |= FILE_ALLOW_EXEC;
	      /* Handle S_ISVTX. */
	      if (S_ISDIR (attr)
		  && (aclbufp[idx].a_perm & (S_IWOTH | S_IXOTH))
		     == (S_IWOTH | S_IXOTH)
		  && (!(attr & S_ISVTX) || aclbufp[idx].a_type & USER_OBJ))
		access |= FILE_DELETE_CHILD;
	      /* For ptys, make sure the Administrators group has WRITE_DAC
		 and WRITE_OWNER perms. */
	      if (dev_has_admins && aclsid[idx] == well_known_admins_sid)
		access |= STD_RIGHTS_OWNER;
	      if (!add_access_allowed_ace (acl, access, aclsid[idx], acl_len,
					   inherit))
		return NULL;
	    }
	  /* 2nd deny loop: Create deny ACEs for groups when they have less
	     permissions than OTHER_OBJ. */
	  if (check_types == (GROUP_OBJ | GROUP))
	    for (idx = start_idx;
		 idx < nentries && aclbufp[idx].a_type & check_types;
		 ++idx)
	      {
		if (aclbufp[idx].a_type & GROUP && aclsid[idx] == group)
		  continue;
		/* Only generate deny masks for SYSTEM and the Administrators
		   group if they are the primary group. */
		if (aclbufp[idx].a_type & GROUP
		    && (aclsid[idx] == well_known_system_sid
			|| aclsid[idx] == well_known_admins_sid))
		  deny = 0;
		else
		  deny = (~aclbufp[idx].a_perm & other_obj);
		if (!deny)
		  continue;
		access = 0;
		if (deny & S_IROTH)
		  access |= FILE_DENY_READ;
		if (deny & S_IWOTH)
		  access |= FILE_DENY_WRITE;
		if (deny & S_IXOTH)
		  access |= FILE_DENY_EXEC;
		if (!add_access_denied_ace (acl, access, aclsid[idx], acl_len,
					    inherit))
		  return NULL;
	      }
	}
      /* For ptys if the admins group isn't in the ACL, add an ACE to make
	 sure the admins group has WRITE_DAC and WRITE_OWNER perms. */
      if (S_ISCHR (attr) && !dev_has_admins
	  && !add_access_allowed_ace (acl,
				      STD_RIGHTS_OWNER | FILE_ALLOW_READ
				      | FILE_ALLOW_WRITE,
				      well_known_admins_sid, acl_len,
				      NO_INHERITANCE))
	return NULL;
      /* Create allow ACE for other.  It's preceeded by class_obj if it exists.
	 If so, skip it. */
      if (aclbufp[idx].a_type & CLASS_OBJ)
	++idx;
      access = STD_RIGHTS_OTHER | (is_samba ? 0 : FILE_READ_ATTRIBUTES);
      if (aclbufp[idx].a_perm & S_IROTH)
	access |= FILE_ALLOW_READ;
      if (aclbufp[idx].a_perm & S_IWOTH)
	access |= FILE_ALLOW_WRITE;
      if (aclbufp[idx].a_perm & S_IXOTH)
	access |= FILE_ALLOW_EXEC;
      /* Handle S_ISVTX. */
      if (S_ISDIR (attr)
	  && (aclbufp[idx].a_perm & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH)
	  && !(attr & S_ISVTX))
	access |= FILE_DELETE_CHILD;
      if (!add_access_allowed_ace (acl, access, aclsid[idx++], acl_len,
				   inherit))
	return NULL;
    }

  /* Set AclSize to computed value. */
  acl->AclSize = acl_len;
  debug_printf ("ACL-Size: %u", acl_len);
  /* Create DACL for local security descriptor. */
  status = RtlSetDaclSecurityDescriptor (&sd, TRUE, acl, FALSE);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return NULL;
    }
  /* Make self relative security descriptor in sd_ret. */
  DWORD sd_size = 0;
  RtlAbsoluteToSelfRelativeSD (&sd, sd_ret, &sd_size);
  if (sd_size <= 0)
    {
      __seterrno ();
      return NULL;
    }
  if (!sd_ret.realloc (sd_size))
    {
      set_errno (ENOMEM);
      return NULL;
    }
  status = RtlAbsoluteToSelfRelativeSD (&sd, sd_ret, &sd_size);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return NULL;
    }
  debug_printf ("Created SD-Size: %u", sd_ret.size ());
  return sd_ret;
}

/* This function *requires* a verified and sorted acl list! */
int
setacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp,
	bool &writable)
{
  security_descriptor sd, sd_ret;
  mode_t attr = pc.isdir () ? S_IFDIR : 0;
  uid_t uid;
  gid_t gid;

  if (get_file_sd (handle, pc, sd, false))
    return -1;
  if (get_posix_access (sd, attr, &uid, &gid, NULL, 0) < 0)
    return -1;
  if (!set_posix_access (attr, uid, gid, aclbufp, nentries,
			 sd_ret, pc.fs_is_samba ()))
    return -1;
  /* FIXME?  Caller needs to know if any write perms are set to allow removing
     the DOS R/O bit. */
  writable = true;
  return set_file_sd (handle, pc, sd_ret, false);
}

/* Temporary access denied bits used by getace and get_posix_access during
   Windows ACL processing.  These bits get removed before the created POSIX
   ACL gets published. */
#define DENY_R 040000
#define DENY_W 020000
#define DENY_X 010000
#define DENY_RWX (DENY_R | DENY_W | DENY_X)

/* New style ACL means, just read the bits and store them away.  Don't
   create masked values on your own. */
static void
getace (aclent_t &acl, int type, int id, DWORD win_ace_mask,
	DWORD win_ace_type, bool new_style)
{
  acl.a_type = type;
  acl.a_id = id;

  if ((win_ace_mask & FILE_READ_BITS)
      && (new_style || !(acl.a_perm & (S_IROTH | DENY_R))))
    {
      if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE)
	acl.a_perm |= S_IROTH;
      else if (win_ace_type == ACCESS_DENIED_ACE_TYPE)
	acl.a_perm |= DENY_R;
    }

  if ((win_ace_mask & FILE_WRITE_BITS)
      && (new_style || !(acl.a_perm & (S_IWOTH | DENY_W))))
    {
      if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE)
	acl.a_perm |= S_IWOTH;
      else if (win_ace_type == ACCESS_DENIED_ACE_TYPE)
	acl.a_perm |= DENY_W;
    }

  if ((win_ace_mask & FILE_EXEC_BITS)
      && (new_style || !(acl.a_perm & (S_IXOTH | DENY_X))))
    {
      if (win_ace_type == ACCESS_ALLOWED_ACE_TYPE)
	acl.a_perm |= S_IXOTH;
      else if (win_ace_type == ACCESS_DENIED_ACE_TYPE)
	acl.a_perm |= DENY_X;
    }
}

/* From the SECURITY_DESCRIPTOR given in psd, compute user, owner, posix
   attributes, as well as the POSIX acl.  The function returns the number
   of entries returned in aclbufp, or -1 in case of error.

   When called from chmod, it also returns the fact if the ACL is a "standard"
   ACL.  A "standard" ACL is an ACL which only consists of ACEs for owner,
   group, other, as well as (this is Windows) the Administrators group and
   SYSTEM.  See fhandler_disk_file::fchmod for how this is used to fake
   stock POSIX perms even if Administrators and SYSTEM is in the ACE. */
int
get_posix_access (PSECURITY_DESCRIPTOR psd,
		  mode_t &attr_ret, uid_t *uid_ret, gid_t *gid_ret,
		  aclent_t *aclbufp, int nentries, bool *std_acl)
{
  tmp_pathbuf tp;
  NTSTATUS status;
  BOOLEAN dummy, acl_exists;
  SECURITY_DESCRIPTOR_CONTROL ctrl;
  ULONG rev;
  PACL acl;
  PACCESS_ALLOWED_ACE ace;
  cygpsid owner_sid, group_sid;
  cyg_ldap cldap;
  uid_t uid;
  gid_t gid;
  mode_t attr = 0;
  aclent_t *lacl = NULL;
  cygpsid ace_sid, *aclsid;
  int pos, type, id, idx;

  bool owner_eq_group;
  bool just_created = false;
  bool standard_ACEs_only = true;
  bool new_style = false;
  bool saw_user_obj = false;
  bool saw_group_obj = false;
  bool saw_other_obj = false;
  bool saw_def_user_obj = false;
  bool saw_def_group_obj = false;
  bool has_class_perm = false;
  bool has_def_class_perm = false;

  mode_t class_perm = 0;
  mode_t def_class_perm = 0;
  int types_def = 0;
  int def_pgrp_pos = -1;

  if (aclbufp && nentries < MIN_ACL_ENTRIES)
    {
      set_errno (EINVAL);
      return -1;
    }
  /* If reading the security descriptor failed, treat the object as
     unreadable. */
  if (!psd)
    {
      attr_ret &= S_IFMT;
      if (uid_ret)
        *uid_ret = ACL_UNDEFINED_ID;
      if (gid_ret)
        *gid_ret = ACL_UNDEFINED_ID;
      if (aclbufp)
	{
	  aclbufp[0].a_type = USER_OBJ;
	  aclbufp[0].a_id = ACL_UNDEFINED_ID;
	  aclbufp[0].a_perm = 0;
	  aclbufp[1].a_type = GROUP_OBJ;
	  aclbufp[1].a_id = ACL_UNDEFINED_ID;
	  aclbufp[1].a_perm = 0;
	  aclbufp[2].a_type = OTHER_OBJ;
	  aclbufp[2].a_id = ACL_UNDEFINED_ID;
	  aclbufp[2].a_perm = 0;
	  return MIN_ACL_ENTRIES;
	}
      return 0;
    }
  /* Fetch owner, group, and ACL from security descriptor. */
  status = RtlGetOwnerSecurityDescriptor (psd, (PSID *) &owner_sid, &dummy);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return -1;
    }
  status = RtlGetGroupSecurityDescriptor (psd, (PSID *) &group_sid, &dummy);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return -1;
    }
  status = RtlGetDaclSecurityDescriptor (psd, &acl_exists, &acl, &dummy);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      return -1;
    }
  /* Set uidret, gidret, and initalize attributes. */
  uid = owner_sid.get_uid (&cldap);
  gid = group_sid.get_gid (&cldap);
  attr = attr_ret & S_IFMT;
  just_created = attr_ret & S_JUSTCREATED;
  /* Remember the fact that owner and group are the same account. */
  owner_eq_group = owner_sid == group_sid;

  /* Create and initialize local aclent_t array. */
  lacl = (aclent_t *) tp.c_get ();
  memset (lacl, 0, MAX_ACL_ENTRIES * sizeof (aclent_t *));
  lacl[0].a_type = USER_OBJ;
  lacl[0].a_id = uid;
  lacl[1].a_type = GROUP_OBJ;
  lacl[1].a_id = gid;
  lacl[2].a_type = OTHER_OBJ;
  lacl[2].a_id = ACL_UNDEFINED_ID;
  /* Create array to collect SIDs of all entries in lacl. */
  aclsid = (cygpsid *) tp.w_get ();
  aclsid[0] = owner_sid;
  aclsid[1] = group_sid;
  aclsid[2] = well_known_world_sid;

  /* No ACEs?  Everybody has full access. */
  if (!acl_exists || !acl || acl->AceCount == 0)
    {
      for (pos = 0; pos < MIN_ACL_ENTRIES; ++pos)
	lacl[pos].a_perm = S_IROTH | S_IWOTH | S_IXOTH;
      goto out;
    }

  /* Files and dirs are created with a NULL descriptor, so inheritence
     rules kick in.  If no inheritable entries exist in the parent object,
     Windows will create entries according to the user token's default DACL.
     These entries are not desired and we ignore them at creation time.
     We're just checking the SE_DACL_AUTO_INHERITED flag here, since that's
     what we set in get_file_sd.  Read the longish comment there before
     changing this test! */
  if (just_created
      && NT_SUCCESS (RtlGetControlSecurityDescriptor (psd, &ctrl, &rev))
      && !(ctrl & SE_DACL_AUTO_INHERITED))
    ;
  else for (idx = 0; idx < acl->AceCount; ++idx)
    {
      if (!NT_SUCCESS (RtlGetAce (acl, idx, (PVOID *) &ace)))
	continue;

      ace_sid = (PSID) &ace->SidStart;

      if (ace_sid == well_known_null_sid)
	{
	  /* Fetch special bits. */
	  attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask);
	  if (ace->Header.AceType == ACCESS_DENIED_ACE_TYPE
	      && ace->Mask & CYG_ACE_NEW_STYLE)
	    {
	      /* New-style ACL.  Note the fact that a mask value is present
		 since that changes how getace fetches the information.  That's
		 fine, because the NULL deny ACE is supposed to precede all
		 USER, GROUP and GROUP_OBJ entries.  Any ACL not created that
		 way has been rearranged by the Windows functionality to create
		 the brain-dead "canonical" ACL order and is broken anyway. */
	      new_style = true;
	      attr |= CYG_ACE_ISBITS_TO_POSIX (ace->Mask);
	      if (ace->Mask & CYG_ACE_MASK_VALID)
		{
		  if (!(ace->Header.AceFlags & INHERIT_ONLY))
		    {
		      if ((pos = searchace (lacl, MAX_ACL_ENTRIES, CLASS_OBJ))
			  >= 0)
			{
			  lacl[pos].a_type = CLASS_OBJ;
			  lacl[pos].a_id = ACL_UNDEFINED_ID;
			  lacl[pos].a_perm = CYG_ACE_MASK_TO_POSIX (ace->Mask);
			  aclsid[pos] = well_known_null_sid;
			  has_class_perm = true;
			  class_perm = lacl[pos].a_perm;
			}
		    }
		  if (S_ISDIR (attr)
		      && (ace->Header.AceFlags & SUB_CONTAINERS_AND_OBJECTS_INHERIT) != 0)
		    {
		      if ((pos = searchace (lacl, MAX_ACL_ENTRIES,
					    DEF_CLASS_OBJ)) >= 0)
			{
			  lacl[pos].a_type = DEF_CLASS_OBJ;
			  lacl[pos].a_id = ACL_UNDEFINED_ID;
			  lacl[pos].a_perm = CYG_ACE_MASK_TO_POSIX (ace->Mask);
			  aclsid[pos] = well_known_null_sid;
			  has_def_class_perm = true;
			  def_class_perm = lacl[pos].a_perm;
			}
		    }
		}
	    }
	  continue;
	}
      if (ace_sid == owner_sid)
	{
	  type = USER_OBJ;
	  id = uid;
	}
      else if (ace_sid == group_sid)
	{
	  type = GROUP_OBJ;
	  id = gid;
	}
      else if (ace_sid == well_known_world_sid)
	{
	  type = OTHER_OBJ;
	  id = ACL_UNDEFINED_ID;
	  if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE
	      && !(ace->Header.AceFlags & INHERIT_ONLY))
	    saw_other_obj = true;
	}
      else if (ace_sid == well_known_creator_owner_sid)
	{
	  type = DEF_USER_OBJ;
	  id = ACL_UNDEFINED_ID;
	  saw_def_user_obj = true;
	}
      else if (ace_sid == well_known_creator_group_sid)
	{
	  type = DEF_GROUP_OBJ;
	  id = ACL_UNDEFINED_ID;
	  saw_def_group_obj = true;
	}
      else
	{
	  id = ace_sid.get_id (TRUE, &type, &cldap);
	  if (!type)
	    continue;
	}
      /* If the SGID attribute is set on a just created file or dir, the
         first group in the ACL is the desired primary group of the new
	 object.  Alternatively, the first repetition of the owner SID is
	 the desired primary group, and we mark the object as owner_eq_group
	 object. */
      if (just_created && attr & S_ISGID && !saw_group_obj
	  && (type == GROUP || (type == USER_OBJ && saw_user_obj)))
	{
	  type = GROUP_OBJ;
	  lacl[1].a_id = gid = id;
	  if (type == USER_OBJ)
	    owner_eq_group = true;
	}
      if (!(ace->Header.AceFlags & INHERIT_ONLY || type & ACL_DEFAULT))
	{
	  if (type == USER_OBJ)
	    {
	      /* If we get a second entry for the owner SID, it's either a
		 GROUP_OBJ entry for the same SID, if owner SID == group SID,
		 or it's an additional USER entry.  The latter can happen
		 when chown'ing a file. */
	      if (saw_user_obj)
		{
		  if (owner_eq_group && !saw_group_obj)
		    {
		      type = GROUP_OBJ;
		      /* Gid and uid are not necessarily the same even if the
			 SID is the same: /etc/group is in use and the user got
			 added to /etc/group using another gid than the uid.
			 This is a border case but it happened and resetting id
			 to gid is not much of a burden. */
		      id = gid;
		      if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
			saw_group_obj = true;
		    }
		  else
		    type = USER;
		}
	      else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
		saw_user_obj = true;
	    }
	  else if (type == GROUP_OBJ)
	    {
	      /* Same for the primary group. */
	      if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
		{
		  if (saw_group_obj)
		    type = GROUP;
		  saw_group_obj = true;
		}
	    }
	  if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0)
	    {
	      getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType,
		      new_style && type & (USER | GROUP_OBJ | GROUP));
	      aclsid[pos] = ace_sid;
	      if (!new_style)
		{
		  /* Fix up CLASS_OBJ value. */
		  if (type & (USER | GROUP))
		    {
		      has_class_perm = true;
		      /* Accommodate Windows: Never add SYSTEM and Admins to
			 CLASS_OBJ.  Unless (implicitly) if they are the
			 GROUP_OBJ entry. */
		      if (ace_sid != well_known_system_sid
			  && ace_sid != well_known_admins_sid)
			{
			  class_perm |= lacl[pos].a_perm;
			  standard_ACEs_only = false;
			}
		    }
		}
	      /* For a newly created file, we'd like to know if we're running
		 with a standard ACL, one only consisting of POSIX perms, plus
		 SYSTEM and Admins as maximum non-POSIX perms entries.  If it's
		 a standard ACL, we apply umask.  That's not entirely correct,
		 but it's probably the best we can do.  Chmod also wants to
		 know this.  See there for the details. */
	      else if (type & (USER | GROUP)
		       && standard_ACEs_only
		       && ace_sid != well_known_system_sid
		       && ace_sid != well_known_admins_sid)
		standard_ACEs_only = false;
	    }
	}
      if (S_ISDIR (attr)
	  && (ace->Header.AceFlags & SUB_CONTAINERS_AND_OBJECTS_INHERIT) != 0)
	{
	  if (type == USER_OBJ)
	    {
	      /* As above: If we get a second entry for the owner SID, it's
		 a GROUP_OBJ entry for the same SID if owner SID == group SID,
		 but this time only if the S_ISGID bit is set. Otherwise it's
		 an additional USER entry. */
	      if (saw_def_user_obj)
		{
		  if (owner_eq_group && !saw_def_group_obj && attr & S_ISGID)
		    {
		      /* Needs post-processing in the following GROUP_OBJ block.
		         Set id to ACL_UNDEFINED_ID to play it safe. */
		      type = GROUP_OBJ;
		      id = ACL_UNDEFINED_ID;
		    }
		  else
		    type = USER;
		}
	      else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
		saw_def_user_obj = true;
	    }
	  if (type == GROUP_OBJ)
	    {
	      /* If the SGID bit is set, the inheritable entry for the
		 primary group is, in fact, the DEF_GROUP_OBJ entry,
		 so don't change the type to GROUP in this case. */
	      if (!new_style || saw_def_group_obj || !(attr & S_ISGID))
		type = GROUP;
	      else if (ace->Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
		saw_def_group_obj = true;
	    }
	  type |= ACL_DEFAULT;
	  types_def |= type;
	  if ((pos = searchace (lacl, MAX_ACL_ENTRIES, type, id)) >= 0)
	    {
	      getace (lacl[pos], type, id, ace->Mask, ace->Header.AceType,
		      new_style && type & (USER | GROUP_OBJ | GROUP));
	      aclsid[pos] = ace_sid;
	      if (!new_style)
		{
		  /* Fix up DEF_CLASS_OBJ value. */
		  if (type & (USER | GROUP))
		    {
		      has_def_class_perm = true;
		      /* Accommodate Windows: Never add SYSTEM and Admins to
			 CLASS_OBJ.  Unless (implicitly) if they are the
			 GROUP_OBJ entry. */
		      if (ace_sid != well_known_system_sid
			  && ace_sid != well_known_admins_sid)
		      def_class_perm |= lacl[pos].a_perm;
		    }
		  /* And note the position of the DEF_GROUP_OBJ entry. */
		  if (type == DEF_GROUP_OBJ)
		    def_pgrp_pos = pos;
		}
	    }
	}
    }
  /* If this is a just created file, and this is an ACL with only standard
     entries, or if standard POSIX permissions are missing (probably no
     inherited ACEs so created from a default DACL), assign the permissions
     specified by the file creation mask.  The values get masked by the
     actually requested permissions by the caller per POSIX 1003.1e draft 17. */
  if (just_created)
    {
      mode_t perms = (S_IRWXU | S_IRWXG | S_IRWXO) & ~cygheap->umask;
      if (standard_ACEs_only || !saw_user_obj)
	lacl[0].a_perm = (perms >> 6) & S_IRWXO;
      if (standard_ACEs_only || !saw_group_obj)
	lacl[1].a_perm = (perms >> 3) & S_IRWXO;
      if (standard_ACEs_only || !saw_other_obj)
	lacl[2].a_perm = perms & S_IRWXO;
    }
  /* If this is an old-style or non-Cygwin ACL, and secondary user and group
     entries exist in the ACL, fake a matching CLASS_OBJ entry. The CLASS_OBJ
     permissions are the or'ed permissions of the primary group permissions
     and all secondary user and group permissions. */
  if (!new_style && has_class_perm
      && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0)
    {
      lacl[pos].a_type = CLASS_OBJ;
      lacl[pos].a_id = ACL_UNDEFINED_ID;
      class_perm |= lacl[1].a_perm;
      lacl[pos].a_perm = class_perm;
      aclsid[pos] = well_known_null_sid;
    }
  /* For ptys, fake a mask if the admins group is neither owner nor group.
     In that case we have an extra ACE for the admins group, and we need a
     CLASS_OBJ to get a valid POSIX ACL.  However, Windows filters the ACE
     Mask value so it only reflects the bit values supported by the object
     type.  The result is that we can't set an explicit CLASS_OBJ value for
     ptys in the NULL SID ACE. */
  else if (S_ISCHR (attr) && owner_sid != well_known_admins_sid
	   && group_sid != well_known_admins_sid
	   && (pos = searchace (lacl, MAX_ACL_ENTRIES, CLASS_OBJ)) >= 0)
    {
      lacl[pos].a_type = CLASS_OBJ;
      lacl[pos].a_id = ACL_UNDEFINED_ID;
      lacl[pos].a_perm = lacl[1].a_perm; /* == group perms */
      aclsid[pos] = well_known_null_sid;
    }
  /* Ensure that the default acl contains at least
     DEF_(USER|GROUP|OTHER)_OBJ entries.  */
  if (types_def && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0)
    {
      if (!(types_def & USER_OBJ))
	{
	  lacl[pos].a_type = DEF_USER_OBJ;
	  lacl[pos].a_id = uid;
	  lacl[pos].a_perm = lacl[0].a_perm;
	  aclsid[pos] = well_known_creator_owner_sid;
	  pos++;
	}
      if (!(types_def & GROUP_OBJ) && pos < MAX_ACL_ENTRIES)
	{
	  lacl[pos].a_type = DEF_GROUP_OBJ;
	  lacl[pos].a_id = gid;
	  lacl[pos].a_perm = lacl[1].a_perm;
	  /* Note the position of the DEF_GROUP_OBJ entry. */
	  def_pgrp_pos = pos;
	  aclsid[pos] = well_known_creator_group_sid;
	  pos++;
	}
      if (!(types_def & OTHER_OBJ) && pos < MAX_ACL_ENTRIES)
	{
	  lacl[pos].a_type = DEF_OTHER_OBJ;
	  lacl[pos].a_id = ACL_UNDEFINED_ID;
	  lacl[pos].a_perm = lacl[2].a_perm;
	  aclsid[pos] = well_known_world_sid;
	  pos++;
	}
    }
  /* If this is an old-style or non-Cygwin ACL, and secondary user default
     and group default entries exist in the ACL, fake a matching DEF_CLASS_OBJ
     entry. The DEF_CLASS_OBJ permissions are the or'ed permissions of the
     primary group default permissions and all secondary user and group def.
     permissions. */
  if (!new_style && has_def_class_perm
      && (pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) >= 0)
    {
      lacl[pos].a_type = DEF_CLASS_OBJ;
      lacl[pos].a_id = ACL_UNDEFINED_ID;
      lacl[pos].a_perm = def_class_perm;
      if (def_pgrp_pos >= 0)
	lacl[pos].a_perm |= lacl[def_pgrp_pos].a_perm;
      aclsid[pos] = well_known_null_sid;
    }

  /* Make sure `pos' contains the number of used entries in lacl. */
  if ((pos = searchace (lacl, MAX_ACL_ENTRIES, 0)) < 0)
    pos = MAX_ACL_ENTRIES;

  /* For old-style or non-Cygwin ACLs, check for merging permissions. */
  if (!just_created && !new_style)
    for (idx = 0; idx < pos; ++idx)
      {
	if (lacl[idx].a_type & (USER_OBJ | USER)
	    && !(lacl[idx].a_type & ACL_DEFAULT))
	  {
	    mode_t perm;

	    /* Don't merge if the user already has all permissions, or... */
	    if (lacl[idx].a_perm == S_IRWXO)
	      continue;
	    /* ...if the sum of perms is less than or equal the user's perms. */
	    perm = lacl[idx].a_perm
		   | (has_class_perm ? class_perm : lacl[1].a_perm)
		   | lacl[2].a_perm;
	    if (perm == lacl[idx].a_perm)
	      continue;
	    /* Otherwise, if we use the Windows user DB, utilize Authz to make
	       sure all user permissions are correctly reflecting the Windows
	       permissions. */
	    if (cygheap->pg.nss_pwd_db ()
		&& authz_get_user_attribute (&perm, psd, aclsid[idx]))
	      lacl[idx].a_perm = perm;
	    /* Otherwise we only check the current user.  If the user entry
	       has a deny ACE, don't check. */
	    else if (lacl[idx].a_id == myself->uid
		     && !(lacl[idx].a_perm & DENY_RWX))
	      {
		/* Sum up all permissions of groups the user is member of, plus
		   everyone perms, and merge them to user perms.  */
		BOOL ret;

		perm = lacl[2].a_perm & S_IRWXO;
		for (int gidx = 1; gidx < pos; ++gidx)
		  if (lacl[gidx].a_type & (GROUP_OBJ | GROUP)
		      && CheckTokenMembership (cygheap->user.issetuid ()
					       ? cygheap->user.imp_token () : NULL,
					       aclsid[gidx], &ret)
		      && ret)
		    perm |= lacl[gidx].a_perm & S_IRWXO;
		lacl[idx].a_perm |= perm;
	      }
	  }
	/* For all groups, if everyone has more permissions, add everyone
	   perms to group perms.  Skip groups with deny ACE. */
	else if (lacl[idx].a_type & (GROUP_OBJ | GROUP)
		 && !(lacl[idx].a_type & ACL_DEFAULT)
		 && !(lacl[idx].a_perm & DENY_RWX))
	  lacl[idx].a_perm |= lacl[2].a_perm & S_IRWXO;
      }
  /* If owner SID == group SID (Microsoft Accounts) merge group perms into
     user perms but leave group perms intact.  That's a fake, but it allows
     to keep track of the POSIX group perms without much effort. */
  if (owner_eq_group)
    lacl[0].a_perm |= lacl[1].a_perm;
  /* Construct POSIX permission bits.  Fortunately we know exactly where
     to fetch the affecting bits from, at least as long as the array
     hasn't been sorted. */
  attr |= (lacl[0].a_perm & S_IRWXO) << 6;
  attr |= ((has_class_perm ? class_perm : lacl[1].a_perm) & S_IRWXO) << 3;
  attr |= (lacl[2].a_perm & S_IRWXO);

out:
  if (uid_ret)
    *uid_ret = uid;
  if (gid_ret)
    *gid_ret = gid;
  attr_ret = attr;
  if (aclbufp)
    {
      if (pos > nentries)
	{
	  set_errno (ENOSPC);
	  return -1;
	}
      memcpy (aclbufp, lacl, pos * sizeof (aclent_t));
      for (idx = 0; idx < pos; ++idx)
	aclbufp[idx].a_perm &= S_IRWXO;
      aclsort (pos, 0, aclbufp);
    }
  if (std_acl)
    *std_acl = standard_ACEs_only;
  return pos;
}

int
getacl (HANDLE handle, path_conv &pc, int nentries, aclent_t *aclbufp)
{
  security_descriptor sd;
  mode_t attr = pc.isdir () ? S_IFDIR : 0;

  if (get_file_sd (handle, pc, sd, false))
    return -1;
  int pos = get_posix_access (sd, attr, NULL, NULL, aclbufp, nentries);
  syscall_printf ("%R = getacl(%S)", pos, pc.get_nt_native_path ());
  return pos;
}

extern "C" int
acl (const char *path, int cmd, int nentries, aclent_t *aclbufp)
{
  int res = -1;

  fhandler_base *fh = build_fh_name (path, PC_SYM_FOLLOW | PC_KEEP_HANDLE,
				     stat_suffixes);
  if (!fh || !fh->exists ())
    set_errno (ENOENT);
  else if (fh->error ())
    {
      debug_printf ("got %d error from build_fh_name", fh->error ());
      set_errno (fh->error ());
    }
  else
    res = fh->facl (cmd, nentries, aclbufp);

  delete fh;
  syscall_printf ("%R = acl(%s)", res, path);
  return res;
}

extern "C" int
facl (int fd, int cmd, int nentries, aclent_t *aclbufp)
{
  cygheap_fdget cfd (fd);
  if (cfd < 0)
    {
      syscall_printf ("-1 = facl (%d)", fd);
      return -1;
    }
  if (cfd->get_flags () & O_PATH)
    {
      set_errno (EBADF);
      return -1;
    }
  int res = cfd->facl (cmd, nentries, aclbufp);
  syscall_printf ("%R = facl(%s) )", res, cfd->get_name ());
  return res;
}

int
__aclcheck (aclent_t *aclbufp, int nentries, int *which, bool posix)
{
  bool has_user_obj = false;
  bool has_group_obj = false;
  bool has_other_obj = false;
  bool has_class_obj = false;
  bool has_ug_objs = false;
  bool has_def_objs = false;
  bool has_def_user_obj = false;
  bool has_def_group_obj = false;
  bool has_def_other_obj = false;
  bool has_def_class_obj = false;
  bool has_def_ug_objs = false;
  int pos2;

  for (int pos = 0; pos < nentries; ++pos)
    {
      /* POSIX ACLs may contain deleted entries.  Just ignore them. */
      if (posix && aclbufp[pos].a_type == ACL_DELETED_TAG)
	continue;
      /* POSIX defines two sorts of ACLs, access and default, none of which
	 is supposed to have the ACL_DEFAULT flag set. */
      if (posix && (aclbufp[pos].a_type & ACL_DEFAULT))
	{
	  if (which)
	    *which = pos;
	  return ENTRY_ERROR;
	}
      switch (aclbufp[pos].a_type)
	{
	case USER_OBJ:
	  if (has_user_obj)
	    {
	      if (which)
		*which = pos;
	      return USER_ERROR;
	    }
	  has_user_obj = true;
	  break;
	case GROUP_OBJ:
	  if (has_group_obj)
	    {
	      if (which)
		*which = pos;
	      return GRP_ERROR;
	    }
	  has_group_obj = true;
	  break;
	case OTHER_OBJ:
	  if (has_other_obj)
	    {
	      if (which)
		*which = pos;
	      return OTHER_ERROR;
	    }
	  has_other_obj = true;
	  break;
	case CLASS_OBJ:
	  if (has_class_obj)
	    {
	      if (which)
		*which = pos;
	      return CLASS_ERROR;
	    }
	  has_class_obj = true;
	  break;
	case USER:
	case GROUP:
	  if ((pos2 = searchace (aclbufp + pos + 1, nentries - pos - 1,
				 aclbufp[pos].a_type, aclbufp[pos].a_id)) >= 0)
	    {
	      if (which)
		*which = pos2;
	      return DUPLICATE_ERROR;
	    }
	  has_ug_objs = true;
	  break;
	case DEF_USER_OBJ:
	  if (has_def_user_obj)
	    {
	      if (which)
		*which = pos;
	      return USER_ERROR;
	    }
	  has_def_objs = has_def_user_obj = true;
	  break;
	case DEF_GROUP_OBJ:
	  if (has_def_group_obj)
	    {
	      if (which)
		*which = pos;
	      return GRP_ERROR;
	    }
	  has_def_objs = has_def_group_obj = true;
	  break;
	case DEF_OTHER_OBJ:
	  if (has_def_other_obj)
	    {
	      if (which)
		*which = pos;
	      return OTHER_ERROR;
	    }
	  has_def_objs = has_def_other_obj = true;
	  break;
	case DEF_CLASS_OBJ:
	  if (has_def_class_obj)
	    {
	      if (which)
		*which = pos;
	      return CLASS_ERROR;
	    }
	  has_def_objs = has_def_class_obj = true;
	  break;
	case DEF_USER:
	case DEF_GROUP:
	  if ((pos2 = searchace (aclbufp + pos + 1, nentries - pos - 1,
				 aclbufp[pos].a_type, aclbufp[pos].a_id)) >= 0)
	    {
	      if (which)
		*which = pos2;
	      return DUPLICATE_ERROR;
	    }
	  has_def_objs = has_def_ug_objs = true;
	  break;
	default:
	  if (which)
	    *which = pos;
	  return ENTRY_ERROR;
	}
    }
  if (!has_user_obj
      || !has_group_obj
      || !has_other_obj
      || (has_ug_objs && !has_class_obj))
    {
      if (which)
	*which = -1;
      return MISS_ERROR;
    }
  /* Check for missing default entries only on Solaris ACLs. */
  if (!posix &&
      ((has_def_objs
	&& !(has_def_user_obj && has_def_group_obj && has_def_other_obj))
       || (has_def_ug_objs && !has_def_class_obj)))
    {
      if (which)
	*which = -1;
      return MISS_ERROR;
    }
  return 0;
}

extern "C" int
aclcheck (aclent_t *aclbufp, int nentries, int *which)
{
  return __aclcheck (aclbufp, nentries, which, false);
}

/* For the sake of acl_calc_mask, return -1 if the ACL doesn't need a mask
   or if a mask entry already exists (__aclcalcmask sets the mask by itself).
   Otherwise return the mask value so acl_calc_mask can create a mask entry.
   This doesn't matter when called from aclsort. */
mode_t
__aclcalcmask (aclent_t *aclbufp, int nentries)
{
  mode_t mask = 0;
  bool need_mask = false;
  int mask_idx = -1;

  for (int idx = 0; idx < nentries; ++idx)
    switch (aclbufp[idx].a_type)
      {
      case USER:
      case GROUP:
	need_mask = true;
	fallthrough;
      case GROUP_OBJ:
	mask |= aclbufp[idx].a_perm;
	break;
      case CLASS_OBJ:
	mask_idx = idx;
	break;
      default:
	break;
      }
  if (mask_idx != -1)
    aclbufp[mask_idx].a_perm = mask;
  if (need_mask && mask_idx == -1)
    return mask;
  return (acl_perm_t) -1;
}

static int
acecmp (const void *a1, const void *a2)
{
#define ace(i) ((const aclent_t *) a##i)
  int ret = ace (1)->a_type - ace (2)->a_type;
  if (!ret)
    ret = ace (1)->a_id - ace (2)->a_id;
  return ret;
#undef ace
}

/* Sorts any acl.  Called from sec_posixacl.cc. */
int
__aclsort (int nentries, aclent_t *aclbufp)
{
  if (!aclbufp || nentries < 0)
    {
      set_errno (EINVAL);
      return -1;
    }
  if (nentries > 0)
    qsort ((void *) aclbufp, nentries, sizeof (aclent_t), acecmp);
  return 0;
}

extern "C" int
aclsort (int nentries, int calclass, aclent_t *aclbufp)
{
  if (!aclbufp || nentries < MIN_ACL_ENTRIES
      || aclcheck (aclbufp, nentries, NULL))
    {
      set_errno (EINVAL);
      return -1;
    }
  qsort ((void *) aclbufp, nentries, sizeof (aclent_t), acecmp);
  if (calclass)
    __aclcalcmask (aclbufp, nentries);
  return 0;
}

extern "C" int
acltomode (aclent_t *aclbufp, int nentries, mode_t *modep)
{
  int pos;

  if (!aclbufp || nentries < 1 || !modep)
    {
      set_errno (EINVAL);
      return -1;
    }
  *modep = 0;
  if ((pos = searchace (aclbufp, nentries, USER_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  *modep |= (aclbufp[pos].a_perm & S_IRWXO) << 6;
  if ((pos = searchace (aclbufp, nentries, GROUP_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  *modep |= (aclbufp[pos].a_perm & S_IRWXO) << 3;
  int cpos;
  if ((cpos = searchace (aclbufp, nentries, CLASS_OBJ)) >= 0
      && aclbufp[cpos].a_type == CLASS_OBJ)
    *modep |= ((aclbufp[pos].a_perm & S_IRWXO) & aclbufp[cpos].a_perm) << 3;
  if ((pos = searchace (aclbufp, nentries, OTHER_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  *modep |= aclbufp[pos].a_perm & S_IRWXO;
  return 0;
}

extern "C" int
aclfrommode (aclent_t *aclbufp, int nentries, mode_t *modep)
{
  int pos;

  if (!aclbufp || nentries < 1 || !modep)
    {
      set_errno (EINVAL);
      return -1;
    }
  if ((pos = searchace (aclbufp, nentries, USER_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  aclbufp[pos].a_perm = (*modep & S_IRWXU) >> 6;
  if ((pos = searchace (aclbufp, nentries, GROUP_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  aclbufp[pos].a_perm = (*modep & S_IRWXG) >> 3;
  if ((pos = searchace (aclbufp, nentries, CLASS_OBJ)) >= 0
      && aclbufp[pos].a_type == CLASS_OBJ)
    aclbufp[pos].a_perm = (*modep & S_IRWXG) >> 3;
  if ((pos = searchace (aclbufp, nentries, OTHER_OBJ)) < 0
      || !aclbufp[pos].a_type)
    {
      set_errno (EINVAL);
      return -1;
    }
  aclbufp[pos].a_perm = (*modep & S_IRWXO);
  return 0;
}

extern "C" int
acltopbits (aclent_t *aclbufp, int nentries, mode_t *pbitsp)
{
  return acltomode (aclbufp, nentries, pbitsp);
}

extern "C" int
aclfrompbits (aclent_t *aclbufp, int nentries, mode_t *pbitsp)
{
  return aclfrommode (aclbufp, nentries, pbitsp);
}

static char *
permtostr (char *bufp, mode_t perm)
{
  *bufp++ = (perm & S_IROTH) ? 'r' : '-';
  *bufp++ = (perm & S_IWOTH) ? 'w' : '-';
  *bufp++ = (perm & S_IXOTH) ? 'x' : '-';
  return bufp;
}

#define _OPT(o) (options & (o))

#define _CHK(l) \
		  if (bufp + (l) >= buf + 2 * NT_MAX_PATH - 1) \
		    { \
		      set_errno (ENOMEM); \
		      return NULL; \
		    }
#define _CPY(s)	({ \
		  const char *_s = (s); \
		  _CHK (strlen (_s)); \
		  bufp = stpcpy (bufp, _s); \
		})
#define _PTS(p) { \
		  _CHK (3); \
		  bufp = permtostr (bufp, p); \
		}

#define _CMP(s)		(!strncmp (bufp, acl_part[s].str, acl_part[s].len))

struct _acl_part
{
  const char *str;
  size_t len;
};

static _acl_part acl_part_l[] =
{
  { "default:",	8 },
  { "user:",	5 },
  { "group:",	6 },
  { "mask:",	5 },
  { "other:",	6 }
};

static _acl_part acl_part_s[] =
{
  { "d:",	2 },
  { "u:",	2 },
  { "g:",	2 },
  { "m:",	2 },
  { "o:",	2 }
};

enum _acl_type {
  default_s,
  user_s,
  group_s,
  mask_s,
  other_s,
  none_s
};

char *
__acltotext (aclent_t *aclbufp, int aclcnt, const char *prefix, char separator,
	     int options)
{
  if (!aclbufp || aclcnt < 0 || aclcnt > MAX_ACL_ENTRIES
      || (aclcnt > 0 && aclsort (aclcnt, 0, aclbufp)))
    {
      set_errno (EINVAL);
      return NULL;
    }
  cyg_ldap cldap;
  tmp_pathbuf tp;
  char *buf = tp.t_get ();
  char *bufp = buf;
  char *entry_start;
  bool first = true;
  struct passwd *pw;
  struct group *gr;
  mode_t mask = S_IRWXO;
  mode_t def_mask = S_IRWXO;
  mode_t effective;
  int pos;
  _acl_part *acl_part = _OPT (TEXT_ABBREVIATE) ? acl_part_s : acl_part_l;

  *bufp = '\0';
  /* If effective rights are requested, fetch mask values. */
  if (_OPT (TEXT_SOME_EFFECTIVE | TEXT_ALL_EFFECTIVE))
    {
      if ((pos = searchace (aclbufp, aclcnt, CLASS_OBJ)) >= 0)
	mask = aclbufp[pos].a_perm;
      if ((pos = searchace (aclbufp, aclcnt, DEF_CLASS_OBJ)) >= 0)
	def_mask = aclbufp[pos].a_perm;
    }
  for (pos = 0; pos < aclcnt; ++pos)
    {
      if (!first)
	{
	  _CHK (1);
	  *bufp++ = separator;
	}
      first = false;
      /* Rememeber start position of entry to compute TEXT_SMART_INDENT tabs. */
      entry_start = bufp;
      /* prefix */
      if (prefix)
	_CPY (prefix);
      /* Solaris default acl? */
      if (!_OPT (TEXT_IS_POSIX) && aclbufp[pos].a_type & ACL_DEFAULT)
	_CPY (acl_part[default_s].str);
      /* acl type */
      switch (aclbufp[pos].a_type & ~ACL_DEFAULT)
	{
	case USER_OBJ:
	case USER:
	  _CPY (acl_part[user_s].str);
	  break;
	case GROUP_OBJ:
	case GROUP:
	  _CPY (acl_part[group_s].str);
	  break;
	case CLASS_OBJ:
	  _CPY (acl_part[mask_s].str);
	  break;
	case OTHER_OBJ:
	  _CPY (acl_part[other_s].str);
	  break;
	}
      /* id, if any  */
      switch (aclbufp[pos].a_type & ~ACL_DEFAULT)
	{
	case USER:
	  if (_OPT (TEXT_NUMERIC_IDS)
	      || !(pw = internal_getpwuid (aclbufp[pos].a_id, &cldap)))
	    {
	      _CHK (11);
	      bufp += __small_sprintf (bufp, "%u:", aclbufp[pos].a_id);
	    }
	  else
	    {
	      _CHK (strlen (pw->pw_name + 1));
	      bufp += __small_sprintf (bufp, "%s:", pw->pw_name);
	    }
	  break;
	case GROUP:
	  if (_OPT (TEXT_NUMERIC_IDS)
	      || !(gr = internal_getgrgid (aclbufp[pos].a_id, &cldap)))
	    {
	      _CHK (11);
	      bufp += __small_sprintf (bufp, "%u:", aclbufp[pos].a_id);
	    }
	  else
	    {
	      _CHK (strlen (gr->gr_name));
	      bufp += __small_sprintf (bufp, "%s:", gr->gr_name);
	    }
	  break;
	default:
	  _CPY (":");
	  break;
	}
      /* real permissions */
      _PTS (aclbufp[pos].a_perm);
      if (!_OPT (TEXT_SOME_EFFECTIVE | TEXT_ALL_EFFECTIVE))
	continue;
      /* effective permissions */
      switch (aclbufp[pos].a_type)
	{
	case USER:
	case GROUP_OBJ:
	case GROUP:
	  effective = aclbufp[pos].a_perm & mask;
	  break;
	case DEF_USER:
	case DEF_GROUP_OBJ:
	case DEF_GROUP:
	  effective = aclbufp[pos].a_perm & def_mask;
	  break;
	default:
	  continue;
	}
      if (_OPT (TEXT_ALL_EFFECTIVE) || effective != aclbufp[pos].a_perm)
	{
	  if (_OPT (TEXT_SMART_INDENT))
	    {
	      int tabs = 3 - (bufp - entry_start) / 8;
	      if (tabs-- > 0)
		{
		  _CHK (tabs);
		  while (tabs-- > 0)
		    *bufp++ = '\t';
		}
	    }
	  _CPY ("\t#effective:");
	  _PTS (effective);
	}
    }
  if (_OPT (TEXT_END_SEPARATOR))
    {
      _CHK (1);
      *bufp++ = separator;
    }
  *bufp = '\0';
  return strdup (buf);
}

extern "C" char *
acltotext (aclent_t *aclbufp, int aclcnt)
{
  return __acltotext (aclbufp, aclcnt, NULL, ',', 0);
}

static mode_t
permfromstr (char *perm, bool posix_long)
{
  mode_t mode = 0;
  int idx;

  if (perm[0] == 'r')
    mode |= S_IROTH;
  else if (perm[0] != '-')
    return 01000;
  if (perm[1] == 'w')
    mode |= S_IWOTH;
  else if (perm[1] != '-')
    return 01000;
  if (perm[2] == 'x')
    mode |= S_IXOTH;
  else if (perm[2] != '-')
    return 01000;
  idx = 3;
  /* In posix long mode, only tabs up to a hash sign allowed. */
  if (posix_long)
    while (perm[idx] == '\t')
      ++idx;
  if (perm[idx] == '\0' || (posix_long && perm[idx] == '#'))
    return mode;
  return 01000;
}

void *
__aclfromtext (const char *acltextp, int *aclcnt, bool posix)
{
  if (!acltextp || strlen (acltextp) >= 2 * NT_MAX_PATH)
    {
      set_errno (EINVAL);
      return NULL;
    }
  cyg_ldap cldap;
  tmp_pathbuf tp;
  const char *delim;
  _acl_part *acl_part;
  char *bufp, *lasts, *qualifier;
  int pos = 0;
  int acl_type;

  aclent_t *lacl = (aclent_t *) tp.c_get ();
  memset (lacl, 0, MAX_ACL_ENTRIES * sizeof (aclent_t *));
  char *buf = tp.t_get ();
  stpcpy (buf, acltextp);

  if (posix)
    {
      /* Posix long or short form.  Any \n in the string means long form. */
      if (strchr (buf, '\n'))
	{
	  delim = "\n";
	  acl_part = acl_part_l;
	}
      else
	{
	  delim = ",";
	  acl_part = acl_part_s;
	}
    }
  else
    {
      /* Solaris aclfromtext format. */
      delim = ",";
      acl_part = acl_part_l;
    }

  for (bufp = strtok_r (buf, delim, &lasts);
       bufp;
       bufp = strtok_r (NULL, delim, &lasts))
    {
      /* Handle default acl entries only for Solaris ACLs. */
      if (!posix && _CMP (default_s))
	{
	  lacl[pos].a_type |= ACL_DEFAULT;
	  bufp += acl_part[default_s].len;
	}
      lacl[pos].a_id = ACL_UNDEFINED_ID;
      for (acl_type = user_s; acl_type < none_s; ++acl_type)
	if (_CMP (acl_type))
	  break;
      if (acl_type == none_s)
	{
	  set_errno (EINVAL);
	  return NULL;
	}
      bufp += acl_part[acl_type].len;
      switch (acl_type)
	{
	case user_s:
	case group_s:
	  qualifier = bufp;
	  bufp = strchrnul (bufp, ':');
	  *bufp++ = '\0';
	  /* No qualifier?  USER_OBJ or GROUP_OBJ */
	  if (!*qualifier)
	    {
	      lacl[pos].a_type |= (acl_type == user_s) ? USER_OBJ : GROUP_OBJ;
	      break;
	    }
	  /* Some qualifier, USER or GROUP */
	  lacl[pos].a_type |= (acl_type == user_s) ? USER : GROUP;
	  if (isdigit (*qualifier))
	    {
	      char *ep;

	      id_t id = strtol (qualifier, &ep, 10);
	      if (*ep == '\0')
		{
		  lacl[pos].a_id = id;
		  break;
		}
	    }
	  if (acl_type == user_s)
	    {
	      struct passwd *pw = internal_getpwnam (qualifier, &cldap);
	      if (pw)
		lacl[pos].a_id = pw->pw_uid;
	    }
	  else
	    {
	      struct group *gr = internal_getgrnam (qualifier, &cldap);
	      if (gr)
		lacl[pos].a_id = gr->gr_gid;
	    }
	  if (lacl[pos].a_id == ACL_UNDEFINED_ID)
	    {
	      set_errno (EINVAL);
	      return NULL;
	    }
	  break;
	case mask_s:
	case other_s:
	  if (*bufp++ != ':')
	    {
	      set_errno (EINVAL);
	      return NULL;
	    }
	  lacl[pos].a_type |= (acl_type == mask_s) ? CLASS_OBJ : OTHER_OBJ;
	  break;
	}
      /* In posix long mode, the next char after the permissions may be a tab
	 followed by effective permissions we can ignore here. */
      if ((lacl[pos].a_perm = permfromstr (bufp, *delim == '\n')) == 01000)
	{
	  set_errno (EINVAL);
	  return NULL;
	}
      ++pos;
    }
  if (posix)
    {
      acl_t acl = (acl_t) acl_init (pos);
      if (acl)
	{
	  memcpy (acl->entry, lacl, pos * sizeof (aclent_t));
	  acl->count = pos;
	  if (aclcnt)
	    *aclcnt = pos;
	}
      return (void *) acl;
    }
  else
    {
      aclent_t *aclp = (aclent_t *) malloc (pos * sizeof (aclent_t));
      if (aclp)
	{
	  memcpy (aclp, lacl, pos * sizeof (aclent_t));
	  if (aclcnt)
	    *aclcnt = pos;
	}
      return (void *) aclp;
    }
}

extern "C" aclent_t *
aclfromtext (char *acltextp, int *aclcnt)
{
  return (aclent_t *) __aclfromtext (acltextp, aclcnt, false);
}
