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)); }
// 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; }
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); }
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); }
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); }
// 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); }
// 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]); }
// 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); }
// 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); }
// 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); }
// 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"); }
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", ¢ered_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", ®ex_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); }