/*
 * fapolicyd.c - Main file for the program
 * Copyright (c) 2016,2018-22 Red Hat Inc.
 * All Rights Reserved.
 *
 * This software may be freely redistributed and/or modified under the
 * terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING. If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor
 * Boston, MA 02110-1335, USA.
 *
 * Authors:
 *   Steve Grubb <sgrubb@redhat.com>
 *   Radovan Sroka <rsroka@redhat.com>
 */

#include "config.h"
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <ctype.h>
#include <cap-ng.h>
#include <sys/prctl.h>
#include <linux/unistd.h>  /* syscall numbers */
#include <sys/stat.h>	   /* umask */
#include <seccomp.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <limits.h>        /* PATH_MAX */
#include <locale.h>
#include <pthread.h>
#ifndef HAVE_GETTID
#include <sys/syscall.h>
#endif
#ifdef HAVE_MALLINFO2
#include <malloc.h>
#endif
#include "notify.h"
#include "policy.h"
#include "event.h"
#include "escape.h"
#include "fd-fgets.h"
#include "file.h"
#include "database.h"
#include "message.h"
#include "daemon-config.h"
#include "conf.h"
#include "queue.h"
#include "gcc-attributes.h"
#include "avl.h"
#include "paths.h"
#include "string-util.h"
#include "filter.h"

// Global program variables
unsigned int debug_mode = 0;
const char* mounts = MOUNTS_FILE;

// Signal handler notifications
atomic_bool stop = false, hup = false, run_stats = false;

// Global configuration state
conf_t config;
// This holds info about all file systems to watch
struct fs_avl {
	avl_tree_t index;
};
// This is the data about a specific file system to watch
typedef struct fs_data {
	avl_t avl;        // This has to be first
	const char *fs_name;
} fs_data_t;
static struct fs_avl filesystems;
static struct fs_avl ignored_mounts;

// List of mounts being watched
static mlist *m = NULL;

// Reconfiguration
static atomic_bool reconfig_running = false;
// Mount handling
static atomic_bool mounts_running = false;

static void usage(void) NORETURN;

#ifndef HAVE_GETTID
pid_t gettid(void)
{
	return syscall(SYS_gettid);
}
#endif

static void install_syscall_filter(void)
{
	scmp_filter_ctx ctx;
	int rc = -1;

	ctx = seccomp_init(SCMP_ACT_ALLOW);
	if (ctx == NULL)
		goto err_out;
#ifndef USE_RPM
	rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES),
				SCMP_SYS(execve), 0);
	if (rc < 0)
		goto err_out;

# ifdef HAVE_FEXECVE
#  ifdef __NR_fexecve
	rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES),
				SCMP_SYS(fexecve), 0);
	if (rc < 0)
		goto err_out;
#  endif
# endif
#endif
	rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EIO),
				SCMP_SYS(sendfile), 0);
	if (rc < 0)
		goto err_out;

	rc = seccomp_load(ctx);
err_out:
	if (rc < 0)
		msg(LOG_ERR, "Failed installing seccomp filter");
	seccomp_release(ctx);
}


static int cmp_fs(void *a, void *b)
{
	return strcmp(((fs_data_t *)a)->fs_name, ((fs_data_t *)b)->fs_name);
}


/*
 * free_filesystem - release storage for a single AVL entry.
 * @s: filesystem data being destroyed.
 * Returns nothing.
 */
static void free_filesystem(fs_data_t *s)
{
	free((void *)s->fs_name);
	free((void *)s);
}

/*
 * destroy_filesystem - remove the first node from an AVL tree.
 * @list: tree holding filesystem style strings.
 * Returns nothing.
 */
static void destroy_filesystem(struct fs_avl *list)
{
	avl_t *cur = list->index.root;

	fs_data_t *tmp =(fs_data_t *)avl_remove(&list->index, cur);
	if ((avl_t *)tmp != cur)
		msg(LOG_DEBUG, "string list: removal of invalid node");
	free_filesystem(tmp);
}

/*
 * destroy_fs_list - free every node from an AVL tree.
 * @list: tree that should be cleared.
 * Returns nothing.
 */
