/* winsec - provides functions for analyzing user and group SIDs
 *
 * Copyright (c) 2008, Charles S. Wilson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifdef __CYGWIN__

#include <syslog.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/cygwin.h>
#include <windows.h>
#include <lmcons.h>
#include <lmaccess.h>
#include <lmerr.h>
#include <wininet.h>

/* not declared by mingw/cygwin w32api headers */
#ifndef SECURITY_MAX_SID_SIZE
#define SECURITY_MAX_SID_SIZE 68
#endif

/**************************************************************************
 * Imported functions
 **************************************************************************/
/*
   AllocateAndInitializeSid : advapi32
   OpenProcessToken         : advapi32
   GetTokenInformation      : advapi32
   EqualSid                 : advapi32
   FreeSid                  : advapi32
   LookupAccountName        : advapi32
   GetLengthSid             : advapi32
   CopySid                  : advapi32

   NetApiBufferFree         : netapi32
   NetUserEnum              : netapi32
   NetLocalGroupEnum        : netapi32
   NetGetDCName             : netapi32
   NetUserGetInfo           : netapi32
   DsGetDcName              : netapi32
*/

typedef WINBASEAPI BOOL (WINAPI *
			 PFN_AllocateAndInitializeSid)
  (PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD,
   DWORD, DWORD, PSID *);
static PFN_AllocateAndInitializeSid pfnAllocateAndInitializeSid = NULL;

typedef WINBASEAPI BOOL (WINAPI * PFN_OpenProcessToken) (HANDLE, DWORD,
							 PHANDLE);
static PFN_OpenProcessToken pfnOpenProcessToken = NULL;

typedef WINBASEAPI BOOL (WINAPI * PFN_GetTokenInformation) (HANDLE,
							    TOKEN_INFORMATION_CLASS,
							    PVOID, DWORD,
							    PDWORD);
static PFN_GetTokenInformation pfnGetTokenInformation = NULL;

typedef WINBASEAPI BOOL (WINAPI * PFN_EqualSid) (PSID, PSID);
static PFN_EqualSid pfnEqualSid = NULL;

typedef WINBASEAPI PVOID (WINAPI * PFN_FreeSid) (PSID);
static PFN_FreeSid pfnFreeSid = NULL;

typedef WINBASEAPI BOOL (WINAPI * PFN_LookupAccountName) (LPCTSTR, LPCTSTR,
							  PSID, LPDWORD,
							  LPTSTR, LPDWORD,
							  PSID_NAME_USE);
static PFN_LookupAccountName pfnLookupAccountName = NULL;

typedef WINBASEAPI DWORD (WINAPI * PFN_GetLengthSid) (PSID);
static PFN_GetLengthSid pfnGetLengthSid = NULL;

typedef WINBASEAPI BOOL (WINAPI * PFN_CopySid) (DWORD, PSID, PSID);
static PFN_CopySid pfnCopySid = NULL;

typedef NET_API_STATUS (WINAPI * PFN_NetApiBufferFree) (PVOID);
static PFN_NetApiBufferFree pfnNetApiBufferFree = NULL;

typedef NET_API_STATUS (WINAPI * PFN_NetUserEnum) (LPWSTR, DWORD, DWORD,
						   PBYTE *, DWORD, PDWORD,
						   PDWORD, PDWORD);
static PFN_NetUserEnum pfnNetUserEnum = NULL;

typedef NET_API_STATUS (WINAPI * PFN_NetLocalGroupEnum) (LPWSTR, DWORD,
							 PBYTE *, DWORD,
							 PDWORD, PDWORD,
							 PDWORD);
static PFN_NetLocalGroupEnum pfnNetLocalGroupEnum = NULL;

typedef NET_API_STATUS (WINAPI * PFN_NetGetDCName) (LPWSTR, LPWSTR, PBYTE *);
static PFN_NetGetDCName pfnNetGetDCName = NULL;

typedef NET_API_STATUS (WINAPI * PFN_NetUserGetInfo) (LPWSTR, LPWSTR, DWORD,
						      PBYTE *);
static PFN_NetUserGetInfo pfnNetUserGetInfo = NULL;

typedef struct
{
  LPWSTR DomainControllerName;
  LPWSTR DomainControllerAddress;
  ULONG DomainControllerAddressType;
  GUID DomainGuid;
  LPWSTR DomainName;
  LPWSTR DnsForestName;
  ULONG Flags;
  LPWSTR DcSiteName;
  LPWSTR ClientSiteName;
} *PDOMAIN_CONTROLLER_INFOW;

typedef NET_API_STATUS (WINAPI * PFN_DsGetDcName) (LPWSTR, LPWSTR, GUID *,
						   LPWSTR, ULONG,
						   PDOMAIN_CONTROLLER_INFOW
						   *);
static PFN_DsGetDcName pfnDsGetDcName = NULL;

/**************************************************************************
 * private function prototypes
 **************************************************************************/
/**
 * returns 0 if no error occurs
 * initializes the function pointers used in this file
 **/
static int initPFN (void);

/**
 * converts unicode string wcs to (multibyte?) in mbs,
 * using current codepage.
 **/
static void uni2ansi (LPWSTR wcs, char *mbs, int size);

