/***************************************************************************
* editrights version 1.04: 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.
****************************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <strings.h>
#include <wchar.h>
#include <locale.h>
#include <pwd.h>
#include <grp.h>
#include <windows.h>
#include <sddl.h>
#include <ntsecapi.h>
#include <ntstatus.h>

#define RETURN_OK 0
#define RETURN_ERROR 1
#define RETURN_YES 0
#define RETURN_NO 2

/* Macros */
#define NTCHECKERROR(StatusWanted,Function,API,retval) if(retval!=StatusWanted) { \
		printf("Error in " Function " (" API " returned 0x%x=",retval); \
		printNTSTATUS(retval); \
		printf(")!\n\n"); \
		exit(RETURN_ERROR); }

#define makeLSAStringA(d,s) \
  { \
    size_t c = (s) ? mbstowcs (NULL, (s), 0) : 1; \
    (d)->MaximumLength = (c + 1) * sizeof (WCHAR); \
    (d)->Buffer = (PWSTR) alloca ((d)->MaximumLength); \
    (d)->Length = mbstowcs ((d)->Buffer, s ?: "", c + 1) * sizeof (WCHAR); \
  }

/* Prototypes */
void addUserRights (LSA_HANDLE hLSA, const char *strUser, PSID AccountSid,
		    char **strRightsToAdd, ULONG intRightsToAddCount);

LSA_HANDLE openPolicy (const char *strMachine, ACCESS_MASK access);
void printNTSTATUS (NTSTATUS err);
void printCopyright ();
void printUsage ();
void printUserRights (LSA_HANDLE hLSA, const char *strUser, PSID Sid);
void removeUserRights (LSA_HANDLE hLSA, const char *strUser, PSID AccountSid,
		       char **strRightsToRemove, ULONG intRightsToRemoveCount);
int testUserRights (LSA_HANDLE hLSA, const char *strUser, PSID Sid,
		    char **strRightsToTest, ULONG intRightsToTestCount);

int optQuiet = 1;
int optListRights = 0;

int
main (int argc, char **argv)
{
  /* Options */
  char *optMachine = NULL;
  char *optUser = NULL;
  int optShowHelp = 0;
  struct passwd *pw;
  struct group *gr;
  char *sid_str = NULL;
  PSID sid = NULL;

  /* variables for getopt logic */
  int c;

  /* main variables */
  LSA_HANDLE hLSA;
  char **strRightsToAdd;
  char **strRightsToRemove;
  char **strRightsToTest;
  int intRightsToAddCount;
  int intRightsToRemoveCount;
  int intRightsToTestCount;

  setlocale (LC_ALL, "");

  opterr = 0;
  /* Allocate enough storage for all parameters to be rights */
  strRightsToAdd = alloca (sizeof (void *) * argc);
  strRightsToRemove = alloca (sizeof (void *) * argc);
  strRightsToTest = alloca (sizeof (void *) * argc);
  intRightsToAddCount = 0;
  intRightsToRemoveCount = 0;
  intRightsToTestCount = 0;

  while ((c = getopt (argc, argv, "a:m:vr:u:lht:")) != -1)
    switch (c)
      {
      case 'a':		/* add right */
	strRightsToAdd[intRightsToAddCount++] = optarg;
	break;
      case 'm':		/* machine name */
	optMachine = optarg;
	break;
      case 'r':		/* remove right */
	strRightsToRemove[intRightsToRemoveCount++] = optarg;
	break;
      case 't':		/* test for right */
	strRightsToTest[intRightsToTestCount++] = optarg;
	break;
      case 'u':		/* user */
	optUser = optarg;
	break;
      case 'v':
	optQuiet = 0;
	break;
      case 'l':
	optListRights = 1;
	break;
      case 'h':
	optShowHelp = 1;
	break;
      case '?':
	if (isprint (optopt))
	  fprintf (stderr, "Unknown option `-%c'.\n", optopt);
	else
	  fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
	printUsage ();
	exit (RETURN_ERROR);
      default:
	abort ();
      }

  if (optShowHelp)
    {
      printUsage ();
      exit (RETURN_OK);
    }

  /* User must be specified */
  if (!optUser || *optUser == '\0')
    {
      printUsage ();
      exit (RETURN_ERROR);
    }

  /* Must have some work to do */
  if (intRightsToRemoveCount == 0 && intRightsToAddCount == 0
      && intRightsToTestCount == 0 && !optListRights)
    {
      printf ("Error: nothing to do!\n");
      exit (RETURN_ERROR);
    }

  /* Convert Cygwin username to windows username. */
  if ((pw = getpwnam (optUser)) != NULL)
    {
      if ((sid_str = strstr (pw->pw_gecos, ",S-1-")) != NULL
	  && (sid_str = strdup (sid_str + 1)) != NULL)
	{
	  size_t len = strspn (sid_str + 1, "0123456789-");
	  sid_str[len + 1] = '\0';
	}
    }
  else if ((gr = getgrnam (optUser)) != NULL)
    sid_str = gr->gr_passwd;
  else
    {
      WCHAR wuser[strlen (optUser) * 2 + 1];
      DWORD sid_size = 128, dom_size = 256;
      WCHAR dom[dom_size];
      SID_NAME_USE use;
      PSID NewSid = (PSID) LocalAlloc (0, sid_size);

      mbstowcs (wuser, optUser, sizeof wuser / sizeof (WCHAR));
      if (!LookupAccountNameW (NULL, wuser, NewSid, &sid_size,
			       dom, &dom_size, &use)
	  || (use != SidTypeUser && use != SidTypeGroup
	      && use != SidTypeAlias && use != SidTypeWellKnownGroup))
	{
	  printf ("No user or group '%s' known.\n", optUser);
	  exit (RETURN_ERROR);
	}
      sid = NewSid;
    }
  if (sid_str && *sid_str && !sid)
    ConvertStringSidToSidA (sid_str, &sid);
  if (!sid)
    {
      printf ("Can't fetch SID for user account '%s'\n", optUser);
      exit (RETURN_ERROR);
    }

  if (!optQuiet)
    printCopyright ();

  hLSA = openPolicy (optMachine, POLICY_ALL_ACCESS);

  /* Testing for rights. Return YES if ALL the rights are granted. */
  if (intRightsToTestCount)
    {
      if (intRightsToRemoveCount || intRightsToAddCount)
	{
	  printf ("Error: you cannot add or remove rights while testing!\n");
	  exit (RETURN_ERROR);
	}

      if (testUserRights
	  (hLSA, optUser, sid, strRightsToTest, intRightsToTestCount))
	{
	  if (!optQuiet)
	    printf ("All rights found!\n");
	  exit (RETURN_YES);
	}
      else
	{
	  if (!optQuiet)
	    printf ("Some or all rights not found!\n");
	  exit (RETURN_NO);
	}
    }

  if (intRightsToRemoveCount)
    removeUserRights (hLSA, optUser, sid, strRightsToRemove,
		      intRightsToRemoveCount);
  if (intRightsToAddCount)
    addUserRights (hLSA, optUser, sid, strRightsToAdd, intRightsToAddCount);

  if (optListRights)
    printUserRights (hLSA, optUser, sid);

  LsaClose (hLSA);

  if (!optQuiet)
    {
      printf ("\nDone!\n");
    }

  return RETURN_OK;
}