static void destroy_fs_list(struct fs_avl *list)
{
	while (list->index.root)
		destroy_filesystem(list);
}

/*
 * add_filesystem - insert a string into an AVL tree.
 * @list: tree receiving the entry.
 * @f: filesystem data to insert.
 * Returns: 0 on failure, 1 if the item is in the tree.
 */
static int add_filesystem(struct fs_avl *list, fs_data_t *f)
{
	fs_data_t *tmp=(fs_data_t *)avl_insert(&list->index,(avl_t *)(f));
	if (tmp) {
		if (tmp != f) {
			// already in the tree, delete the current item
			msg(LOG_DEBUG, "fs_list: duplicate filesystem found");
			free_filesystem(f);
		}
		return 1;
	}
	return 0;
}

/*
 * new_filesystem - allocate and add a string into an AVL tree.
 * @list: tree receiving the new entry.
 * @fs: string representing the filesystem or mount point.
 * Returns 1 on success and 0 on failure.
 */
static int new_filesystem(struct fs_avl *list, const char *fs)
{
	fs_data_t *tmp = malloc(sizeof(fs_data_t));
	if (tmp) {
		tmp->fs_name = fs ? strdup(fs) : strdup("");
		if (add_filesystem(list, tmp) == 0) {
			free((void *)tmp->fs_name);
			free(tmp);
			return 0;
		}
		return 1;
	}
	return 0;
}

/*
 * find_filesystem - search the supplied AVL tree for a string.
 * @list: tree that should be searched.
 * @f: string to locate.
 * Returns a pointer to the stored entry or NULL if not found.
 */
static fs_data_t *find_filesystem(struct fs_avl *list, const char *f)
{
	fs_data_t tmp;

	tmp.fs_name = f;
	return (fs_data_t *)avl_search(&list->index, (avl_t *) &tmp);
}

/*
 * add_ignore_mount_entry - callback that validates and stores ignored mounts.
 * @mount: trimmed ignore_mounts entry.
 * @unused: unused context pointer.
 * Returns 0 to continue iterating entries.
 */
static int add_ignore_mount_entry(const char *mount,
	void *unused __attribute__ ((unused)))
{
	const char *warning;
	int rc;

	rc = check_ignore_mount_warning(mounts, mount, &warning);
	if (warning)
		msg(LOG_ERR, warning, mount, mounts);

	if (rc == 1) {
		if (!new_filesystem(&ignored_mounts, mount))
			msg(LOG_ERR, "Cannot store ignore_mounts entry %s", mount);
	}

	return 0;
}
/*
 * init_ignore_mounts - create the ignored mount AVL tree.
 * @ignore_list: comma separated list of mount points to ignore.
 * Returns nothing.
 */
static void init_ignore_mounts(const char *ignore_list)
{
	avl_init(&ignored_mounts.index, cmp_fs);

	if (ignore_list == NULL)
		return;

	if (iterate_ignore_mounts(ignore_list, add_ignore_mount_entry, NULL)) {
		msg(LOG_ERR, "Cannot duplicate ignore_mounts list");
		return;
	}

	if (ignored_mounts.index.root == NULL) {
		free((void *)config.ignore_mounts);
		config.ignore_mounts = NULL;
	}
}
/*
 * mount_is_ignored - check if a mount point is in the ignore list.
 * @point: mount point path.
 * Returns 1 when the mount point should be skipped and 0 otherwise.
 */
static int mount_is_ignored(const char *point)
{
	return find_filesystem(&ignored_mounts, point) ? 1 : 0;
}

/*
 * init_fs_list - create the filesystem type AVL tree.
 * @watch_fs: comma separated list of filesystem types to watch.
 * Returns nothing. Exits on failure when the list is missing.
 */
static void init_fs_list(const char *watch_fs)
{
	if (watch_fs == NULL) {
		msg(LOG_ERR, "File systems to watch is empty");
		exit(1);
	}
	avl_init(&filesystems.index, cmp_fs);

	// Now parse up list and push into avl
	char *ptr, *saved, *tmp = strdup(watch_fs);

	if (tmp == NULL) {
		msg(LOG_ERR, "Cannot duplicate watch_fs list");
		return;
	}

	ptr = strtok_r(tmp, ",", &saved);
	while (ptr) {
		new_filesystem(&filesystems, ptr);
		ptr = strtok_r(NULL, ",", &saved);
	}
	free(tmp);
}

