/* sec_helper.cc: NT security helper functions

   Copyright 2000, 2001, 2002, 2003, 2004, 2006, 2007, 2008, 2009,
   2010, 2011 Red Hat, Inc.

   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 <sys/acl.h>
#include <wchar.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "dtable.h"
#include "pinfo.h"
#include "cygheap.h"
#include "pwdgrp.h"
#include "ntdll.h"

/* General purpose security attribute objects for global use. */
SECURITY_ATTRIBUTES NO_COPY sec_none;
SECURITY_ATTRIBUTES NO_COPY sec_none_nih;
SECURITY_ATTRIBUTES NO_COPY sec_all;
SECURITY_ATTRIBUTES NO_COPY sec_all_nih;

MKSID (well_known_null_sid, "S-1-0-0",
       SECURITY_NULL_SID_AUTHORITY, 1, SECURITY_NULL_RID);
MKSID (well_known_world_sid, "S-1-1-0",
       SECURITY_WORLD_SID_AUTHORITY, 1, SECURITY_WORLD_RID);
MKSID (well_known_local_sid, "S-1-2-0",
       SECURITY_LOCAL_SID_AUTHORITY, 1, SECURITY_LOCAL_RID);
MKSID (well_known_console_logon_sid, "S-1-2-1",
       SECURITY_LOCAL_SID_AUTHORITY, 1, 1);
MKSID (well_known_creator_owner_sid, "S-1-3-0",
       SECURITY_CREATOR_SID_AUTHORITY, 1, SECURITY_CREATOR_OWNER_RID);
MKSID (well_known_creator_group_sid, "S-1-3-1",
       SECURITY_CREATOR_SID_AUTHORITY, 1, SECURITY_CREATOR_GROUP_RID);
MKSID (well_known_dialup_sid, "S-1-5-1",
       SECURITY_NT_AUTHORITY, 1, SECURITY_DIALUP_RID);
MKSID (well_known_network_sid, "S-1-5-2",
       SECURITY_NT_AUTHORITY, 1, SECURITY_NETWORK_RID);
MKSID (well_known_batch_sid, "S-1-5-3",
       SECURITY_NT_AUTHORITY, 1, SECURITY_BATCH_RID);
MKSID (well_known_interactive_sid, "S-1-5-4",
       SECURITY_NT_AUTHORITY, 1, SECURITY_INTERACTIVE_RID);
MKSID (well_known_service_sid, "S-1-5-6",
       SECURITY_NT_AUTHORITY, 1, SECURITY_SERVICE_RID);
MKSID (well_known_authenticated_users_sid, "S-1-5-11",
       SECURITY_NT_AUTHORITY, 1, SECURITY_AUTHENTICATED_USER_RID);
MKSID (well_known_this_org_sid, "S-1-5-15",
       SECURITY_NT_AUTHORITY, 1, 15);
MKSID (well_known_system_sid, "S-1-5-18",
       SECURITY_NT_AUTHORITY, 1, SECURITY_LOCAL_SYSTEM_RID);
MKSID (well_known_builtin_sid, "S-1-5-32",
       SECURITY_NT_AUTHORITY, 1, SECURITY_BUILTIN_DOMAIN_RID);
MKSID (well_known_admins_sid, "S-1-5-32-544",
       SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID,
				 DOMAIN_ALIAS_RID_ADMINS);
MKSID (well_known_users_sid, "S-1-5-32-545",
       SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID,
				 DOMAIN_ALIAS_RID_USERS);
MKSID (fake_logon_sid, "S-1-5-5-0-0",
       SECURITY_NT_AUTHORITY, 3, SECURITY_LOGON_IDS_RID, 0, 0);
MKSID (mandatory_medium_integrity_sid, "S-1-16-8192",
       SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_MEDIUM_RID);
MKSID (mandatory_high_integrity_sid, "S-1-16-12288",
       SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_HIGH_RID);
MKSID (mandatory_system_integrity_sid, "S-1-16-16384",
       SECURITY_MANDATORY_LABEL_AUTHORITY, 1, SECURITY_MANDATORY_SYSTEM_RID);
/* UNIX accounts on a Samba server have the SID prefix "S-1-22-1" */
#define SECURITY_SAMBA_UNIX_AUTHORITY {0,0,0,0,0,22}
MKSID (well_known_samba_unix_user_fake_sid, "S-1-22-1-0",
       SECURITY_SAMBA_UNIX_AUTHORITY, 2, 1, 0);

bool
cygpsid::operator== (const char *nsidstr) const
{
  cygsid nsid (nsidstr);
  return psid == nsid;
}

__uid32_t
cygpsid::get_id (BOOL search_grp, int *type)
{
    /* First try to get SID from group, then passwd */
  __uid32_t id = ILLEGAL_UID;

  if (search_grp)
    {
      struct __group32 *gr;
      if (cygheap->user.groups.pgsid == psid)
	id = myself->gid;
      else if ((gr = internal_getgrsid (*this)))
	id = gr->gr_gid;
      if (id != ILLEGAL_UID)
	{
	  if (type)
	    *type = GROUP;
	  return id;
	}
    }
  if (!search_grp || type)
    {
      struct passwd *pw;
      if (*this == cygheap->user.sid ())
	id = myself->uid;
      else if ((pw = internal_getpwsid (*this)))
	id = pw->pw_uid;
      if (id != ILLEGAL_UID && type)
	*type = USER;
    }
  return id;
}

PWCHAR
cygpsid::string (PWCHAR nsidstr) const
{
  UNICODE_STRING sid;

  if (!psid || !nsidstr)
    return NULL;
  RtlInitEmptyUnicodeString (&sid, nsidstr, 256);
  RtlConvertSidToUnicodeString (&sid, psid, FALSE);
  return nsidstr;
}

char *
cygpsid::string (char *nsidstr) const
{
  char *t;
  DWORD i;

  if (!psid || !nsidstr)
    return NULL;
  strcpy (nsidstr, "S-1-");
  t = nsidstr + sizeof ("S-1-") - 1;
  t += __small_sprintf (t, "%u", RtlIdentifierAuthoritySid (psid)->Value[5]);
  for (i = 0; i < *RtlSubAuthorityCountSid (psid); ++i)
    t += __small_sprintf (t, "-%lu", *RtlSubAuthoritySid (psid, i));
  return nsidstr;
}

PSID
cygsid::get_sid (DWORD s, DWORD cnt, DWORD *r, bool well_known)
{
  DWORD i;
  SID_IDENTIFIER_AUTHORITY sid_auth = { SECURITY_NULL_SID_AUTHORITY };
# define SECURITY_NT_AUTH 5

  if (s > 255 || cnt < 1 || cnt > 8)
    {
      psid = NO_SID;
      return NULL;
    }
  sid_auth.Value[5] = s;
  set ();
  RtlInitializeSid (psid, &sid_auth, cnt);
  for (i = 0; i < cnt; ++i)
    memcpy ((char *) psid + 8 + sizeof (DWORD) * i, &r[i], sizeof (DWORD));
  /* If the well_known flag isn't set explicitely, we check the SID
     for being a well-known SID ourselves. That's necessary because this
     cygsid is created from a SID string, usually from /etc/passwd or
     /etc/group.  The calling code just doesn't know if the SID is well-known
     or not.  All SIDs are well-known SIDs, except those in the non-unique NT
     authority range. */
  if (well_known)
    well_known_sid = well_known;
  else
    well_known_sid = (s != SECURITY_NT_AUTH
		      || r[0] != SECURITY_NT_NON_UNIQUE_RID);
  return psid;
}

const PSID
cygsid::getfromstr (const char *nsidstr, bool well_known)
{
  char *lasts;
  DWORD s, cnt = 0;
  DWORD r[8];

  if (nsidstr && !strncmp (nsidstr, "S-1-", 4))
    {
      s = strtoul (nsidstr + 4, &lasts, 10);
      while (cnt < 8 && *lasts == '-')
	r[cnt++] = strtoul (lasts + 1, &lasts, 10);
      if (!*lasts)
	return get_sid (s, cnt, r, well_known);
    }
  return psid = NO_SID;
}

BOOL
cygsid::getfrompw (const struct passwd *pw)
{
  char *sp = (pw && pw->pw_gecos) ? strrchr (pw->pw_gecos, ',') : NULL;
  return (*this = sp ? sp + 1 : sp) != NULL;
}

BOOL
cygsid::getfromgr (const struct __group32 *gr)
{
  char *sp = (gr && gr->gr_passwd) ? gr->gr_passwd : NULL;
  return (*this = sp) != NULL;
}

cygsid *
cygsidlist::alloc_sids (int n)
{
  if (n > 0)
    return (cygsid *) cmalloc (HEAP_STR, n * sizeof (cygsid));
  else
    return NULL;
}

void
cygsidlist::free_sids ()
{
  if (sids)
    cfree (sids);
  sids = NULL;
  cnt = maxcnt = 0;
  type = cygsidlist_empty;
}

BOOL
cygsidlist::add (const PSID nsi, bool well_known)
{
  if (contains (nsi))
    return TRUE;
  if (cnt >= maxcnt)
    {
      cygsid *tmp = new cygsid [2 * maxcnt];
      if (!tmp)
	return FALSE;
      maxcnt *= 2;
      for (int i = 0; i < cnt; ++i)
	tmp[i] = sids[i];
      delete [] sids;
      sids = tmp;
    }
  if (well_known)
    sids[cnt++] *= nsi;
  else
    sids[cnt++] = nsi;
  return TRUE;
}

bool
get_sids_info (cygpsid owner_sid, cygpsid group_sid, __uid32_t * uidret, __gid32_t * gidret)
{
  struct passwd *pw;
  struct __group32 *gr = NULL;
  bool ret = false;

  owner_sid.debug_print ("get_sids_info: owner SID =");
  group_sid.debug_print ("get_sids_info: group SID =");

  if (group_sid == cygheap->user.groups.pgsid)
    *gidret = myself->gid;
  else if ((gr = internal_getgrsid (group_sid)))
    *gidret = gr->gr_gid;
  else
    *gidret = ILLEGAL_GID;

  if (owner_sid == cygheap->user.sid ())
    {
      *uidret = myself->uid;
      if (*gidret == myself->gid)
	ret = true;
      else
	ret = (internal_getgroups (0, NULL, &group_sid) > 0);
    }
  else if ((pw = internal_getpwsid (owner_sid)))
    {
      *uidret = pw->pw_uid;
      if (gr || (*gidret != ILLEGAL_GID
		 && (gr = internal_getgrgid (*gidret))))
	for (int idx = 0; gr->gr_mem[idx]; ++idx)
	  if ((ret = strcasematch (pw->pw_name, gr->gr_mem[idx])))
	    break;
    }
  else
    *uidret = ILLEGAL_UID;

  return ret;
}

PSECURITY_DESCRIPTOR
security_descriptor::malloc (size_t nsize)
{
  free ();
  if ((psd = (PSECURITY_DESCRIPTOR) ::malloc (nsize)))
    sd_size = nsize;
  return psd;
}

PSECURITY_DESCRIPTOR
security_descriptor::realloc (size_t nsize)
{
  PSECURITY_DESCRIPTOR tmp;

  /* Can't re-use buffer allocated by GetSecurityInfo. */
  if (psd && !sd_size)
    free ();
  if (!(tmp = (PSECURITY_DESCRIPTOR) ::realloc (psd, nsize)))
    return NULL;
  sd_size = nsize;
  return psd = tmp;
}

