use crate::shell::tables::SHELLS;
use crate::shell::tokens::basename;
const LAUNCHERS: &[(&str, &[&str])] = &[
("taskset", &[]),
(
"chrt",
&[
"-T",
"--sched-runtime",
"-P",
"--sched-period",
"-D",
"--sched-deadline",
],
),
(
"unshare",
&[
"-S",
"--setuid",
"-G",
"--setgid",
"-R",
"--root",
"-w",
"--wd",
"--map-user",
"--map-group",
"--map-users",
"--map-groups",
"--propagation",
"--setgroups",
"--monotonic",
"--boottime",
],
),
(
"nsenter",
&["-t", "--target", "-S", "--setuid", "-G", "--setgid"],
),
("prlimit", &["-p", "--pid", "-o", "--output"]),
(
"numactl",
&[
"-N",
"--cpunodebind",
"-m",
"--membind",
"-C",
"--physcpubind",
"-p",
"--preferred",
"-i",
"--interleave",
"-P",
"--preferred-many",
"-w",
"--weighted-interleave",
],
),
("cgexec", &["-g"]),
("firejail", &[]),
(
"systemd-run",
&[
"-u",
"--unit",
"-p",
"--property",
"--description",
"--slice",
"-H",
"--host",
"-M",
"--machine",
"-C",
"--capsule",
"--uid",
"--gid",
"--nice",
"-E",
"--setenv",
"--working-directory",
"--on-active",
"--on-boot",
"--on-startup",
"--on-unit-active",
"--on-unit-inactive",
"--on-calendar",
"--timer-property",
"--path-property",
"--socket-property",
"--service-type",
"--background",
],
),
("systemd-inhibit", &["--what", "--who", "--why", "--mode"]),
(
"strace",
&[
"-o", "-e", "-p", "-s", "-a", "-E", "-I", "-O", "-S", "-u", "-X", "-P", "-b",
],
),
(
"ltrace",
&[
"-o", "-e", "-p", "-s", "-a", "-n", "-u", "-l", "-x", "-F", "-A", "-D", "-w",
],
),
("valgrind", &[]),
("catchsegv", &[]),
("arch", &["-arch", "-d", "-e"]),
("sandbox-exec", &["-f", "-n", "-p", "-D"]),
(
"chpst",
&[
"-u", "-U", "-b", "-e", "-/", "-n", "-l", "-L", "-m", "-d", "-o", "-p", "-f", "-c",
"-r", "-t",
],
),
(
"setpriv",
&[
"--reuid",
"--regid",
"--ruid",
"--euid",
"--rgid",
"--egid",
"--groups",
"--securebits",
"--pdeathsig",
"--selinux-label",
"--apparmor-profile",
"--inh-caps",
"--ambient-caps",
"--bounding-set",
"--landlock-access",
"--landlock-rule",
],
),
("faketime", &["--date-prog"]),
(
"torsocks",
&[
"-u",
"--user",
"-p",
"--pass",
"-a",
"--address",
"-P",
"--port",
],
),
("proxychains", &["-f"]),
("proxychains4", &["-f"]),
("daemonize", &["-c", "-e", "-o", "-p", "-l", "-u", "-E"]),
("busybox", &[]),
("toybox", &[]),
];
const WITH_VALUE: &[&str] = &["taskset", "chrt", "faketime"];
const DAEMON_PROGRAMS: &[(&str, &str)] = &[("-x", "--exec"), ("-a", "--startas")];
const SUBMIT_VALUES: &[&str] = &["-l", "-p", "-o", "-e"];
pub(super) fn options(program: &str) -> Option<&'static [&'static str]> {
LAUNCHERS
.iter()
.find(|(name, _)| *name == program)
.map(|(_, options)| *options)
}
pub(super) fn is_launcher(program: &str) -> bool {
options(program).is_some() || matches!(program, "gdb" | "start-stop-daemon" | "launchctl")
}
pub(super) fn takes_value(program: &str) -> bool {
WITH_VALUE.contains(&program)
}
pub(super) fn runs(argv: &[String], program: &str, wrapped: Option<usize>) -> Vec<Vec<String>> {
let mut found = match program {
"gdb" => gdb(argv).into_iter().collect(),
"start-stop-daemon" => start_stop_daemon(argv),
"launchctl" => submit(argv),
_ => Vec::new(),
};
let launcher = options(program).is_some() || !found.is_empty();
if launcher {
for start in 1..argv.len() {
if Some(start) != wrapped && starts_command(&argv[start]) {
found.push(argv[start..].to_vec());
}
}
}
found
}
fn starts_command(word: &str) -> bool {
let name = basename(word);
SHELLS.contains(&name) || matches!(name, "openmoat" | "moat")
}
fn gdb(argv: &[String]) -> Option<Vec<String>> {
let at = argv.iter().position(|a| a == "--args" || a == "-args")?;
let rest = argv.get(at + 1..)?;
(!rest.is_empty()).then(|| rest.to_vec())
}
fn start_stop_daemon(argv: &[String]) -> Vec<Vec<String>> {
let end = argv.iter().position(|a| a == "--");
let options = &argv[1..end.unwrap_or(argv.len())];
let after_end = end.map_or(&[][..], |end| &argv[end + 1..]);
let mut found = Vec::new();
for (i, arg) in options.iter().enumerate() {
for (short, long) in DAEMON_PROGRAMS {
let value = if arg == short || arg == long {
options.get(i + 1).cloned()
} else {
arg.strip_prefix(long)
.and_then(|rest| rest.strip_prefix('='))
.or_else(|| arg.strip_prefix(short).filter(|r| !r.is_empty()))
.map(str::to_owned)
};
if let Some(program) = value {
found.push(
std::iter::once(program)
.chain(after_end.iter().cloned())
.collect(),
);
}
}
}
found
}
fn submit(argv: &[String]) -> Vec<Vec<String>> {
if argv.get(1).map(String::as_str) != Some("submit") {
return Vec::new();
}
let mut program = None;
let mut i = 2;
while let Some(arg) = argv.get(i) {
if arg == "--" {
i += 1;
break;
}
if !arg.starts_with('-') {
break;
}
if SUBMIT_VALUES.contains(&arg.as_str()) {
if arg == "-p" {
program = argv.get(i + 1).cloned();
}
i += 1;
}
i += 1;
}
let command = argv.get(i..).unwrap_or_default();
if command.is_empty() {
return Vec::new();
}
let mut found = vec![command.to_vec()];
if let Some(program) = program {
found.push(
std::iter::once(program)
.chain(command[1..].iter().cloned())
.collect(),
);
}
found
}
#[cfg(test)]
mod tests {
use super::runs;
fn of(line: &str, wrapped: Option<usize>) -> Vec<String> {
let argv: Vec<String> = line.split(' ').map(str::to_owned).collect();
runs(&argv, &argv[0], wrapped)
.iter()
.map(|run| run.join(" "))
.collect()
}
#[test]
fn finds_the_command_each_special_launcher_runs() {
assert_eq!(of("gdb -q --args ./a x", None), ["./a x"]);
assert_eq!(of("gdb ./a", None), Vec::<String>::new());
assert_eq!(
of("start-stop-daemon --start -b --exec /bin/x -- a b", None),
["/bin/x a b"]
);
assert_eq!(
of("start-stop-daemon -S -x /bin/x --startas=/bin/y", None),
["/bin/x", "/bin/y"]
);
assert_eq!(
of("launchctl submit -l x -p /bin/y -- a b", None),
["a b", "/bin/y b"]
);
assert_eq!(of("launchctl submit -l x a", None), ["a"]);
assert_eq!(of("launchctl list", None), Vec::<String>::new());
}
#[test]
fn a_shell_or_openmoat_after_an_unknown_option_is_a_command() {
assert_eq!(of("taskset --x 1 sh -c y", None), ["sh -c y"]);
assert_eq!(of("taskset 1 sh -c y", Some(2)), Vec::<String>::new());
assert_eq!(
of("prlimit --frob v /usr/local/bin/openmoat allow", None),
["/usr/local/bin/openmoat allow"]
);
assert_eq!(
of("taskset -c 0 cargo build", Some(3)),
Vec::<String>::new()
);
assert_eq!(of("cargo run -- sh", None), Vec::<String>::new());
}
}