/**
 * helper method for getUserSIDForUID
 * returns negative on error
 *
 * 0 on success
 *   bufptr is populated with the requested user info
 *   uni_servername is populated with the associated server,
 *      if applicable (or null)
 *   it is the caller's responsibility to free both:
 *      (*pfnNetApiBufferFree)((PVOID)bufptr);
 *      HeapFree(GetProcessHeap(), 0, (LPVOID)uni_servername)
 *
 * 1 if account name corresponding to uid matches *any*
 *   element in the skipNames list, which is a null-terminated
 *      argv-style array of *local* user names.  In this case,
 *      the index of the matched name is stored in *matchedName,
 *      and the UserInfo buffer is NOT populated.
 *
 * NB: name comparison is performed in a case-insensitive manner
 * NB: To match the LocalSystem account, include both
 *     "LocalSystem" and "SYSTEM" in skipNames).
 */
static int getUserInfoForUID (uid_t uid,
			      const char **skipNames,
			      DWORD level,
			      LPBYTE * bufptr,
			      LPWSTR * uni_servername, int *matchedName);

/**
 * helper method for currentUserIsMemberOfGroupSID
 * returns negative on error
 * zero if the ptg object contains a SID that matches groupSID
 * positive otherwise
 */
static int currentProcessTokenGroupsContainsGroupSID (PSID groupSID,
						      PTOKEN_GROUPS ptg,
						      PSID
						      localAdminGroupSID);

/**************************************************************************
 * public function prototypes
 **************************************************************************/

/* returns pointer to appropriate function on success, NULL otherwise */
FARPROC getProcAddress (LPCTSTR lpModuleName, LPCSTR lpProcName);

/**
 * each of the following return:
 * negative if error
 * 0 if true
 * 1 if false
 **/
int currentUserIsLocalSystem ();
int currentUserIsMemberOfLocalAdministrators ();
int uidIsLocalSystem (uid_t uid);
int uidIsMemberOfLocalAdministrators (uid_t uid);

/**
 * Obtain the SID for the specified uid
 * returns 0 on success, nonzero otherwise
 * caller must free userSid using
 *    HeapFree(GetCurrentHeap(), 0, userSid),
 *    not (*pfnFreeSid)(userSid)
 **/
int getUserSIDForUID (uid_t uid, PSID * userSID);

/**
 * test the specified uid_t's corresponds to the specified user sid
 * returns negative on error
 * zero if the specified uid_t matches the specified user sid
 * positive otherwise
 **/
int uidMatchesAccountSID (uid_t uid,
			  BYTE nsubauthoritycount,
			  DWORD dwsubauthority0,
			  DWORD dwsubauthority1,
			  DWORD dwsubauthority2,
			  DWORD dwsubauthority3,
			  DWORD dwsubauthority4,
			  DWORD dwsubauthority5,
			  DWORD dwsubauthority6, DWORD dwsubauthority7);

/**
 * !!!! NOT YET IMPLEMENTED !!!!
 * test whether the specified uid_t is a member of the specified group
 * returns negative on error
 * zero if the specified uid_t is a member of the specified group
 * positive otherwise
 */
int uidIsMemberOfGroupSID (uid_t uid,
			   BYTE nsubauthoritycount,
			   DWORD dwsubauthority0,
			   DWORD dwsubauthority1,
			   DWORD dwsubauthority2,
			   DWORD dwsubauthority3,
			   DWORD dwsubauthority4,
			   DWORD dwsubauthority5,
			   DWORD dwsubauthority6, DWORD dwsubauthority7);

/**
 * test whether the current user corresponds to the specified user sid
 * returns negative on error
 * zero if current account matches the specified SID
 * positive otherwise
 **/
int currentUserMatchesAccountSID (BYTE nSubAuthorityCount,
				  DWORD dwSubAuthority0,
				  DWORD dwSubAuthority1,
				  DWORD dwSubAuthority2,
				  DWORD dwSubAuthority3,
				  DWORD dwSubAuthority4,
				  DWORD dwSubAuthority5,
				  DWORD dwSubAuthority6,
				  DWORD dwSubAuthority7);

/**
 * test whether the current user is a member of the specified group
 * returns negative on error
 * zero if current account is a member of the specified group
 * positive otherwise
 */
int currentUserIsMemberOfGroupSID (BYTE nSubAuthorityCount,
				   DWORD dwSubAuthority0,
				   DWORD dwSubAuthority1,
				   DWORD dwSubAuthority2,
				   DWORD dwSubAuthority3,
				   DWORD dwSubAuthority4,
				   DWORD dwSubAuthority5,
				   DWORD dwSubAuthority6,
				   DWORD dwSubAuthority7);


/**************************************************************************
 * getProcAddress
 **************************************************************************/
FARPROC
getProcAddress (LPCTSTR lpModuleName, LPCSTR lpProcName)
{
  HMODULE hModule = NULL;
  FARPROC fpProcAddress = NULL;

  hModule = GetModuleHandle (lpModuleName);
  if (!hModule)
    {
      hModule = LoadLibrary (lpModuleName);
      if (!hModule)
	return NULL;
    }
  fpProcAddress = GetProcAddress (hModule, lpProcName);
  return fpProcAddress;
}

/**************************************************************************
 * initPFN
 *    returns 0 if no error occurs, nonzero otherwise
 **************************************************************************/
static int pfnERROR = 1;
static int
initPFN (void)
{
  if (pfnERROR)
    {
      if ((pfnAllocateAndInitializeSid =
	   (PFN_AllocateAndInitializeSid) getProcAddress ("advapi32",
							  "AllocateAndInitializeSid"))
	  == NULL)
	return pfnERROR;
      if ((pfnOpenProcessToken =
	   (PFN_OpenProcessToken) getProcAddress ("advapi32",
						  "OpenProcessToken")) ==
	  NULL)
	return pfnERROR;
      if ((pfnGetTokenInformation =
	   (PFN_GetTokenInformation) getProcAddress ("advapi32",
						     "GetTokenInformation"))
	  == NULL)
	return pfnERROR;
      if ((pfnEqualSid =
	   (PFN_EqualSid) getProcAddress ("advapi32", "EqualSid")) == NULL)
	return pfnERROR;
      if ((pfnFreeSid =
	   (PFN_FreeSid) getProcAddress ("advapi32", "FreeSid")) == NULL)
	return pfnERROR;
      if ((pfnCopySid =
	   (PFN_CopySid) getProcAddress ("advapi32", "CopySid")) == NULL)
	return pfnERROR;
      if ((pfnGetLengthSid =
	   (PFN_GetLengthSid) getProcAddress ("advapi32",
					      "GetLengthSid")) == NULL)
	return pfnERROR;
      if ((pfnLookupAccountName =
	   (PFN_LookupAccountName) getProcAddress ("advapi32",
						   "LookupAccountNameA")) ==
	  NULL)
	return pfnERROR;
      if ((pfnNetApiBufferFree =
	   (PFN_NetApiBufferFree) getProcAddress ("netapi32",
						  "NetApiBufferFree")) ==
	  NULL)
	return pfnERROR;
      if ((pfnNetUserEnum =
	   (PFN_NetUserEnum) getProcAddress ("netapi32",
					     "NetUserEnum")) == NULL)
	return pfnERROR;
      if ((pfnNetLocalGroupEnum =
	   (PFN_NetLocalGroupEnum) getProcAddress ("netapi32",
						   "NetLocalGroupEnum")) ==
	  NULL)
	return pfnERROR;
      if ((pfnNetGetDCName =
	   (PFN_NetGetDCName) getProcAddress ("netapi32",
					      "NetGetDCName")) == NULL)
	return pfnERROR;
      if ((pfnNetUserGetInfo =
	   (PFN_NetUserGetInfo) getProcAddress ("netapi32",
						"NetUserGetInfo")) == NULL)
	return pfnERROR;

      /* ok if this one is not found */
      pfnDsGetDcName =
	(PFN_DsGetDcName) getProcAddress ("netapi32", "DsGetDcName");
      pfnERROR = FALSE;
    }
  return pfnERROR;
}

/**************************************************************************
 * uni2ansi
 **************************************************************************/
static void
uni2ansi (LPWSTR wcs, char *mbs, int size)
{
  if (wcs)
    WideCharToMultiByte (CP_ACP, 0, wcs, -1, mbs, size, NULL, NULL);
  else
    *mbs = '\0';
}

/**************************************************************************
 * currentUserIsLocalSystem
 *   returns 0 if yes
 **************************************************************************/
int
currentUserIsLocalSystem ()
{
  return currentUserMatchesAccountSID (1,
				       SECURITY_LOCAL_SYSTEM_RID,
				       0, 0, 0, 0, 0, 0, 0);
}

/**************************************************************************
 * currentUserIsMemberOfLocalAdministrators
 *   returns 0 if yes
 **************************************************************************/
int
currentUserIsMemberOfLocalAdministrators ()
{
  return currentUserIsMemberOfGroupSID (2,
					SECURITY_BUILTIN_DOMAIN_RID,
					DOMAIN_ALIAS_RID_ADMINS,
					0, 0, 0, 0, 0, 0);
}

/**************************************************************************
 * uidIsLocalSystem
 *   returns 0 if yes
 **************************************************************************/
int
uidIsLocalSystem (uid_t uid)
{
  return uidMatchesAccountSID (uid,
			       1,
			       SECURITY_LOCAL_SYSTEM_RID,
			       0, 0, 0, 0, 0, 0, 0);
}

/**************************************************************************
 * uidIsMemberOfLocalAdministrators
 *   returns 0 if yes
 **************************************************************************/
