/*
 * Copyright (C) 2014-2019 Firejail Authors
 *
 * This file is part of firejail project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, 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; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "firejail.h"
#include <sys/stat.h>
#include <linux/loop.h>

#define MAX_READ 8192				  // line buffer for profile files

static int initialized = 0;
static int cfg_val[CFG_MAX];
char *xephyr_screen = "800x600";
char *xephyr_extra_params = "";
char *xpra_extra_params = "";
char *xvfb_screen = "800x600x24";
char *xvfb_extra_params = "";
char *netfilter_default = NULL;

int checkcfg(int val) {
	assert(val < CFG_MAX);
	int line = 0;
	FILE *fp = NULL;
	char *ptr;

	if (!initialized) {
		// initialize defaults
		int i;
		for (i = 0; i < CFG_MAX; i++)
			cfg_val[i] = 1; // most of them are enabled by default
		cfg_val[CFG_RESTRICTED_NETWORK] = 0; // disabled by default
		cfg_val[CFG_FORCE_NONEWPRIVS] = 0;
		cfg_val[CFG_PRIVATE_BIN_NO_LOCAL] = 0;
		cfg_val[CFG_FIREJAIL_PROMPT] = 0;
		cfg_val[CFG_DISABLE_MNT] = 0;
		cfg_val[CFG_ARP_PROBES] = DEFAULT_ARP_PROBES;
		cfg_val[CFG_XPRA_ATTACH] = 0;

		// open configuration file
		const char *fname = SYSCONFDIR "/firejail.config";
		fp = fopen(fname, "r");
		if (!fp) {
#ifdef HAVE_GLOBALCFG
			fprintf(stderr, "Error: Firejail configuration file %s not found\n", fname);
			exit(1);
#else
			initialized = 1;
			return	cfg_val[val];
#endif
		}

		// read configuration file
		char buf[MAX_READ];
		while (fgets(buf,MAX_READ, fp)) {
			line++;
			if (*buf == '#' || *buf == '\n')
				continue;

#define PARSE_YESNO(key, string) \
			else if (strncmp(ptr, string " ", strlen(string)+1) == 0) { \
				if (strcmp(ptr + strlen(string) + 1, "yes") == 0) \
					cfg_val[key] = 1; \
				else if (strcmp(ptr + strlen(string) + 1, "no") == 0) \
					cfg_val[key] = 0; \
				else \
					goto errout; \
			}

			// parse line
			ptr = line_remove_spaces(buf);
			if (!ptr)
				continue;
			PARSE_YESNO(CFG_FILE_TRANSFER, "file-transfer")
			PARSE_YESNO(CFG_DBUS, "dbus")
			PARSE_YESNO(CFG_JOIN, "join")
			PARSE_YESNO(CFG_X11, "x11")
			PARSE_YESNO(CFG_APPARMOR, "apparmor")
			PARSE_YESNO(CFG_BIND, "bind")
			PARSE_YESNO(CFG_CGROUP, "cgroup")
			PARSE_YESNO(CFG_NAME_CHANGE, "name-change")
			PARSE_YESNO(CFG_USERNS, "userns")
			PARSE_YESNO(CFG_CHROOT, "chroot")
			PARSE_YESNO(CFG_FIREJAIL_PROMPT, "firejail-prompt")
			PARSE_YESNO(CFG_FOLLOW_SYMLINK_AS_USER, "follow-symlink-as-user")
			PARSE_YESNO(CFG_FORCE_NONEWPRIVS, "force-nonewprivs")
			PARSE_YESNO(CFG_SECCOMP, "seccomp")
			PARSE_YESNO(CFG_WHITELIST, "whitelist")
			PARSE_YESNO(CFG_NETWORK, "network")
			PARSE_YESNO(CFG_RESTRICTED_NETWORK, "restricted-network")
			PARSE_YESNO(CFG_XEPHYR_WINDOW_TITLE, "xephyr-window-title")
			PARSE_YESNO(CFG_OVERLAYFS, "overlayfs")
			PARSE_YESNO(CFG_PRIVATE_HOME, "private-home")
			PARSE_YESNO(CFG_PRIVATE_CACHE, "private-cache")
			PARSE_YESNO(CFG_PRIVATE_LIB, "private-lib")
			PARSE_YESNO(CFG_PRIVATE_BIN_NO_LOCAL, "private-bin-no-local")
			PARSE_YESNO(CFG_DISABLE_MNT, "disable-mnt")
			PARSE_YESNO(CFG_XPRA_ATTACH, "xpra-attach")
			PARSE_YESNO(CFG_BROWSER_DISABLE_U2F, "browser-disable-u2f")
			PARSE_YESNO(CFG_BROWSER_ALLOW_DRM, "browser-allow-drm")
#undef PARSE_YESNO

			// netfilter
			else if (strncmp(ptr, "netfilter-default ", 18) == 0) {
				char *fname = ptr + 18;
				while (*fname == ' ' || *fname == '\t')
					ptr++;
				char *end = strchr(fname, ' ');
				if (end)
					*end = '\0';

				// is the file present?
				struct stat s;
				if (stat(fname, &s) == -1) {
					fprintf(stderr, "Error: netfilter-default file %s not available\n", fname);
					exit(1);
				}

				if (netfilter_default)
					goto errout;
				netfilter_default = strdup(fname);
				if (!netfilter_default)
					errExit("strdup");
				if (arg_debug)
					printf("netfilter default file %s\n", fname);
			}

			// Xephyr screen size
			else if (strncmp(ptr, "xephyr-screen ", 14) == 0) {
				// expecting two numbers and an x between them
				int n1;
				int n2;
				int rv = sscanf(ptr + 14, "%dx%d", &n1, &n2);
				if (rv != 2)
					goto errout;
				if (asprintf(&xephyr_screen, "%dx%d", n1, n2) == -1)
					errExit("asprintf");
			}

			// Xephyr command extra parameters
			else if (strncmp(ptr, "xephyr-extra-params ", 20) == 0) {
				if (*xephyr_extra_params != '\0')
					goto errout;
				xephyr_extra_params = strdup(ptr + 20);
				if (!xephyr_extra_params)
					errExit("strdup");
			}

			// xpra server extra parameters
			else if (strncmp(ptr, "xpra-extra-params ", 18) == 0) {
				if (*xpra_extra_params != '\0')
					goto errout;
				xpra_extra_params = strdup(ptr + 18);
				if (!xpra_extra_params)
					errExit("strdup");
			}

			// Xvfb screen size
			else if (strncmp(ptr, "xvfb-screen ", 12) == 0) {
				// expecting three numbers separated by x's
				unsigned int n1;
				unsigned int n2;
				unsigned int n3;
				int rv = sscanf(ptr + 12, "%ux%ux%u", &n1, &n2, &n3);
				if (rv != 3)
					goto errout;
				if (asprintf(&xvfb_screen, "%ux%ux%u", n1, n2, n3) == -1)
					errExit("asprintf");
			}

			// Xvfb extra parameters
			else if (strncmp(ptr, "xvfb-extra-params ", 18) == 0) {
				if (*xvfb_extra_params != '\0')
					goto errout;
				xvfb_extra_params = strdup(ptr + 18);
				if (!xvfb_extra_params)
					errExit("strdup");
			}

			// quiet by default
			else if (strncmp(ptr, "quiet-by-default ", 17) == 0) {
				if (strcmp(ptr + 17, "yes") == 0)
					arg_quiet = 1;
				else if (strcmp(ptr + 17, "no") == 0)
					arg_quiet = 0;
				else
					goto errout;
			}
			// arp probes
			else if (strncmp(ptr, "arp-probes ", 11) == 0) {
				int arp_probes = atoi(ptr + 11);
				if (arp_probes <= 1 || arp_probes > 30)
					goto errout;
				cfg_val[CFG_ARP_PROBES] = arp_probes;
			}

			// file copy limit
			else if (strncmp(ptr, "file-copy-limit ", 16) == 0) {
				if (setenv("FIREJAIL_FILE_COPY_LIMIT", ptr + 16, 1) == -1)
					errExit("setenv");
			}
			else
				goto errout;

			free(ptr);
		}

		fclose(fp);
		initialized = 1;
	}


	// merge CFG_RESTRICTED_NETWORK into CFG_NETWORK
	if (val == CFG_NETWORK) {
		if (cfg_val[CFG_RESTRICTED_NETWORK] && getuid() != 0)
			return 0;
	}

	return cfg_val[val];

errout:
	assert(ptr);
	free(ptr);
	assert(fp);
	fclose(fp);
	fprintf(stderr, "Error: invalid line %d in firejail configuration file\n", line );
	exit(1);
}


void print_compiletime_support(void) {
	printf("Compile time support:\n");
	printf("\t- AppArmor support is %s\n",
#ifdef HAVE_APPARMOR
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- AppImage support is %s\n",
#ifdef LOOP_CTL_GET_FREE	// test for older kernels; this definition is found in /usr/include/linux/loop.h
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- chroot support is %s\n",
#ifdef HAVE_CHROOT
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- file and directory whitelisting support is %s\n",
#ifdef HAVE_WHITELIST
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- file transfer support is %s\n",
#ifdef HAVE_FILE_TRANSFER
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- firetunnel support is %s\n",
#ifdef HAVE_FIRETUNNEL
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- networking support is %s\n",
#ifdef HAVE_NETWORK
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- overlayfs support is %s\n",
#ifdef HAVE_OVERLAYFS
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- private-home support is %s\n",
#ifdef HAVE_PRIVATE_HOME
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- seccomp-bpf support is %s\n",
#ifdef HAVE_SECCOMP
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- user namespace support is %s\n",
#ifdef HAVE_USERNS
		"enabled"
#else
		"disabled"
#endif
		);

	printf("\t- X11 sandboxing support is %s\n",
#ifdef HAVE_X11
		"enabled"
#else
		"disabled"
#endif
		);
}
