
#include <Log.h>
#include <TAPIInlineQuery.h>
#include <TAPIMarkup.h>
#include <TAPIManager.h>
#include <functional> //std bind
#include <signal.h> // signal
#include <fstream>

#include <sched.h> //unshare
#include <unistd.h> //getpid
#include <sys/mount.h> // umount
#include <sys/prctl.h>
#include <seccomp.h>
#include <fcntl.h>
#include <sys/capability.h>

#include <nlohmann/json.hpp>
using json = nlohmann::json;

#include "bot.h"

static bool run = true;

void sig_handler(int sig_num) {
	Log::info << "signalHandler triggered";
	run = false;
	(void) sig_num;
}

class CommandStart : public TelegramAPI::Command {
public:
	CommandStart() : TelegramAPI::Command("/start") {}
	virtual bool process(TelegramAPI::Manager* m, const TelegramAPI::Message& msg) {
		TelegramAPI::InlineKeyboard kb;
		kb.addButton(TelegramAPI::InlineButton::createSwitchInlineQuery("Open in Chat"), 0);
		return m->sendMessage(msg.chat.id, "This bot is inline only.", kb);
	}
};

static bool enableSecurity() {
	// create new mount and user namespace
	if(unshare(CLONE_NEWUSER|CLONE_NEWNS) == -1) {
		Log::warn << "unsahre(NEWUSER|NEWNS) failed: " << strerror(errno);
	}

	Log::info << "new userid: " << getuid();

	// chroot in current directory
	if(chroot("./") == -1) {
		Log::fatal << "chroot failed " << strerror(errno);
		return false;
	}

	//setup seccomp
	scmp_filter_ctx scmp = seccomp_init(SCMP_ACT_KILL_PROCESS);
	if(!scmp) {
		Log::fatal << "init seccmp failed";
		return false;
	}

	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(poll), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1, SCMP_A2_64(SCMP_CMP_MASKED_EQ, ~ (O_RDONLY | O_NONBLOCK | O_CLOEXEC), 0)); // datein nur readonly und ggf nonblock öffnen
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(stat), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0);

	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(readv), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(pwritev), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(preadv), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(pwritev2), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(preadv2), 0);

	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(restart_syscall), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(connect), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(socketpair), 0); // what?
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(get_robust_list), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(sendmmsg), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0);

	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0); // curl wants to spawn threads
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
	seccomp_rule_add(scmp, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0);

	//apply seccomp
	int err = seccomp_load(scmp);
	if(err != 0) {
		seccomp_release(scmp);
		Log::fatal << "could not load seccomp " << strerror(err);
		return false;
	}
	seccomp_release(scmp);

	/*
	//drop all capabilities and apply nobody uid
	const uid_t nobody = 65534;
	const gid_t groups[] = {65534};
	if (cap_setgroups(groups[0], 1, groups) != 0) {
		Log::fatal << "cap_setgroups() failed: " << strerror(errno);
		return false;
	}
	if (cap_setuid(nobody) != 0) {
		Log::fatal << "cap_setuid() failed: " << strerror(errno);
		return false;
	}

	if(cap_set_mode(CAP_MODE_NOPRIV) != 0) {
		Log::fatal << "cap_set_mode(CAP_MODE_NOPRIV) failed: " << strerror(errno);
		return false;
	}
	*/

	return true;
}

int main(int argc, const char** argv) {
	Log::init();
	Log::setConsoleLogLevel(Log::Level::TRACE);
	Log::addLogfile("log.txt", Log::Level::WARN);
#if __unix__
	Log::setColoredOutput(true);
#endif

	Log::info << "Hello, World! pid: " << getpid() << " userid: " << getuid();

	//read config
	std::ifstream configfile("config.json");
	json j;
	configfile >> j;

	FontBot bot;
	TelegramAPI::Manager tapi(j["telegram"]["apikey"]);

	if(!enableSecurity())
		return 1;

	// test seccomp settings
	// Log::info << "try to open this file";
	// int fd = open(argv[0], O_WRONLY);
	// Log::fatal << "ok";

	std::unique_ptr<TelegramAPI::Command> cmd(new CommandStart());
	tapi.registerCommand(std::move(cmd));

	namespace pl = std::placeholders;
	tapi.setInlineQueryHandler(std::bind(&FontBot::handleInline, &bot, pl::_1, pl::_2));

	signal(SIGINT, sig_handler);

	while(run) {
		tapi.getUpdates();
	}

	Log::stop();
	return 0;
}

