/*
  Inno Setup
  Copyright (C) 1997-2008 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  64-bit helper process

  Compiled on Visual Studio 2005 SP1
  Tested on x64 and IA-64 architectures (Athlon 64 and Merced specifically).

  $jrsoftware: issrc/Projects/Helper/Helper.c,v 1.8 2008/10/16 19:27:24 jr Exp $
*/

#define _WIN32_IE 0x0600
#include <windows.h>
#include <commctrl.h>
#include <shlwapi.h>
#include <aclapi.h>

#ifndef UNICODE
#error UNICODE isn't defined
#endif

// As of Inno Setup 5.1.11, the helper is no longer used to register
// DLLs, so avoid linking in support for REQUEST_REGISTER_SERVER.
#undef IMPLEMENT_REGISTER_SERVER

#define HELPER_VERSION 105

// Request commands
#define REQUEST_PING 1
#define REQUEST_GRANT_PERMISSION 2
#ifdef IMPLEMENT_REGISTER_SERVER
#define REQUEST_REGISTER_SERVER 3
#endif
#define REQUEST_REGISTER_TYPE_LIBRARY 4

// These must be kept in synch with Struct.pas:
typedef struct {
	SID_IDENTIFIER_AUTHORITY Authority;
	BYTE SubAuthCount;
	DWORD SubAuth[2];
} TGrantPermissionSid;
typedef struct {
	TGrantPermissionSid Sid;
    DWORD AccessMask;
} TGrantPermissionEntry;

// This value must be kept in synch with Struct.pas:
#define MAX_GRANT_PERMISSION_ENTRIES 32

// These must be kept in synch with Helper.pas:
typedef struct {
	DWORD ObjectType;
	DWORD EntryCount;
	DWORD Inheritance;
	WCHAR ObjectName[4096];
	TGrantPermissionEntry Entries[MAX_GRANT_PERMISSION_ENTRIES];
} REQUEST_GRANT_PERMISSION_DATA;

typedef struct {
	BOOL Unregister;
	BOOL FailCriticalErrors;
	WCHAR Filename[4096];
	WCHAR Directory[4096];
} REQUEST_REGISTER_SERVER_DATA;

typedef struct {
	BOOL Unregister;
	WCHAR Filename[4096];
} REQUEST_REGISTER_TYPE_LIBRARY_DATA;

typedef struct {
	DWORD SequenceNumber;
	DWORD Command;
	DWORD DataSize;
	union {
		BYTE Data[65536];
		REQUEST_GRANT_PERMISSION_DATA GrantPermissionData;
		REQUEST_REGISTER_SERVER_DATA RegisterServerData;
		REQUEST_REGISTER_TYPE_LIBRARY_DATA RegisterTypeLibraryData;
	};
} REQUEST_DATA;

typedef struct {
	DWORD SequenceNumber;
	DWORD StatusCode;
	DWORD ErrorCode;
	DWORD DataSize;
	BYTE Data[65536];
} RESPONSE_DATA;

static TCHAR SystemDir[MAX_PATH+1];

