mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
parent
25d4a08387
commit
f60790dd41
3 changed files with 172 additions and 51 deletions
|
@ -15,7 +15,7 @@ edition = "2018"
|
||||||
path = "src/chroot.rs"
|
path = "src/chroot.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "0.2.18"
|
clap= "2.33"
|
||||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
|
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
|
||||||
|
|
|
@ -10,60 +10,81 @@
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
use uucore::entries;
|
use clap::{App, Arg};
|
||||||
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
|
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use uucore::entries;
|
||||||
|
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
|
||||||
|
|
||||||
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static NAME: &str = "chroot";
|
static NAME: &str = "chroot";
|
||||||
|
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
|
||||||
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
|
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
|
||||||
static SUMMARY: &str = "Run COMMAND with root directory set to NEWROOT.";
|
|
||||||
static LONG_HELP: &str = "
|
mod options {
|
||||||
If COMMAND is not specified, it defaults to '$(SHELL) -i'.
|
pub const NEWROOT: &str = "newroot";
|
||||||
If $(SHELL) is not set, /bin/sh is used.
|
pub const USER: &str = "user";
|
||||||
";
|
pub const GROUP: &str = "group";
|
||||||
|
pub const GROUPS: &str = "groups";
|
||||||
|
pub const USERSPEC: &str = "userspec";
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let args = args.collect_str();
|
let args = args.collect_str();
|
||||||
|
|
||||||
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
|
let matches = App::new(executable!())
|
||||||
.optopt(
|
.version(VERSION)
|
||||||
"u",
|
.about(ABOUT)
|
||||||
"user",
|
.usage(SYNTAX)
|
||||||
"User (ID or name) to switch before running the program",
|
.arg(Arg::with_name(options::NEWROOT).hidden(true).required(true))
|
||||||
"USER",
|
.arg(
|
||||||
|
Arg::with_name(options::USER)
|
||||||
|
.short("u")
|
||||||
|
.long(options::USER)
|
||||||
|
.help("User (ID or name) to switch before running the program")
|
||||||
|
.value_name("USER"),
|
||||||
)
|
)
|
||||||
.optopt("g", "group", "Group (ID or name) to switch to", "GROUP")
|
.arg(
|
||||||
.optopt(
|
Arg::with_name(options::GROUP)
|
||||||
"G",
|
.short("g")
|
||||||
"groups",
|
.long(options::GROUP)
|
||||||
"Comma-separated list of groups to switch to",
|
.help("Group (ID or name) to switch to")
|
||||||
"GROUP1,GROUP2...",
|
.value_name("GROUP"),
|
||||||
)
|
)
|
||||||
.optopt(
|
.arg(
|
||||||
"",
|
Arg::with_name(options::GROUPS)
|
||||||
"userspec",
|
.short("G")
|
||||||
"Colon-separated user and group to switch to. \
|
.long(options::GROUPS)
|
||||||
|
.help("Comma-separated list of groups to switch to")
|
||||||
|
.value_name("GROUP1,GROUP2..."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(options::USERSPEC)
|
||||||
|
.long(options::USERSPEC)
|
||||||
|
.help(
|
||||||
|
"Colon-separated user and group to switch to. \
|
||||||
Same as -u USER -g GROUP. \
|
Same as -u USER -g GROUP. \
|
||||||
Userspec has higher preference than -u and/or -g",
|
Userspec has higher preference than -u and/or -g",
|
||||||
"USER:GROUP",
|
)
|
||||||
|
.value_name("USER:GROUP"),
|
||||||
)
|
)
|
||||||
.parse(args);
|
.get_matches_from(args);
|
||||||
|
|
||||||
if matches.free.is_empty() {
|
|
||||||
println!("Missing operand: NEWROOT");
|
|
||||||
println!("Try `{} --help` for more information.", NAME);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let default_shell: &'static str = "/bin/sh";
|
let default_shell: &'static str = "/bin/sh";
|
||||||
let default_option: &'static str = "-i";
|
let default_option: &'static str = "-i";
|
||||||
let user_shell = std::env::var("SHELL");
|
let user_shell = std::env::var("SHELL");
|
||||||
|
|
||||||
let newroot = Path::new(&matches.free[0][..]);
|
let newroot: &Path = match matches.value_of(options::NEWROOT) {
|
||||||
|
Some(v) => Path::new(v),
|
||||||
|
None => crash!(
|
||||||
|
1,
|
||||||
|
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
|
||||||
|
NAME
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
if !newroot.is_dir() {
|
if !newroot.is_dir() {
|
||||||
crash!(
|
crash!(
|
||||||
1,
|
1,
|
||||||
|
@ -72,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let command: Vec<&str> = match matches.free.len() {
|
let command: Vec<&str> = match matches.args.len() {
|
||||||
1 => {
|
1 => {
|
||||||
let shell: &str = match user_shell {
|
let shell: &str = match user_shell {
|
||||||
Err(_) => default_shell,
|
Err(_) => default_shell,
|
||||||
|
@ -80,7 +101,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
};
|
};
|
||||||
vec![shell, default_option]
|
vec![shell, default_option]
|
||||||
}
|
}
|
||||||
_ => matches.free[1..].iter().map(|x| &x[..]).collect(),
|
_ => {
|
||||||
|
let mut vector: Vec<&str> = Vec::new();
|
||||||
|
for (&k, v) in matches.args.iter() {
|
||||||
|
vector.push(k.clone());
|
||||||
|
vector.push(&v.vals[0].to_str().unwrap());
|
||||||
|
}
|
||||||
|
vector
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
set_context(&newroot, &matches);
|
set_context(&newroot, &matches);
|
||||||
|
@ -97,30 +125,26 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_context(root: &Path, options: &getopts::Matches) {
|
fn set_context(root: &Path, options: &clap::ArgMatches) {
|
||||||
let userspec_str = options.opt_str("userspec");
|
let userspec_str = options.value_of(options::USERSPEC);
|
||||||
let user_str = options.opt_str("user").unwrap_or_default();
|
let user_str = options.value_of(options::USER).unwrap_or_default();
|
||||||
let group_str = options.opt_str("group").unwrap_or_default();
|
let group_str = options.value_of(options::GROUP).unwrap_or_default();
|
||||||
let groups_str = options.opt_str("groups").unwrap_or_default();
|
let groups_str = options.value_of(options::GROUPS).unwrap_or_default();
|
||||||
let userspec = match userspec_str {
|
let userspec = match userspec_str {
|
||||||
Some(ref u) => {
|
Some(ref u) => {
|
||||||
let s: Vec<&str> = u.split(':').collect();
|
let s: Vec<&str> = u.split(':').collect();
|
||||||
if s.len() != 2 {
|
if s.len() != 2 || s.iter().any(|&spec| spec == "") {
|
||||||
crash!(1, "invalid userspec: `{}`", u)
|
crash!(1, "invalid userspec: `{}`", u)
|
||||||
};
|
};
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
};
|
||||||
let user = if userspec.is_empty() {
|
|
||||||
&user_str[..]
|
let (user, group) = if userspec.is_empty() {
|
||||||
|
(&user_str[..], &group_str[..])
|
||||||
} else {
|
} else {
|
||||||
&userspec[0][..]
|
(&userspec[0][..], &userspec[1][..])
|
||||||
};
|
|
||||||
let group = if userspec.is_empty() {
|
|
||||||
&group_str[..]
|
|
||||||
} else {
|
|
||||||
&userspec[1][..]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enter_chroot(root);
|
enter_chroot(root);
|
||||||
|
|
|
@ -1 +1,98 @@
|
||||||
// ToDO: add tests
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_operand() {
|
||||||
|
let result = new_ucmd!().run();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
true,
|
||||||
|
result
|
||||||
|
.stderr
|
||||||
|
.starts_with("error: The following required arguments were not provided")
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(true, result.stderr.contains("<newroot>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enter_chroot_fails() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.mkdir("jail");
|
||||||
|
|
||||||
|
let result = ucmd.arg("jail").run();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
true,
|
||||||
|
result.stderr.starts_with(
|
||||||
|
"chroot: error: cannot chroot to jail: Operation not permitted (os error 1)"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_such_directory() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.touch(&at.plus_as_string("a"));
|
||||||
|
|
||||||
|
ucmd.arg("a")
|
||||||
|
.fails()
|
||||||
|
.stderr_is("chroot: error: cannot change root directory to `a`: no such directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_user_spec() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.mkdir("a");
|
||||||
|
|
||||||
|
let result = ucmd.arg("a").arg("--userspec=ARABA:").run();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
true,
|
||||||
|
result.stderr.starts_with("chroot: error: invalid userspec")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_preference_of_userspec() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let result = scene.cmd("whoami").run();
|
||||||
|
if is_ci() && result.stderr.contains("No such user/group") {
|
||||||
|
// In the CI, some server are failing to return whoami.
|
||||||
|
// As seems to be a configuration issue, ignoring it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
println!("result.stdout {}", result.stdout);
|
||||||
|
println!("result.stderr = {}", result.stderr);
|
||||||
|
let username = result.stdout.trim_end();
|
||||||
|
|
||||||
|
let ts = TestScenario::new("id");
|
||||||
|
let result = ts.cmd("id").arg("-g").arg("-n").run();
|
||||||
|
println!("result.stdout {}", result.stdout);
|
||||||
|
println!("result.stderr = {}", result.stderr);
|
||||||
|
|
||||||
|
if is_ci() && result.stderr.contains("cannot find name for user ID") {
|
||||||
|
// In the CI, some server are failing to return id.
|
||||||
|
// As seems to be a configuration issue, ignoring it
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let group_name = result.stdout.trim_end();
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.mkdir("a");
|
||||||
|
|
||||||
|
let result = ucmd
|
||||||
|
.arg("a")
|
||||||
|
.arg("--user")
|
||||||
|
.arg("fake")
|
||||||
|
.arg("-G")
|
||||||
|
.arg("ABC,DEF")
|
||||||
|
.arg(format!("--userspec={}:{}", username, group_name))
|
||||||
|
.run();
|
||||||
|
|
||||||
|
println!("result.stdout {}", result.stdout);
|
||||||
|
println!("result.stderr = {}", result.stderr);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue