/***************************************************************************
* winpriv.c -- adapted from editrights/main.c. Original
* copyright notice and license text follows.
*
* ==========================================================================
* editrights version 1.01: a cygwin application to edit user
*                          rights on a windows NT system
* 
* Copyright (c) 2003, Chris Rodgers <editrights-at-bulk.rodgers.org.uk>
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
* 
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 
* The name of Chris Rodgers may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY CHRIS RODGERS "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL CHRIS RODGERS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ==========================================================================
*
****************************************************************************/

/* Copyright (C) 2007 Jari Aalto; Licenced under GPL v2 or later */

#include <unistd.h>
#include <pwd.h>
#include <sys/cygwin.h>
#include <syslog.h>
#include <wchar.h>
#include <windows.h>
#include <wininet.h>
#include <lmcons.h>
#include <ntsecapi.h>
#include <ntstatus.h>

/* Macros */
#define NTCHECKERROR(StatusWanted,LogLevel,Function,API,retval) if(retval!=StatusWanted) { \
		syslog(LogLevel,"Error in " Function " (" API " returned 0x%x)",retval); \
                NTCHECKERROR_FAILED = 1; }

/* Prototypes */
PSID getSID (LSA_HANDLE hLSA, const char *strUser);
PLSA_UNICODE_STRING makeLSAStringA (PLSA_UNICODE_STRING strLSA,
				    const char *strIn, size_t len);
LSA_HANDLE openPolicy (const char *strMachine, ACCESS_MASK access);
int testUserRights (LSA_HANDLE hLSA, const char *strUser,
		    const char **strRightsToTest, ULONG intRightsToTestCount);
int testUserRightsByUID (uid_t uid, const char **strRightsToTest,
			 ULONG intRightsToTestCount);

/* like testUserRights, returns 0 if uid does NOT have all of the required privileged,
   and returns non-zero if it DOES. */
int
testUserRightsByUID (uid_t uid, const char **strRightsToTest,
		     ULONG intRightsToTestCount)
{
  int retVal = 0;
  LSA_HANDLE hLSA;
  struct passwd *pw;
  char strUser[UNLEN + 1];
  char *domain = (char *) alloca (INTERNET_MAX_HOST_NAME_LENGTH + 1);

  pw = getpwuid (uid);
  if (!pw)
    return retVal;
  cygwin_internal (CW_EXTRACT_DOMAIN_AND_USER, pw, domain, strUser);

  hLSA = openPolicy (NULL, POLICY_ALL_ACCESS);	/* always this machine */
  if (hLSA)
    {
      retVal =
	testUserRights (hLSA, strUser, strRightsToTest, intRightsToTestCount);
      LsaClose (hLSA);
    }
  return retVal;
}

/* Set an LSA_UNICODE_STRING based on an ascii string. *
 * strLSA->Buffer must point to allocated storage of   *
 * size (len+1) * sizeof(WCHAR).                       */
PLSA_UNICODE_STRING
makeLSAStringA (PLSA_UNICODE_STRING strLSA, const char *strIn, size_t len)
{
  if (!strIn)			/* Fed NULL pointer */
    {
      strLSA->Buffer[0] = 0;	/* unicode NULL char */
      strLSA->Length = 0;
      strLSA->MaximumLength = 0;
    }
  else
    {
      mbstate_t mbst = { 0 };	/* multibyte char initial state. */
      mbsrtowcs (strLSA->Buffer, &strIn, len, &mbst);
      strLSA->Buffer[len] = 0;	/* NULL terminate. */
      strLSA->Length = wcslen (strLSA->Buffer) * sizeof (WCHAR);
      strLSA->MaximumLength = strLSA->Length + sizeof (WCHAR);
    }
  return strLSA;
}

/* Open a connection to the Local Security Authority *
 * Policy object on the specified system.            */