static DWORD GrantPermission(const DWORD ObjectType, const LPWSTR ObjectName,
	TGrantPermissionEntry *Entries, const DWORD EntryCount,
	const DWORD Inheritance)
{
	DWORD ErrorCode;
	PSECURITY_DESCRIPTOR SD;
	PACL Dacl, NewDacl;
	EXPLICIT_ACCESSW ExplicitAccess[MAX_GRANT_PERMISSION_ENTRIES];
	DWORD I;

	// Note: SecureZeroMemory is used because memset/ZeroMemory aren't available
	SecureZeroMemory(&ExplicitAccess, sizeof(ExplicitAccess));

	if (EntryCount > ARRAYSIZE(ExplicitAccess)) {
		return ERROR_INVALID_PARAMETER;
	}

	ErrorCode = GetNamedSecurityInfoW(ObjectName, ObjectType,
		DACL_SECURITY_INFORMATION, NULL, NULL, &Dacl, NULL, &SD);
	if (ErrorCode != ERROR_SUCCESS) {
		return ErrorCode;
	}

	// From here on, use "goto out" instead of "return"

	for (I = 0; I < EntryCount; I++) {
		TGrantPermissionEntry *E = &Entries[I];
		PSID Sid;

		if (!AllocateAndInitializeSid(&E->Sid.Authority, E->Sid.SubAuthCount,
				E->Sid.SubAuth[0], E->Sid.SubAuth[1], 0, 0, 0, 0, 0, 0, &Sid)) {
			ErrorCode = GetLastError();
			if (ErrorCode == 0) ErrorCode = ERROR_INVALID_PARAMETER;  // just in case
			goto out;
		}
		ExplicitAccess[I].grfAccessPermissions = E->AccessMask;
		ExplicitAccess[I].grfAccessMode = GRANT_ACCESS;
		ExplicitAccess[I].grfInheritance = Inheritance;
		ExplicitAccess[I].Trustee.TrusteeForm = TRUSTEE_IS_SID;
		ExplicitAccess[I].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN;
		ExplicitAccess[I].Trustee.ptstrName = Sid;
	}

	ErrorCode = SetEntriesInAclW(EntryCount, ExplicitAccess, Dacl, &NewDacl);
	if (ErrorCode == ERROR_SUCCESS) {
		ErrorCode = SetNamedSecurityInfoW(ObjectName, ObjectType,
			DACL_SECURITY_INFORMATION, NULL, NULL, NewDacl, NULL);
		LocalFree(NewDacl);
	}

out:
	for (I = 0; I < EntryCount; I++) {
		PSID Sid = ExplicitAccess[I].Trustee.ptstrName;
		if (Sid) FreeSid(Sid);
	}
	LocalFree(SD);

	return ErrorCode;
}