static void term_handler(int sig __attribute__((unused)))
{
	stop = true;
	nudge_queue();
}


static void coredump_handler(int sig)
{
	if (getpid() == gettid()) {
		unmark_fanotify(m);
		unlink_fifo();
		signal(sig, SIG_DFL);
		kill(getpid(), sig);
	} else {
		/*
		 * Fatal signals are usually delivered to the thread generating
		 * them, if this is not main thread, raised the signal again to
		 * handle it there, then wait forever to die.
		 */
		kill(getpid(), sig);
		for (;;) pause();
	}
}


static void hup_handler(int sig __attribute__((unused)))
{
	hup = true;
}

static void usr1_handler(int sig __attribute__((unused)))
{
	run_stats = true;
	nudge_queue();
}

/*
 * reload_configuration - refresh runtime configuration settings.
 * @void: no arguments are required.
 * Returns 0 when the configuration was reloaded, non-zero otherwise.
 */
static int reload_configuration(void)
{
	conf_t new_config;

	if (load_daemon_config(&new_config)) {
		free_daemon_config(&new_config);
		msg(LOG_ERR, "Failed reloading daemon configuration");
		return 1;
	}

	config.permissive = new_config.permissive;

	if (setpriority(PRIO_PROCESS, 0, -(int)new_config.nice_val) == -1)
		msg(LOG_WARNING, "Couldn't adjust priority (%s)",
		strerror(errno));
	config.nice_val = new_config.nice_val;

	config.do_stat_report = new_config.do_stat_report;
	config.detailed_report = new_config.detailed_report;

	if (new_config.integrity != config.integrity) {
		set_integrity_mode(new_config.integrity);
		config.integrity = new_config.integrity;
	}

	if (new_config.syslog_format &&
		(!config.syslog_format ||
		 strcmp(new_config.syslog_format, config.syslog_format) != 0)) {
		char *new_syslog = strdup(new_config.syslog_format);
		if (new_syslog) {
			char *old_syslog;

			lock_rule();
			old_syslog = (char *)config.syslog_format;
			config.syslog_format = new_syslog;
			unlock_rule();

			free(old_syslog);
		} else
			msg(LOG_ERR,
				"Failed replacing syslog_format string");
		}

	config.rpm_sha256_only = new_config.rpm_sha256_only;

	if (new_config.trust && (!config.trust ||
				strcmp(new_config.trust, config.trust) != 0)) {
		char *new_trust = strdup(new_config.trust);
		if (new_trust) {
			char *old_trust = (char *)config.trust;
			config.trust = new_trust;
			free(old_trust);
		} else
			msg(LOG_ERR,
			    "Failed replacing trust backend list");
	}

	/*
	 * Remaining daemon_config fields require restart-time changes:
	 * q_size, subj_cache_size, and obj_cache_size are consumed when the
	 * event queue and caches are created. uid/gid, allow_filesystem_mark,
	 * watch_fs, and ignore_mounts are applied while fanotify marks are
	 * installed. db_max_size fixes the LMDB map when the database opens,
	 * and report_interval is bound to the decision thread's timer. None
	 * of these components support resizing in-place yet, so their
	 * configuration stays static.
	 */

	free_daemon_config(&new_config);

	return 0;
}

static void reconfigure(void)
{
	if (reload_configuration())
		msg(LOG_WARNING,
			"Continuing with previous configuration settings");

	filter_destroy();
	if (filter_init())
		msg(LOG_ERR, "Failed initializing filter configuration");
	else if (filter_load_file(NULL))
		msg(LOG_ERR, "Failed reloading filter configuration");

	set_reload_rules();
	set_reload_trust_database();
}

/*
 * reconfigure_thread_main - run configuration reload outside event loop.
 * @arg: unused pointer.
 * Returns NULL.
 */
