/*
   Copyright (C) Andrzej Hajda 2006
   Contact: andrzej.hajda@wp.pl
   License: GNU General Public License version 2
*/

#include <windows.h>
#include <aclapi.h>

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

#include "shared.h"

#define BUFSIZE 256

#if 0
static void SvcDebugOut(const char *a, int b)
{
	FILE *f = fopen("C:\\" SERVICE_NAME ".log", "at");
	if (f) {
		fprintf(f, a, b);
		fclose(f);
	}
}
#else
#define SvcDebugOut(a,b) 0
#endif

SECURITY_ATTRIBUTES sa;

/* Creates SECURITY_ATTRIBUTES sa with full access for BUILTIN\Administrators */
int CreatePipesSA()
{
	DWORD dwRes;
	PSID pAdminSID = NULL;
	PACL pACL = NULL;
	PSECURITY_DESCRIPTOR pSD = NULL;
	EXPLICIT_ACCESS ea;
	SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;

	/* Create a SID for the BUILTIN\Administrators group. */
	if (!AllocateAndInitializeSid(&SIDAuthNT, 2,
				      SECURITY_BUILTIN_DOMAIN_RID,
				      DOMAIN_ALIAS_RID_ADMINS,
				      0, 0, 0, 0, 0, 0, &pAdminSID)) {
		SvcDebugOut("AllocateAndInitializeSid Error %u\n",
			    GetLastError());
		return 0;
	}
	/* Initialize an EXPLICIT_ACCESS structure for an ACE.
	   The ACE will allow the Administrators group full access to the key.
	*/
	ea.grfAccessPermissions = FILE_ALL_ACCESS;
	ea.grfAccessMode = SET_ACCESS;
	ea.grfInheritance = NO_INHERITANCE;
	ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
	ea.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
	ea.Trustee.ptstrName = (LPTSTR) pAdminSID;

	/* Create a new ACL that contains the new ACEs */
	dwRes = SetEntriesInAcl(1, &ea, NULL, &pACL);
	if (ERROR_SUCCESS != dwRes) {
		SvcDebugOut("SetEntriesInAcl Error %u\n", GetLastError());
		return 0;
	}
	/* Initialize a security descriptor */
	pSD =
	    (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR,
					      SECURITY_DESCRIPTOR_MIN_LENGTH);
	if (NULL == pSD) {
		SvcDebugOut("LocalAlloc Error %u\n", GetLastError());
		return 0;
	}

	if (!InitializeSecurityDescriptor
	    (pSD, SECURITY_DESCRIPTOR_REVISION)) {
		SvcDebugOut("InitializeSecurityDescriptor Error %u\n",
			    GetLastError());
		return 0;
	}
	/* Add the ACL to the security descriptor */
	if (!SetSecurityDescriptorDacl(pSD, TRUE,	/* bDaclPresent flag */
				       pACL, FALSE))	/* not a default DACL */
	{
		SvcDebugOut("SetSecurityDescriptorDacl Error %u\n",
			    GetLastError());
		return 0;
	}
	/* Initialize a security attributes structure */
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = pSD;
	sa.bInheritHandle = FALSE;
	return 1;
}

typedef struct {
	HANDLE h;
	OVERLAPPED o;
} OV_HANDLE;

int hgets(char *str, int n, OV_HANDLE *pipe)
{
	DWORD res;
	DWORD count = 0;
	--n;
	while (--n >= 0) {
		if (!ReadFile(pipe->h, str, 1, NULL, &pipe->o) && GetLastError() != ERROR_IO_PENDING)
			goto finish;
		if (!GetOverlappedResult(pipe->h, &pipe->o, &res, TRUE) || !res)
			goto finish;
		if (*str == '\n')
			goto finish;
		++count;
		++str;
	}
finish:
	*str = 0;
	return count;
}

int hprintf(OV_HANDLE *pipe, const char *fmt, ...)
{
	int res;
	char buf[1024];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if (!WriteFile(pipe->h, buf, strlen(buf), NULL, &pipe->o) && GetLastError() == ERROR_IO_PENDING)
		GetOverlappedResult(pipe->h, &pipe->o, (LPDWORD)&res, TRUE);
	FlushFileBuffers(pipe->h);
	return res;
}

typedef struct {
	OV_HANDLE *pipe;
	const char *cmd;
	HANDLE pio;
	HANDLE perr;
	HANDLE token;
	int implevel;
	int system;
	char *runas;
	int conn_number;
} connection_context;

typedef int CMD_FUNC(connection_context *);

typedef struct {
	const char *name;
	CMD_FUNC *func;
} CMD_ITEM;

int cmd_set(connection_context *c)
{
	static const char* var_system = "system";
	static const char* var_implevel = "implevel";
	static const char* var_runas = "runas";
	char *cmdline;
	int res = 0;

	cmdline = strchr(c->cmd, ' ');
	if (!cmdline) {
		goto finish;
	}
	++cmdline;
	int l;
	if ((strstr(cmdline, var_system) == cmdline) &&
            (cmdline[l = strlen(var_system)] == ' ')) {
		c->system = atoi(cmdline + l + 1);
	} else if ((strstr(cmdline, var_implevel) == cmdline) &&
            (cmdline[l = strlen(var_implevel)] == ' ')) {
		c->implevel = atoi(cmdline + l + 1);
	} else if ((strstr(cmdline, var_runas) == cmdline) &&
            (cmdline[l = strlen(var_runas)] == ' ')) {
		c->runas = strdup(cmdline + l + 1);
	} else {
	    hprintf(c->pipe, "error Unknown commad (%s)\n", c->cmd);
	    goto finish;
	}
	res = 1;
finish:
	return res;
}

int cmd_get(connection_context *c)
{
	static const char* var_version = "version";
	char *cmdline;
	int res = 0;

	cmdline = strchr(c->cmd, ' ');
	if (!cmdline) {
		goto finish;
	}
	++cmdline;
	int l;
	if ((strstr(cmdline, var_version) == cmdline) &&
            (cmdline[l = strlen(var_version)] == 0)) {
		hprintf(c->pipe, "version 0x%04X\n", VERSION);
	} else {
		hprintf(c->pipe, "error Unknown argument (%s)\n", c->cmd);
	        goto finish;
	}
	res = 1;
finish:
	return res;
}

typedef struct {
	char *user;
	char *domain;
	char *password;
} credentials;

int prepare_credentials(char *str, credentials *crd)
{
	char *p;
	p = strchr(str, '/');
	if (!p) p = strchr(str, '\\');
	if (p) {
		*p++ = 0;
		crd->domain = str;
	} else {
	        p = str;
		crd->domain = ".";
	}
	crd->user = p;
	p = strchr(p, '%');
	if (p)
	    *p++ = 0;
	crd->password = p;
	return 1;
}

int get_token(connection_context *c)
{
	int res = 0;
	int wres;
	HANDLE token;

	if (c->runas) {
		credentials crd;
		if (!prepare_credentials(c->runas, &crd)) {
			hprintf(c->pipe, "error Incorrect runas credentials\n");
			goto finish;
		}
		wres = LogonUser(crd.user, crd.domain, crd.password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &c->token);
		if (!wres) {
			hprintf(c->pipe, "error Cannot LogonUser(%s,%s,%s) %d\n", crd.user, crd.domain, crd.password, GetLastError());
			goto finish;
		}
		res = 1;
		goto finish;
	} else if (c->system) {
		if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) {
			hprintf(c->pipe, "error Cannot OpenProcessToken %d\n", GetLastError());
			goto finish;
		}
	} else {
		if (!ImpersonateNamedPipeClient(c->pipe->h)) {
			hprintf(c->pipe, "error Cannot ImpersonateNamedPipeClient %d\n", GetLastError());
			goto finish;
		}
		if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &token)) {
			hprintf(c->pipe, "error Cannot OpenThreadToken %d\n", GetLastError());
			goto finishRevertToSelf;
		}
	}
	if (!DuplicateTokenEx(token, MAXIMUM_ALLOWED, 0, c->implevel, TokenPrimary, &c->token)) {
		hprintf(c->pipe, "error Cannot Duplicate Token %d\n", GetLastError());
		goto finishCloseToken;
	}
	res = 1;
finishCloseToken:
	CloseHandle(token);
finishRevertToSelf:
	if (!c->system) {
		if (!RevertToSelf()) {
			hprintf(c->pipe, "error Cannot RevertToSelf %d\n", GetLastError());
			res = 0;
		}
	}
finish:
	return res;
}