void
security_descriptor::free ()
{
  if (psd)
    {
      if (!sd_size)
	LocalFree (psd);
      else
	::free (psd);
    }
  psd = NULL;
  sd_size = 0;
}

#undef TEXT
#define TEXT(q) L##q

/* Index must match the corresponding foo_PRIVILEGE value, see security.h. */
static const struct {
  const wchar_t *name;
  bool		 high_integrity; /* UAC: High Mandatory Label required to
				    be allowed to enable this privilege in
				    the user token. */
} cygpriv[] =
{
  { L"",				false },
  { L"",				false },
  { SE_CREATE_TOKEN_NAME,		true  },
  { SE_ASSIGNPRIMARYTOKEN_NAME,		true  },
  { SE_LOCK_MEMORY_NAME,		false },
  { SE_INCREASE_QUOTA_NAME,		true  },
  { SE_MACHINE_ACCOUNT_NAME,		false },
  { SE_TCB_NAME,			true  },
  { SE_SECURITY_NAME,			true  },
  { SE_TAKE_OWNERSHIP_NAME,		true  },
  { SE_LOAD_DRIVER_NAME,		true  },
  { SE_SYSTEM_PROFILE_NAME,		true  },
  { SE_SYSTEMTIME_NAME,			true  },
  { SE_PROF_SINGLE_PROCESS_NAME,	true  },
  { SE_INC_BASE_PRIORITY_NAME,		true  },
  { SE_CREATE_PAGEFILE_NAME,		true  },
  { SE_CREATE_PERMANENT_NAME,		false },
  { SE_BACKUP_NAME,			true  },
  { SE_RESTORE_NAME,			true  },
  { SE_SHUTDOWN_NAME,			false },
  { SE_DEBUG_NAME,			true  },
  { SE_AUDIT_NAME,			false },
  { SE_SYSTEM_ENVIRONMENT_NAME,		true  },
  { SE_CHANGE_NOTIFY_NAME,		false },
  { SE_REMOTE_SHUTDOWN_NAME,		true  },
  { SE_UNDOCK_NAME,			false },
  { SE_SYNC_AGENT_NAME,			false },
  { SE_ENABLE_DELEGATION_NAME,		false },
  { SE_MANAGE_VOLUME_NAME,		true  },
  { SE_IMPERSONATE_NAME,		true  },
  { SE_CREATE_GLOBAL_NAME,		false },
  { SE_TRUSTED_CREDMAN_ACCESS_NAME,	false },
  { SE_RELABEL_NAME,			true  },
  { SE_INCREASE_WORKING_SET_NAME,	false },
  { SE_TIME_ZONE_NAME,			true  },
  { SE_CREATE_SYMBOLIC_LINK_NAME,	true  }
};

bool
privilege_luid (const PWCHAR pname, LUID &luid, bool &high_integrity)
{
  ULONG idx;
  for (idx = SE_CREATE_TOKEN_PRIVILEGE;
       idx <= SE_MAX_WELL_KNOWN_PRIVILEGE;
       ++idx)
    if (!wcscmp (cygpriv[idx].name, pname))
      {
	luid.HighPart = 0;
	luid.LowPart = idx;
	high_integrity = cygpriv[idx].high_integrity;
	return true;
      }
  return false;
}

static const wchar_t *
privilege_name (const LUID &priv_luid)
{
  if (priv_luid.HighPart || priv_luid.LowPart < SE_CREATE_TOKEN_PRIVILEGE
      || priv_luid.LowPart > SE_MAX_WELL_KNOWN_PRIVILEGE)
    return L"<unknown privilege>";
  return cygpriv[priv_luid.LowPart].name;
}