int
uidIsMemberOfLocalAdministrators (uid_t uid)
{
/* The following implementation would be nice, but 
   uidIsMemberOfGroupSID is not yet implemented. so
   instead, we use an alternate procedure...

  return uidIsMemberOfGroupSID(
      uid,
      2,
      SECURITY_BUILTIN_DOMAIN_RID,
      DOMAIN_ALIAS_RID_ADMINS,
      0, 0, 0, 0, 0, 0);
*/
  LPUSER_INFO_3 ui = NULL;
  int rv = -1;
  int rc;
  LPWSTR uni_servername = NULL;
  int matchedName;

  static const char *skipNames[] = {
    "LocalSystem",
    "SYSTEM",
    NULL
  };

  if (initPFN ())
    {
      return rv;
    }

  rc = getUserInfoForUID (uid,
			  skipNames,
			  3, (LPBYTE *) (&ui), &uni_servername, &matchedName);

  if (rc < 0)
    {
      goto cleanup_uidIsMemberOfLocalAdministrators;
    }
  if (rc == 1)
    {
      rv = 1;
      goto cleanup_uidIsMemberOfLocalAdministrators;
    }
  if (rc != 0)
    {
      syslog (LOG_ERR, "unknown error from getUserInfoForUID");
      goto cleanup_uidIsMemberOfLocalAdministrators;
    }

  rv = 1;
  if (ui->usri3_priv & USER_PRIV_ADMIN)
    {
      rv = 0;
    }

cleanup_uidIsMemberOfLocalAdministrators:
  if (ui)
    (*pfnNetApiBufferFree) ((PVOID) ui);
  if (uni_servername)
    HeapFree (GetProcessHeap (), 0, (LPVOID) uni_servername);
  return rv;
}

/**************************************************************************
 * currentUserMatchesAccountSID
 *   returns 0 if current user matches specified SID
 **************************************************************************/
int
currentUserMatchesAccountSID (BYTE nSubAuthorityCount,
			      DWORD dwSubAuthority0,
			      DWORD dwSubAuthority1,
			      DWORD dwSubAuthority2,
			      DWORD dwSubAuthority3,
			      DWORD dwSubAuthority4,
			      DWORD dwSubAuthority5,
			      DWORD dwSubAuthority6, DWORD dwSubAuthority7)
{
  SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
  PSID requestedSID = NULL;
  PSID userSID = NULL;
  HANDLE hToken = NULL;
  DWORD dwLength = 0;
  PTOKEN_USER ptu = NULL;
  int rv = -1;

  if (initPFN ())
    {
      return rv;
    }

  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
				       nSubAuthorityCount,
				       dwSubAuthority0,
				       dwSubAuthority1,
				       dwSubAuthority2,
				       dwSubAuthority3,
				       dwSubAuthority4,
				       dwSubAuthority5,
				       dwSubAuthority6,
				       dwSubAuthority7, &requestedSID))
    {
      syslog (LOG_ERR, "failed to create requested sid, error = %lu",
	      GetLastError ());
      return rv;
    }

  if (!(*pfnOpenProcessToken) (GetCurrentProcess (), TOKEN_READ, &hToken))
    {
      syslog (LOG_ERR, "failed to obtain process token, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserMatchesAccountSID;
    }

  /* first we get the required size */
  if (!(*pfnGetTokenInformation)
      (hToken, TokenUser, (LPVOID) ptu, 0, &dwLength))
    {
      if (GetLastError () != ERROR_INSUFFICIENT_BUFFER)
	{
	  syslog (LOG_ERR, "failed to obtain token information, error = %lu",
		  GetLastError ());
	  goto cleanup_currentUserMatchesAccountSID;
	}
      ptu = (PTOKEN_USER) LocalAlloc (LMEM_FIXED, dwLength);
      if (ptu == NULL)
	{
	  syslog (LOG_ERR,
		  "failed to allocate memory for token information, error = %lu",
		  GetLastError ());
	  goto cleanup_currentUserMatchesAccountSID;
	}
    }
  /* now populate with the actual token information */
  if (!(*pfnGetTokenInformation)
      (hToken, TokenUser, (LPVOID) ptu, dwLength, &dwLength))
    {
      syslog (LOG_ERR, "failed to obtain token information, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserMatchesAccountSID;
    }

  userSID = ptu->User.Sid;
  if (userSID == NULL)
    {
      syslog (LOG_ERR, "failed to obtain user SID, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserMatchesAccountSID;
    }

  /* EqualSid returns 0 if not equal. We return 0 if equal. */
  rv = ((*pfnEqualSid) (userSID, requestedSID) == 0);

cleanup_currentUserMatchesAccountSID:
  if (requestedSID)
    (*pfnFreeSid) (requestedSID);
  if (ptu)
    LocalFree (ptu);
  return rv;
}

/**************************************************************************
 * currentUserIsMemberOfGroupSID
 *   returns 0 if current user is a member of the specified group
 **************************************************************************/
int
currentUserIsMemberOfGroupSID (BYTE nSubAuthorityCount,
			       DWORD dwSubAuthority0,
			       DWORD dwSubAuthority1,
			       DWORD dwSubAuthority2,
			       DWORD dwSubAuthority3,
			       DWORD dwSubAuthority4,
			       DWORD dwSubAuthority5,
			       DWORD dwSubAuthority6, DWORD dwSubAuthority7)
{
  SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
  PSID requestedSID = NULL;
  PSID localAdministratorsSID = NULL;
  HANDLE hToken;
  DWORD dwLength = 0;
  PTOKEN_GROUPS ptg = NULL;
  int rv = -1;
  int i;

  if (initPFN ())
    {
      return rv;
    }

  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
				       nSubAuthorityCount,
				       dwSubAuthority0,
				       dwSubAuthority1,
				       dwSubAuthority2,
				       dwSubAuthority3,
				       dwSubAuthority4,
				       dwSubAuthority5,
				       dwSubAuthority6,
				       dwSubAuthority7, &requestedSID))
    {
      syslog (LOG_ERR, "failed to create requested sid, error = %lu",
	      GetLastError ());
      return rv;
    }

  /* need this to deal with Vista issues */
  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
				       2,
				       SECURITY_BUILTIN_DOMAIN_RID,
				       DOMAIN_ALIAS_RID_ADMINS,
				       0, 0, 0, 0, 0, 0,
				       &localAdministratorsSID))
    {
      syslog (LOG_ERR,
	      "failed to create local administrators group sid, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserIsMemberOfGroupSID;
    }


  if (!(*pfnOpenProcessToken) (GetCurrentProcess (), TOKEN_READ, &hToken))
    {
      syslog (LOG_ERR, "failed to obtain process token, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserIsMemberOfGroupSID;
    }

  /* first we get the required size */
  if (!(*pfnGetTokenInformation)
      (hToken, TokenGroups, (LPVOID) ptg, 0, &dwLength))
    {
      if (GetLastError () != ERROR_INSUFFICIENT_BUFFER)
	{
	  syslog (LOG_ERR, "failed to obtain token information, error = %lu",
		  GetLastError ());
	  goto cleanup_currentUserIsMemberOfGroupSID;
	}
      ptg = (PTOKEN_GROUPS) LocalAlloc (LMEM_FIXED, dwLength);
      if (ptg == NULL)
	{
	  syslog (LOG_ERR,
		  "failed to allocate memory for token information, error = %lu",
		  GetLastError ());
	  goto cleanup_currentUserIsMemberOfGroupSID;
	}
    }
  /* now populate with the actual token information */
  if (!(*pfnGetTokenInformation)
      (hToken, TokenGroups, (LPVOID) ptg, dwLength, &dwLength))
    {
      syslog (LOG_ERR, "failed to obtain token information, error = %lu",
	      GetLastError ());
      goto cleanup_currentUserIsMemberOfGroupSID;
    }

  rv =
    currentProcessTokenGroupsContainsGroupSID (requestedSID, ptg,
					       localAdministratorsSID);

cleanup_currentUserIsMemberOfGroupSID:
  if (ptg)
    LocalFree (ptg);
  if (localAdministratorsSID)
    (*pfnFreeSid) (localAdministratorsSID);
  if (requestedSID)
    (*pfnFreeSid) (requestedSID);
  return rv;
}

/**************************************************************************
 * uidMatchesAccountSID
 *   returns 0 if the user with the specified uid matches the specified SID
 **************************************************************************/
int
uidMatchesAccountSID (uid_t uid,
		      BYTE nSubAuthorityCount,
		      DWORD dwSubAuthority0,
		      DWORD dwSubAuthority1,
		      DWORD dwSubAuthority2,
		      DWORD dwSubAuthority3,
		      DWORD dwSubAuthority4,
		      DWORD dwSubAuthority5,
		      DWORD dwSubAuthority6, DWORD dwSubAuthority7)
{
  SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
  PSID requestedSID = NULL;
  PSID userSID = NULL;
  DWORD dwLength = 0;
  int rv = -1;

  if (initPFN ())
    {
      return rv;
    }

  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
				       nSubAuthorityCount,
				       dwSubAuthority0,
				       dwSubAuthority1,
				       dwSubAuthority2,
				       dwSubAuthority3,
				       dwSubAuthority4,
				       dwSubAuthority5,
				       dwSubAuthority6,
				       dwSubAuthority7, &requestedSID))
    {
      syslog (LOG_ERR, "failed to create requested sid, error = %lu",
	      GetLastError ());
      return rv;
    }

  if (getUserSIDForUID (uid, &userSID))
    {
      syslog (LOG_ERR, "could not obtain SID for uid %d", uid);
      goto cleanup_uidMatchesAccountSID;
    }

  /* EqualSid returns 0 if not equal. We return 0 if equal. */
  rv = ((*pfnEqualSid) (userSID, requestedSID) == 0);

cleanup_uidMatchesAccountSID:
  if (userSID)
    HeapFree (GetProcessHeap (), 0, (LPVOID) userSID);
  if (requestedSID)
    (*pfnFreeSid) (requestedSID);
  return rv;
}

/**************************************************************************
 * uidIsMemberOfGroupSID
 *   returns 0 if the user with the specified uid is a member of the
 *   specified group
 **************************************************************************/