int cmd_run(connection_context *c)
{
	char buf[256];
	int res = 0;
	int wres;
	char *cmdline;
	DWORD pipe_nr;

	cmdline = strchr(c->cmd, ' ');
	if (!cmdline) {
		goto finish;
	}
	++cmdline;

	if (!get_token(c))
		return 0;

	pipe_nr = (GetCurrentProcessId() << 16) + (DWORD) c->conn_number;
	sprintf(buf, "\\\\.\\pipe\\" PIPE_NAME_IO, pipe_nr);
	c->pio = CreateNamedPipe(buf,
			      PIPE_ACCESS_DUPLEX,
			      PIPE_WAIT,
			      1,
			      BUFSIZE,
			      BUFSIZE,
			      NMPWAIT_USE_DEFAULT_WAIT,
			      &sa);
	if (c->pio == INVALID_HANDLE_VALUE) {
		hprintf(c->pipe, "error Cannot create io pipe(%s), error 0x%08X\n", buf, GetLastError());
		goto finishCloseToken;
	}

	sprintf(buf, "\\\\.\\pipe\\" PIPE_NAME_ERR, pipe_nr);
	c->perr = CreateNamedPipe(buf,
			       PIPE_ACCESS_DUPLEX,
			       PIPE_WAIT,
			       1,
			       BUFSIZE,
			       BUFSIZE,
			       NMPWAIT_USE_DEFAULT_WAIT,
			       &sa);
	if (c->perr == INVALID_HANDLE_VALUE) {
		hprintf(c->pipe, "error Cannot create err pipe\n");
		goto finishClosePio;
	}
	/* Send handle to client (it will use it to connect pipes) */
	hprintf(c->pipe, CMD_STD_IO_ERR " %08X\n", pipe_nr);

	wres = ConnectNamedPipe(c->pio, NULL);
	if (!wres)
		wres = (GetLastError() == ERROR_PIPE_CONNECTED);

	if (!wres) {
		hprintf(c->pipe, "error ConnectNamedPipe(pio)\n");
		goto finishClosePerr;
	}

	wres = ConnectNamedPipe(c->perr, NULL);
	if (!wres)
		wres = (GetLastError() == ERROR_PIPE_CONNECTED);

	if (!wres) {
		hprintf(c->pipe, "error ConnectNamedPipe(perr)\n");
		goto finishDisconnectPio;
	}

	SetHandleInformation(c->pio, HANDLE_FLAG_INHERIT, 1);
	SetHandleInformation(c->perr, HANDLE_FLAG_INHERIT, 1);


	SECURITY_ATTRIBUTES sattr;
	sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
	sattr.bInheritHandle = TRUE;
	sattr.lpSecurityDescriptor = NULL;

	PROCESS_INFORMATION pi;
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

	STARTUPINFO si;
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.hStdInput = c->pio;
	si.hStdOutput = c->pio;
	si.hStdError = c->perr;
	si.dwFlags |= STARTF_USESTDHANDLES;

	if (CreateProcessAsUser(
			  c->token,
			  NULL, 
			  cmdline,	/* command line */
			  NULL,	/* process security attributes */
			  NULL,	/* primary thread security attributes */
			  TRUE,	/* handles are inherited */
			  0,	/* creation flags */
			  NULL,	/* use parent's environment */
			  NULL,	/* use parent's current directory */
			  &si,	/* STARTUPINFO pointer */
			  &pi))	/* receives PROCESS_INFORMATION */
	{
		HANDLE hlist[2] = {c->pipe->o.hEvent, pi.hProcess};
		DWORD ec;
		char str[1];
		
		if (!ResetEvent(c->pipe->o.hEvent))
			SvcDebugOut("ResetEvent error - %d\n", GetLastError());
                if (!ReadFile(c->pipe->h, str, 1, NULL, &c->pipe->o) && GetLastError() != ERROR_IO_PENDING)
			SvcDebugOut("ReadFile(control_pipe) error - %d\n", GetLastError());
		ec = WaitForMultipleObjects(2, hlist, FALSE, INFINITE);
		SvcDebugOut("WaitForMultipleObjects=%d\n", ec - WAIT_OBJECT_0);
		if (ec != WAIT_OBJECT_0)
			GetExitCodeProcess(pi.hProcess, &ec);
		else
			TerminateProcess(pi.hProcess, ec = 0x1234);
		FlushFileBuffers(c->pio);
		FlushFileBuffers(c->perr);
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
		hprintf(c->pipe, CMD_RETURN_CODE " %08X\n", ec);
	} else {
		hprintf(c->pipe, "error Creating process(%s) %d\n", cmdline, GetLastError());
	}

	DisconnectNamedPipe(c->perr);
finishDisconnectPio:
	DisconnectNamedPipe(c->pio);
finishClosePerr:
	CloseHandle(c->perr);
finishClosePio:
	CloseHandle(c->pio);
finishCloseToken:
	CloseHandle(c->token);
finish:
	return res;
}

CMD_ITEM cmd_table[] = {
	{"run", cmd_run},
	{"set", cmd_set},
	{"get", cmd_get},
	{NULL, NULL}
};

typedef struct {
	OV_HANDLE *pipe;
	int conn_number;
} connection_data;

#define MAX_COMMAND_LENGTH (32768)