int
set_privilege (HANDLE token, DWORD privilege, bool enable)
{
  int ret = -1;
  TOKEN_PRIVILEGES new_priv, orig_priv;
  ULONG size;
  NTSTATUS status;

  new_priv.PrivilegeCount = 1;
  new_priv.Privileges[0].Luid.HighPart = 0L;
  new_priv.Privileges[0].Luid.LowPart = privilege;
  new_priv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;

  status = NtAdjustPrivilegesToken (token, FALSE, &new_priv, sizeof orig_priv,
				    &orig_priv, &size);
  if (!NT_SUCCESS (status))
    {
      __seterrno_from_nt_status (status);
      goto out;
    }

  /* If orig_priv.PrivilegeCount is 0, the privilege hasn't been changed. */
  if (!orig_priv.PrivilegeCount)
    ret = enable ? 1 : 0;
  else
    ret = (orig_priv.Privileges[0].Attributes & SE_PRIVILEGE_ENABLED) ? 1 : 0;

out:
  if (ret < 0)
    debug_printf ("%d = set_privilege((token %x) %W, %d)", ret, token,
		  privilege_name (new_priv.Privileges[0].Luid), enable);
  return ret;
}

/* This is called very early in process initialization.  The code must
   not depend on anything. */
void
set_cygwin_privileges (HANDLE token)
{
  /* Setting these rights at process startup allows processes running under
     user tokens which are in the administrstors group to have root-like
     permissions. */
  /* Allow to access all files, independent of their ACL settings. */
  set_privilege (token, SE_RESTORE_PRIVILEGE, true);
  set_privilege (token, SE_BACKUP_PRIVILEGE, true);
  /* Allow full access to other user's processes. */
  set_privilege (token, SE_DEBUG_PRIVILEGE, true);
#if 0
  /* Allow to create global shared memory.  This isn't required anymore since
     Cygwin 1.7.  It uses its own subdirectories in the global NT namespace
     which isn't affected by the SE_CREATE_GLOBAL_PRIVILEGE restriction. */
  if (wincap.has_create_global_privilege ())
    set_privilege (token, SE_CREATE_GLOBAL_PRIVILEGE, true);
#endif
}

/* Function to return a common SECURITY_DESCRIPTOR that
   allows all access.  */

static inline PSECURITY_DESCRIPTOR
get_null_sd ()
{
  static NO_COPY SECURITY_DESCRIPTOR sd;
  static NO_COPY PSECURITY_DESCRIPTOR null_sdp;

  if (!null_sdp)
    {
      RtlCreateSecurityDescriptor (&sd, SECURITY_DESCRIPTOR_REVISION);
      RtlSetDaclSecurityDescriptor (&sd, TRUE, NULL, FALSE);
      null_sdp = &sd;
    }
  return null_sdp;
}

/* Initialize global security attributes.
   Called from dcrt0.cc (_dll_crt0).  */

void
init_global_security ()
{
  sec_none.nLength = sec_none_nih.nLength =
  sec_all.nLength = sec_all_nih.nLength = sizeof (SECURITY_ATTRIBUTES);
  sec_none.bInheritHandle = sec_all.bInheritHandle = TRUE;
  sec_none_nih.bInheritHandle = sec_all_nih.bInheritHandle = FALSE;
  sec_none.lpSecurityDescriptor = sec_none_nih.lpSecurityDescriptor = NULL;
  sec_all.lpSecurityDescriptor = sec_all_nih.lpSecurityDescriptor =
    get_null_sd ();
}

