//
// $Id: AccountPrivileges.cpp,v 1.1 2004/06/01 17:05:20 bakerj Exp $
//
//************************** Property of the MITRE Corporation ***************************//
//
// Copyright (c) 2003 - The MITRE Corporation
//
// This file is part of the OVAL Query Interpreter.
//
// The OVAL Query Interpreter is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License.
//
// The OVAL Query Interpreter is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with the OVAL
// Query Interpreter; if not, write to the Free Software Foundation, Inc., 59 Temple
// Place, Suite 330, Boston, MA 02111-1307 USA
//
//****************************************************************************************//

#include "AccountPrivileges.h"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Class AccountPrivileges  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

AccountPrivileges::AccountPrivileges(string osNameIn, DBInterface *dbIn)
{
	ap_tablename = "";
	ap_tablename.append(osNameIn);
	ap_tablename.append("AccountPrivileges");
	
	db = dbIn;
}

AccountPrivileges::~AccountPrivileges()
{

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void AccountPrivileges::Run()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  TODO - Doesn't print user privileges inherited from group.
	//  When gathering the privileges for each user, only those that have been explicitly
	//  set for that user are returned.  Privileges that the user has via membership in a
	//  particular group are not returned.  Ideally, we need to find a way to get this
	//  info so we return a complete list of user privileges.
	//
	//------------------------------------------------------------------------------------//

	// Delete any old data from this table.
	db->ClearTable(ap_tablename);

	// Gather new data and put in table.
	GetAccountPrivileges();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

void AccountPrivileges::GetAccountPrivileges()
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Call the appropiate NetXXXXEnum() function to get a list of accounts.  Then pass
	//  the name of each account to GetAccountInformation().
	//
	//------------------------------------------------------------------------------------//

	NTSTATUS nts;
	int i;

	// Get a handle to the policy object.

	LSA_HANDLE polHandle;
	LSA_OBJECT_ATTRIBUTES ObjectAttributes;

	ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

	nts = LsaOpenPolicy(NULL, &ObjectAttributes, POLICY_LOOKUP_NAMES, &polHandle);
	if (nts != ERROR_SUCCESS)
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (AccountPrivileges) Unable to open a handle to the ");
		errorMessage.append("Policy object.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
		return;
	} 

	//////////////////////////////////////////////////////
	//////////////////////////////////////////////////////

	NET_API_STATUS nas;
    DWORD recordsEnumerated = 0;
    DWORD totalRecords = 0;

	//////////////////////////////////////////////////////
	/////////////////  User Account Info  ////////////////
	//////////////////////////////////////////////////////

    USER_INFO_0* userInfo = NULL;

	do
	{
		// NOTE: Even though MAX_PREFERRED_LENGTH is specified, we must still check for
		// ERROR_MORE_DATA. (I think!)  I assume that if the server can not allocate the
		// total amount of space required, then it will allocate a smaller buffer and we
		// will need to make multiple calls to NetUserEnum().
		//
		// NOTE: NetUserEnum() requires us to link to Netapi32.lib.

		nas = NetUserEnum(NULL,
						  0,
						  FILTER_NORMAL_ACCOUNT,
						  (unsigned char**) &userInfo,
						  MAX_PREFERRED_LENGTH,
						  &recordsEnumerated,
						  &totalRecords,
						  NULL);

		if ((nas == NERR_Success) || (nas == ERROR_MORE_DATA))
		{
			// User account names are limited to 20 characters.

			char tmpUserName[21];

			// Loop through each user.
			//
			// SMC-AUDIT: ISSUE: can usernames be wide chars?  See Howard/LeBlanc tricky
			// example...
			//
			// ARB:  userInfo[i].usri0_name is a wide character string by definition.
			// That is why %S is used in the _snprintf() function.  Am I missing the point
			// of this question?

			for (i=0; i<recordsEnumerated; i++)
			{
				ZeroMemory(tmpUserName, 21);
				_snprintf(tmpUserName, sizeof(tmpUserName) - 1, "%S", userInfo[i].usri0_name);
				tmpUserName[sizeof(tmpUserName)-1] = '\0';

				// Get the account information.

				GetAccountInformation(polHandle, tmpUserName);
			}
		}
		else
		{
			nts = LsaClose(polHandle);

			char buffer[33];
			_itoa(nas, buffer, 10);

			string errorMessage = "";
			errorMessage.append("\nERROR: (AccountPrivileges) Unable to enumerate local group ");
			errorMessage.append("information due to error ");
			errorMessage.append(buffer);
			errorMessage.append(".\n\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);
		}

		// Free the allocated buffer.

		if (userInfo != NULL)
		{
			NetApiBufferFree(userInfo);
			userInfo = NULL;
		}

	} while (nas==ERROR_MORE_DATA); 

	// Check again for allocated memory.

	if (userInfo != NULL) NetApiBufferFree(userInfo);

	//////////////////////////////////////////////////////
	/////////////////  Local Group Info  /////////////////
	//////////////////////////////////////////////////////

    LOCALGROUP_INFO_0* localGroupInfo = NULL;

	do
	{ 
		nas = NetLocalGroupEnum(NULL, 
								0,
								(unsigned char**) &localGroupInfo,
								MAX_PREFERRED_LENGTH,
								&recordsEnumerated,
								&totalRecords,
								NULL);

		if ((nas == NERR_Success) || (nas==ERROR_MORE_DATA))
		{
			// Group account names are limited to 256 characters.

			char tmpGroupName[257];

			// Loop through each group.
			//
			// SMC-AUDIT: ISSUE: can groupnames be wide chars?  See Howard/LeBlanc tricky
			// example...
			//
			// ARB:  localGroupInfo[i].lgrpi0_name is a wide character string by definition.
			// That is why %S is used in the _snprintf() function.  Am I missing the point
			// of this question?

			for (i=0; i<recordsEnumerated; i++)
			{
				ZeroMemory(tmpGroupName, 257);
				_snprintf(tmpGroupName, sizeof(tmpGroupName) - 1, "%S", localGroupInfo[i].lgrpi0_name);
				tmpGroupName[sizeof(tmpGroupName)-1] = '\0';

				// Get the account information.

				GetAccountInformation(polHandle, tmpGroupName);
			}
		}
		else
		{
			nts = LsaClose(polHandle);

			char buffer[33];
			_itoa(nas, buffer, 10);

			string errorMessage = "";
			errorMessage.append("\nERROR: (AccountPrivileges) Unable to enumerate local group ");
			errorMessage.append("information due to error ");
			errorMessage.append(buffer);
			errorMessage.append(".\n\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);
		}

		// Free the allocated buffer.

		if (localGroupInfo != NULL)
		{
			NetApiBufferFree(localGroupInfo);
			localGroupInfo = NULL;
		}
	} while (nas==ERROR_MORE_DATA); 

	// Check again for allocated memory.

	if (localGroupInfo != NULL) NetApiBufferFree(localGroupInfo);

	//////////////////////////////////////////////////////
	/////////////////  Global Group Info  ////////////////
	//////////////////////////////////////////////////////

    GROUP_INFO_0* globalGroupInfo = NULL;

	do
	{ 
		nas = NetGroupEnum(NULL,
						   0,
						   (unsigned char**) &globalGroupInfo,
						   MAX_PREFERRED_LENGTH,
						   &recordsEnumerated,
						   &totalRecords,
						   NULL);

		if ((nas == NERR_Success) || (nas==ERROR_MORE_DATA))
		{
			// Group account names are limited to 256 characters.

			char tmpGroupName[257];

			// Loop through each group.
			//
			// SMC-AUDIT: ISSUE: can globalgroupnames be wide chars?  See Howard/LeBlanc
			// tricky example...
			//
			// ARB:  globalGroupInfo[i].grpi0_name is a wide character string by definition.
			// That is why %S is used in the _snprintf() function.  Am I missing the point
			// of this question?

			for (i=0; i<recordsEnumerated; i++)
			{
				ZeroMemory(tmpGroupName, 257);
				_snprintf(tmpGroupName, sizeof(tmpGroupName) - 1, "%S", globalGroupInfo[i].grpi0_name);
				tmpGroupName[sizeof(tmpGroupName)-1] = '\0';

				// If there are no global groups, then the globalGroupInfo structure will
				// contain one record with a name of 'None'.  If this is the case, then
				// don't call GetAccountInformation.
				//
				// SMC-AUDIT: RISK: can someone bypass this by creating a group like
				// 'noneXXXX'?
				//
				// ARB: To guard against this, I added a check on the length of the group
				// name.

				if ((strnicmp(tmpGroupName, "none", 4) == 0) && (strlen(tmpGroupName) == 4)) break;

				// Get the account information.

				GetAccountInformation(polHandle, tmpGroupName);
			}
		}
		else
		{
			nts = LsaClose(polHandle);

			char buffer[33];
			_itoa(nas, buffer, 10);

			string errorMessage = "";
			errorMessage.append("\nERROR: (AccountPrivileges) Unable to enumerate global ");
			errorMessage.append("group information due to error ");
			errorMessage.append(buffer);
			errorMessage.append(".\n\n");

			cerr << errorMessage;
			Log::WriteLog(errorMessage);
		}

		// Free the allocated buffer.

		if (globalGroupInfo != NULL)
		{
			NetApiBufferFree(globalGroupInfo);
			globalGroupInfo = NULL;
		}

	} while (nas==ERROR_MORE_DATA); 

	// Check again for allocated memory.

	if (globalGroupInfo != NULL) NetApiBufferFree(globalGroupInfo);

	//////////////////////////////////////////////////////
	//////////////////////////////////////////////////////

	// Close the handle to the open policy object.

	nts = LsaClose(polHandle);

	return;
}

void AccountPrivileges::GetAccountInformation(LSA_HANDLE polHandleIn, LPTSTR accountNameIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Given an account name, gather information about it and insert this information
	//  into the database.
	//
	//------------------------------------------------------------------------------------//

	NTSTATUS nts;

	// Get the SID.

	PSID psid = NULL;
	LPTSTR domain = NULL;

	if (GetAccountSid(NULL, accountNameIn, &psid, &domain) == TRUE)
	{
		// Create an array to hold the access rights.

		int x;
		char mask[39];

		for (x = 0; x < 40; x++)
		{
			mask[x] = '0';
		}

		// Convert the SID to a textual string

		LPTSTR sidString;
		Common::GetTextualSid(psid, &sidString);

		// Enumerate Access Rights

		PLSA_UNICODE_STRING userRights = NULL;
		ULONG CountOfRights = 0;

		nts = LsaEnumerateAccountRights(polHandleIn, psid, &userRights, &CountOfRights);
		if (nts != ERROR_SUCCESS)
		{
			CountOfRights = 0;
		}

		// Alter the access mask to show the correct rights.
		char tmpPrivBuf[128];
		for (ULONG i=0; i<CountOfRights; i++)
		{
			ZeroMemory(tmpPrivBuf, 128);

			WideCharToMultiByte(CP_ACP,							// code page
								0,								// performance and mapping flags
								userRights[i].Buffer,			// wide-character string
								wcslen(userRights[i].Buffer),	// number of chars in string
								tmpPrivBuf,						// buffer for new string
								128,							// size of buffer
								NULL,							// default for unmappable chars
								NULL);							// set when default char used

			if (strnicmp(tmpPrivBuf, "SeAssignPrimaryTokenPrivilege", 29) == 0)
				mask[1] = '1';
			else if (strnicmp(tmpPrivBuf, "SeAuditPrivilege", 16) == 0)
				mask[20] = '1';
			else if (strnicmp(tmpPrivBuf, "SeBackupPrivilege", 17) == 0)
				mask[16] = '1';
			else if (strnicmp(tmpPrivBuf, "SeChangeNotifyPrivilege", 23) == 0)
				mask[22] = '1';
			else if (strnicmp(tmpPrivBuf, "SeCreateGlobalPrivilege", 23) == 0)
				mask[22] = '1';
			else if (strnicmp(tmpPrivBuf, "SeCreatePagefilePrivilege", 25) == 0)
				mask[14] = '1';
			else if (strnicmp(tmpPrivBuf, "SeCreatePermanentPrivilege", 26) == 0)
				mask[15] = '1';
			else if (strnicmp(tmpPrivBuf, "SeCreateTokenPrivilege", 22) == 0)
				mask[0] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDebugPrivilege", 16) == 0)
				mask[19] = '1';
			else if (strnicmp(tmpPrivBuf, "SeEnableDelegationPrivilege", 27) == 0)
				mask[26] = '1';
			else if (strnicmp(tmpPrivBuf, "SeImpersonatePrivilege", 22) == 0)
				mask[3] = '1';
			else if (strnicmp(tmpPrivBuf, "SeIncreaseBasePriorityPrivilege", 31) == 0)
				mask[13] = '1';
			else if (strnicmp(tmpPrivBuf, "SeIncreaseQuotaPrivilege", 24) == 0)
				mask[3] = '1';
			else if (strnicmp(tmpPrivBuf, "SeLoadDriverPrivilege", 21) == 0)
				mask[9] = '1';
			else if (strnicmp(tmpPrivBuf, "SeLockMemoryPrivilege", 21) == 0)
				mask[2] = '1';
			else if (strnicmp(tmpPrivBuf, "SeMachineAccountPrivilege", 25) == 0)
				mask[5] = '1';
			else if (strnicmp(tmpPrivBuf, "SeManageVolumePrivilege", 23) == 0)
				mask[27] = '1';
			else if (strnicmp(tmpPrivBuf, "SeProfileSingleProcessPrivilege", 31) == 0)
				mask[12] = '1';
			else if (strnicmp(tmpPrivBuf, "SeRemoteShutdownPrivilege", 25) == 0)
				mask[23] = '1';
			else if (strnicmp(tmpPrivBuf, "SeRestorePrivilege", 18) == 0)
				mask[17] = '1';
			else if (strnicmp(tmpPrivBuf, "SeSecurityPrivilege", 19) == 0)
				mask[7] = '1';
			else if (strnicmp(tmpPrivBuf, "SeShutdownPrivilege", 19) == 0)
				mask[18] = '1';
			else if (strnicmp(tmpPrivBuf, "SeSyncAgentPrivilege", 20) == 0)
				mask[25] = '1';
			else if (strnicmp(tmpPrivBuf, "SeSystemEnvironmentPrivilege", 28) == 0)
				mask[21] = '1';
			else if (strnicmp(tmpPrivBuf, "SeSystemProfilePrivilege", 24) == 0)
				mask[10] = '1';
			else if (strnicmp(tmpPrivBuf, "SeSystemtimePrivilege", 21) == 0)
				mask[11] = '1';
			else if (strnicmp(tmpPrivBuf, "SeTakeOwnershipPrivilege", 24) == 0)
				mask[8] = '1';
			else if (strnicmp(tmpPrivBuf, "SeTcbPrivilege", 14) == 0)
				mask[6] = '1';
			else if (strnicmp(tmpPrivBuf, "SeUndockPrivilege", 17) == 0)
				mask[24] = '1';
			else if (strnicmp(tmpPrivBuf, "SeUnsolicitedInputPrivilege", 27) == 0)
				mask[4] = '1';
			else if (strnicmp(tmpPrivBuf, "SeBatchLogonRight", 17) == 0)
				mask[28] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDenyBatchLogonRight", 21) == 0)
				mask[29] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDenyInteractiveLogonRight", 27) == 0)
				mask[30] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDenyNetworkLogonRight", 23) == 0)
				mask[31] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDenyServiceLogonRight", 23) == 0)
				mask[32] = '1';
			else if (strnicmp(tmpPrivBuf, "SeDenyRemoteInteractiveLogonRight", 33) == 0)
				mask[37] = '1';
			else if (strnicmp(tmpPrivBuf, "SeInteractiveLogonRight", 23) == 0)
				mask[33] = '1';
			else if (strnicmp(tmpPrivBuf, "SeNetworkLogonRight", 19) == 0)
				mask[34] = '1';
			else if (strnicmp(tmpPrivBuf, "SeRemoteInteractiveLogonRight", 29) == 0)
				mask[36] = '1';
			else if (strnicmp(tmpPrivBuf, "SeServiceLogonRight", 19) == 0)
				mask[35] = '1';
			else
			{
				string errorMessage = "";
				errorMessage.append("\nERROR: (AccountPrivileges) Unknown account privilege '");
				errorMessage.append(tmpPrivBuf);
				errorMessage.append("'.\n\n");
				cerr << errorMessage;
				Log::WriteLog(errorMessage);
			}
		}

		// Make the SQL statement.

		string sqlStmt = "";
		sqlStmt.append("INSERT INTO '");
		sqlStmt.append(ap_tablename);
		sqlStmt.append("' VALUES ('");
		sqlStmt.append(Common::FixQuotes(accountNameIn));
		sqlStmt.append("','");
		sqlStmt.append(Common::FixQuotes(domain));
		sqlStmt.append("','");
		sqlStmt.append(Common::FixQuotes(sidString));
		sqlStmt.append("'");

		for (int j=0; j<40; j++)
		{
			sqlStmt.append(",'");
			sqlStmt.append(1, mask[j]);
			sqlStmt.append("'");
		}

		sqlStmt.append(")");

		// Update the database.
		db->ExecSQL(sqlStmt);

		// Free memory.

		LsaFreeMemory(userRights);
		free(sidString);
	}
	else
	{
		string errorMessage = "";
		errorMessage.append("\nERROR: (AccountPrivileges) Unable to get account SID for '");
		errorMessage.append(accountNameIn);
		errorMessage.append("'.\n\n");
		cerr << errorMessage;
		Log::WriteLog(errorMessage);
	}

	free(psid);
	free(domain);
}

BOOL AccountPrivileges::GetAccountSid(LPTSTR systemNameIn,
									  LPTSTR accountNameIn,
									  PSID* psidIn,
									  LPTSTR* domainIn)
{
	//------------------------------------------------------------------------------------//
	//
	//  ABSTRACT
	//
	//  Given a system name and an account name, get the SID.  Return TRUE if successful
	//  and FALSE if there was an error.
	//
	//------------------------------------------------------------------------------------//

	DWORD sidSize = 128;
	DWORD domainSize = 128;
	SID_NAME_USE sidUse;
	BOOL retVal = FALSE;

	do
	{
		// Initial memory allocations for the SID and DOMAIN.

		*psidIn = (PSID)realloc(*psidIn, sidSize);
		if (*psidIn == NULL)
		{
			retVal = FALSE;
			break;
		}

		*domainIn = (LPTSTR)realloc(*domainIn, domainSize);
		if (*domainIn == NULL)
		{
			retVal = FALSE;
			break;
		}

		// Call LookupAccountName to get the SID.

		retVal = LookupAccountName(systemNameIn,			// system name
								   accountNameIn,			// account name
								   *psidIn,					// security identifier
								   &sidSize,				// size of security identifier
								   *domainIn,				// domain name
								   &domainSize,				// size of domain name
								   &sidUse);				// SID-type indicator

	} while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);

	return retVal;
}