static void *reconfigure_thread_main(void *arg  __attribute__ ((unused)))
{
	sigset_t sigs;

	/* This is a worker thread. Don't handle external signals. */
	sigemptyset(&sigs);
	sigaddset(&sigs, SIGTERM);
	sigaddset(&sigs, SIGHUP);
	sigaddset(&sigs, SIGUSR1);
	sigaddset(&sigs, SIGINT);
	sigaddset(&sigs, SIGQUIT);
	pthread_sigmask(SIG_SETMASK, &sigs, NULL);

	reconfigure();
	atomic_store(&reconfig_running, false);
	return NULL;
}

/*
 * maybe_start_reconfigure_thread - start a reconfigure thread when requested
 * and prevent a second one running until first finishes.
 * @void: no arguments are required.
 * Returns nothing.
 */
static void maybe_start_reconfigure_thread(void)
{
	int rc;
	bool expected = false;

	// Make sure one is not runnning since it is detached
	if (!atomic_compare_exchange_strong(&reconfig_running, &expected, true))
		return;

	// OK to start up the thread
	pthread_t tid;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	rc = pthread_create(&tid, &attr,
			reconfigure_thread_main, NULL);
	if (rc) {
		msg(LOG_ERR, "Failed starting reconfigure thread (%s)",
			strerror(rc));
		atomic_store(&reconfig_running, false);
	}
	pthread_attr_destroy(&attr);
}

// This is a workaround for https://bugzilla.redhat.com/show_bug.cgi?id=643031
#define UNUSED(x) (void)(x)
#ifdef USE_RPM
extern int rpmsqEnable (int signum, void *handler);
int rpmsqEnable (int signum, void *handler)
{
	UNUSED(signum);
	UNUSED(handler);
	return 0;
}
#endif


static int write_pid_file(void)
{
	int pidfd, len;
	char val[16];

	len = snprintf(val, sizeof(val), "%u\n", getpid());
	if (len <= 0) {
		msg(LOG_ERR, "Pid error (%s)", strerror(errno));
		return 1;
	}
	pidfd = open(pidfile, O_CREAT | O_TRUNC | O_NOFOLLOW | O_WRONLY, 0644);
	if (pidfd < 0) {
		msg(LOG_ERR, "Unable to create pidfile (%s)",
			strerror(errno));
		return 1;
	}
	if (write(pidfd, val, (unsigned int)len) != len) {
		msg(LOG_ERR, "Unable to write pidfile (%s)",
			strerror(errno));
		close(pidfd);
		return 1;
	}
	close(pidfd);
	return 0;
}


static int become_daemon(void)
{
	int fd;
	pid_t pid;

	pid = fork();
	switch (pid)
	{
		case 0: // Child
			fd = open("/dev/null", O_RDWR);
			if (fd < 0)
				return -1;
			if (dup2(fd, 0) < 0) {
				close(fd);
				return -1;
			}
			if (dup2(fd, 1) < 0) {
				close(fd);
				close(0);
				return -1;
			}
			if (dup2(fd, 2) < 0) {
				close(fd);
				close(0);
				close(1);
				return -1;
			}
			close(fd);
			chdir("/");
			if (setsid() < 0)
				return -1;
			break;
		case -1:
			return -1;
			break;
		default:	// Parent
			_exit(0);
			break;
	}
	return 0;
}


// Returns 1 if we care about the entry and 0 if we do not
static int check_mount_entry(const char *point, const char *type)
{
	// Skip entries explicitly ignored by configuration
	if (mount_is_ignored(point))
		return 0;

	// Some we know we don't want
	if (strncmp(point, "/sys/", 5) == 0)
		return 0;

	if (find_filesystem(&filesystems, type))
		return 1;
	else
		return 0;
}