int
uidIsMemberOfGroupSID (uid_t uid,
		       BYTE nSubAuthorityCount,
		       DWORD dwSubAuthority0,
		       DWORD dwSubAuthority1,
		       DWORD dwSubAuthority2,
		       DWORD dwSubAuthority3,
		       DWORD dwSubAuthority4,
		       DWORD dwSubAuthority5,
		       DWORD dwSubAuthority6, DWORD dwSubAuthority7)
{
  SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
  PSID requestedSID = NULL;
  PSID userSID = NULL;
  DWORD dwLength = 0;
  int rv = -1;

  if (initPFN ())
    {
      return rv;
    }

  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
				       nSubAuthorityCount,
				       dwSubAuthority0,
				       dwSubAuthority1,
				       dwSubAuthority2,
				       dwSubAuthority3,
				       dwSubAuthority4,
				       dwSubAuthority5,
				       dwSubAuthority6,
				       dwSubAuthority7, &requestedSID))
    {
      syslog (LOG_ERR, "failed to create requested sid, error = %lu",
	      GetLastError ());
      return rv;
    }

  if (getUserSIDForUID (uid, &userSID))
    {
      syslog (LOG_ERR, "could not obtain SID for uid %d", uid);
      goto cleanup_uidIsMemberOfGroupSID;
    }

  /* not yet implemented */
  rv = 1;

cleanup_uidIsMemberOfGroupSID:
  if (userSID)
    HeapFree (GetProcessHeap (), 0, (LPVOID) userSID);
  if (requestedSID)
    (*pfnFreeSid) (requestedSID);
  return rv;
}

/**************************************************************************
 * currentProcessTokenGroupsContainsGroupSID
 *   returns 0 if the ptg contains a SID that matches groupSID
 **************************************************************************/
static int
currentProcessTokenGroupsContainsGroupSID (PSID groupSID,
					   PTOKEN_GROUPS ptg,
					   PSID localAdminGroupSID)
{
  if (groupSID && ptg && localAdminGroupSID)
    {
      int rv = 1;
      int i;

      // Loop through the group SIDs looking for the requested SID.
      for (i = 0; i < ptg->GroupCount; i++)
	{
	  if ((*pfnEqualSid) (groupSID, ptg->Groups[i].Sid))
	    {
	      /* note: when requestedSid is the Administrators group,
	       *       there is a wrinkle on Vista. The SID may be
	       *       present, but in one of a few different states.
	       * (1) enabled. This is the normal case on NT/2k/XP
	       *     it also indicates an administrative user whose
	       *     privileges are currently elevated via UAC.
	       *     ptg->Groups[i].Attributes & SE_GROUP_ENABLED
	       * (2) deny-only. This is a Vista user who is a member
	       *     of the Administrators group, but whose privileges
	       *     are currently not elevated.
	       *     ptg->Groups[i].Attributes & SE_GROUP_USE_FOR_DENY_ONLY
	       * (3) disabled: not really a member of the administrators
	       *     group.
	       */
	      if ((*pfnEqualSid) (groupSID, localAdminGroupSID))
		{
		  /* handle Vista issues */
		  if ((ptg->Groups[i].Attributes & SE_GROUP_ENABLED) ||
		      (ptg->Groups[i].
		       Attributes & SE_GROUP_USE_FOR_DENY_ONLY))
		    {
		      /* yes, we have valid (but possibly unelevated) membership */
		      rv = 0;
		      break;
		    }
		  else
		    {
		      /* no, we are not really a member of local administrators */
		      /* stop looking, rv is already = 1 */
		      break;
		    }
		}
	      else
		{
		  rv = 0;
		  break;
		}
	    }			/* EqualSid */
	}			/* while */
      return rv;
    }				/* all parameters are non-null */
  return -1;
}

/**************************************************************************
 * getUserSIDForUID
 *   returns 0 on success, nonzero otherwise
 *   caller must free userSID using HeapFree(GetCurrentHeap(), 0, userSid)
 **************************************************************************/