bool
sec_acl (PACL acl, bool original, bool admins, PSID sid1, PSID sid2, DWORD access2)
{
  NTSTATUS status;
  size_t acl_len = MAX_DACL_LEN (5);
  LPVOID pAce;
  cygpsid psid;

#ifdef DEBUGGING
  if ((unsigned long) acl % 4)
    api_fatal ("Incorrectly aligned incoming ACL buffer!");
#endif
  status = RtlCreateAcl (acl, acl_len, ACL_REVISION);
  if (!NT_SUCCESS (status))
    {
      debug_printf ("RtlCreateAcl: %p", status);
      return false;
    }
  if (sid1)
    {
      status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, sid1);
      if (!NT_SUCCESS (status))
	debug_printf ("RtlAddAccessAllowedAce(sid1) %p", status);
    }
  if (original && (psid = cygheap->user.saved_sid ())
      && psid != sid1 && psid != well_known_system_sid)
    {
      status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL, psid);
      if (!NT_SUCCESS (status))
	debug_printf ("RtlAddAccessAllowedAce(original) %p", status);
    }
  if (sid2)
    {
      status = RtlAddAccessAllowedAce (acl, ACL_REVISION, access2, sid2);
      if (!NT_SUCCESS (status))
	debug_printf ("RtlAddAccessAllowedAce(sid2) %p", status);
    }
  if (admins)
    {
      status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL,
				       well_known_admins_sid);
      if (!NT_SUCCESS (status))
	debug_printf ("RtlAddAccessAllowedAce(admin) %p", status);
    }
  status = RtlAddAccessAllowedAce (acl, ACL_REVISION, GENERIC_ALL,
				   well_known_system_sid);
  if (!NT_SUCCESS (status))
    debug_printf ("RtlAddAccessAllowedAce(system) %p", status);
  status = RtlFirstFreeAce (acl, &pAce);
  if (NT_SUCCESS (status) && pAce)
    acl->AclSize = (char *) pAce - (char *) acl;
  else
    debug_printf ("RtlFirstFreeAce: %p", status);

  return true;
}

PSECURITY_ATTRIBUTES __stdcall
__sec_user (PVOID sa_buf, PSID sid1, PSID sid2, DWORD access2, BOOL inherit)
{
  PSECURITY_ATTRIBUTES psa = (PSECURITY_ATTRIBUTES) sa_buf;
  PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR)
			     ((char *) sa_buf + sizeof (*psa));
  PACL acl = (PACL) ((char *) sa_buf + sizeof (*psa) + sizeof (*psd));
  NTSTATUS status;

#ifdef DEBUGGING
  if ((unsigned long) sa_buf % 4)
    api_fatal ("Incorrectly aligned incoming SA buffer!");
#endif
  if (!sec_acl (acl, true, true, sid1, sid2, access2))
    return inherit ? &sec_none : &sec_none_nih;

  RtlCreateSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION);
  status = RtlSetDaclSecurityDescriptor (psd, TRUE, acl, FALSE);
  if (!NT_SUCCESS (status))
    debug_printf ("RtlSetDaclSecurityDescriptor %p", status);

  psa->nLength = sizeof (SECURITY_ATTRIBUTES);
  psa->lpSecurityDescriptor = psd;
  psa->bInheritHandle = inherit;
  return psa;
}

/* Helper function to create an event security descriptor which only allows
   specific access to everyone.  Only the creating process has all access
   rights. */

PSECURITY_DESCRIPTOR
_everyone_sd (void *buf, ACCESS_MASK access)
{
  NTSTATUS status;
  PSECURITY_DESCRIPTOR psd = (PSECURITY_DESCRIPTOR) buf;

  if (psd)
    {
      RtlCreateSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION);
      PACL dacl = (PACL) (psd + 1);
      RtlCreateAcl (dacl, MAX_DACL_LEN (1), ACL_REVISION);
      status = RtlAddAccessAllowedAce (dacl, ACL_REVISION, access,
				       well_known_world_sid);
      if (!NT_SUCCESS (status))
	{
	  debug_printf ("RtlAddAccessAllowedAce: %p", status);
	  return NULL;
	}
      LPVOID ace;
      status = RtlFirstFreeAce (dacl, &ace);
      if (!NT_SUCCESS (status))
	{
	  debug_printf ("RtlFirstFreeAce: %p", status);
	  return NULL;
	}
      dacl->AclSize = (char *) ace - (char *) dacl;
      RtlSetDaclSecurityDescriptor (psd, TRUE, dacl, FALSE);
    }
  return psd;
}