/* 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;

  makeLSAStringA (&machine, strMachine);

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

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

void
printCopyright ()
{
  printf
    ("editrights version 1.04: a cygwin application to edit user rights\n                         on a Windows NT system.\n");
  printf
    ("Copyright Chris Rodgers <editrights-at-bulk.rodgers.org.uk>, Sep, 2003.\nAll rights reserved. See LICENCE for further details.\n\n");
}

void
printUsage ()
{
  printCopyright ();
  printf ("Usage: editrights -u USER {-a|-l|-r|-t} [options]\n");
  printf ("  -a Se...         Add right to the specified user.\n");
  printf
    ("  -h               Show this help message. -hv lists available user rights.\n");
  printf
    ("  -l               List user rights. May be combined with -a or\n                   -r to list final state.\n");
  printf ("  -m MACHINE       Make all changes on the specified MACHINE.\n");
  printf ("  -r Se...         Remove right from the specified user.\n");
  printf
    ("  -t Se...         Test if the specified right is held by user. Returns\n0 for YES and 2 for NO.\n");
  printf
    ("  -u USER/GROUP    Make changes to the specified USER or GROUP.\n");
  printf ("  -v               Verbose mode.\n\n");
  printf ("Return values:\n");
  printf ("   0               Success or YES.\n");
  printf ("   1               Error.\n");
  printf ("   2               NO.\n");
  if (optQuiet)
    return;
  printf ("\nAvailable user rights include:\n");
  printf ("SeAssignPrimaryTokenPrivilege\n");
  printf ("SeAuditPrivilege\n");
  printf ("SeBackupPrivilege\n");
  printf ("SeBatchLogonRight\n");
  printf ("SeChangeNotifyPrivilege\n");
  printf ("SeCreateGlobalPrivilege\n");
  printf ("SeCreatePagefilePrivilege\n");
  printf ("SeCreatePermanentPrivilege\n");
  printf ("SeCreateSymbolicLinkPrivilege\n");
  printf ("SeCreateTokenPrivilege\n");
  printf ("SeDebugPrivilege\n");
  printf ("SeDenyBatchLogonRight\n");
  printf ("SeDenyInteractiveLogonRight\n");
  printf ("SeDenyNetworkLogonRight\n");
  printf ("SeDenyRemoteInteractiveLogonRight\n");
  printf ("SeDenyServiceLogonRight\n");
  printf ("SeEnableDelegationPrivilege\n");
  printf ("SeImpersonatePrivilege\n");
  printf ("SeIncreaseBasePriorityPrivilege\n");
  printf ("SeIncreaseQuotaPrivilege\n");
  printf ("SeIncreaseWorkingSetPrivilege\n");
  printf ("SeInteractiveLogonRight\n");
  printf ("SeLoadDriverPrivilege\n");
  printf ("SeLockMemoryPrivilege\n");
  printf ("SeMachineAccountPrivilege\n");
  printf ("SeManageVolumePrivilege\n");
  printf ("SeNetworkLogonRight\n");
  printf ("SeProfileSingleProcessPrivilege\n");
  printf ("SeRelabelPrivilege\n");
  printf ("SeRemoteInteractiveLogonRight\n");
  printf ("SeRemoteShutdownPrivilege\n");
  printf ("SeRestorePrivilege\n");
  printf ("SeSecurityPrivilege\n");
  printf ("SeServiceLogonRight\n");
  printf ("SeShutdownPrivilege\n");
  printf ("SeSyncAgentPrivilege\n");
  printf ("SeSystemEnvironmentPrivilege\n");
  printf ("SeSystemProfilePrivilege\n");
  printf ("SeSystemtimePrivilege\n");
  printf ("SeTakeOwnershipPrivilege\n");
  printf ("SeTcbPrivilege\n");
  printf ("SeTimeZonePrivilege\n");
  printf ("SeUndockPrivilege\n");
  printf ("SeUnsolicitedInputPrivilege\n");
}

void
printUserRights (LSA_HANDLE hLSA, const char *strUser, PSID Sid)
{
  /* Print out a list of rights assigned to the specified user */
  PLSA_UNICODE_STRING UserRights;
  ULONG CountOfRights;
  NTSTATUS status;
  int c;

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

  NTCHECKERROR (STATUS_SUCCESS && status != STATUS_OBJECT_NAME_NOT_FOUND,
		"printUserRights", "LsaEnumerateAccountRights", status);

  if (!optQuiet)
    {
      printf ("Listing rights for %s:\n", strUser);
    }

  for (c = 0; c < CountOfRights; c++)
    {
      const wchar_t *src = UserRights[c].Buffer;
      size_t len = wcslen (src);
      mbstate_t mbst = { 0 };
      /* allocate 1 byte per character + 1 byte for null terminator. */
      char *dst = alloca (len + 1);
      dst[wcsrtombs (dst, &src, len, &mbst)] = '\0';
      printf ("%s\n", dst);
    }
}