LSA_HANDLE
openPolicy (const char *strMachine, ACCESS_MASK access)
{
  LSA_OBJECT_ATTRIBUTES objattr;
  LSA_HANDLE hLSA;
  NTSTATUS status;
  LSA_UNICODE_STRING machine;
  int c;
  int NTCHECKERROR_FAILED __attribute__ ((__unused__)) = 0;

  c = strMachine ? strlen (strMachine) : 0;
  machine.Buffer = (PWSTR) alloca ((c + 1) * 2);	/* Keep within this function so we *
							 * can use alloca(...)             */
  makeLSAStringA (&machine, strMachine, c);

  objattr.Length = sizeof (objattr);	/* Reserved structure */
  objattr.RootDirectory = NULL;
  objattr.ObjectName = NULL;
  objattr.Attributes = 0;
  objattr.SecurityDescriptor = NULL;
  objattr.SecurityQualityOfService = NULL;

  status = LsaOpenPolicy (machine.Length ? &machine : NULL,	/* If no machine, use localhost. */
			  &objattr, access, &hLSA);
  NTCHECKERROR (STATUS_SUCCESS, LOG_WARNING, "openPolicy", "LsaOpenPolicy",
		status) return hLSA;
}

int
testUserRights (LSA_HANDLE hLSA, const char *strUser,
		const char **strRightsToTest, ULONG intRightsToTestCount)
{
  /* Test if the user has the specified rights. Returns zero if they don't all match, non-zero if they do all match. */
  PLSA_UNICODE_STRING UserRights;
  ULONG CountOfRights;
  NTSTATUS status;
  int c, d;
  int intMatched = 0;
  int NTCHECKERROR_FAILED = 0;

  PSID Sid = getSID (hLSA, strUser);
  if (!Sid)
    {
      return 0;
    }

  status = LsaEnumerateAccountRights (hLSA, Sid, &UserRights, &CountOfRights);

  LocalFree (Sid);

  NTCHECKERROR (STATUS_SUCCESS
		&& status != STATUS_OBJECT_NAME_NOT_FOUND, LOG_WARNING,
		"testUserRights", "LsaEnumerateAccountRights", status);
  if (NTCHECKERROR_FAILED)
    {
      return 0;
    }

  for (c = 0; c < CountOfRights; c++)
    {
      const wchar_t *src = UserRights[c].Buffer;
      size_t len = wcslen (src);
      mbstate_t mbst = { 0 };	/* multibyte char initial state. */
      char *rightname = alloca (len + 1);	/* allocate 1 byte per character + 1 byte for null terminator. */
      rightname[wcsrtombs (rightname, &src, len, &mbst)] = '\0';
      for (d = 0; d < intRightsToTestCount; d++)
	{
	  if (!strcasecmp (rightname, strRightsToTest[d]))
	    intMatched++;
	}
    }
  return (intMatched == intRightsToTestCount);
}

/* Warning: The caller must call LocalFree() on the returned PSID */
PSID
getSID (LSA_HANDLE hLSA, const char *strUser)
{
  LSA_UNICODE_STRING lsastrUser;
  PLSA_REFERENCED_DOMAIN_LIST referencedDomains;
  PLSA_TRANSLATED_SID sids;
  int c;
  ULONG cSubAuth, NewSidLength;
  PSID NewSid = NULL;
  NTSTATUS status;
  int NTCHECKERROR_FAILED = 0;

  c = strUser ? strlen (strUser) : 0;
  lsastrUser.Buffer = (PWSTR) alloca ((c + 1) * 2);	/* Keep within this function so we *
							 * can use alloca()                */
  makeLSAStringA (&lsastrUser, strUser, c);

  status = LsaLookupNames (hLSA, 1, &lsastrUser, &referencedDomains, &sids);
  NTCHECKERROR (STATUS_SUCCESS, LOG_WARNING, "getSID", "LsaLookupNames",
		status);
  if (NTCHECKERROR_FAILED)
    {
      if (referencedDomains)
	{
	  LsaFreeMemory (referencedDomains);
	}
      if (sids)
	{
	  LsaFreeMemory (sids);
	}
      return NULL;
    }

  cSubAuth =
    *GetSidSubAuthorityCount (referencedDomains->Domains[sids->DomainIndex].
			      Sid);
  NewSidLength = GetSidLengthRequired ((UCHAR) (cSubAuth + 1));
  NewSid = (PSID) LocalAlloc (0, NewSidLength);
  CopySid (NewSidLength, NewSid,
	   referencedDomains->Domains[sids->DomainIndex].Sid);
  *GetSidSubAuthorityCount (NewSid) = (UCHAR) cSubAuth + 1;
  *GetSidSubAuthority (NewSid, cSubAuth) = sids->RelativeId;

  LsaFreeMemory (referencedDomains);
  LsaFreeMemory (sids);

  return NewSid;
}