static void handle_mounts(int fd)
{
	char buf[PATH_MAX * 2], device[1025], point[4097];
	char type[32], mntops[128];
	int fs_req, fs_passno;

	if (m == NULL) {
		m = malloc(sizeof(mlist));
		mlist_create(m);
	}

	// Rewind the descriptor
	lseek(fd, 0, SEEK_SET);
	fd_fgets_state_t *st = fd_fgets_init();
	if (!st)
		return;

	mlist_mark_all_deleted(m);
	do {
		int rc = fd_fgets_r(st, buf, sizeof(buf), fd);
		// Get a line
		if (rc > 0) {
			// Parse it
			sscanf(buf, "%1024s %4096s %31s %127s %d %d\n",
			    device, point, type, mntops, &fs_req, &fs_passno);
			unescape_shell(device, strlen(device));
			unescape_shell(point, strlen(point));
			// Is this one that we care about?
			if (check_mount_entry(point, type)) {
				// Can we find it in the old list?
				if (mlist_find(m, point)) {
					// Mark no change
					m->cur->status = MNT_NO_CHANGE;
				} else
					mlist_append(m, point);
			}
		} else if (rc < 0) // Some kind of error - stop
			break;
	} while (!fd_fgets_eof_r(st));

	fd_fgets_destroy(st);
	// update marks
	fanotify_update(m);
}

/*
 * handle_mounts_thread_main - run mount processing outside event loop.
 * @arg: pointer to a heap-allocated file descriptor integer.
 * Returns NULL.
 */
static void *handle_mounts_thread_main(void *arg)
{
	int fd;
	sigset_t sigs;

	/* This is a worker thread. Don't handle external signals. */
	sigemptyset(&sigs);
	sigaddset(&sigs, SIGTERM);
	sigaddset(&sigs, SIGHUP);
	sigaddset(&sigs, SIGUSR1);
	sigaddset(&sigs, SIGINT);
	sigaddset(&sigs, SIGQUIT);
	pthread_sigmask(SIG_SETMASK, &sigs, NULL);

	if (arg == NULL) {
		atomic_store(&mounts_running, false);
		return NULL;
	}

	fd = *((int *)arg);
	free(arg);

	handle_mounts(fd);
	atomic_store(&mounts_running, false);

	return NULL;
}

/*
 * maybe_start_mounts_thread - start a detached mounts thread when needed.
 * @fd: /proc/mounts file descriptor.
 * Returns nothing.
 */
static void maybe_start_mounts_thread(int fd)
{
	int rc;
	bool expected = false;
	int *arg;

	// Make sure one is not runnning since it is detached
	if (!atomic_compare_exchange_strong(&mounts_running, &expected, true))
		return;

	arg = malloc(sizeof(int));
	if (arg == NULL) {
		msg(LOG_ERR, "Failed allocating mount thread arg");
		atomic_store(&mounts_running, false);
		return;
	}
	*arg = fd;

	pthread_t tid;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	rc = pthread_create(&tid, &attr,
		handle_mounts_thread_main, arg);
	if (rc) {
		msg(LOG_ERR, "Failed starting mount thread (%s)",
				strerror(rc));
		atomic_store(&mounts_running, false);
		free(arg);
	}
	pthread_attr_destroy(&attr);
}


static void usage(void)
{
	fprintf(stderr,
		"Usage: fapolicyd [--debug|--debug-deny] [--permissive] "
		"[--no-details] [--version]\n");
	exit(1);
}

#ifdef HAVE_MALLINFO2
static struct mallinfo2 last_mi;
static void memory_use_report(FILE *f)
{
	struct mallinfo2 mi = mallinfo2();

	fprintf(f, "glibc arena (total memory) is: %zu KiB, was: %zu KiB\n",
			(size_t)mi.arena/1024, (size_t)last_mi.arena/1024);
	fprintf(f, "glibc uordblks (in use memory) is: %zu KiB, was: %zu KiB\n",
			(size_t)mi.uordblks/1024,(size_t)last_mi.uordblks/1024);
	fprintf(f,"glibc fordblks (total free space) is: %zu KiB, was: %zu KiB\n",
			(size_t)mi.fordblks/1024,(size_t)last_mi.fordblks/1024);

	memcpy(&last_mi, &mi, sizeof(struct mallinfo2));
}

static void close_memory_report(void)
{
	struct mallinfo2 mi = mallinfo2();

	msg(LOG_DEBUG, "total memory: %zu KiB, was: %zu KiB",
			(size_t)mi.arena/1024, (size_t)last_mi.arena/1024);
	msg(LOG_DEBUG, "in use memory: %zu KiB, was: %zu KiB",
			(size_t)mi.uordblks/1024,(size_t)last_mi.uordblks/1024);
	msg(LOG_DEBUG,"total free memory: %zu KiB, was: %zu KiB",
			(size_t)mi.fordblks/1024,(size_t)last_mi.fordblks/1024);
}
#endif