int
testUserRights (LSA_HANDLE hLSA, const char *strUser, PSID Sid,
		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;

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

  NTCHECKERROR (STATUS_SUCCESS && status != STATUS_OBJECT_NAME_NOT_FOUND,
		"testUserRights", "LsaEnumerateAccountRights", status);

  if (!optQuiet)
    {
      printf ("Testing rights for %s:\n", strUser);
    }

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

/* Added for debugging purposes. */
typedef struct
{
  BYTE Revision;
  BYTE SubAuthorityCount;
  SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
  DWORD SubAuthority[8];
} DBGSID, *PDBGSID;

volatile PDBGSID dbgsid;

void
addUserRights (LSA_HANDLE hLSA, const char *strUser, PSID AccountSid,
	       char **strRightsToAdd, ULONG intRightsToAddCount)
{
  NTSTATUS status;
  PLSA_UNICODE_STRING UserRights;
  int idx;

  UserRights = alloca (sizeof (LSA_UNICODE_STRING) * intRightsToAddCount);

  for (idx = 0; idx < intRightsToAddCount; idx++)
    makeLSAStringA (&UserRights[idx], strRightsToAdd[idx]);

  status = LsaAddAccountRights (hLSA,
				AccountSid, UserRights, intRightsToAddCount);

  NTCHECKERROR (STATUS_SUCCESS, "addUserRights", "LsaAddAccountRights",
		status);
  if (!optQuiet)
    {
      printf ("Added rights OK.\n\n");
    }
}

void
removeUserRights (LSA_HANDLE hLSA, const char *strUser, PSID AccountSid,
		  char **strRightsToRemove, ULONG intRightsToRemoveCount)
{
  NTSTATUS status;
  PLSA_UNICODE_STRING UserRights;
  int idx;

  UserRights = alloca (sizeof (LSA_UNICODE_STRING) * intRightsToRemoveCount);

  for (idx = 0; idx < intRightsToRemoveCount; idx++)
    makeLSAStringA (&UserRights[idx], strRightsToRemove[idx]);

  status = LsaRemoveAccountRights (hLSA,
				   AccountSid,
				   FALSE, UserRights, intRightsToRemoveCount);

  NTCHECKERROR (STATUS_SUCCESS, "removeUserRights", "LsaRemoveAccountRights",
		status);
  if (!optQuiet)
    {
      printf ("Removed rights OK.\n\n");
    }
}
