コード例 #1
0
ファイル: start-stop-daemon.c プロジェクト: mattz7/openrc-1
int main(int argc, char **argv)
{
	int devnull_fd = -1;
#ifdef TIOCNOTTY
	int tty_fd = -1;
#endif

#ifdef HAVE_PAM
	pam_handle_t *pamh = NULL;
	int pamr;
	const char *const *pamenv = NULL;
#endif

	int opt;
	bool start = false;
	bool stop = false;
	bool oknodo = false;
	bool test = false;
	char *exec = NULL;
	char *startas = NULL;
	char *name = NULL;
	char *pidfile = NULL;
	char *retry = NULL;
	int sig = -1;
	int nicelevel = 0, ionicec = -1, ioniced = 0;
	bool background = false;
	bool makepidfile = false;
	bool interpreted = false;
	bool progress = false;
	uid_t uid = 0;
	gid_t gid = 0;
	char *home = NULL;
	int tid = 0;
	char *redirect_stderr = NULL;
	char *redirect_stdout = NULL;
	int stdin_fd;
	int stdout_fd;
	int stderr_fd;
	pid_t pid, spid;
	int i;
	char *svcname = getenv("RC_SVCNAME");
	RC_STRINGLIST *env_list;
	RC_STRING *env;
	char *tmp, *newpath, *np;
	char *p;
	char *token;
	char exec_file[PATH_MAX];
	struct passwd *pw;
	struct group *gr;
	char line[130];
	FILE *fp;
	size_t len;
	mode_t numask = 022;
	char **margv;
	unsigned int start_wait = 0;

	applet = basename_c(argv[0]);
	TAILQ_INIT(&schedule);
#ifdef DEBUG_MEMORY
	atexit(cleanup);
#endif

	signal_setup(SIGINT, handle_signal);
	signal_setup(SIGQUIT, handle_signal);
	signal_setup(SIGTERM, handle_signal);

	if ((tmp = getenv("SSD_NICELEVEL")))
		if (sscanf(tmp, "%d", &nicelevel) != 1)
			eerror("%s: invalid nice level `%s' (SSD_NICELEVEL)",
			    applet, tmp);

	/* Get our user name and initial dir */
	p = getenv("USER");
	home = getenv("HOME");
	if (home == NULL || p == NULL) {
		pw = getpwuid(getuid());
		if (pw != NULL) {
			if (p == NULL)
				setenv("USER", pw->pw_name, 1);
			if (home == NULL) {
				setenv("HOME", pw->pw_dir, 1);
				home = pw->pw_dir;
			}
		}
	}

	while ((opt = getopt_long(argc, argv, getoptstring, longopts,
		    (int *) 0)) != -1)
		switch (opt) {
		case 'I': /* --ionice */
			if (sscanf(optarg, "%d:%d", &ionicec, &ioniced) == 0)
				eerrorx("%s: invalid ionice `%s'",
				    applet, optarg);
			if (ionicec == 0)
				ioniced = 0;
			else if (ionicec == 3)
				ioniced = 7;
			ionicec <<= 13; /* class shift */
			break;

		case 'K':  /* --stop */
			stop = true;
			break;

		case 'N':  /* --nice */
			if (sscanf(optarg, "%d", &nicelevel) != 1)
				eerrorx("%s: invalid nice level `%s'",
				    applet, optarg);
			break;

		case 'P':  /* --progress */
			progress = true;
			break;

		case 'R':  /* --retry <schedule>|<timeout> */
			retry = optarg;
			break;

		case 'S':  /* --start */
			start = true;
			break;

		case 'b':  /* --background */
			background = true;
			break;

		case 'c':  /* --chuid <username>|<uid> */
			/* DEPRECATED */
			ewarn("WARNING: -c/--chuid is deprecated and will be removed in the future, please use -u/--user instead");
		case 'u':  /* --user <username>|<uid> */
		{
			p = optarg;
			tmp = strsep(&p, ":");
			changeuser = xstrdup(tmp);
			if (sscanf(tmp, "%d", &tid) != 1)
				pw = getpwnam(tmp);
			else
				pw = getpwuid((uid_t)tid);

			if (pw == NULL)
				eerrorx("%s: user `%s' not found",
				    applet, tmp);
			uid = pw->pw_uid;
			home = pw->pw_dir;
			unsetenv("HOME");
			if (pw->pw_dir)
				setenv("HOME", pw->pw_dir, 1);
			unsetenv("USER");
			if (pw->pw_name)
				setenv("USER", pw->pw_name, 1);
			if (gid == 0)
				gid = pw->pw_gid;

			if (p) {
				tmp = strsep (&p, ":");
				if (sscanf(tmp, "%d", &tid) != 1)
					gr = getgrnam(tmp);
				else
					gr = getgrgid((gid_t) tid);

				if (gr == NULL)
					eerrorx("%s: group `%s'"
					    " not found",
					    applet, tmp);
				gid = gr->gr_gid;
			}
		}
		break;

		case 'd':  /* --chdir /new/dir */
			ch_dir = optarg;
			break;

		case 'e': /* --env */
			putenv(optarg);
			break;

		case 'g':  /* --group <group>|<gid> */
			if (sscanf(optarg, "%d", &tid) != 1)
				gr = getgrnam(optarg);
			else
				gr = getgrgid((gid_t)tid);
			if (gr == NULL)
				eerrorx("%s: group `%s' not found",
				    applet, optarg);
			gid = gr->gr_gid;
			break;

		case 'i': /* --interpreted */
			interpreted = true;
			break;

		case 'k':
			if (parse_mode(&numask, optarg))
				eerrorx("%s: invalid mode `%s'",
				    applet, optarg);
			break;

		case 'm':  /* --make-pidfile */
			makepidfile = true;
			break;

		case 'n':  /* --name <process-name> */
			name = optarg;
			break;

		case 'o':  /* --oknodo */
			/* DEPRECATED */
			ewarn("WARNING: -o/--oknodo is deprecated and will be removed in the future");
			oknodo = true;
			break;

		case 'p':  /* --pidfile <pid-file> */
			pidfile = optarg;
			break;

		case 's':  /* --signal <signal> */
			sig = parse_signal(optarg);
			break;

		case 't':  /* --test */
			test = true;
			break;

		case 'r':  /* --chroot /new/root */
			ch_root = optarg;
			break;

		case 'a': /* --startas <name> */
			/* DEPRECATED */
			ewarn("WARNING: -a/--startas is deprecated and will be removed in the future, please use -x/--exec or -n/--name instead");
			startas = optarg;
			break;
		case 'w':
			if (sscanf(optarg, "%d", &start_wait) != 1)
				eerrorx("%s: `%s' not a number",
				    applet, optarg);
			break;
		case 'x':  /* --exec <executable> */
			exec = optarg;
			break;

		case '1':   /* --stdout /path/to/stdout.lgfile */
			redirect_stdout = optarg;
			break;

		case '2':  /* --stderr /path/to/stderr.logfile */
			redirect_stderr = optarg;
			break;

		case_RC_COMMON_GETOPT
		}

	endpwent();
	argc -= optind;
	argv += optind;

	/* Allow start-stop-daemon --signal HUP --exec /usr/sbin/dnsmasq
	 * instead of forcing --stop --oknodo as well */
	if (!start &&
	    !stop &&
	    sig != SIGINT &&
	    sig != SIGTERM &&
	    sig != SIGQUIT &&
	    sig != SIGKILL)
		oknodo = true;

	if (!exec)
		exec = startas;
	else if (!name)
		name = startas;

	if (!exec) {
		exec = *argv;
		if (!exec)
			exec = name;
		if (name && start)
			*argv = name;
	} else if (name) {
		*--argv = name;
		++argc;
    } else if (exec) {
		*--argv = exec;
		++argc;
	};

	if (stop || sig != -1) {
		if (sig == -1)
			sig = SIGTERM;
		if (!*argv && !pidfile && !name && !uid)
			eerrorx("%s: --stop needs --exec, --pidfile,"
			    " --name or --user", applet);
		if (background)
			eerrorx("%s: --background is only relevant with"
			    " --start", applet);
		if (makepidfile)
			eerrorx("%s: --make-pidfile is only relevant with"
			    " --start", applet);
		if (redirect_stdout || redirect_stderr)
			eerrorx("%s: --stdout and --stderr are only relevant"
			    " with --start", applet);
	} else {
		if (!exec)
			eerrorx("%s: nothing to start", applet);
		if (makepidfile && !pidfile)
			eerrorx("%s: --make-pidfile is only relevant with"
			    " --pidfile", applet);
		if ((redirect_stdout || redirect_stderr) && !background)
			eerrorx("%s: --stdout and --stderr are only relevant"
			    " with --background", applet);
	}

	/* Expand ~ */
	if (ch_dir && *ch_dir == '~')
		ch_dir = expand_home(home, ch_dir);
	if (ch_root && *ch_root == '~')
		ch_root = expand_home(home, ch_root);
	if (exec) {
		if (*exec == '~')
			exec = expand_home(home, exec);

		/* Validate that the binary exists if we are starting */
		if (*exec == '/' || *exec == '.') {
			/* Full or relative path */
			if (ch_root)
				snprintf(exec_file, sizeof(exec_file),
				    "%s/%s", ch_root, exec);
			else
				snprintf(exec_file, sizeof(exec_file),
				    "%s", exec);
		} else {
			/* Something in $PATH */
			p = tmp = xstrdup(getenv("PATH"));
			*exec_file = '\0';
			while ((token = strsep(&p, ":"))) {
				if (ch_root)
					snprintf(exec_file, sizeof(exec_file),
					    "%s/%s/%s",
					    ch_root, token, exec);
				else
					snprintf(exec_file, sizeof(exec_file),
					    "%s/%s", token, exec);
				if (exists(exec_file))
					break;
				*exec_file = '\0';
			}
			free(tmp);
		}
	}
	if (start && !exists(exec_file)) {
		eerror("%s: %s does not exist", applet,
		    *exec_file ? exec_file : exec);
		exit(EXIT_FAILURE);
	}

	/* If we don't have a pidfile we should check if it's interpreted
	 * or not. If it we, we need to pass the interpreter through
	 * to our daemon calls to find it correctly. */
	if (interpreted && !pidfile) {
		fp = fopen(exec_file, "r");
		if (fp) {
			p = fgets(line, sizeof(line), fp);
			fclose(fp);
			if (p != NULL && line[0] == '#' && line[1] == '!') {
				p = line + 2;
				/* Strip leading spaces */
				while (*p == ' ' || *p == '\t')
					p++;
				/* Remove the trailing newline */
				len = strlen(p) - 1;
				if (p[len] == '\n')
					p[len] = '\0';
				token = strsep(&p, " ");
				strncpy(exec_file, token, sizeof(exec_file));
				opt = 0;
				for (nav = argv; *nav; nav++)
					opt++;
				nav = xmalloc(sizeof(char *) * (opt + 3));
				nav[0] = exec_file;
				len = 1;
				if (p)
					nav[len++] = p;
				for (i = 0; i < opt; i++)
					nav[i + len] = argv[i];
				nav[i + len] = '\0';
			}
		}
	}
	margv = nav ? nav : argv;

	if (stop || sig != -1) {
		if (sig == -1)
			sig = SIGTERM;
		if (!stop)
			oknodo = true;
		if (retry)
			parse_schedule(retry, sig);
		else if (test || oknodo)
			parse_schedule("0", sig);
		else
			parse_schedule(NULL, sig);
		i = run_stop_schedule(exec, (const char *const *)margv,
		    pidfile, uid, test, progress);

		if (i < 0)
			/* We failed to stop something */
			exit(EXIT_FAILURE);
		if (test || oknodo)
			return i > 0 ? EXIT_SUCCESS : EXIT_FAILURE;

		/* Even if we have not actually killed anything, we should
		 * remove information about it as it may have unexpectedly
		 * crashed out. We should also return success as the end
		 * result would be the same. */
		if (pidfile && exists(pidfile))
			unlink(pidfile);
		if (svcname)
			rc_service_daemon_set(svcname, exec,
			    (const char *const *)argv,
			    pidfile, false);
		exit(EXIT_SUCCESS);
	}

	if (pidfile)
		pid = get_pid(pidfile);
	else
		pid = 0;

	if (do_stop(exec, (const char * const *)margv, pid, uid,
		0, test) > 0)
		eerrorx("%s: %s is already running", applet, exec);

	if (test) {
		if (rc_yesno(getenv("EINFO_QUIET")))
			exit (EXIT_SUCCESS);

		einfon("Would start");
		while (argc-- > 0)
			printf(" %s", *argv++);
		printf("\n");
		eindent();
		if (uid != 0)
			einfo("as user id %d", uid);
		if (gid != 0)
			einfo("as group id %d", gid);
		if (ch_root)
			einfo("in root `%s'", ch_root);
		if (ch_dir)
			einfo("in dir `%s'", ch_dir);
		if (nicelevel != 0)
			einfo("with a priority of %d", nicelevel);
		if (name)
			einfo ("with a process name of %s", name);
		eoutdent();
		exit(EXIT_SUCCESS);
	}

	ebeginv("Detaching to start `%s'", exec);
	eindentv();

	/* Remove existing pidfile */
	if (pidfile)
		unlink(pidfile);

	if (background)
		signal_setup(SIGCHLD, handle_signal);

	if ((pid = fork()) == -1)
		eerrorx("%s: fork: %s", applet, strerror(errno));

	/* Child process - lets go! */
	if (pid == 0) {
		pid_t mypid = getpid();
		umask(numask);

#ifdef TIOCNOTTY
		tty_fd = open("/dev/tty", O_RDWR);
#endif

		devnull_fd = open("/dev/null", O_RDWR);

		if (nicelevel) {
			if (setpriority(PRIO_PROCESS, mypid, nicelevel) == -1)
				eerrorx("%s: setpritory %d: %s",
				    applet, nicelevel,
				    strerror(errno));
		}

		if (ionicec != -1 &&
		    ioprio_set(1, mypid, ionicec | ioniced) == -1)
			eerrorx("%s: ioprio_set %d %d: %s", applet,
			    ionicec, ioniced, strerror(errno));

		if (ch_root && chroot(ch_root) < 0)
			eerrorx("%s: chroot `%s': %s",
			    applet, ch_root, strerror(errno));

		if (ch_dir && chdir(ch_dir) < 0)
			eerrorx("%s: chdir `%s': %s",
			    applet, ch_dir, strerror(errno));

		if (makepidfile && pidfile) {
			fp = fopen(pidfile, "w");
			if (! fp)
				eerrorx("%s: fopen `%s': %s", applet, pidfile,
				    strerror(errno));
			fprintf(fp, "%d\n", mypid);
			fclose(fp);
		}

#ifdef HAVE_PAM
		if (changeuser != NULL) {
			pamr = pam_start("start-stop-daemon",
			    changeuser, &conv, &pamh);

			if (pamr == PAM_SUCCESS)
				pamr = pam_acct_mgmt(pamh, PAM_SILENT);
			if (pamr == PAM_SUCCESS)
				pamr = pam_open_session(pamh, PAM_SILENT);
			if (pamr != PAM_SUCCESS)
				eerrorx("%s: pam error: %s",
					applet, pam_strerror(pamh, pamr));
		}
#endif

		if (gid && setgid(gid))
			eerrorx("%s: unable to set groupid to %d",
			    applet, gid);
		if (changeuser && initgroups(changeuser, gid))
			eerrorx("%s: initgroups (%s, %d)",
			    applet, changeuser, gid);
		if (uid && setuid(uid))
			eerrorx ("%s: unable to set userid to %d",
			    applet, uid);

		/* Close any fd's to the passwd database */
		endpwent();

#ifdef TIOCNOTTY
		ioctl(tty_fd, TIOCNOTTY, 0);
		close(tty_fd);
#endif

		/* Clean the environment of any RC_ variables */
		env_list = rc_stringlist_new();
		i = 0;
		while (environ[i])
			rc_stringlist_add(env_list, environ[i++]);

#ifdef HAVE_PAM
		if (changeuser != NULL) {
			pamenv = (const char *const *)pam_getenvlist(pamh);
			if (pamenv) {
				while (*pamenv) {
					/* Don't add strings unless they set a var */
					if (strchr(*pamenv, '='))
						putenv(xstrdup(*pamenv));
					else
						unsetenv(*pamenv);
					pamenv++;
				}
			}
		}
#endif

		TAILQ_FOREACH(env, env_list, entries) {
			if ((strncmp(env->value, "RC_", 3) == 0 &&
				strncmp(env->value, "RC_SERVICE=", 10) != 0 &&
				strncmp(env->value, "RC_SVCNAME=", 10) != 0) ||
			    strncmp(env->value, "SSD_NICELEVEL=", 14) == 0)
			{
				p = strchr(env->value, '=');
				*p = '\0';
				unsetenv(env->value);
				continue;
			}
		}
		rc_stringlist_free(env_list);

		/* For the path, remove the rcscript bin dir from it */
		if ((token = getenv("PATH"))) {
			len = strlen(token);
			newpath = np = xmalloc(len + 1);
			while (token && *token) {
				p = strchr(token, ':');
				if (p) {
					*p++ = '\0';
					while (*p == ':')
						p++;
				}
				if (strcmp(token, RC_LIBEXECDIR "/bin") != 0 &&
				    strcmp(token, RC_LIBEXECDIR "/sbin") != 0)
				{
					len = strlen(token);
					if (np != newpath)
						*np++ = ':';
					memcpy(np, token, len);
					np += len;
				}
				token = p;
			}
			*np = '\0';
			unsetenv("PATH");
			setenv("PATH", newpath, 1);
		}

		stdin_fd = devnull_fd;
		stdout_fd = devnull_fd;
		stderr_fd = devnull_fd;
		if (redirect_stdout) {
			if ((stdout_fd = open(redirect_stdout,
				    O_WRONLY | O_CREAT | O_APPEND,
				    S_IRUSR | S_IWUSR)) == -1)
				eerrorx("%s: unable to open the logfile"
				    " for stdout `%s': %s",
				    applet, redirect_stdout, strerror(errno));
		}
		if (redirect_stderr) {
			if ((stderr_fd = open(redirect_stderr,
				    O_WRONLY | O_CREAT | O_APPEND,
				    S_IRUSR | S_IWUSR)) == -1)
				eerrorx("%s: unable to open the logfile"
				    " for stderr `%s': %s",
				    applet, redirect_stderr, strerror(errno));
		}

		if (background)
			dup2(stdin_fd, STDIN_FILENO);
		if (background || redirect_stdout || rc_yesno(getenv("EINFO_QUIET")))
			dup2(stdout_fd, STDOUT_FILENO);
		if (background || redirect_stderr || rc_yesno(getenv("EINFO_QUIET")))
			dup2(stderr_fd, STDERR_FILENO);

		for (i = getdtablesize() - 1; i >= 3; --i)
			close(i);

		setsid();
		execvp(exec, argv);
#ifdef HAVE_PAM
		if (changeuser != NULL && pamr == PAM_SUCCESS)
			pam_close_session(pamh, PAM_SILENT);
#endif
		eerrorx("%s: failed to exec `%s': %s",
		    applet, exec,strerror(errno));
	}
コード例 #2
0
ファイル: profile.c プロジェクト: irregulator/firejail
// check profile line; if line == 0, this was generated from a command line option
// return 1 if the command is to be added to the linked list of profile commands
// return 0 if the command was already executed inside the function
int profile_check_line(char *ptr, int lineno, const char *fname) {
	EUID_ASSERT();
	
	// check ignore list
	int i;
	for (i = 0; i < MAX_PROFILE_IGNORE; i++) {
		if (cfg.profile_ignore[i] == NULL)
			break;
		
		if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0)
			return 0;	// ignore line
	}
	
	if (strncmp(ptr, "ignore ", 7) == 0) {
		char *str = strdup(ptr + 7);
		if (*str == '\0') {
			fprintf(stderr, "Error: invalid ignore option\n");
			exit(1);
		}
		// find an empty entry in profile_ignore array
		int j;
		for (j = 0; j < MAX_PROFILE_IGNORE; j++) {
			if (cfg.profile_ignore[j] == NULL) 
				break;
		}
		if (j >= MAX_PROFILE_IGNORE) {
			fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE);
			exit(1);
		}
		// ... and configure it
		else 
			cfg.profile_ignore[j] = str;

		return 0;
	}

	// mkdir 
	if (strncmp(ptr, "mkdir ", 6) == 0) {
		fs_mkdir(ptr + 6);
		return 1;	// process mkdir again while applying blacklists
	}
	// mkfile 
	if (strncmp(ptr, "mkfile ", 7) == 0) {
		fs_mkfile(ptr + 7);
		return 1;	// process mkfile again while applying blacklists
	}
	// sandbox name
	else if (strncmp(ptr, "name ", 5) == 0) {
		cfg.name = ptr + 5;
		if (strlen(cfg.name) == 0) {
			fprintf(stderr, "Error: invalid sandbox name\n");
			exit(1);
		}
		return 0;
	}
	else if (strcmp(ptr, "ipc-namespace") == 0) {
		arg_ipc = 1;
		return 0;
	}
	// seccomp, caps, private, user namespace
	else if (strcmp(ptr, "noroot") == 0) {
#if HAVE_USERNS
		if (checkcfg(CFG_USERNS))
			check_user_namespace();
		else
			warning_feature_disabled("noroot");
#endif

		return 0;
	}
	else if (strcmp(ptr, "nonewprivs") == 0) {
		arg_nonewprivs = 1;
		return 0;
	}
	else if (strcmp(ptr, "seccomp") == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP))
			arg_seccomp = 1;
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}
	else if (strcmp(ptr, "caps") == 0) {
		arg_caps_default_filter = 1;
		return 0;
	}
	else if (strcmp(ptr, "caps.drop all") == 0) {
		arg_caps_drop_all = 1;
		return 0;
	}
	else if (strcmp(ptr, "shell none") == 0) {
		arg_shell_none = 1;
		return 0;
	}	
	else if (strcmp(ptr, "tracelog") == 0) {
		arg_tracelog = 1;
		return 0;
	}
	else if (strcmp(ptr, "private") == 0) {
		arg_private = 1;
		return 0;
	}
	if (strncmp(ptr, "private-home ", 13) == 0) {
#ifdef HAVE_PRIVATE_HOME
		if (checkcfg(CFG_PRIVATE_HOME)) {
			if (cfg.home_private_keep) {
				if ( asprintf(&cfg.home_private_keep, "%s,%s", cfg.home_private_keep, ptr + 13) < 0 )
					errExit("asprintf");
			} else
				cfg.home_private_keep = ptr + 13;
			arg_private = 1;
		}
		else
			warning_feature_disabled("private-home");
#endif
		return 0;
	}
	else if (strcmp(ptr, "allusers") == 0) {
		arg_allusers = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-dev") == 0) {
		arg_private_dev = 1;
		return 0;
	}
	else if (strcmp(ptr, "private-tmp") == 0) {
		arg_private_tmp = 1;
		return 0;
	}
	else if (strcmp(ptr, "nogroups") == 0) {
		arg_nogroups = 1;
		return 0;
	}
	else if (strcmp(ptr, "nosound") == 0) {
		arg_nosound = 1;
		return 0;
	}
	else if (strcmp(ptr, "no3d") == 0) {
		arg_no3d = 1;
		return 0;
	}
	else if (strcmp(ptr, "allow-private-blacklist") == 0) {
		arg_allow_private_blacklist = 1;
		return 0;
	}	
	else if (strcmp(ptr, "netfilter") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK))
			arg_netfilter = 1;
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter = 1;
			arg_netfilter_file = strdup(ptr + 10);
			if (!arg_netfilter_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter_file);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "netfilter6 ", 11) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_netfilter6 = 1;
			arg_netfilter6_file = strdup(ptr + 11);
			if (!arg_netfilter6_file)
				errExit("strdup");
			check_netfilter_file(arg_netfilter6_file);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strcmp(ptr, "net none") == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			arg_nonetwork  = 1;
			cfg.bridge0.configured = 0;
			cfg.bridge1.configured = 0;
			cfg.bridge2.configured = 0;
			cfg.bridge3.configured = 0;
			cfg.interface0.configured = 0;
			cfg.interface1.configured = 0;
			cfg.interface2.configured = 0;
			cfg.interface3.configured = 0;
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	else if (strncmp(ptr, "net ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
#ifdef HAVE_NETWORK_RESTRICTED
			// compile time restricted networking
			if (getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}
#endif
			// run time restricted networking
			if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) {
				fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n");
				exit(1);
			}
			
			if (strcmp(ptr + 4, "lo") == 0) {
				fprintf(stderr, "Error: cannot attach to lo device\n");
				exit(1);
			}

			Bridge *br;
			if (cfg.bridge0.configured == 0)
				br = &cfg.bridge0;
			else if (cfg.bridge1.configured == 0)
				br = &cfg.bridge1;
			else if (cfg.bridge2.configured == 0)
				br = &cfg.bridge2;
			else if (cfg.bridge3.configured == 0)
				br = &cfg.bridge3;
			else {
				fprintf(stderr, "Error: maximum 4 network devices are allowed\n");
				exit(1);
			}
			net_configure_bridge(br, ptr + 4);
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}
	
	else if (strncmp(ptr, "veth-name ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}

			br->veth_name = strdup(ptr + 10);
			if (br->veth_name == NULL)
				errExit("strdup");
			if (*br->veth_name == '\0') {
				fprintf(stderr, "Error: no veth-name configured\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "iprange ", 8) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->iprange_start || br->iprange_end) {
				fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n");
				exit(1);
			}

			// parse option arguments
			char *firstip = ptr + 8;
			char *secondip = firstip;
			while (*secondip != '\0') {
				if (*secondip == ',')
					break;
				secondip++;
			}
			if (*secondip == '\0') {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			*secondip = '\0';
			secondip++;
			
			// check addresses
			if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) ||
			    br->iprange_start >= br->iprange_end) {
				fprintf(stderr, "Error: invalid IP range\n");
				exit(1);
			}
			if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) {
				fprintf(stderr, "Error: IP range addresses not in network range\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}


	else if (strncmp(ptr, "mac ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			
			if (mac_not_zero(br->macsandbox)) {
				fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n");
				exit(1);
			}

			// read the address
			if (atomac(ptr + 4, br->macsandbox)) {
				fprintf(stderr, "Error: invalid MAC address\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "mtu ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			
			if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) {
				fprintf(stderr, "Error: invalid mtu value\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip ", 3) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ipsandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			if (strcmp(ptr + 3, "none") == 0)
				br->arg_ip_none = 1;
			else {
				if (atoip(ptr + 3, &br->ipsandbox)) {
					fprintf(stderr, "Error: invalid IP address\n");
					exit(1);
				}
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "ip6 ", 4) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			Bridge *br = last_bridge_configured();
			if (br == NULL) {
				fprintf(stderr, "Error: no network device configured\n");
				exit(1);
			}
			if (br->arg_ip_none || br->ip6sandbox) {
				fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n");
				exit(1);
			}

			// configure this IP address for the last bridge defined
			// todo: verify ipv6 syntax
			br->ip6sandbox = ptr + 4;
//			if (atoip(argv[i] + 5, &br->ipsandbox)) {
//				fprintf(stderr, "Error: invalid IP address\n");
//				exit(1);
//			}
			
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	else if (strncmp(ptr, "defaultgw ", 10) == 0) {
#ifdef HAVE_NETWORK
		if (checkcfg(CFG_NETWORK)) {
			if (atoip(ptr + 10, &cfg.defaultgw)) {
				fprintf(stderr, "Error: invalid IP address\n");
				exit(1);
			}
		}
		else
			warning_feature_disabled("networking");
#endif
		return 0;
	}

	if (strcmp(ptr, "apparmor") == 0) {
#ifdef HAVE_APPARMOR		
		arg_apparmor = 1;
#endif
		return 0;
	}

	if (strncmp(ptr, "protocol ", 9) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			if (cfg.protocol) {
				if (!arg_quiet)
					fprintf(stderr, "Warning: a protocol list is present, the new list \"%s\" will not be installed\n", ptr + 9);
				return 0;
			}
			
			// store list
			cfg.protocol = strdup(ptr + 9);
			if (!cfg.protocol)
				errExit("strdup");
		}
		else
			warning_feature_disabled("seccomp");
#endif
		return 0;
	}
	
	if (strncmp(ptr, "env ", 4) == 0) {
		env_store(ptr + 4, SETENV);
		return 0;
	}
	if (strncmp(ptr, "rmenv ", 6) == 0) {
		env_store(ptr + 6, RMENV);
		return 0;
	}
	
	// seccomp drop list on top of default list
	if (strncmp(ptr, "seccomp ", 8) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list = seccomp_check_list(ptr + 8);
		}
		else if (!arg_quiet)
			warning_feature_disabled("seccomp");
#endif

		return 0;
	}
	
	// seccomp drop list without default list
	if (strncmp(ptr, "seccomp.drop ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_drop = seccomp_check_list(ptr + 13);
		}
		else
			warning_feature_disabled("seccomp");
#endif		
		return 0;
	}

	// seccomp keep list
	if (strncmp(ptr, "seccomp.keep ", 13) == 0) {
#ifdef HAVE_SECCOMP
		if (checkcfg(CFG_SECCOMP)) {
			arg_seccomp = 1;
			cfg.seccomp_list_keep= seccomp_check_list(ptr + 13);
		}
		else
			warning_feature_disabled("seccomp");
#endif		
		return 0;
	}
	
	// caps drop list
	if (strncmp(ptr, "caps.drop ", 10) == 0) {
		arg_caps_drop = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify caps list and exit if problems
		caps_check_list(arg_caps_list, NULL);
		return 0;
	}
	
	// caps keep list
	if (strncmp(ptr, "caps.keep ", 10) == 0) {
		arg_caps_keep = 1;
		arg_caps_list = strdup(ptr + 10);
		if (!arg_caps_list)
			errExit("strdup");
		// verify caps list and exit if problems
		caps_check_list(arg_caps_list, NULL);
		return 0;
	}

	// hostname
	if (strncmp(ptr, "hostname ", 9) == 0) {
		cfg.hostname = ptr + 9;
		return 0;
	}
	
	// hosts-file
	if (strncmp(ptr, "hosts-file ", 11) == 0) {
		cfg.hosts_file = fs_check_hosts_file(ptr + 11);
		return 0;
	}
	
	// dns
	if (strncmp(ptr, "dns ", 4) == 0) {
		uint32_t dns;
		if (atoip(ptr + 4, &dns)) {
			fprintf(stderr, "Error: invalid DNS server IP address\n");
			return 1;
		}
		
		if (cfg.dns1 == 0)
			cfg.dns1 = dns;
		else if (cfg.dns2 == 0)
			cfg.dns2 = dns;
		else if (cfg.dns3 == 0)
			cfg.dns3 = dns;
		else {
			fprintf(stderr, "Error: up to 3 DNS servers can be specified\n");
			return 1;
		}
		return 0;
	}
	
	// cpu affinity
	if (strncmp(ptr, "cpu ", 4) == 0) {
		read_cpu_list(ptr + 4);
		return 0;
	}
	
	// nice value
	if (strncmp(ptr, "nice ", 4) == 0) {
		cfg.nice = atoi(ptr + 5);
		if (getuid() != 0 &&cfg.nice < 0)
			cfg.nice = 0;
		arg_nice = 1;
		return 0;
	}

	// cgroup
	if (strncmp(ptr, "cgroup ", 7) == 0) {
		set_cgroup(ptr + 7);
		return 0;
	}
	
	// writable-etc
	if (strcmp(ptr, "writable-etc") == 0) {
		if (cfg.etc_private_keep) {
			fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n");
			exit(1);
		}
		arg_writable_etc = 1;
		return 0;
	}
	
	if (strcmp(ptr, "machine-id") == 0) {
		arg_machineid = 1;
		return 0;
	}
	// writable-var
	if (strcmp(ptr, "writable-var") == 0) {
		arg_writable_var = 1;
		return 0;
	}
	if (strcmp(ptr, "writable-var-log") == 0) {
		arg_writable_var_log = 1;
		return 0;
	}
	
	// private directory
	if (strncmp(ptr, "private ", 8) == 0) {
		cfg.home_private = ptr + 8;
		fs_check_private_dir();
		arg_private = 1;
		return 0;
	}

	if (strcmp(ptr, "x11 none") == 0) {
		arg_x11_block = 1;
		return 0;
	}

	if (strcmp(ptr, "x11 xephyr") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xephyr(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11 xorg") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11))
			arg_x11_xorg = 1;
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}
	
	if (strcmp(ptr, "x11 xpra") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xpra(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}
	
	if (strcmp(ptr, "x11 xvfb") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start_xvfb(cfg.original_argc, cfg.original_argv);
				exit(0);
			}
		}
		else
			warning_feature_disabled("x11");
#endif
		return 0;
	}

	if (strcmp(ptr, "x11") == 0) {
#ifdef HAVE_X11
		if (checkcfg(CFG_X11)) {
			char *x11env = getenv("FIREJAIL_X11");
			if (x11env && strcmp(x11env, "yes") == 0) {
				return 0;
			}
			else {
				// start x11
				x11_start(cfg.original_argc, cfg.original_argv);
				exit(0);		
			}
		}
		else
			warning_feature_disabled("x11");
#endif		
		return 0;
	}
	
	// private /etc list of files and directories
	if (strncmp(ptr, "private-etc ", 12) == 0) {
		if (arg_writable_etc) {
			fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
			exit(1);
		}
		if (cfg.etc_private_keep) {
			if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.etc_private_keep = ptr + 12;
		}
		arg_private_etc = 1;
		
		return 0;
	}

	// private /opt list of files and directories
	if (strncmp(ptr, "private-opt ", 12) == 0) {
		if (cfg.opt_private_keep) {
			if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.opt_private_keep = ptr + 12;
		}
		arg_private_opt = 1;
		
		return 0;
	}

	// private /srv list of files and directories
	if (strncmp(ptr, "private-srv ", 12) == 0) {
		if (cfg.srv_private_keep) {
			if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.srv_private_keep = ptr + 12;
		}
		arg_private_srv = 1;
		
		return 0;
	}

	// private /bin list of files
	if (strncmp(ptr, "private-bin ", 12) == 0) {
		if (cfg.bin_private_keep) {
			if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 )
				errExit("asprintf");
		} else {
			cfg.bin_private_keep = ptr + 12;
		}
		arg_private_bin = 1;
		return 0;
	}

	// filesystem bind
	if (strncmp(ptr, "bind ", 5) == 0) {
#ifdef HAVE_BIND		
		if (checkcfg(CFG_BIND)) {
			if (getuid() != 0) {
				fprintf(stderr, "Error: --bind option is available only if running as root\n");
				exit(1);
			}
	
			// extract two directories
			char *dname1 = ptr + 5;
			char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories
			if (dname2 == NULL) {
				fprintf(stderr, "Error: missing second directory for bind\n");
				exit(1);
			}
			
			// check directories
			invalid_filename(dname1);
			invalid_filename(dname2);
			if (strstr(dname1, "..") || strstr(dname2, "..")) {
				fprintf(stderr, "Error: invalid file name.\n");
				exit(1);
			}
			if (is_link(dname1) || is_link(dname2)) {
				fprintf(stderr, "Symbolic links are not allowed for bind command\n");
				exit(1);
			}
			
			// insert comma back
			*(dname2 - 1) = ',';
			return 1;
		}
		else
			warning_feature_disabled("bind");
#endif		
		return 0;
	}

	// rlimit
	if (strncmp(ptr, "rlimit", 6) == 0) {
		if (strncmp(ptr, "rlimit-nofile ", 14) == 0) {
			ptr += 14;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nofile);
			arg_rlimit_nofile = 1;
		}
		else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_nproc);
			arg_rlimit_nproc = 1;
		}
		else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) {
			ptr += 13;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_fsize);
			arg_rlimit_fsize = 1;
		}
		else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) {
			ptr += 18;
			if (not_unsigned(ptr)) {
				fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
				exit(1);
			}
			sscanf(ptr, "%u", &cfg.rlimit_sigpending);
			arg_rlimit_sigpending = 1;
		}
		else {
			fprintf(stderr, "Invalid rlimit option on line %d\n", lineno);
			exit(1);
		}
		
		return 0;		
	}

	if (strncmp(ptr, "join-or-start ", 14) == 0) {
		// try to join by name only
		pid_t pid;
		if (!name2pid(ptr + 14, &pid)) {
			if (!cfg.shell && !arg_shell_none)
				cfg.shell = guess_shell();

			// find first non-option arg
			int i;
			for (i = 1; i < cfg.original_argc && strncmp(cfg.original_argv[i], "--", 2) != 0; i++);

			join(pid, cfg.original_argc,cfg.original_argv, i + 1);
			exit(0);
		}

		// set sandbox name and start normally
		cfg.name = ptr + 14;
		if (strlen(cfg.name) == 0) {
			fprintf(stderr, "Error: invalid sandbox name\n");
			exit(1);
		}
		return 0;
	}

	// rest of filesystem
	if (strncmp(ptr, "blacklist ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "blacklist-nolog ", 16) == 0)
		ptr += 16;
	else if (strncmp(ptr, "noblacklist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "whitelist ", 10) == 0) {
#ifdef HAVE_WHITELIST		
		if (checkcfg(CFG_WHITELIST)) {
			arg_whitelist = 1;
			ptr += 10;
		}
		else
			return 0;
#else		
		return 0;
#endif
	}
	else if (strncmp(ptr, "nowhitelist ", 12) == 0)
		ptr += 12;
	else if (strncmp(ptr, "read-only ", 10) == 0)
		ptr += 10;
	else if (strncmp(ptr, "read-write ", 11) == 0)
		ptr += 11;
	else if (strncmp(ptr, "noexec ", 7) == 0)
		ptr += 7;
	else if (strncmp(ptr, "tmpfs ", 6) == 0) {
		if (getuid() != 0) {
			// allow a non-root user  to mount tmpfs in user home directory, links are not allowed
			invalid_filename(ptr + 6);
			char *newfname = expand_home(ptr + 6, cfg.homedir);
			assert(newfname);
			if (is_link(newfname)) {
				fprintf(stderr, "Error: for regular user, tmpfs is not available for symbolic links\n");
				exit(1);	
			}
			if (strncmp(newfname, cfg.homedir, strlen(cfg.homedir)) != 0) {
				fprintf(stderr, "Error: for regular user, tmpfs is available only for files in user home directory\n");
				exit(1);	
			}
			free(newfname);
		}
		ptr += 6;
	}
	else {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}

	// some characters just don't belong in filenames
	invalid_filename(ptr);
	if (strstr(ptr, "..")) {
		if (lineno == 0)
			fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr);
		else if (fname != NULL)
			fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname);
		else
			fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno);
		exit(1);
	}
	return 1;
}
コード例 #3
0
ファイル: profile.c プロジェクト: thomasjfox/firejail
void profile_read(const char *fname) {
	EUID_ASSERT();

	// exit program if maximum include level was reached
	if (include_level > MAX_INCLUDE_LEVEL) {
		fprintf(stderr, "Error: maximum profile include level was reached\n");
		exit(1);
	}

	// check file
	invalid_filename(fname);
	if (strlen(fname) == 0 || is_dir(fname)) {
		fprintf(stderr, "Error: invalid profile file\n");
		exit(1);
	}
	if (access(fname, R_OK)) {
		// if the file ends in ".local", do not exit
		const char *base = gnu_basename(fname);
		char *ptr = strstr(base, ".local");
		if (ptr && strlen(ptr) == 6)
			return;

		fprintf(stderr, "Error: cannot access profile file\n");
		exit(1);
	}

	// allow debuggers
	if (arg_allow_debuggers) {
		char *tmp = strrchr(fname, '/');
		if (tmp && *(tmp + 1) != '\0') {
			tmp++;
			if (strcmp(tmp, "disable-devel.inc") == 0)
				return;
		}
	}

	// open profile file:
	FILE *fp = fopen(fname, "r");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open profile file %s\n", fname);
		exit(1);
	}

	int msg_printed = 0;

	// read the file line by line
	char buf[MAX_READ + 1];
	int lineno = 0;
	while (fgets(buf, MAX_READ, fp)) {
		++lineno;
		// remove empty space - ptr in allocated memory
		char *ptr = line_remove_spaces(buf);
		if (ptr == NULL)
			continue;

		// comments
		if (*ptr == '#' || *ptr == '\0') {
			free(ptr);
			continue;
		}

		// process quiet
		if (strcmp(ptr, "quiet") == 0) {
			arg_quiet = 1;
			free(ptr);
			continue;
		}
		if (!msg_printed) {
			if (!arg_quiet)
				fprintf(stderr, "Reading profile %s\n", fname);
			msg_printed = 1;
		}

		// process include
		if (strncmp(ptr, "include ", 8) == 0) {
			include_level++;

			// extract profile filename and new skip params
			char *newprofile = ptr + 8; // profile name

			// expand ${HOME}/ in front of the new profile file
			char *newprofile2 = expand_home(newprofile, cfg.homedir);

			// recursivity
			profile_read((newprofile2)? newprofile2:newprofile);
			include_level--;
			if (newprofile2)
				free(newprofile2);
			free(ptr);
			continue;
		}

		// verify syntax, exit in case of error
		if (profile_check_line(ptr, lineno, fname))
			profile_add(ptr);
// we cannot free ptr here, data is extracted from ptr and linked as a pointer in cfg structure
//		else {
//			free(ptr);
//		}
#ifdef HAVE_GCOV
		__gcov_flush();
#endif
	}
	fclose(fp);
}
コード例 #4
0
ファイル: supervise-daemon.c プロジェクト: Whissi/openrc
int main(int argc, char **argv)
{
	int opt;
	bool start = false;
	bool stop = false;
	char *exec = NULL;
	char *pidfile = NULL;
	char *home = NULL;
	int tid = 0;
	pid_t child_pid, pid;
	char *svcname = getenv("RC_SVCNAME");
	char *tmp;
	char *p;
	char *token;
	int i;
	char exec_file[PATH_MAX];
	struct passwd *pw;
	struct group *gr;
	FILE *fp;
	mode_t numask = 022;

	applet = basename_c(argv[0]);
	atexit(cleanup);

	signal_setup(SIGINT, handle_signal);
	signal_setup(SIGQUIT, handle_signal);
	signal_setup(SIGTERM, handle_signal);
	openlog(applet, LOG_PID, LOG_DAEMON);

	if ((tmp = getenv("SSD_NICELEVEL")))
		if (sscanf(tmp, "%d", &nicelevel) != 1)
			eerror("%s: invalid nice level `%s' (SSD_NICELEVEL)",
			    applet, tmp);

	/* Get our user name and initial dir */
	p = getenv("USER");
	home = getenv("HOME");
	if (home == NULL || p == NULL) {
		pw = getpwuid(getuid());
		if (pw != NULL) {
			if (p == NULL)
				setenv("USER", pw->pw_name, 1);
			if (home == NULL) {
				setenv("HOME", pw->pw_dir, 1);
				home = pw->pw_dir;
			}
		}
	}

	while ((opt = getopt_long(argc, argv, getoptstring, longopts,
		    (int *) 0)) != -1)
		switch (opt) {
		case 'I': /* --ionice */
			if (sscanf(optarg, "%d:%d", &ionicec, &ioniced) == 0)
				eerrorx("%s: invalid ionice `%s'",
				    applet, optarg);
			if (ionicec == 0)
				ioniced = 0;
			else if (ionicec == 3)
				ioniced = 7;
			ionicec <<= 13; /* class shift */
			break;

		case 'K':  /* --stop */
			stop = true;
			break;

		case 'N':  /* --nice */
			if (sscanf(optarg, "%d", &nicelevel) != 1)
				eerrorx("%s: invalid nice level `%s'",
				    applet, optarg);
			break;

		case 'S':  /* --start */
			start = true;
			break;

		case 'd':  /* --chdir /new/dir */
			ch_dir = optarg;
			break;

		case 'e': /* --env */
			putenv(optarg);
			break;

		case 'g':  /* --group <group>|<gid> */
			if (sscanf(optarg, "%d", &tid) != 1)
				gr = getgrnam(optarg);
			else
				gr = getgrgid((gid_t)tid);
			if (gr == NULL)
				eerrorx("%s: group `%s' not found",
				    applet, optarg);
			gid = gr->gr_gid;
			break;

		case 'k':
			if (parse_mode(&numask, optarg))
				eerrorx("%s: invalid mode `%s'",
				    applet, optarg);
			break;

		case 'p':  /* --pidfile <pid-file> */
			pidfile = optarg;
			break;

		case 'r':  /* --chroot /new/root */
			ch_root = optarg;
			break;

		case 'u':  /* --user <username>|<uid> */
		{
			p = optarg;
			tmp = strsep(&p, ":");
			changeuser = xstrdup(tmp);
			if (sscanf(tmp, "%d", &tid) != 1)
				pw = getpwnam(tmp);
			else
				pw = getpwuid((uid_t)tid);

			if (pw == NULL)
				eerrorx("%s: user `%s' not found",
				    applet, tmp);
			uid = pw->pw_uid;
			home = pw->pw_dir;
			unsetenv("HOME");
			if (pw->pw_dir)
				setenv("HOME", pw->pw_dir, 1);
			unsetenv("USER");
			if (pw->pw_name)
				setenv("USER", pw->pw_name, 1);
			if (gid == 0)
				gid = pw->pw_gid;

			if (p) {
				tmp = strsep (&p, ":");
				if (sscanf(tmp, "%d", &tid) != 1)
					gr = getgrnam(tmp);
				else
					gr = getgrgid((gid_t) tid);

				if (gr == NULL)
					eerrorx("%s: group `%s'"
					    " not found",
					    applet, tmp);
				gid = gr->gr_gid;
			}
		}
		break;

		case '1':   /* --stdout /path/to/stdout.lgfile */
			redirect_stdout = optarg;
			break;

		case '2':  /* --stderr /path/to/stderr.logfile */
			redirect_stderr = optarg;
			break;

		case_RC_COMMON_GETOPT
		}

	if (!pidfile)
		eerrorx("%s: --pidfile must be specified", applet);

	endpwent();
	argc -= optind;
	argv += optind;
	exec = *argv;

	if (start) {
		if (!exec)
			eerrorx("%s: nothing to start", applet);
	}

	/* Expand ~ */
	if (ch_dir && *ch_dir == '~')
		ch_dir = expand_home(home, ch_dir);
	if (ch_root && *ch_root == '~')
		ch_root = expand_home(home, ch_root);
	if (exec) {
		if (*exec == '~')
			exec = expand_home(home, exec);

		/* Validate that the binary exists if we are starting */
		if (*exec == '/' || *exec == '.') {
			/* Full or relative path */
			if (ch_root)
				snprintf(exec_file, sizeof(exec_file),
				    "%s/%s", ch_root, exec);
			else
				snprintf(exec_file, sizeof(exec_file),
				    "%s", exec);
		} else {
			/* Something in $PATH */
			p = tmp = xstrdup(getenv("PATH"));
			*exec_file = '\0';
			while ((token = strsep(&p, ":"))) {
				if (ch_root)
					snprintf(exec_file, sizeof(exec_file),
					    "%s/%s/%s",
					    ch_root, token, exec);
				else
					snprintf(exec_file, sizeof(exec_file),
					    "%s/%s", token, exec);
				if (exists(exec_file))
					break;
				*exec_file = '\0';
			}
			free(tmp);
		}
	}
	if (start && !exists(exec_file))
		eerrorx("%s: %s does not exist", applet,
		    *exec_file ? exec_file : exec);

	if (stop) {
		pid = get_pid(pidfile);
		if (pid == -1)
			i = pid;
		else
			i = kill(pid, SIGTERM);
		if (i != 0)
			/* We failed to stop something */
			exit(EXIT_FAILURE);

		/* Even if we have not actually killed anything, we should
		 * remove information about it as it may have unexpectedly
		 * crashed out. We should also return success as the end
		 * result would be the same. */
		if (pidfile && exists(pidfile))
			unlink(pidfile);
		if (svcname)
			rc_service_daemon_set(svcname, exec,
			    (const char *const *)argv,
			    pidfile, false);
		exit(EXIT_SUCCESS);
	}

	pid = get_pid(pidfile);
	if (pid != -1)
		if (kill(pid, 0) == 0)
			eerrorx("%s: %s is already running", applet, exec);

	einfov("Detaching to start `%s'", exec);
	eindentv();

	/* Remove existing pidfile */
	if (pidfile)
		unlink(pidfile);

	/*
	 * Make sure we can write a pid file
	 */
	fp = fopen(pidfile, "w");
	if (! fp)
		eerrorx("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
		fclose(fp);

	child_pid = fork();
	if (child_pid == -1)
		eerrorx("%s: fork: %s", applet, strerror(errno));

	/* first parent process, do nothing. */
	if (child_pid != 0)
		exit(EXIT_SUCCESS);

	child_pid = fork();
	if (child_pid == -1)
		eerrorx("%s: fork: %s", applet, strerror(errno));

	if (child_pid != 0) {
		/* this is the supervisor */
		umask(numask);

#ifdef TIOCNOTTY
		tty_fd = open("/dev/tty", O_RDWR);
#endif

		devnull_fd = open("/dev/null", O_RDWR);

		fp = fopen(pidfile, "w");
		if (! fp)
			eerrorx("%s: fopen `%s': %s", applet, pidfile, strerror(errno));
		fprintf(fp, "%d\n", getpid());
		fclose(fp);

		/*
		 * Supervisor main loop
		 */
		i = 0;
		while (!exiting) {
			wait(&i);
			if (exiting) {
				syslog(LOG_INFO, "stopping %s, pid %d", exec, child_pid);
				kill(child_pid, SIGTERM);
			} else {
				if (WIFEXITED(i))
					syslog(LOG_INFO, "%s, pid %d, exited with return code %d",
							exec, child_pid, WEXITSTATUS(i));
				else if (WIFSIGNALED(i))
					syslog(LOG_INFO, "%s, pid %d, terminated by signal %d",
							exec, child_pid, WTERMSIG(i));
				child_pid = fork();
				if (child_pid == -1)
					eerrorx("%s: fork: %s", applet, strerror(errno));
				if (child_pid == 0)
					child_process(exec, argv);
			}
		}

		if (svcname)
			rc_service_daemon_set(svcname, exec,
									(const char * const *) argv, pidfile, true);

		exit(EXIT_SUCCESS);
	} else if (child_pid == 0)
		child_process(exec, argv);
}
コード例 #5
0
ファイル: profile.c プロジェクト: CaffeinatedStud/firejail
void profile_read(const char *fname) {
	EUID_ASSERT();
	
	// exit program if maximum include level was reached
	if (include_level > MAX_INCLUDE_LEVEL) {
		fprintf(stderr, "Error: maximum profile include level was reached\n");
		exit(1);	
	}

	if (strlen(fname) == 0) {
		fprintf(stderr, "Error: invalid profile file\n");
		exit(1);
	}

	// open profile file:
	FILE *fp = fopen(fname, "r");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open profile file %s\n", fname);
		exit(1);
	}

	if (!arg_quiet)
		fprintf(stderr, "Reading profile %s\n", fname);

	// read the file line by line
	char buf[MAX_READ + 1];
	int lineno = 0;
	while (fgets(buf, MAX_READ, fp)) {
		++lineno;
		// remove empty space - ptr in allocated memory
		char *ptr = line_remove_spaces(buf);
		if (ptr == NULL)
			continue;
		
		// comments
		if (*ptr == '#' || *ptr == '\0') {
			free(ptr);
			continue;
		}
		
		// process include
		if (strncmp(ptr, "include ", 8) == 0) {
			include_level++;
			
			// extract profile filename and new skip params
			char *newprofile = ptr + 8; // profile name
			
			// expand ${HOME}/ in front of the new profile file
			char *newprofile2 = expand_home(newprofile, cfg.homedir);
			
			// recursivity
			profile_read((newprofile2)? newprofile2:newprofile);
			include_level--;
			if (newprofile2)
				free(newprofile2);
			free(ptr);
			continue;
		}
		
		// verify syntax, exit in case of error
		if (profile_check_line(ptr, lineno, fname))
			profile_add(ptr);
// we cannot free ptr here, data is extracted from ptr and linked as a pointer in cfg structure
//		else {
//			free(ptr);
//		}
	}
	fclose(fp);
}
コード例 #6
0
ファイル: profile.c プロジェクト: emacsomancer/firejail
// skip1, skip2 - if the string is found in the line, the line is not interpreted
void profile_read(const char *fname, const char *skip1, const char *skip2) {
	// exit program if maximum include level was reached
	if (include_level > MAX_INCLUDE_LEVEL) {
		fprintf(stderr, "Error: maximum profile include level was reached\n");
		exit(1);	
	}

	if (strlen(fname) == 0) {
		fprintf(stderr, "Error: invalid profile file\n");
		exit(1);
	}

	// open profile file:
	FILE *fp = fopen(fname, "r");
	if (fp == NULL) {
		fprintf(stderr, "Error: cannot open profile file\n");
		exit(1);
	}

	if (!arg_quiet)
		fprintf(stderr, "Reading profile %s\n", fname);

	// read the file line by line
	char buf[MAX_READ + 1];
	int lineno = 0;
	while (fgets(buf, MAX_READ, fp)) {
		++lineno;
		// remove empty space - ptr in allocated memory
		char *ptr = line_remove_spaces(buf);
		if (ptr == NULL)
			continue;
		
		// comments
		if (*ptr == '#' || *ptr == '\0') {
			free(ptr);
			continue;
		}
		
		// process include
		if (strncmp(ptr, "include ", 8) == 0) {
			include_level++;
			
			// extract profile filename and new skip params
			char *newprofile = ptr + 8; // profile name
			char *newskip1 = NULL; // new skip1
			char *newskip2 = NULL; // new skip2
			char *p = newprofile;
			while (*p != '\0') {
				if (*p == ' ') {
					*p = '\0';
					if (newskip1 == NULL)
						newskip1 = p + 1;
					else if (newskip2 == NULL)
						newskip2 = p + 1;
				}
				p++;
			}
			
			// expand ${HOME}/ in front of the new profile file
			char *newprofile2 = expand_home(newprofile, cfg.homedir);
			
			// recursivity
			profile_read((newprofile2)? newprofile2:newprofile, newskip1, newskip2);
			include_level--;
			if (newprofile2)
				free(newprofile2);
			free(ptr);
			continue;
		}
		
		// skip
		if (skip1) {
			if (strstr(ptr, skip1)) {
				free(ptr);
				continue;
			}
		}
		if (skip2) {
			if (strstr(ptr, skip2)) {
				free(ptr);
				continue;
			}
		}
		
		// verify syntax, exit in case of error
		if (profile_check_line(ptr, lineno))
			profile_add(ptr);
	}
	fclose(fp);
}
コード例 #7
0
ファイル: fs.c プロジェクト: emacsomancer/firejail
// blacklist files or directoies by mounting empty files on top of them
void fs_blacklist(void) {
	char *homedir = cfg.homedir;
	assert(homedir);
	ProfileEntry *entry = cfg.profile;
	if (!entry)
		return;
		
	char *emptydir = create_empty_dir();
	char *emptyfile = create_empty_file();

	// a statically allocated buffer works for all current needs
	// TODO: if dynamic allocation is ever needed, we should probably add
	// libraries that make it easy to do without introducing security bugs
	char *noblacklist[32];
	size_t noblacklist_c = 0;

	while (entry) {
		OPERATION op = OPERATION_MAX;
		char *ptr;

		// whitelist commands handled by fs_whitelist()
		if (strncmp(entry->data, "whitelist ", 10) == 0 || *entry->data == '\0') {
			entry = entry->next;
			continue;
		}

		// process bind command
		if (strncmp(entry->data, "bind ", 5) == 0)  {
			char *dname1 = entry->data + 5;
			char *dname2 = split_comma(dname1);
			if (dname2 == NULL) {
				fprintf(stderr, "Error: second directory missing in bind command\n");
				entry = entry->next;
				continue;
			}
			struct stat s;
			if (stat(dname1, &s) == -1) {
				fprintf(stderr, "Error: cannot find directories for bind command\n");
				entry = entry->next;
				continue;
			}
			if (stat(dname2, &s) == -1) {
				fprintf(stderr, "Error: cannot find directories for bind command\n");
				entry = entry->next;
				continue;
			}
			
			// mount --bind olddir newdir
			if (arg_debug)
				printf("Mount-bind %s on top of %s\n", dname1, dname2);
			// preserve dname2 mode and ownership
			if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0)
				errExit("mount bind");
			/* coverity[toctou] */
			if (chown(dname2, s.st_uid, s.st_gid) == -1)
				errExit("mount-bind chown");
			/* coverity[toctou] */
			if (chmod(dname2, s.st_mode) == -1)
				errExit("mount-bind chmod");
				
			entry = entry->next;
			continue;
		}

		// Process noblacklist command
		if (strncmp(entry->data, "noblacklist ", 12) == 0) {
			if (noblacklist_c >= sizeof(noblacklist) / sizeof(noblacklist[0])) {
				fputs("Error: out of memory for noblacklist entries\n", stderr);
				exit(1);
			}
			else
				noblacklist[noblacklist_c++] = expand_home(entry->data + 12, homedir);
			entry = entry->next;
			continue;
		}

		// process blacklist command
		if (strncmp(entry->data, "blacklist ", 10) == 0)  {
			ptr = entry->data + 10;
			op = BLACKLIST_FILE;
		}
		else if (strncmp(entry->data, "read-only ", 10) == 0) {
			ptr = entry->data + 10;
			op = MOUNT_READONLY;
		}			
		else if (strncmp(entry->data, "tmpfs ", 6) == 0) {
			ptr = entry->data + 6;
			op = MOUNT_TMPFS;
		}			
		else {
			fprintf(stderr, "Error: invalid profile line %s\n", entry->data);
			entry = entry->next;
			continue;
		}

		// replace home macro in blacklist array
		char *new_name = expand_home(ptr, homedir);
		ptr = new_name;

		// expand path macro - look for the file in /bin, /usr/bin, /sbin and  /usr/sbin directories
		// TODO: should we look for more bin paths?
		if (ptr) {
			if (strncmp(ptr, "${PATH}", 7) == 0) {
				char *fname = ptr + 7;
				size_t fname_len = strlen(fname);
				char **path, *paths[] = {"/bin", "/sbin", "/usr/bin", "/usr/sbin", NULL};
				for (path = &paths[0]; *path; path++) {
					char newname[strlen(*path) + fname_len + 1];
					sprintf(newname, "%s%s", *path, fname);
					globbing(op, newname, (const char**)noblacklist, noblacklist_c, emptydir, emptyfile);
				}
			}
			else
				globbing(op, ptr, (const char**)noblacklist, noblacklist_c, emptydir, emptyfile);
		}

		if (new_name)
			free(new_name);
		entry = entry->next;
	}

	size_t i;
	for (i = 0; i < noblacklist_c; i++) free(noblacklist[i]);
}
コード例 #8
0
ファイル: fs_whitelist.c プロジェクト: ilikenwf/firejail
// whitelist for /home/user directory
void fs_whitelist(void) {
    char *homedir = cfg.homedir;
    assert(homedir);
    ProfileEntry *entry = cfg.profile;
    if (!entry)
        return;

    char *new_name = NULL;
    int home_dir = 0;	// /home/user directory flag
    int tmp_dir = 0;	// /tmp directory flag
    int media_dir = 0;	// /media directory flag
    int var_dir = 0;		// /var directory flag
    int dev_dir = 0;		// /dev directory flag

    // verify whitelist files, extract symbolic links, etc.
    while (entry) {
        // handle only whitelist commands
        if (strncmp(entry->data, "whitelist ", 10)) {
            entry = entry->next;
            continue;
        }

        // replace ~/ or ${HOME} into /home/username
        new_name = expand_home(entry->data + 10, cfg.homedir);
        assert(new_name);

        // extract the absolute path of the file
        // realpath function will fail with ENOENT if the file is not found
        char *fname = realpath(new_name, NULL);
        if (!fname) {
            // file not found, blank the entry in the list and continue
            if (arg_debug)
                printf("Removed whitelist path: %s\n", entry->data);
            *entry->data = '\0';
            continue;
        }

        // valid path referenced to filesystem root
        if (*new_name != '/')
            goto errexit;

        // check for supported directories
        if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) {
            entry->home_dir = 1;
            home_dir = 1;
            // both path and absolute path are under /home
            if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0)
                goto errexit;
        }
        else if (strncmp(new_name, "/tmp/", 5) == 0) {
            entry->tmp_dir = 1;
            tmp_dir = 1;
            // both path and absolute path are under /tmp
            if (strncmp(fname, "/tmp/", 5) != 0)
                goto errexit;
        }
        else if (strncmp(new_name, "/media/", 7) == 0) {
            entry->media_dir = 1;
            media_dir = 1;
            // both path and absolute path are under /media
            if (strncmp(fname, "/media/", 7) != 0)
                goto errexit;
        }
        else if (strncmp(new_name, "/var/", 5) == 0) {
            entry->var_dir = 1;
            var_dir = 1;
            // both path and absolute path are under /var
            if (strncmp(fname, "/var/", 5) != 0)
                goto errexit;
        }
        else if (strncmp(new_name, "/dev/", 5) == 0) {
            entry->dev_dir = 1;
            dev_dir = 1;
            // both path and absolute path are under /dev
            if (strncmp(fname, "/dev/", 5) != 0)
                goto errexit;
        }
        else
            goto errexit;

        // mark symbolic links
        if (is_link(new_name))
            entry->link = new_name;
        else
            free(new_name);

        // change file name in entry->data
        if (strcmp(fname, entry->data + 10) != 0) {
            char *newdata;
            if (asprintf(&newdata, "whitelist %s", fname) == -1)
                errExit("asprintf");
            entry->data = newdata;
            if (arg_debug)
                printf("Replaced whitelist path: %s\n", entry->data);
        }
        free(fname);
        entry = entry->next;
    }

    // create mount points
    fs_build_mnt_dir();

    // /home/user
    if (home_dir) {
        // keep a copy of real home dir in WHITELIST_HOME_DIR
        int rv = mkdir(WHITELIST_HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
        if (rv == -1)
            errExit("mkdir");
        if (chown(WHITELIST_HOME_DIR, getuid(), getgid()) < 0)
            errExit("chown");
        if (chmod(WHITELIST_HOME_DIR, 0755) < 0)
            errExit("chmod");

        if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
            errExit("mount bind");

        // mount a tmpfs and initialize /home/user
        fs_private();
    }

    // /tmp mountpoint
    if (tmp_dir) {
        // keep a copy of real /tmp directory in WHITELIST_TMP_DIR
        int rv = mkdir(WHITELIST_TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
        if (rv == -1)
            errExit("mkdir");
        if (chown(WHITELIST_TMP_DIR, 0, 0) < 0)
            errExit("chown");
        if (chmod(WHITELIST_TMP_DIR, 0777) < 0)
            errExit("chmod");

        if (mount("/tmp", WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
            errExit("mount bind");

        // mount tmpfs on /tmp
        if (arg_debug)
            printf("Mounting tmpfs on /tmp directory\n");
        if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=777,gid=0") < 0)
            errExit("mounting tmpfs on /tmp");
    }

    // /media mountpoint
    if (media_dir) {
        // keep a copy of real /media directory in WHITELIST_MEDIA_DIR
        int rv = mkdir(WHITELIST_MEDIA_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
        if (rv == -1)
            errExit("mkdir");
        if (chown(WHITELIST_MEDIA_DIR, 0, 0) < 0)
            errExit("chown");
        if (chmod(WHITELIST_MEDIA_DIR, 0755) < 0)
            errExit("chmod");

        if (mount("/media", WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
            errExit("mount bind");

        // mount tmpfs on /media
        if (arg_debug)
            printf("Mounting tmpfs on /media directory\n");
        if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
            errExit("mounting tmpfs on /media");
    }

    // /media mountpoint
    if (var_dir) {
        // keep a copy of real /var directory in WHITELIST_VAR_DIR
        int rv = mkdir(WHITELIST_VAR_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
        if (rv == -1)
            errExit("mkdir");
        if (chown(WHITELIST_VAR_DIR, 0, 0) < 0)
            errExit("chown");
        if (chmod(WHITELIST_VAR_DIR, 0755) < 0)
            errExit("chmod");

        if (mount("/var", WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
            errExit("mount bind");

        // mount tmpfs on /var
        if (arg_debug)
            printf("Mounting tmpfs on /var directory\n");
        if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
            errExit("mounting tmpfs on /var");
    }

    // /dev mountpoint
    if (dev_dir) {
        // keep a copy of real /dev directory in WHITELIST_DEV_DIR
        int rv = mkdir(WHITELIST_DEV_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
        if (rv == -1)
            errExit("mkdir");
        if (chown(WHITELIST_DEV_DIR, 0, 0) < 0)
            errExit("chown");
        if (chmod(WHITELIST_DEV_DIR, 0755) < 0)
            errExit("chmod");

        if (mount("/dev", WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
            errExit("mount bind");

        // mount tmpfs on /var
        if (arg_debug)
            printf("Mounting tmpfs on /dev directory\n");
        if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
            errExit("mounting tmpfs on /dev");
    }

    // go through profile rules again, and interpret whitelist commands
    entry = cfg.profile;
    while (entry) {
        // handle only whitelist commands
        if (strncmp(entry->data, "whitelist ", 10)) {
            entry = entry->next;
            continue;
        }

//printf("here %d#%s#\n", __LINE__, entry->data);
        // whitelist the real file
        whitelist_path(entry);

        // create the link if any
        if (entry->link) {
            // if the link is already there, do not bother
            struct stat s;
            if (stat(entry->link, &s) != 0) {
                int rv = symlink(entry->data + 10, entry->link);
                if (rv)
                    fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link);
                else if (arg_debug)
                    printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10);
            }
        }

        entry = entry->next;
    }

    // mask the real home directory, currently mounted on WHITELIST_HOME_DIR
    if (home_dir) {
        if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
            errExit("mount tmpfs");
    }

    // mask the real /tmp directory, currently mounted on WHITELIST_TMP_DIR
    if (tmp_dir) {
        if (mount("tmpfs", WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
            errExit("mount tmpfs");
    }

    return;

errexit:
    fprintf(stderr, "Error: invalid whitelist path %s\n", new_name);
    exit(1);
}
コード例 #9
0
ファイル: fs_whitelist.c プロジェクト: SeriousBug/firejail
// whitelist for /home/user directory
void fs_whitelist(void) {
	char *homedir = cfg.homedir;
	assert(homedir);
	ProfileEntry *entry = cfg.profile;
	if (!entry)
		return;

	char *new_name = NULL;
	int home_dir = 0;	// /home/user directory flag
	int tmp_dir = 0;	// /tmp directory flag
	int media_dir = 0;	// /media directory flag
	int var_dir = 0;		// /var directory flag
	int dev_dir = 0;		// /dev directory flag
	int opt_dir = 0;		// /opt directory flag

	// verify whitelist files, extract symbolic links, etc.
	while (entry) {
		// handle only whitelist commands
		if (strncmp(entry->data, "whitelist ", 10)) {
			entry = entry->next;
			continue;
		}

		// resolve ${DOWNLOADS}
		if (strcmp(entry->data + 10, "${DOWNLOADS}") == 0) {
			char *tmp = resolve_downloads();
			if (tmp)
				entry->data = tmp;
			else {
				*entry->data = '\0';
				fprintf(stderr, "***\n");
				fprintf(stderr, "*** Warning: cannot whitelist Downloads directory\n");
				fprintf(stderr, "*** \tAny file saved will be lost when the sandbox is closed.\n");
				fprintf(stderr, "*** \tPlease create a proper Downloads directory for your application.\n");
				fprintf(stderr, "***\n");
				continue;
			}
		}

		// replace ~/ or ${HOME} into /home/username
		new_name = expand_home(entry->data + 10, cfg.homedir);
		assert(new_name);

		// extract the absolute path of the file
		// realpath function will fail with ENOENT if the file is not found
		char *fname = realpath(new_name, NULL);
		if (!fname) {
			// file not found, blank the entry in the list and continue
			if (arg_debug || arg_debug_whitelists) {
				printf("Removed whitelist path: %s\n", entry->data);
				printf("\texpanded: %s\n", new_name);
				printf("\treal path: (null)\n");
				printf("\t");fflush(0);
				perror("realpath");
			}
			*entry->data = '\0';
			continue;
		}
		
		// valid path referenced to filesystem root
		if (*new_name != '/')
			goto errexit;

		// check for supported directories
		if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) {
			// whitelisting home directory is disabled if --private or --private-home option is present
			if (arg_private) {
				if (arg_debug || arg_debug_whitelists)
					printf("Removed whitelist path %s, --private option is present\n", entry->data);

				*entry->data = '\0';
				continue;
			}

			entry->home_dir = 1;
			home_dir = 1;
			// both path and absolute path are under /home
			if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0)
				goto errexit;
		}
		else if (strncmp(new_name, "/tmp/", 5) == 0) {
			entry->tmp_dir = 1;
			tmp_dir = 1;
			// both path and absolute path are under /tmp
			if (strncmp(fname, "/tmp/", 5) != 0)
				goto errexit;
		}
		else if (strncmp(new_name, "/media/", 7) == 0) {
			entry->media_dir = 1;
			media_dir = 1;
			// both path and absolute path are under /media
			if (strncmp(fname, "/media/", 7) != 0)
				goto errexit;
		}
		else if (strncmp(new_name, "/var/", 5) == 0) {
			entry->var_dir = 1;
			var_dir = 1;
			// both path and absolute path are under /var
			if (strncmp(fname, "/var/", 5) != 0)
				goto errexit;
		}
		else if (strncmp(new_name, "/dev/", 5) == 0) {
			entry->dev_dir = 1;
			dev_dir = 1;
			// both path and absolute path are under /dev
			if (strncmp(fname, "/dev/", 5) != 0)
				goto errexit;
		}
		else if (strncmp(new_name, "/opt/", 5) == 0) {
			entry->opt_dir = 1;
			opt_dir = 1;
			// both path and absolute path are under /dev
			if (strncmp(fname, "/opt/", 5) != 0)
				goto errexit;
		}
		else
			goto errexit;

		// mark symbolic links
		if (is_link(new_name))
			entry->link = new_name;
		else {
			free(new_name);
			new_name = NULL;
		}

		// change file name in entry->data
		if (strcmp(fname, entry->data + 10) != 0) {
			char *newdata;
			if (asprintf(&newdata, "whitelist %s", fname) == -1)
				errExit("asprintf");
			entry->data = newdata;
			if (arg_debug || arg_debug_whitelists)
				printf("Replaced whitelist path: %s\n", entry->data);
		}
		free(fname);
		entry = entry->next;
	}
		
	// create mount points
	fs_build_mnt_dir();
	

	// /home/user
	if (home_dir) {
		// keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR
		int rv = mkdir(RUN_WHITELIST_HOME_USER_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_HOME_USER_DIR, getuid(), getgid()) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_HOME_USER_DIR, 0755) < 0)
			errExit("chmod");
	
		if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount a tmpfs and initialize /home/user
		 fs_private();
	}
	
	// /tmp mountpoint
	if (tmp_dir) {
		// keep a copy of real /tmp directory in WHITELIST_TMP_DIR
		int rv = mkdir(RUN_WHITELIST_TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_TMP_DIR, 0, 0) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_TMP_DIR, 0777) < 0)
			errExit("chmod");
	
		if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount tmpfs on /tmp
		if (arg_debug || arg_debug_whitelists)
			printf("Mounting tmpfs on /tmp directory\n");
		if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=777,gid=0") < 0)
			errExit("mounting tmpfs on /tmp");
		fs_logger("mount tmpfs on /tmp");
	}
	
	// /media mountpoint
	if (media_dir) {
		// keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR
		int rv = mkdir(RUN_WHITELIST_MEDIA_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_MEDIA_DIR, 0, 0) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_MEDIA_DIR, 0755) < 0)
			errExit("chmod");
	
		if (mount("/media", RUN_WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount tmpfs on /media
		if (arg_debug || arg_debug_whitelists)
			printf("Mounting tmpfs on /media directory\n");
		if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mounting tmpfs on /media");
		fs_logger("mount tmpfs on /media");
	}

	// /var mountpoint
	if (var_dir) {
		// keep a copy of real /var directory in RUN_WHITELIST_VAR_DIR
		int rv = mkdir(RUN_WHITELIST_VAR_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_VAR_DIR, 0, 0) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_VAR_DIR, 0755) < 0)
			errExit("chmod");
	
		if (mount("/var", RUN_WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount tmpfs on /var
		if (arg_debug || arg_debug_whitelists)
			printf("Mounting tmpfs on /var directory\n");
		if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mounting tmpfs on /var");
		fs_logger("mount tmpfs on /var");
	}

	// /dev mountpoint
	if (dev_dir) {
		// keep a copy of real /dev directory in RUN_WHITELIST_DEV_DIR
		int rv = mkdir(RUN_WHITELIST_DEV_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_DEV_DIR, 0, 0) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_DEV_DIR, 0755) < 0)
			errExit("chmod");
	
		if (mount("/dev", RUN_WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount tmpfs on /dev
		if (arg_debug || arg_debug_whitelists)
			printf("Mounting tmpfs on /dev directory\n");
		if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mounting tmpfs on /dev");
		fs_logger("mount tmpfs on /dev");
	}

	// /opt mountpoint
	if (opt_dir) {
		// keep a copy of real /opt directory in RUN_WHITELIST_DEV_DIR
		int rv = mkdir(RUN_WHITELIST_OPT_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
		if (rv == -1)
			errExit("mkdir");
		if (chown(RUN_WHITELIST_OPT_DIR, 0, 0) < 0)
			errExit("chown");
		if (chmod(RUN_WHITELIST_OPT_DIR, 0755) < 0)
			errExit("chmod");
	
		if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
			errExit("mount bind");
	
		// mount tmpfs on /opt
		if (arg_debug || arg_debug_whitelists)
			printf("Mounting tmpfs on /opt directory\n");
		if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mounting tmpfs on /opt");
		fs_logger("mount tmpfs on /opt");
	}

	// go through profile rules again, and interpret whitelist commands
	entry = cfg.profile;
	while (entry) {
		// handle only whitelist commands
		if (strncmp(entry->data, "whitelist ", 10)) {
			entry = entry->next;
			continue;
		}

//printf("here %d#%s#\n", __LINE__, entry->data);
		// whitelist the real file
		whitelist_path(entry);

		// create the link if any
		if (entry->link) {
			// if the link is already there, do not bother
			struct stat s;
			if (stat(entry->link, &s) != 0) {
				// create the path if necessary
				mkpath(entry->link, s.st_mode);

				int rv = symlink(entry->data + 10, entry->link);
				if (rv)
					fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link);
				else if (arg_debug || arg_debug_whitelists)
					printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10);
			}
		}

		entry = entry->next;
	}

	// mask the real home directory, currently mounted on RUN_WHITELIST_HOME_DIR
	if (home_dir) {
		if (mount("tmpfs", RUN_WHITELIST_HOME_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mount tmpfs");
	}
	
	// mask the real /tmp directory, currently mounted on RUN_WHITELIST_TMP_DIR
	if (tmp_dir) {
		if (mount("tmpfs", RUN_WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
			errExit("mount tmpfs");
	}

	if (new_name)
		free(new_name);
		
	return;

errexit:
	fprintf(stderr, "Error: invalid whitelist path %s\n", new_name);
	exit(1);
}
コード例 #10
0
ファイル: fs.c プロジェクト: Nanreit/firejail
// blacklist files or directoies by mounting empty files on top of them
void fs_blacklist(void) {
	char *homedir = cfg.homedir;
	assert(homedir);
	ProfileEntry *entry = cfg.profile;
	if (!entry)
		return;
		
	size_t noblacklist_c = 0;
	size_t noblacklist_m = 32;
	char **noblacklist = calloc(noblacklist_m, sizeof(*noblacklist));

	if (noblacklist == NULL)
		errExit("failed allocating memory for noblacklist entries");

	while (entry) {
		OPERATION op = OPERATION_MAX;
		char *ptr;

		// whitelist commands handled by fs_whitelist()
		if (strncmp(entry->data, "whitelist ", 10) == 0 || *entry->data == '\0') {
			entry = entry->next;
			continue;
		}

		// process bind command
		if (strncmp(entry->data, "bind ", 5) == 0)  {
			char *dname1 = entry->data + 5;
			char *dname2 = split_comma(dname1);
			if (dname2 == NULL) {
				fprintf(stderr, "Error: second directory missing in bind command\n");
				entry = entry->next;
				continue;
			}
			struct stat s;
			if (stat(dname1, &s) == -1) {
				fprintf(stderr, "Error: cannot find directories for bind command\n");
				entry = entry->next;
				continue;
			}
			if (stat(dname2, &s) == -1) {
				fprintf(stderr, "Error: cannot find directories for bind command\n");
				entry = entry->next;
				continue;
			}
			
			// mount --bind olddir newdir
			if (arg_debug)
				printf("Mount-bind %s on top of %s\n", dname1, dname2);
			// preserve dname2 mode and ownership
			if (mount(dname1, dname2, NULL, MS_BIND|MS_REC, NULL) < 0)
				errExit("mount bind");
			/* coverity[toctou] */
			if (chown(dname2, s.st_uid, s.st_gid) == -1)
				errExit("mount-bind chown");
			/* coverity[toctou] */
			if (chmod(dname2, s.st_mode) == -1)
				errExit("mount-bind chmod");
				
			entry = entry->next;
			continue;
		}

		// Process noblacklist command
		if (strncmp(entry->data, "noblacklist ", 12) == 0) {
			if (noblacklist_c >= noblacklist_m) {
                                noblacklist_m *= 2;
                                noblacklist = realloc(noblacklist, sizeof(*noblacklist) * noblacklist_m);
                                if (noblacklist == NULL)
                                        errExit("failed increasing memory for noblacklist entries");
			}
			else
				noblacklist[noblacklist_c++] = expand_home(entry->data + 12, homedir);
			entry = entry->next;
			continue;
		}

		// process blacklist command
		if (strncmp(entry->data, "blacklist ", 10) == 0)  {
			ptr = entry->data + 10;
			op = BLACKLIST_FILE;
		}
		else if (strncmp(entry->data, "blacklist-nolog ", 16) == 0)  {
			ptr = entry->data + 16;
			op = BLACKLIST_NOLOG;
		}
		else if (strncmp(entry->data, "read-only ", 10) == 0) {
			ptr = entry->data + 10;
			op = MOUNT_READONLY;
		}			
		else if (strncmp(entry->data, "tmpfs ", 6) == 0) {
			ptr = entry->data + 6;
			op = MOUNT_TMPFS;
		}			
		else {
			fprintf(stderr, "Error: invalid profile line %s\n", entry->data);
			entry = entry->next;
			continue;
		}

		// replace home macro in blacklist array
		char *new_name = expand_home(ptr, homedir);
		ptr = new_name;

		// expand path macro - look for the file in /bin, /usr/bin, /sbin and  /usr/sbin directories
		if (ptr) {
			if (strncmp(ptr, "${PATH}", 7) == 0) {
				char *fname = ptr + 7;
				size_t fname_len = strlen(fname);
				char **paths = build_paths(); //{"/bin", "/sbin", "/usr/bin", "/usr/sbin", NULL};
				int i = 0;
				while (paths[i] != NULL) {
					char *path = paths[i];
					i++;
					char newname[strlen(path) + fname_len + 1];
					sprintf(newname, "%s%s", path, fname);
					globbing(op, newname, (const char**)noblacklist, noblacklist_c);
				}
			}
			else
				globbing(op, ptr, (const char**)noblacklist, noblacklist_c);
		}

		if (new_name)
			free(new_name);
		entry = entry->next;
	}

	size_t i;
	for (i = 0; i < noblacklist_c; i++) free(noblacklist[i]);
        free(noblacklist);
}
コード例 #11
0
ファイル: fs_whitelist.c プロジェクト: satai/firejail
// whitelist for /home/user directory
void fs_whitelist(void) {
	char *homedir = cfg.homedir;
	assert(homedir);
	ProfileEntry *entry = cfg.profile;
	if (!entry)
		return;
	
	// realpath function will fail with ENOENT if the file is not found
	// we need to expand the path before installing a new, empty home directory
	while (entry) {
		// handle only whitelist commands
		if (strncmp(entry->data, "whitelist ", 10)) {
			entry = entry->next;
			continue;
		}

		char *new_name = expand_home(entry->data + 10, cfg.homedir);
		assert(new_name);
		char *fname = realpath(new_name, NULL);
		free(new_name);
		if (fname) {
			// change file name in entry->data
			if (strcmp(fname, entry->data + 10) != 0) {
				char *newdata;
				if (asprintf(&newdata, "whitelist %s", fname) == -1)
					errExit("asprintf");
				entry->data = newdata;
				if (arg_debug)
					printf("Replaced whitelist path: %s\n", entry->data);
			}
						
			free(fname);
		}
		else {
			// file not found, blank the entry in the list
			if (arg_debug)
				printf("Removed whitelist path: %s\n", entry->data);
			*entry->data = '\0';
		}
		entry = entry->next;
	}
		
	// create /tmp/firejail/mnt/whome directory
	fs_build_mnt_dir();
	int rv = mkdir(WHITELIST_HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO);
	if (rv == -1)
		errExit("mkdir");
	if (chown(WHITELIST_HOME_DIR, getuid(), getgid()) < 0)
		errExit("chown");
	if (chmod(WHITELIST_HOME_DIR, 0755) < 0)
		errExit("chmod");

	// keep a copy of real home dir in /tmp/firejail/mnt/whome
	if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
		errExit("mount bind");

	// start building the new home directory by mounting a tmpfs fielsystem
	 fs_private();

	// go through profile rules again, and interpret whitelist commands
	entry = cfg.profile;
	while (entry) {
		// handle only whitelist commands
		if (strncmp(entry->data, "whitelist ", 10)) {
			entry = entry->next;
			continue;
		}

		whitelist_path(entry->data + 10);

		entry = entry->next;
	}

	// mask the real home directory, currently mounted on /tmp/firejail/mnt/whome
	if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC,  "mode=755,gid=0") < 0)
		errExit("mount tmpfs");
}
コード例 #12
0
bool Configuration::read(const std::vector<std::string> &config_paths, bool ignore_errors)
{
	option_parser p;

	p.add<void>("visualizer_sample_multiplier", nullptr, "", [](std::string v) {
			if (!v.empty())
				deprecated(
					"visualizer_sample_multiplier",
					0.9,
					"visualizer scales automatically");
		});
	p.add<void>("progressbar_boldness", nullptr, "", [](std::string v) {
			if (!v.empty())
				deprecated(
					"progressbar_boldness",
					0.9,
					"use extended progressbar_color and progressbar_elapsed_color instead");
		});

	// keep the same order of variables as in configuration file
	p.add("ncmpcpp_directory", &ncmpcpp_directory, "~/.ncmpcpp/", adjust_directory);
	p.add("lyrics_directory", &lyrics_directory, "~/.lyrics/", adjust_directory);
	p.add<void>("mpd_host", nullptr, "localhost", [](std::string host) {
			expand_home(host);
			Mpd.SetHostname(host);
		});
	p.add<void>("mpd_port", nullptr, "6600", [](std::string port) {
			Mpd.SetPort(verbose_lexical_cast<unsigned>(port));
		});
	p.add("mpd_music_dir", &mpd_music_dir, "~/music", adjust_directory);
	p.add("mpd_connection_timeout", &mpd_connection_timeout, "5");
	p.add("mpd_crossfade_time", &crossfade_time, "5");
	p.add("visualizer_fifo_path", &visualizer_fifo_path, "/tmp/mpd.fifo", adjust_path);
	p.add("visualizer_output_name", &visualizer_output_name, "Visualizer feed");
	p.add("visualizer_in_stereo", &visualizer_in_stereo, "yes", yes_no);
	p.add("visualizer_sync_interval", &visualizer_sync_interval, "30",
	      [](std::string v) {
		      unsigned sync_interval = verbose_lexical_cast<unsigned>(v);
		      lowerBoundCheck<unsigned>(sync_interval, 10);
		      return boost::posix_time::seconds(sync_interval);
	});
	p.add("visualizer_type", &visualizer_type, "wave");
	p.add("visualizer_look", &visualizer_chars, "●▮", [](std::string s) {
			auto result = ToWString(std::move(s));
			boundsCheck<std::wstring::size_type>(result.size(), 2, 2);
			return result;
	});
	p.add("visualizer_color", &visualizer_colors,
	      "blue, cyan, green, yellow, magenta, red", list_of<NC::FormattedColor>);
	p.add("system_encoding", &system_encoding, "", [](std::string encoding) {
#ifdef HAVE_LANGINFO_H
			// try to autodetect system encoding
			if (encoding.empty())
			{
				encoding = nl_langinfo(CODESET);
				if (encoding == "UTF-8") // mpd uses utf-8 by default so no need to convert
					encoding.clear();
			}
#endif // HAVE_LANGINFO_H
			return encoding;
		});
	p.add("playlist_disable_highlight_delay", &playlist_disable_highlight_delay,
	      "5", [](std::string v) {
		      return boost::posix_time::seconds(verbose_lexical_cast<unsigned>(v));
	      });
	p.add("message_delay_time", &message_delay_time, "5");
	p.add("song_list_format", &song_list_format,
	      "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [](std::string v) {
		      return Format::parse(v);
	      });
	p.add("song_status_format", &song_status_format,
	      "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string v) {
		      auto flags = Format::Flags::All ^ Format::Flags::OutputSwitch;
		      // precompute wide format for status display
		      song_status_wformat = Format::parse(ToWString(v), flags);
		      return Format::parse(v, flags);
	});
	p.add("song_library_format", &song_library_format,
	      "{%n - }{%t}|{%f}", [](std::string v) {
		      return Format::parse(v);
	      });
	p.add("alternative_header_first_line_format", &new_header_first_line,
	      "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [](std::string v) {
		      return Format::parse(ToWString(std::move(v)),
		                           Format::Flags::All ^ Format::Flags::OutputSwitch);
	});
	p.add("alternative_header_second_line_format", &new_header_second_line,
	      "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [](std::string v) {
		      return Format::parse(ToWString(std::move(v)),
		                           Format::Flags::All ^ Format::Flags::OutputSwitch);
	});
	p.add("now_playing_prefix", &now_playing_prefix,
	      "$b", [this](std::string v) {
		      NC::Buffer result = buffer(v);
		      now_playing_prefix_length = wideLength(ToWString(result.str()));
		      return result;
	});
	p.add("now_playing_suffix", &now_playing_suffix,
	      "$/b", [this](std::string v) {
		      NC::Buffer result = buffer(v);
		      now_playing_suffix_length = wideLength(ToWString(result.str()));
		      return result;
	});
	p.add("browser_playlist_prefix", &browser_playlist_prefix, "$2playlist$9 ", buffer);
	p.add("selected_item_prefix", &selected_item_prefix,
	      "$6", [this](std::string v) {
		      NC::Buffer result = buffer(v);
		      selected_item_prefix_length = wideLength(ToWString(result.str()));
		      return result;
	      });
	p.add("selected_item_suffix", &selected_item_suffix,
	      "$9", [this](std::string v) {
		      NC::Buffer result = buffer(v);
		      selected_item_suffix_length = wideLength(ToWString(result.str()));
		      return result;
	      });
	p.add("modified_item_prefix", &modified_item_prefix, "$3>$9 ", buffer);
	p.add("song_window_title_format", &song_window_title_format,
	      "{%a - }{%t}|{%f}", [](std::string v) {
		      return Format::parse(v, Format::Flags::Tag);
	      });
	p.add("browser_sort_mode", &browser_sort_mode, "name");
	p.add("browser_sort_format", &browser_sort_format,
	      "{%a - }{%t}|{%f} {(%l)}", [](std::string v) {
		      return Format::parse(v, Format::Flags::Tag);
	      });
	p.add("song_columns_list_format", &song_columns_mode_format,
	      "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
	      [this](std::string v) {
		      columns = generate_columns(v);
		      return columns_to_format(columns);
	      });
	p.add("execute_on_song_change", &execute_on_song_change, "", adjust_path);
	p.add("execute_on_player_state_change", &execute_on_player_state_change,
	      "", adjust_path);
	p.add("playlist_show_mpd_host", &playlist_show_mpd_host, "no", yes_no);
	p.add("playlist_show_remaining_time", &playlist_show_remaining_time, "no", yes_no);
	p.add("playlist_shorten_total_times", &playlist_shorten_total_times, "no", yes_no);
	p.add("playlist_separate_albums", &playlist_separate_albums, "no", yes_no);
	p.add("playlist_display_mode", &playlist_display_mode, "columns");
	p.add("browser_display_mode", &browser_display_mode, "classic");
	p.add("search_engine_display_mode", &search_engine_display_mode, "classic");
	p.add("playlist_editor_display_mode", &playlist_editor_display_mode, "classic");
	p.add("discard_colors_if_item_is_selected", &discard_colors_if_item_is_selected,
	      "yes", yes_no);
	p.add("show_duplicate_tags", &MPD::Song::ShowDuplicateTags, "yes", yes_no);
	p.add("incremental_seeking", &incremental_seeking, "yes", yes_no);
	p.add("seek_time", &seek_time, "1");
	p.add("volume_change_step", &volume_change_step, "2");
	p.add("autocenter_mode", &autocenter_mode, "no", yes_no);
	p.add("centered_cursor", &centered_cursor, "no", yes_no);
	p.add("progressbar_look", &progressbar, "=>", [](std::string v) {
			auto result = ToWString(std::move(v));
			boundsCheck<std::wstring::size_type>(result.size(), 2, 3);
			// If two characters were specified, fill \0 as the third one.
			result.resize(3);
			return result;
	});
	p.add("default_place_to_search_in", &search_in_db, "database", [](std::string v) {
			if (v == "database")
				return true;
			else if (v == "playlist")
				return false;
			else
				invalid_value(v);
	});
	p.add("user_interface", &design, "classic");
	p.add("data_fetching_delay", &data_fetching_delay, "yes", yes_no);
	p.add("media_library_primary_tag", &media_lib_primary_tag, "artist", [](std::string v) {
			if (v == "artist")
				return MPD_TAG_ARTIST;
			else if (v == "album_artist")
				return MPD_TAG_ALBUM_ARTIST;
			else if (v == "date")
				return MPD_TAG_DATE;
			else if (v == "genre")
				return MPD_TAG_GENRE;
			else if (v == "composer")
				return MPD_TAG_COMPOSER;
			else if (v == "performer")
				return MPD_TAG_PERFORMER;
			else
				invalid_value(v);
		});
	p.add("default_find_mode", &wrapped_search, "wrapped", [](std::string v) {
			if (v == "wrapped")
				return true;
			else if (v == "normal")
				return false;
			else
				invalid_value(v);
		});
	p.add("default_tag_editor_pattern", &pattern, "%n - %t");
	p.add("header_visibility", &header_visibility, "yes", yes_no);
	p.add("statusbar_visibility", &statusbar_visibility, "yes", yes_no);
	p.add("titles_visibility", &titles_visibility, "yes", yes_no);
	p.add("header_text_scrolling", &header_text_scrolling, "yes", yes_no);
	p.add("cyclic_scrolling", &use_cyclic_scrolling, "no", yes_no);
	p.add("lines_scrolled", &lines_scrolled, "2");
	p.add("lyrics_fetchers", &lyrics_fetchers,
	      "lyricwiki, azlyrics, genius, sing365, lyricsmania, metrolyrics, justsomelyrics, tekstowo, internet",
	      list_of<LyricsFetcher_>);
	p.add("follow_now_playing_lyrics", &now_playing_lyrics, "no", yes_no);
	p.add("fetch_lyrics_for_current_song_in_background", &fetch_lyrics_in_background,
	      "no", yes_no);
	p.add("store_lyrics_in_song_dir", &store_lyrics_in_song_dir, "no", yes_no);
	p.add("generate_win32_compatible_filenames", &generate_win32_compatible_filenames,
	      "yes", yes_no);
	p.add("allow_for_physical_item_deletion", &allow_for_physical_item_deletion,
	      "no", yes_no);
	p.add("lastfm_preferred_language", &lastfm_preferred_language, "en");
	p.add("space_add_mode", &space_add_mode, "add_remove");
	p.add("show_hidden_files_in_local_browser", &local_browser_show_hidden_files,
	      "no", yes_no);
	p.add<void>(
		"screen_switcher_mode", nullptr, "playlist, browser",
		[this](std::string v) {
			if (v == "previous")
				screen_switcher_previous = true;
			else
			{
				screen_switcher_previous = false;
				screen_sequence = list_of<ScreenType>(v, [](std::string s) {
						auto screen = stringtoStartupScreenType(s);
						if (screen == ScreenType::Unknown)
							invalid_value(s);
						return screen;
					});
			}
		});
	p.add("startup_screen", &startup_screen_type, "playlist", [](std::string v) {
			auto screen = stringtoStartupScreenType(v);
			if (screen == ScreenType::Unknown)
				invalid_value(v);
			return screen;
		});
	p.add("startup_slave_screen", &startup_slave_screen_type, "", [](std::string v) {
			boost::optional<ScreenType> screen;
			if (!v.empty())
			{
				screen = stringtoStartupScreenType(v);
				if (screen == ScreenType::Unknown)
					invalid_value(v);
			}
			return screen;
		});
	p.add("startup_slave_screen_focus", &startup_slave_screen_focus, "no", yes_no);
	p.add("locked_screen_width_part", &locked_screen_width_part,
	      "50", [](std::string v) {
		      return verbose_lexical_cast<double>(v) / 100;
	      });
	p.add("ask_for_locked_screen_width_part", &ask_for_locked_screen_width_part,
	      "yes", yes_no);
	p.add("jump_to_now_playing_song_at_start", &jump_to_now_playing_song_at_start,
	      "yes", yes_no);
	p.add("ask_before_clearing_playlists", &ask_before_clearing_playlists,
	      "yes", yes_no);
	p.add("ask_before_shuffling_playlists", &ask_before_shuffling_playlists,
	      "yes", yes_no);
	p.add("clock_display_seconds", &clock_display_seconds, "no", yes_no);
	p.add("display_volume_level", &display_volume_level, "yes", yes_no);
	p.add("display_bitrate", &display_bitrate, "no", yes_no);
	p.add("display_remaining_time", &display_remaining_time, "no", yes_no);
	p.add("regular_expressions", &regex_type, "perl", [](std::string v) {
			if (v == "none")
				return boost::regex::icase | boost::regex::literal;
			else if (v == "basic")
				return boost::regex::icase | boost::regex::basic;
			else if (v == "extended")
				return boost::regex::icase |  boost::regex::extended;
			else if (v == "perl")
				return boost::regex::icase | boost::regex::perl;
			else
				invalid_value(v);
	});
	p.add("ignore_leading_the", &ignore_leading_the, "no", yes_no);
	p.add("block_search_constraints_change_if_items_found",
	      &block_search_constraints_change, "yes", yes_no);
	p.add("mouse_support", &mouse_support, "yes", yes_no);
	p.add("mouse_list_scroll_whole_page", &mouse_list_scroll_whole_page, "yes", yes_no);
	p.add("empty_tag_marker", &empty_tag, "<empty>");
	p.add("tags_separator", &MPD::Song::TagsSeparator, " | ");
	p.add("tag_editor_extended_numeration", &tag_editor_extended_numeration, "no", yes_no);
	p.add("media_library_sort_by_mtime", &media_library_sort_by_mtime, "no", yes_no);
	p.add("enable_window_title", &set_window_title, "yes", [](std::string v) {
			// Consider this variable only if TERM variable is available and we're not
			// in emacs terminal nor tty (through any wrapper like screen).
			auto term = getenv("TERM");
			if (term != nullptr
			    && strstr(term, "linux") == nullptr
			    && strncmp(term, "eterm", const_strlen("eterm")))
				return yes_no(v);
			else
			{
				std::clog << "Terminal doesn't support window title, skipping 'enable_window_title'.\n";
				return false;
			}
		});
	p.add("search_engine_default_search_mode", &search_engine_default_search_mode,
	      "1", [](std::string v) {
		      auto mode = verbose_lexical_cast<unsigned>(v);
		      boundsCheck<unsigned>(mode, 1, 3);
		      return --mode;
	      });
	p.add("external_editor", &external_editor, "nano", adjust_path);
	p.add("use_console_editor", &use_console_editor, "yes", yes_no);
	p.add("colors_enabled", &colors_enabled, "yes", yes_no);
	p.add("empty_tag_color", &empty_tags_color, "cyan");
	p.add("header_window_color", &header_color, "default");
	p.add("volume_color", &volume_color, "default");
	p.add("state_line_color", &state_line_color, "default");
	p.add("state_flags_color", &state_flags_color, "default:b");
	p.add("main_window_color", &main_color, "yellow");
	p.add("color1", &color1, "white");
	p.add("color2", &color2, "green");
	p.add("main_window_highlight_color", &main_highlight_color, "yellow");
	p.add("progressbar_color", &progressbar_color, "black:b");
	p.add("progressbar_elapsed_color", &progressbar_elapsed_color, "green:b");
	p.add("statusbar_color", &statusbar_color, "default");
	p.add("statusbar_time_color", &statusbar_time_color, "default:b");
	p.add("player_state_color", &player_state_color, "default:b");
	p.add("alternative_ui_separator_color", &alternative_ui_separator_color, "black:b");
	p.add("active_column_color", &active_column_color, "red");
	p.add("window_border_color", &window_border, "green", verbose_lexical_cast<NC::Color>);
	p.add("active_window_border", &active_window_border, "red",
	      verbose_lexical_cast<NC::Color>);

	return std::all_of(
		config_paths.begin(),
		config_paths.end(),
		[&](const std::string &config_path) {
			std::ifstream f(config_path);
			if (f.is_open())
				std::clog << "Reading configuration from " << config_path << "...\n";
			return p.run(f, ignore_errors);
		}
	) && p.initialize_undefined(ignore_errors);
}