int
getUserSIDForUID (uid_t uid, PSID * userSID)
{
  DWORD dwLength = 0;
  LPUSER_INFO_3 ui = NULL;
  int rv = -1;
  int rc;
  LPWSTR uni_servername = NULL;
  int matchedName;
  char servername[256];

  static const char *skipNames[] = {
    "LocalSystem",
    "SYSTEM",
    NULL
  };

  if (initPFN ())
    {
      return rv;
    }

  rc = getUserInfoForUID (uid,
			  skipNames,
			  3, (LPBYTE *) (&ui), &uni_servername, &matchedName);
  if (rc < 0)
    {
      goto error_getUserSIDForUID;
    }
  if (rc == 1)
    {
      PSID tmpSID = NULL;
      SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;

      /* unfortunately, NetUserGetInfo fails for LocalSystem. So, as a
         special case, check for that and create the SID manually.
         Not sure what other accounts fail.  Probably LocalService
         and NetworkService, among others. */
      if ((matchedName == 0) ||	/* "LocalSystem" */
	  (matchedName == 1))	/* "SYSTEM" */
	{
	  if (!(*pfnAllocateAndInitializeSid) (&nt_auth,
					       1,
					       SECURITY_LOCAL_SYSTEM_RID,
					       0, 0, 0, 0, 0, 0, 0, &tmpSID))
	    {
	      syslog (LOG_ERR,
		      "failed to create user sid (LocalSystem), error = %lu",
		      GetLastError ());
	      goto error_getUserSIDForUID;
	    }
	}
      else
	{
	  syslog (LOG_ERR,
		  "internal: don't know how to create standard SID for %s",
		  skipNames[matchedName]);
	  goto error_getUserSIDForUID;
	}
      /* Because SIDs created via AllocateAndInitializeSid are freed using
         FreeSid, but we explicitly declare our SIDs must be freed using
         HeapFree, we have to make a copy */
      dwLength = (*pfnGetLengthSid) (tmpSID);
      *userSID =
	(PSID) HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, dwLength);
      if (*userSID == NULL)
	{
	  syslog (LOG_ERR, "unable to allocate memory for user sid");
	  goto error_getUserSIDForUID;
	}
      if (!(*pfnCopySid) (dwLength, *userSID, tmpSID))
	{
	  syslog (LOG_ERR, "unable to copy user sid");
	  goto error_getUserSIDForUID;
	}
      (*pfnFreeSid) (tmpSID);

      /* success! */
      rv = 0;
      goto cleanup_getUserSIDForUID;
    }
  if (rc != 0)
    {
      syslog (LOG_ERR, "unknown error from getUserInfoForUID");
      goto error_getUserSIDForUID;
    }

  if (uni_servername)
    uni2ansi (uni_servername, servername, sizeof (servername));

  /* a local block for scoped variables */
  {
    char ansi_username[100];
    char ansi_domainname[100];
    DWORD ansi_domainname_len = 100;
    char psid_buffer[SECURITY_MAX_SID_SIZE];
    PSID psid = (PSID) psid_buffer;
    DWORD sid_len = SECURITY_MAX_SID_SIZE;
    SID_NAME_USE acc_type;

    uni2ansi (ui->usri3_name, ansi_username, sizeof (ansi_username));

    if (!(*pfnLookupAccountName) (uni_servername ? servername : NULL,
				  ansi_username,
				  psid, &sid_len,
				  ansi_domainname, &ansi_domainname_len,
				  &acc_type))
      {
	if (uni_servername)
	  {
	    syslog (LOG_ERR,
		    "unable to obtain user sid for %s on domain controller %s\n",
		    ansi_username, servername);
	  }
	else
	  {
	    syslog (LOG_ERR,
		    "unable to obtain user sid for %s on localhost\n",
		    ansi_username);
	  }
	goto error_getUserSIDForUID;
      }
    else if (acc_type == SidTypeDomain)
      {
	char ansi_extended_username[356];
	strcpy (ansi_extended_username, ansi_domainname);
	strcat (ansi_extended_username, "\\");
	strcat (ansi_extended_username, ansi_username);
	sid_len = SECURITY_MAX_SID_SIZE;
	ansi_domainname_len = 100;
	if (!(*pfnLookupAccountName) (uni_servername ? servername : NULL,
				      ansi_extended_username,
				      psid, &sid_len,
				      ansi_domainname, &ansi_domainname_len,
				      &acc_type))
	  {
	    if (uni_servername)
	      {
		syslog (LOG_ERR,
			"unable to obtain user sid for %s on domain controller %s\n",
			ansi_extended_username, servername);
	      }
	    else
	      {
		syslog (LOG_ERR,
			"unable to obtain user sid for %s on localhost\n",
			ansi_extended_username);
	      }
	    goto error_getUserSIDForUID;
	  }
      }
    /* we got the SID! now, make a dynamically allocated copy
       of it before we leave this block */
    dwLength = (*pfnGetLengthSid) (psid);
    *userSID =
      (PSID) HeapAlloc (GetProcessHeap (), HEAP_ZERO_MEMORY, dwLength);
    if (*userSID == NULL)
      {
	syslog (LOG_ERR, "unable to allocate memory for user sid");
	goto error_getUserSIDForUID;
      }
    if (!(*pfnCopySid) (dwLength, *userSID, psid))
      {
	syslog (LOG_ERR, "unable to copy user sid");
	goto error_getUserSIDForUID;
      }
  }				/* lexical block */

  rv = 0;
  goto cleanup_getUserSIDForUID;

error_getUserSIDForUID:
  if (*userSID)
    {
      HeapFree (GetProcessHeap (), 0, (LPVOID) (*userSID));
      *userSID = NULL;
    }

cleanup_getUserSIDForUID:
  if (ui)
    (*pfnNetApiBufferFree) ((PVOID) ui);
  if (uni_servername)
    HeapFree (GetProcessHeap (), 0, (LPVOID) uni_servername);
  return rv;
}


/**
 * helper method for getUserSIDForUID
 * returns negative on error
 *
 * 0 on success
 *   bufptr is populated with the requested user info
 *   uni_servername is populated with the associated server,
 *      if applicable (or null)
 *   it is the caller's responsibility to free both:
 *      (*pfnNetApiBufferFree)((PVOID)bufptr);
 *      HeapFree(GetProcessHeap(), 0, (LPVOID)uni_servername)
 *
 * 1 if account name corresponding to uid matches *any*
 *   element in the skipNames list, which is a null-terminated
 *      argv-style array of *local* user names.  In this case,
 *      the index of the matched name is stored in *matchedName,
 *      and the UserInfo buffer is NOT populated.
 *
 * NB: name comparison is performed in a case-insensitive manner
 * NB: To match the LocalSystem account, include both
 *     "LocalSystem" and "SYSTEM" in skipNames).
 */