#ifdef IMPLEMENT_REGISTER_SERVER
static DWORD RegisterServer(const BOOL Unregister, const LPWSTR Filename,
	const LPWSTR Directory, const BOOL FailCriticalErrors, DWORD *ErrorCode)
{
	DWORD retval = 0;
	HRESULT OleInitResult;
	UINT SaveErrorMode;
	HANDLE LibHandle;

	// Initialize OLE. Regsvr32 does this.
	OleInitResult = OleInitialize(NULL);
	if (FAILED(OleInitResult)) {
		*ErrorCode = OleInitResult;
		return 4;
	}
	
	SaveErrorMode = SetErrorMode(FailCriticalErrors ?
		SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS :
		SEM_NOOPENFILEERRORBOX);

	// If no directory was supplied, we use the system directory.
	// Relative paths are assumed to be relative to the system directory.
	SetCurrentDirectory(SystemDir);
	if (*Directory) {
		SetCurrentDirectoryW(Directory);
	}

	LibHandle = LoadLibraryExW(Filename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
	if (LibHandle) {
		typedef HRESULT (STDAPICALLTYPE *DLLREGISTERSERVER)(void);
		DLLREGISTERSERVER RegisterServerProc;

		RegisterServerProc = (DLLREGISTERSERVER)GetProcAddress(LibHandle,
			Unregister ? "DllUnregisterServer" : "DllRegisterServer");
		if (RegisterServerProc) {
			*ErrorCode = (*RegisterServerProc)();
			retval = 3;
		} else {
			*ErrorCode = GetLastError();
			retval = 2;
		}

		FreeLibrary(LibHandle);
	} else {
		*ErrorCode = GetLastError();
		retval = 1;
	}

	// Revert the current directory and error mode changes
	SetCurrentDirectory(SystemDir);
	SetErrorMode(SaveErrorMode);

	OleUninitialize();

	return retval;
}
#endif

static DWORD RegisterTypeLibrary(const BOOL Unregister,
	const LPWSTR Filename, DWORD *ErrorCode)
{
	DWORD retval;
	HRESULT hr;
	ITypeLib *TypeLib;
	TLIBATTR *LibAttr;

	retval = 1;
	hr = LoadTypeLib(Filename, &TypeLib);
	if (hr == S_OK) {
		if (!Unregister) {
			retval = 2;
			hr = RegisterTypeLib(TypeLib, Filename, NULL);			
		} else {
			retval = 3;
			hr = TypeLib->lpVtbl->GetLibAttr(TypeLib, &LibAttr);
			if (hr == S_OK) {
				retval = 4;
				hr = UnRegisterTypeLib(&LibAttr->guid, LibAttr->wMajorVerNum,
					LibAttr->wMinorVerNum, LibAttr->lcid, LibAttr->syskind);
				TypeLib->lpVtbl->ReleaseTLibAttr(TypeLib, LibAttr);
			}
		}
		TypeLib->lpVtbl->Release(TypeLib);
	}

	*ErrorCode = hr;
	return retval;
}

static void ProcessRequest(REQUEST_DATA *request, RESPONSE_DATA *response)
{
	response->SequenceNumber = request->SequenceNumber;
	response->StatusCode = 0;  // 0 means "didn't execute command"
	response->ErrorCode = 0;
	response->DataSize = 0;

	switch (request->Command) {
		case REQUEST_PING:
			response->StatusCode = 1;
			break;
		case REQUEST_GRANT_PERMISSION:
			{
				// response:
				//   StatusCode    1 if request was valid
				//   ErrorCode     Error code from GrantPermission()
				
				REQUEST_GRANT_PERMISSION_DATA *data = &request->GrantPermissionData;

				if (request->DataSize == sizeof(*data) &&
					data->EntryCount <= ARRAYSIZE(data->Entries)) {
					response->ErrorCode = GrantPermission(data->ObjectType,
						data->ObjectName, data->Entries, data->EntryCount,
						data->Inheritance);
					response->StatusCode = 1;
				}
			}
			break;
#ifdef IMPLEMENT_REGISTER_SERVER
		case REQUEST_REGISTER_SERVER:
			{
				// response:
				//   StatusCode    1 if LoadLibrary failed,
				//                 2 if GetProcAddress failed,
				//                 3 if Dll(Un)RegisterServer called; possibly succeeded,
				//                 4 if OleInitialize failed
				//   ErrorCode     Error code or HRESULT depending on the StatusCode

				REQUEST_REGISTER_SERVER_DATA *data = &request->RegisterServerData;

				if (request->DataSize == sizeof(*data)) {
					response->StatusCode = RegisterServer(data->Unregister,
						data->Filename, data->Directory, data->FailCriticalErrors,
						&response->ErrorCode);
				}
			}
			break;
#endif
		case REQUEST_REGISTER_TYPE_LIBRARY:
			{
				// response:
				//   StatusCode    1 if LoadTypeLib didn't return S_OK
				//                 Register only:
				//                   2 if RegisterTypeLib called; possibly succeeded
				//                 Unregister only:
				//                   3 if ITypeLib::GetLibAttr didn't return S_OK
				//                   4 if UnRegisterTypeLib called; possibly succeeded
				//   ErrorCode     An HRESULT

				REQUEST_REGISTER_TYPE_LIBRARY_DATA *data = &request->RegisterTypeLibraryData;

				if (request->DataSize == sizeof(*data)) {
					response->StatusCode = RegisterTypeLibrary(data->Unregister,
						data->Filename, &response->ErrorCode);
				}
			}
			break;
	}
}

static BOOL WINAPI ConsoleCtrlHandlerRoutine(DWORD dwCtrlType)
{
	// By default, during shutdown, applications without windows are killed
	// unconditionally. This handler suppresses that default handling, and
	// just ignores the termination request.
	//
	// Note: This can cause a "Process not responding" dialog to appear, but
	// that's still better than dying unconditionally. I don't think it's
	// possible to programmatically abort the shutdown sequence in a
	// non-windowed application. (Windowed applications, of course, can
	// return FALSE in response to WM_QUERYENDSESSION. But we have no message
	// loop so creating a window is not an option.)

	return TRUE;
}

/*
void Test(void)
{
	TGrantPermissionEntry Entry = { 0 };
	Entry.Sid.Authority.Value[5] = 1;
	Entry.Sid.SubAuthCount = 1;
	Entry.AccessMask = 0x1200A9;
	GrantPermission(SE_FILE_OBJECT, "c:\\testfile.txt", &Entry, 1, 0);
}
*/

int Main(void)
{
	int argc;
	LPWSTR* argv;
	LONGLONG llHandle;
	HANDLE hPipe;
	LONG retval = 0;
	static REQUEST_DATA request;
	static RESPONSE_DATA response;

	// Work around bug in Windows XP Gold & SP1: If the application manifest
	// specifies COMCTL32.DLL version 6.0 (to enable visual styles), we must
	// call InitCommonControls() to ensure that we actually link to
	// COMCTL32.DLL, otherwise calls to MessageBox() fail. (XP SP2 appears
	// to fix this.)
	// NOTE: This workaround was brought over from RegDLL for consistency,
	// but may not actually be needed here since Setup (currently) refuses
	// to spawn Helper on pre-5.02 SP1 versions of Windows.
	InitCommonControls();

	SetErrorMode(SEM_FAILCRITICALERRORS);

	GetSystemDirectory(SystemDir, ARRAYSIZE(SystemDir));
	SetCurrentDirectory(SystemDir);

	// Give us a lower-than-default shutdown priority so that if for some
	// reason a shutdown is initiated while we're running, Windows
	// "shouldn't" try to kill us before Setup.
	SetProcessShutdownParameters(0x100, 0);

	SetConsoleCtrlHandler(ConsoleCtrlHandlerRoutine, TRUE);

	//
	// Get version and message-mode pipe handle from command line
	//

	argv = CommandLineToArgvW(GetCommandLineW(), &argc);
	if (argv == NULL) {
		return MAKELONG(GetLastError(), 1);
	}
	if (argc != 3) {
		return MAKELONG(0, 2);
	}
	if (StrToIntW(argv[1]) != HELPER_VERSION) {
		return MAKELONG(0, 3);
	}
	if (!StrToInt64ExW(argv[2], STIF_SUPPORT_HEX, &llHandle)) {
		return MAKELONG(0, 4);
	}
	hPipe = (HANDLE)(INT_PTR)llHandle;

	//
	// Wait for and process incoming requests
	//

	while (TRUE) {
		DWORD dwBytesRead, dwResponseLength, dwBytesWritten;

		if (!ReadFile(hPipe, &request, sizeof(request), &dwBytesRead, NULL)) {
			// We can normally expect ReadFile to fail with ERROR_BROKEN_PIPE
			// when the client has disconnected.
			// Note: We don't bother handling ERROR_MORE_DATA because the client
			// shouldn't be sending us oversized requests in the first place.
			if (GetLastError() != ERROR_BROKEN_PIPE) {
				retval = MAKELONG(GetLastError(), 5);
			}
			break;
		}
		if (dwBytesRead < FIELD_OFFSET(REQUEST_DATA, Data) ||
			request.DataSize != dwBytesRead - FIELD_OFFSET(REQUEST_DATA, Data)) {
			// Request message is too short or too long
			retval = MAKELONG(0, 6);
			break;
		}

		ProcessRequest(&request, &response);

		dwResponseLength = FIELD_OFFSET(RESPONSE_DATA, Data) + response.DataSize;
		if (!WriteFile(hPipe, &response, dwResponseLength, &dwBytesWritten, NULL)) {
			// WriteFile could fail if the client disconnected for some reason.
			retval = MAKELONG(GetLastError(), 7);
			break;
		}
		if (dwBytesWritten != dwResponseLength) {
			// Should never get here.
			retval = MAKELONG(0, 8);
			break;
		}
	}

	CloseHandle(hPipe);

	return retval;
}

int mainCRTStartup(void)
{
	int retval;

	retval = Main();
	
	// Good idea to call ExitProcess because it'll terminate any other
	// threads the system might've created. A simple "return" won't.
	ExitProcess(retval);

	return retval;
}