void do_stat_report(FILE *f, int shutdown)
{
	const char *ptr = lookup_integrity(config.integrity);

	fprintf(f, "Permissive: %s\n", config.permissive ? "true" : "false");
	fprintf(f, "Integrity: %s\n", ptr ? ptr : "unknown");
	fprintf(f, "CPU cores: %ld\n", sysconf(_SC_NPROCESSORS_ONLN));
	fprintf(f, "q_size: %u\n", config.q_size);
	q_report(f);
	decision_report(f);
	database_report(f);
#ifdef HAVE_MALLINFO2
	memory_use_report(f);
#endif
	if (!shutdown)
		do_cache_reports(f);

	// Report mounts under fanotify watch
	if (m) {
		const char *path = mlist_first(m);
		while (path) {
			fprintf(f, "watching mount: %s\n", path);
			path = mlist_next(m);
		}
	}

	if (shutdown)
		fputs("\n", f);
}

int already_running(void)
{
	fd_fgets_state_t *st = fd_fgets_init();
	if (!st)
		return 1;

	int pidfd = open(pidfile, O_RDONLY);
	if (pidfd >= 0) {
		char pid_buf[16];

		if (fd_fgets_r(st, pid_buf, sizeof(pid_buf), pidfd)) {
			int pid;
			char exe_buf[80], my_path[80];

			// Get our path
			if (get_program_from_pid(getpid(),
					sizeof(exe_buf), my_path) == NULL)
				goto err_out; // shouldn't happen, but be safe

			// convert pidfile to integer
			errno = 0;
			pid = strtoul(pid_buf, NULL, 10);
			if (errno)
				goto err_out; // shouldn't happen, but be safe

			// verify it really is fapolicyd
			if (get_program_from_pid(pid,
					sizeof(exe_buf), exe_buf) == NULL)
				goto good; //if pid doesn't exist, we're OK

			// If the path doesn't have fapolicyd in it, we're OK
			if (strstr(exe_buf, "fapolicyd") == NULL)
				goto good;

			if (strcmp(exe_buf, my_path) == 0)
				goto err_out; // if the same, we need to exit

			// one last sanity check in case path is unexpected
			// for example: /sbin/fapolicyd & /home/test/fapolicyd
			if (pid != getpid())
				goto err_out;
good:
			fd_fgets_destroy(st);
			close(pidfd);
			unlink(pidfile);
			return 0;
		} else
		    msg(LOG_ERR, "fapolicyd pid file found but unreadable");
err_out: // At this point, we have a pid file, let's just assume it's alive
	 // because if 2 are running, it deadlocks the machine

		fd_fgets_destroy(st);
		close(pidfd);
		return 1;
	}

	fd_fgets_destroy(st);
	return 0; // pid file doesn't exist, we're good to go
}

