 /*
 * Copyright (c) 2000, Red Hat, Inc.
 * Copyright (c) 2003, Robert Collins <rbtcollins@hotmail.com>
 * Copyright (c) 2005-2006, Tom N Harris <telliamed@whoopdedo.org>
 *
 *     This program 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, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by DJ Delorie <dj@cygnus.com>
 *            Robert Collins <rbtcollins@hotmail.com>
 *            Tom N Harris <telliamed@whoopdedo.org>
 *
 *
 */

#include "win32.h"
#include <commctrl.h>

#include "PropPage.h"
#include "PropSheet.h"

#include "Splash.h"
#include "Choose.h"
#include "Dirs.h"
#include "Progress.h"
#include "Finish.h"

#include "Installer.h"

#include "globals.h"

HINSTANCE ghInstance;

/* Maximum size of a SID on NT/W2K. */
#define MAX_SID_LEN	40

/* Computes the size of an ACL in relation to the number of ACEs it
   should contain. */
#define TOKEN_ACL_SIZE(cnt)  (sizeof(ACL) + \
			     (cnt) * (sizeof(ACCESS_ALLOWED_ACE) + MAX_SID_LEN))


namespace Setup
{

class TokenGroupCollection
{
public:
	TokenGroupCollection(DWORD, HANDLEWrapper&);
	~TokenGroupCollection();
	/* prevent synthetics */
	TokenGroupCollection& operator=(TokenGroupCollection const &);
	TokenGroupCollection(TokenGroupCollection const &);

	bool find(SIDWrapper const &) const;
	bool populated() const { return m_populated; }
	void populate();
private:
	mutable bool m_populated;
	char *m_buffer;
	DWORD m_bufferSize;
	HANDLEWrapper& m_token;
};

TokenGroupCollection::TokenGroupCollection(DWORD aSize, HANDLEWrapper& aHandle)
	: m_populated(false), 
	  m_buffer(new char[aSize]), 
	  m_bufferSize(aSize), 
	  m_token(aHandle)
{
}

TokenGroupCollection::~TokenGroupCollection()
{
	if (m_buffer)
		delete[] m_buffer;
}

void
TokenGroupCollection::populate()
{
	if (!::GetTokenInformation(m_token.theHANDLE(), TokenGroups, m_buffer, m_bufferSize, &m_bufferSize))
	{
		//log(LOG_TIMESTAMP) << "GetTokenInformation() failed: " <<
		//	::GetLastError() << endLog;
		return;
	}
	m_populated = true;
}

bool
TokenGroupCollection::find(SIDWrapper const & aSID) const
{
	if (!populated())
		return false;
	TOKEN_GROUPS *groups = (TOKEN_GROUPS *)m_buffer;
	for (DWORD pg = 0; pg < groups->GroupCount; ++pg)
		if (::EqualSid(groups->Groups[pg].Sid, aSID.theSID()))
			return true;
	return false;
}

class NTSecurity
{
public:
	//static void NoteFailedAPI(std::string const &);
	NTSecurity();
	~NTSecurity();
	/* prevent synthetics */
	NTSecurity& operator= (NTSecurity const &);
	NTSecurity(NTSecurity const &);

	void setDefaultSecurity();

private:
	void failed(bool const &);
	bool const& failed() const;
	void initialiseEveryOneSID();
	void setDefaultDACL();

	SIDWrapper m_everyOneSID, m_administratorsSID, m_uSID;
	HANDLEWrapper m_token;
	bool m_failed;
	struct 
	{
		PSID psid;
		char buf[MAX_SID_LEN];
	} m_osid;
	DWORD m_size;
};

NTSecurity::NTSecurity()
	: m_everyOneSID(),
	  m_administratorsSID(),
	  m_uSID(), 
	  m_token(), 
	  m_failed(false)
{
	/* Get the processes access token. */
	if (!::OpenProcessToken(::GetCurrentProcess(),
		TOKEN_READ | TOKEN_ADJUST_DEFAULT, &m_token.theHANDLE()))
	{
		//NoteFailedAPI ("OpenProcessToken");
		failed(true);
	}
}

NTSecurity::~NTSecurity()
{
}

void 
NTSecurity::failed(bool const & aBool)
{
	m_failed = aBool;
}

bool const &
NTSecurity::failed() const
{
	return m_failed;
}

void 
NTSecurity::initialiseEveryOneSID()
{
	SID_IDENTIFIER_AUTHORITY sid_auth = { SECURITY_WORLD_SID_AUTHORITY };
	if (!::AllocateAndInitializeSid(&sid_auth, 1, 0, 0, 0, 0, 0, 0, 0, 0, &m_everyOneSID.theSID()))
	{
		//NoteFailedAPI("AllocateAndInitializeSid");
		failed(true);
	}
}

void
NTSecurity::setDefaultDACL()
{
	/* To assure that the created files have a useful ACL, the 
	   default DACL in the process token is set to full access to
	   everyone. This applies to files and subdirectories created
	   in directories which don't propagate permissions to child
	   objects. 
	   To assure that the files group is meaningful, a token primary
	   group of None is changed to Users or Administrators. */

	initialiseEveryOneSID();
	if (failed())
		return;

	/* Create a buffer which has enough room to contain the TOKEN_DEFAULT_DACL
	   structure plus an ACL with one ACE. */
	size_t bufferSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) 
		+ GetLengthSid(m_everyOneSID.theSID()) - sizeof(DWORD);