VOID handle_connection(connection_data *data)
{
        char *cmd = 0;
	int res;
	connection_context _c, *c = &_c;
        cmd = malloc(MAX_COMMAND_LENGTH);
        if (!cmd) {
	    hprintf(data->pipe, 
		    "error: unable to allocate buffer for command\n");
	    return;
	}
        ZeroMemory(cmd, MAX_COMMAND_LENGTH);
	ZeroMemory(c, sizeof(connection_context));
	c->pipe = data->pipe;
	c->cmd = cmd;
	c->conn_number = data->conn_number;
	free(data);
	/* FIXME make wait for end of process or ctrl_pipe input */
	while (1) {
		res = hgets(cmd, MAX_COMMAND_LENGTH, c->pipe);
		if (res <= 0) {
			SvcDebugOut("Error reading from pipe(%08X)\n", (int) c->pipe->h);
			goto finish;
		}
		SvcDebugOut("Retrieved line: \"%s\"\n", (int)cmd);
		CMD_ITEM *ci;
		for (ci = cmd_table; ci->name; ++ci) {
			if (strstr(cmd, ci->name) != cmd) continue;
			char c = cmd[strlen(ci->name)];
			if (!c || (c == ' '))
				break;
		}
		if (ci->name) {
			if (!ci->func(c))
				goto finish;
		} else
			hprintf(c->pipe, "error Ignoring unknown command (%s)\n", cmd);
	}
finish:
	FlushFileBuffers(c->pipe->h);
	DisconnectNamedPipe(c->pipe->h);
	CloseHandle(c->pipe->h);
	CloseHandle(c->pipe->o.hEvent);
	free(c->pipe);
        free(cmd);
}

static int conn_number = 0;

DWORD WINAPI server_loop(LPVOID lpParameter)
{
	BOOL res;

	SvcDebugOut("server_loop: alive\n", 0);
	if (!CreatePipesSA()) {
		SvcDebugOut("CreatePipesSA failed (%08X)\n",
			    GetLastError());
		return -1;
	}
	SvcDebugOut("server_loop: CreatePipesSA done\n", 0);
	for (;;) {
		SvcDebugOut("server_loop: Create Pipe\n", 0);
		OV_HANDLE *pipe;
		pipe = (OV_HANDLE *)malloc(sizeof(OV_HANDLE));
		ZeroMemory(&pipe->o, sizeof(OVERLAPPED));
		pipe->o.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
		pipe->h = CreateNamedPipe("\\\\.\\pipe\\" PIPE_NAME,
				       PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
				       PIPE_WAIT,
				       PIPE_UNLIMITED_INSTANCES,
				       BUFSIZE,
				       BUFSIZE,
				       NMPWAIT_USE_DEFAULT_WAIT,
				       &sa);

		if (pipe->h == INVALID_HANDLE_VALUE) {
			SvcDebugOut("CreatePipe failed(%08X)\n",
				    GetLastError());
			CloseHandle(pipe->o.hEvent);
			free(pipe);
			return 0;
		}

		SvcDebugOut("server_loop: Connect Pipe\n", 0);
		if (ConnectNamedPipe(pipe->h, &pipe->o)) {
			SvcDebugOut("server_loop: Connect Pipe err %08X\n", GetLastError());
			res = FALSE;
		} else {
			switch (GetLastError()) {
			case ERROR_IO_PENDING:
				SvcDebugOut("server_loop: Connect Pipe(0) pending\n", 0);
				DWORD t;
				res = GetOverlappedResult(pipe->h, &pipe->o, &t, TRUE);
				break;
			case ERROR_PIPE_CONNECTED:
				SvcDebugOut("server_loop: Connect Pipe(0) connected\n", 0);
				res = TRUE;
				break;
			default:
				SvcDebugOut("server_loop: Connect Pipe(0) err %08X\n", GetLastError());
				res = FALSE;
			}
		}

		if (res) {
			connection_data *cd = malloc(sizeof(connection_data));
			cd->pipe = pipe;
			cd->conn_number = ++conn_number;
			SvcDebugOut("server_loop: CreateThread\n", 0);
			HANDLE th = CreateThread(NULL,	/* no security attribute */
						 0,	/* default stack size */
						 (LPTHREAD_START_ROUTINE)
						 handle_connection,
						 (LPVOID) cd,	/* thread parameter */
						 0,	/* not suspended */
						 NULL);	/* returns thread ID */
			if (!th) {
				SvcDebugOut("Cannot create thread\n", 0);
				CloseHandle(pipe->h);
				CloseHandle(pipe->o.hEvent);
				free(pipe);
			} else {
				CloseHandle(th);
				SvcDebugOut("server_loop: Thread created\n", 0);
			}
		} else {
			SvcDebugOut("server_loop: Pipe not connected\n", 0);
			CloseHandle(pipe->h);
			CloseHandle(pipe->o.hEvent);
			free(pipe);
		}
	}
	SvcDebugOut("server_loop: STH wrong\n", 0);
}