int main(int argc, const char *argv[])
{
	struct pollfd pfd[2];
	struct sigaction sa;
	struct rlimit limit;

	setlocale(LC_TIME, "");

	for (int i=1; i < argc; i++) {
		if (strcmp(argv[i], "--help") == 0)
			usage();
		else if (strcmp(argv[i], "--version") == 0) {
			printf("fapolicyd %s\n", VERSION);
			return 0;
		}
	}
	set_message_mode(MSG_STDERR, debug_mode);
	if (load_daemon_config(&config)) {
		free_daemon_config(&config);
		msg(LOG_ERR, "Exiting due to bad configuration");
		return 1;
        }

	// set the debug flags
	for (int i=1; i < argc; i++) {
		if (strcmp(argv[i], "--debug") == 0) {
			debug_mode = 1;
			set_message_mode(MSG_STDERR, DBG_YES);
		} else if (strcmp(argv[i], "--debug-deny") == 0) {
			debug_mode = 2;
			set_message_mode(MSG_STDERR, DBG_YES);
		}
	}
	// process remaining flags
	for (int i=1; i < argc; i++) {
		if (strcmp(argv[i], "--permissive") == 0) {
			config.permissive = 1;
		} else if (strcmp(argv[i], "--boost") == 0) {
			i++;
			msg(LOG_ERR, "boost value on the command line is"
				" deprecated - ignoring");
		} else if (strcmp(argv[i], "--queue") == 0) {
			i++;
			msg(LOG_ERR, "queue value on the command line is"
				" deprecated - ignoring");
		} else if (strcmp(argv[i], "--user") == 0) {
			i++;
			msg(LOG_ERR, "user value on the command line is"
				" deprecated - ignoring");
		} else if (strcmp(argv[i], "--group") == 0) {
			i++;
			msg(LOG_ERR, "group value on the command line is"
				" deprecated - ignoring");
		} else if (strcmp(argv[i], "--no-details") == 0) {
			config.detailed_report = 0;
		} else if (strncmp(argv[i], "--mounts", 8) == 0) {
			if (!debug_mode) {
				msg(LOG_ERR, "the mounts flag can only be"
							 " used in debug mode");
				return 1;
			}
			// require an equals and at least one char long path
			if (strlen(argv[i]) < 10 || argv[i][8] != '=') {
				msg(LOG_ERR, "the mounts flag requires a file"
							 " path: --mounts=/tmp/mounts.txt");
				return 1;
			}
			// ensure we have specified a regular file
			struct stat sb;
			const char *tmp = argv[i] + 9;
			if (stat(tmp, &sb) != 0) {
				msg(LOG_ERR, "cannot stat mounts file %s, %s",
					tmp, strerror(errno));
				return 1;
			}
			if (!S_ISREG(sb.st_mode)) {
				msg(LOG_ERR, "mounts path %s is not a regular file", tmp);
				return 1;
			}
			msg(LOG_INFO, "Overriding mounts file: %s", tmp);
			mounts = tmp;
		} else if (strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "--debug-deny") == 0) {
			// nop; debug flags already set
		} else {
			msg(LOG_ERR, "unknown command option:%s\n", argv[i]);
			free_daemon_config(&config);
			usage();
		}
	}

	if (already_running()) {
		msg(LOG_ERR, "fapolicyd is already running");
		exit(1);
	}

	// Set a couple signal handlers
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);
	sa.sa_handler = hup_handler;
	sigaction(SIGHUP, &sa, NULL);
	sa.sa_handler = coredump_handler;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGABRT, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);
	sigaction(SIGFPE, &sa, NULL);
	sigaction(SIGILL, &sa, NULL);
	sigaction(SIGSYS, &sa, NULL);
	sigaction(SIGTRAP, &sa, NULL);
	sigaction(SIGXCPU, &sa, NULL);
	sigaction(SIGXFSZ, &sa, NULL);
	sigaction(SIGQUIT, &sa, NULL);

	sa.sa_handler = usr1_handler;
	sigaction(SIGUSR1, &sa, NULL);
	/* These need to be last since they are used later */
	sa.sa_handler = term_handler;
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT, &sa, NULL);

	// Bump up resources
	limit.rlim_cur = RLIM_INFINITY;
	limit.rlim_max = RLIM_INFINITY;
	setrlimit(RLIMIT_FSIZE, &limit);
	getrlimit(RLIMIT_NOFILE, &limit);
	if (limit.rlim_max >= 16384)
		limit.rlim_cur = limit.rlim_max;
	else
		limit.rlim_max = limit.rlim_cur = 16834;

	if (setrlimit(RLIMIT_NOFILE, &limit))
		msg(LOG_WARNING, "Can't increase file number rlimit - %s",
		    strerror(errno));
	else
		msg(LOG_INFO,"Can handle %lu file descriptors", limit.rlim_cur);

	// get more time slices because everything is waiting on us
	errno = 0;
	nice(-config.nice_val);
	if (errno)
		msg(LOG_WARNING, "Couldn't adjust priority (%s)",
				strerror(errno));

	// Load the rule configuration
	if (load_rules(&config))
		exit(1);
	if (!debug_mode) {
		if (become_daemon() < 0) {
			msg(LOG_ERR, "Exiting due to failure daemonizing");
			exit(1);
		}
		set_message_mode(MSG_SYSLOG, DBG_NO);
		openlog("fapolicyd", LOG_PID, LOG_DAEMON);
	}

	// Set the exit function so there is always a fifo cleanup
	if (atexit(unlink_fifo)) {
		msg(LOG_ERR, "Cannot set exit function");
		exit(1);
	}

	// Setup filesystem to watch list
	init_ignore_mounts(config.ignore_mounts);
	init_fs_list(config.watch_fs);

	// Write the pid file for the init system
	write_pid_file();

	// Set strict umask
	(void) umask( 0117 );

	if (preconstruct_fifo(&config)) {
		unlink(pidfile);
		msg(LOG_ERR, "Cannot construct a pipe");
		exit(1);
	}

	// If we are not going to be root, then setup necessary capabilities
	if (config.uid != 0) {
		capng_clear(CAPNG_SELECT_BOTH);
		capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED,
			CAP_DAC_OVERRIDE, CAP_SYS_ADMIN, CAP_SYS_PTRACE,
			CAP_SYS_NICE, CAP_SYS_RESOURCE, CAP_AUDIT_WRITE, -1);
		if (capng_change_id(config.uid, config.gid,
							CAPNG_DROP_SUPP_GRP)) {
			msg(LOG_ERR, "Cannot change to uid %d", config.uid);
			exit(1);
		} else
			msg(LOG_DEBUG, "Changed to uid %d", config.uid);
	}

	// Install seccomp filter to prevent escalation
	install_syscall_filter();

	// Setup lru caches
	init_event_system(&config);

	// Init the database
	if (init_database(&config)) {
		destroy_event_system();
		destroy_rules();
		destroy_fs_list(&filesystems);
		destroy_fs_list(&ignored_mounts);
		free_daemon_config(&config);
		unlink(pidfile);
		exit(1);
	}

	// Init the file test libraries
	file_init();

	// Initialize the file watch system
	pfd[0].fd = open(mounts, O_RDONLY);
	pfd[0].events = POLLPRI;
	handle_mounts(pfd[0].fd);
	pfd[1].fd = init_fanotify(&config, m);
	pfd[1].events = POLLIN;

	msg(LOG_INFO, "Starting to listen for events");
	while (!stop) {
		int rc;
		if (hup) {
			hup = false;
			msg(LOG_DEBUG, "Got SIGHUP");
			maybe_start_reconfigure_thread();
		}
		rc = poll(pfd, 2, -1);

#ifdef DEBUG
		msg(LOG_DEBUG, "Main poll interrupted");
#endif
		if (rc < 0) {
			if (errno == EINTR)
				continue;
			else {
				stop = true;
				nudge_queue();
				close_database();
				msg(LOG_ERR, "Poll error (%s)\n",
					strerror(errno));
				exit(1);
			}
		} else if (rc > 0) {
			if (pfd[1].revents & POLLIN) {
				handle_events();
			}
			if (pfd[0].revents & POLLPRI) {
				msg(LOG_DEBUG, "Mount change detected");
				maybe_start_mounts_thread(pfd[0].fd);
			}

			// This will always need to be here as long as we
			// link against librpm. Turns out that librpm masks
			// signals to prevent corrupted databases during an
			// update. Since we only do read access, we can turn
			// them back on.
#ifdef USE_RPM
			sigaction(SIGTERM, &sa, NULL);
			sigaction(SIGINT, &sa, NULL);
#endif
		}
	}
	msg(LOG_INFO, "shutting down...");
	shutdown_fanotify(m);
	close(pfd[0].fd);
	file_close();
	close_database();
#ifdef HAVE_MALLINFO2
	close_memory_report();
#endif
	unlink(pidfile);
	// Reinstate the strict umask in case rpm messed with it
	(void) umask( 0237 );
	if (config.do_stat_report) {
		FILE *f = fopen(REPORT, "w");
		if (f == NULL)
			msg(LOG_WARNING, "Cannot create usage report");
		else {
			do_stat_report(f, 1);
			run_usage_report(&config, f);
			fclose(f);
		}
	}
	mlist_clear(m);	// removes mounts
	free(m);
	destroy_event_system(); // clears lru caches
	destroy_rules();
	destroy_fs_list(&filesystems);
	destroy_fs_list(&ignored_mounts);
	free_daemon_config(&config);

	return 0;
}