/**************************************************************************
 * getUserInfoForUID
 *   returns 0 on success, -1 on error
 *   returns 1 if short-circuit by name
 *   caller must free:
 *      (*pfnNetApiBufferFree)((PVOID)bufptr);
 *      HeapFree(GetProcessHeap(), 0, (LPVOID)uni_servername)
 **************************************************************************/
static int
getUserInfoForUID (uid_t uid,
		   const char **skipNames,
		   DWORD level,
		   LPBYTE * bufptr, LPWSTR * uni_servername, int *matchedName)
{
  DWORD dwLength = 0;
  int rv = -1;
  struct passwd *pw;
  char name[UNLEN + 1];
  WCHAR uni_name[2 * (UNLEN + 1)];
  char domain[INTERNET_MAX_HOST_NAME_LENGTH + 1];
  WCHAR uni_domain[2 * (INTERNET_MAX_HOST_NAME_LENGTH + 1)];
  char servername[256];

  if (initPFN ())
    {
      return rv;
    }
  if (!uni_servername)
    {
      syslog (LOG_ERR, "called with null parameter: uni_servername");
      return rv;
    }
  if (!bufptr)
    {
      syslog (LOG_ERR, "called with null parameter: bufptr");
      return rv;
    }
  *uni_servername = NULL;
  *bufptr = NULL;

  pw = getpwuid (uid);
  if (!pw)
    {
      syslog (LOG_ERR, "unable to obtain passwd struct for uid %d", uid);
      goto error_getUserInfoForUID;
    }
  cygwin_internal (CW_EXTRACT_DOMAIN_AND_USER, pw, domain, name);

  /* check for name match */
  if (skipNames && *skipNames && matchedName)
    {
      int foundMatch = 0;
      const char **p = skipNames;
      int c = 0;
      size_t namelen = strlen (name);
      while (*p && !foundMatch)
	{
	  if (strncasecmp (name, *p, namelen) == 0)
	    {
	      foundMatch = 1;
	      break;
	    }
	  p++;
	  c++;
	}
      if (foundMatch)
	{
	  *matchedName = c;
	  rv = 1;
	  return rv;
	}
      /* not found, so reset matchedName */
      *matchedName = -1;
    }

  MultiByteToWideChar (CP_ACP, 0, name, -1, uni_name, 2 * (UNLEN + 1));

  /* create a block we can break out of */
  do
    {
      int rc, len;
      LPWSTR tmp_uni_servername = NULL;
      PDOMAIN_CONTROLLER_INFOW pdci = NULL;
      if (pfnDsGetDcName)
	{
	  rc = (*pfnDsGetDcName) (NULL, NULL, NULL, NULL, 0, &pdci);
	  if (rc != ERROR_SUCCESS)
	    {
	      /* not a member of a domain */
	      break;
	    }
	  tmp_uni_servername = pdci->DomainControllerName;
	}
      else
	{
	  rc = (*pfnNetGetDCName) (NULL, NULL, (void *) &tmp_uni_servername);
	  if (rc != ERROR_SUCCESS)
	    {
	      /* not a member of a domain */
	      break;
	    }
	}

      /* save the value of servername */
      len = lstrlenW (tmp_uni_servername);
      if (len)
	{
	  *uni_servername =
	    (LPWSTR) HeapAlloc (GetProcessHeap (), 0,
				(len + 1) * sizeof (WCHAR));
	  if (*uni_servername)
	    lstrcpyW (*uni_servername, tmp_uni_servername);
	}

      /* clean up */
      (*pfnNetApiBufferFree) (pdci ? (PVOID) pdci : (PVOID)
			      tmp_uni_servername);
    }
  while (0);

  if (*uni_servername)
    uni2ansi (*uni_servername, servername, sizeof (servername));

  /* now, look for user that corresponds to uid (e.g. uni_name) */
  if ((*pfnNetUserGetInfo)
      (*uni_servername, (LPWSTR) & uni_name, level, bufptr) != NERR_Success)
    {
      if (*uni_servername)
	{
	  /* if we had a domain, then try again locally. if we didn't have a
	     domain, then the initial call WAS local */
	  if ((*pfnNetUserGetInfo)
	      (*uni_servername, (LPWSTR) & uni_name, level,
	       bufptr) != NERR_Success)
	    {
	      syslog (LOG_ERR,
		      "unable to obtain user info for %s [tried domain controller %s and localhost]\n",
		      name, servername);
	      goto error_getUserInfoForUID;
	    }
	  /* else ok */
	}
      else
	{
	  syslog (LOG_ERR, "unable to obtain user info for %s on localhost\n",
		  name);
	  goto error_getUserInfoForUID;
	}
    }
  if (*bufptr == NULL)
    {
      syslog (LOG_ERR, "unable to obtain user info for %s\n", name);
      goto error_getUserInfoForUID;
    }
  rv = 0;
  return rv;

error_getUserInfoForUID:
  if (*bufptr)
    {
      (*pfnNetApiBufferFree) ((PVOID) (*bufptr));
      *bufptr = NULL;
    }
  if (*uni_servername)
    {
      HeapFree (GetProcessHeap (), 0, (LPVOID) (*uni_servername));
      *uni_servername = NULL;
    }
  return rv;
}

#endif /* __CYGWIN__ */