	std::auto_ptr<char> buf(new char[bufferSize]);

	/* First initialize the TOKEN_DEFAULT_DACL structure. */
	PACL dacl = (PACL)buf.get();

	/* Initialize the ACL for containing one ACE. */
	if (!::InitializeAcl(dacl, bufferSize, ACL_REVISION))
	{
		//NoteFailedAPI ("InitializeAcl");
		failed(true);
		return;
	}

	/* Create the ACE which grants full access to "Everyone" and store it
	   in dacl. */
	if (!::AddAccessAllowedAce(dacl, ACL_REVISION, GENERIC_ALL, m_everyOneSID.theSID()))
	{
		//NoteFailedAPI ("AddAccessAllowedAce");
		failed(true);
		return;
	}

	/* Set the default DACL to the above computed ACL. */
	if (!::SetTokenInformation(m_token.theHANDLE(), TokenDefaultDacl, &dacl, bufferSize))
	{
		//NoteFailedAPI ("SetTokenInformation");
		failed(true);
	}
}

void
NTSecurity::setDefaultSecurity()
{
	setDefaultDACL();
	if (failed())
		return;

	/* Get the user */
	if (!::GetTokenInformation(m_token.theHANDLE(), TokenUser, &m_osid, sizeof m_osid, &m_size))
	{
		//NoteFailedAPI("GetTokenInformation");
		return;
	}
	/* Make it the owner */
	if (!::SetTokenInformation(m_token.theHANDLE(), TokenOwner, &m_osid, sizeof m_osid))
	{
		//NoteFailedAPI("SetTokenInformation");
		return;
	}

	SID_IDENTIFIER_AUTHORITY sid_auth;
	sid_auth = (SID_IDENTIFIER_AUTHORITY) { SECURITY_NT_AUTHORITY };
	/* Get the SID for "Administrators" S-1-5-32-544 */
	if (!::AllocateAndInitializeSid(&sid_auth, 2, SECURITY_BUILTIN_DOMAIN_RID, 
		DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &m_administratorsSID.theSID()))
	{
		//NoteFailedAPI("AllocateAndInitializeSid");
		return;
	}
	/* Get the SID for "Users" S-1-5-32-545 */
	if (!::AllocateAndInitializeSid (&sid_auth, 2, SECURITY_BUILTIN_DOMAIN_RID, 
			DOMAIN_ALIAS_RID_USERS, 0, 0, 0, 0, 0, 0, &m_uSID.theSID()))
	{
		//NoteFailedAPI("AllocateAndInitializeSid");
		return;
	}
	/* Get the token groups */
	if (!::GetTokenInformation(m_token.theHANDLE(), TokenGroups, NULL, 0, &m_size)
	  && ::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
	{
		//NoteFailedAPI("GetTokenInformation");
		return;
	}
	TokenGroupCollection ntGroups(m_size, m_token);
	ntGroups.populate();
	if (!ntGroups.populated())
		return;
	/* Set the default group to one of the above computed SID. */
	PSID nsid = NULL;
	if (ntGroups.find(m_uSID))
	{
		nsid = m_uSID.theSID();
		//log(LOG_TIMESTAMP) << "Changing gid to Users" << endLog;
	}
	else if (ntGroups.find(m_administratorsSID))
	{
		nsid = m_administratorsSID.theSID();
		//log(LOG_TIMESTAMP) << "Changing gid to Administrators" << endLog;
	}
	//if (nsid && !SetTokenInformation(m_token.theHANDLE(), TokenPrimaryGroup, &nsid, sizeof nsid))
	//	NoteFailedAPI ("SetTokenInformation");
}


void
SetDefaultSecurity()
{
	NTSecurity worker;
	worker.setDefaultSecurity();
}

};

using namespace Setup;

int WINAPI 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pszCommandLine, int nCmdShow)
{
	ghInstance = hInstance;

	::InitCommonControls();

	if (Win32::IsNT())
		SetDefaultSecurity();

	Window::SetAppInstance(hInstance);

	Installer inst;
	if (! inst.LoadFromRdf(rdf_install_file))
	{
		exit(2);
	}

	PropertySheet MainWindow;

	SplashPage Splash;
	ChooserPage Choose(inst);
	GameDirPage Dirs(inst);
	ProgressPage Progress(inst);
	InstallSuccessPage Finish;
	InstallNothingPage Nothing;
	InstallFailPage Fail;
	Splash.create();
	Choose.create();
	Dirs.create();
	Progress.create();
	Finish.create();
	Nothing.create();
	Fail.create();

	MainWindow.addPage(&Splash);
	MainWindow.addPage(&Choose);
	MainWindow.addPage(&Dirs);
	MainWindow.addPage(&Progress);
	MainWindow.addPage(&Finish);
	MainWindow.addPage(&Fail);

	MainWindow.create();

	return 0;
}
