From 6efcc02c39fc2b18a56684285f37c7e99336208c Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Mon, 16 Jun 2014 19:35:50 +0400 Subject: [PATCH 1/8] Common function to get user group info --- common/c_types.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/common/c_types.rs b/common/c_types.rs index fec14f4e5..5de352d53 100644 --- a/common/c_types.rs +++ b/common/c_types.rs @@ -88,6 +88,7 @@ extern { groups: *gid_t, ngroups: *mut c_int) -> c_int; pub fn getgrgid(gid: gid_t) -> *c_group; + pub fn getgrnam(name: *c_char) -> *c_group; } pub fn get_pw_from_args(free: &Vec) -> Option { @@ -108,7 +109,7 @@ pub fn get_pw_from_args(free: &Vec) -> Option { // Passed the username as a string } else { let pw_pointer = unsafe { - getpwnam(username.as_slice().as_ptr() as *i8) + getpwnam(username.as_slice().to_c_str().unwrap() as *libc::c_char) }; if pw_pointer.is_not_null() { Some(unsafe { read(pw_pointer) }) @@ -121,6 +122,21 @@ pub fn get_pw_from_args(free: &Vec) -> Option { } } +pub fn get_group(groupname: &str) -> Option { + let group = if groupname.chars().all(|c| c.is_digit()) { + unsafe { getgrgid(from_str::(groupname).unwrap()) } + } else { + unsafe { getgrnam(groupname.to_c_str().unwrap() as *c_char) } + }; + + if group.is_not_null() { + Some(unsafe { read(group) }) + } + else { + None + } +} + static NGROUPS: i32 = 20; pub fn group(possible_pw: Option, nflag: bool) { From c5b423048c5a6c8708cf3dba39b47a999c22d17b Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Mon, 16 Jun 2014 19:37:03 +0400 Subject: [PATCH 2/8] chroot implementation --- Makefile | 1 + chroot/chroot.rs | 196 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 chroot/chroot.rs diff --git a/Makefile b/Makefile index e390530f9..d2c81ae6c 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ PROGS := \ whoami UNIX_PROGS := \ + chroot \ groups \ hostid \ hostname \ diff --git a/chroot/chroot.rs b/chroot/chroot.rs new file mode 100644 index 000000000..cff607eaa --- /dev/null +++ b/chroot/chroot.rs @@ -0,0 +1,196 @@ +#![crate_id = "chroot#1.0.0"] +/* + * This file is part of the uutils coreutils package. + * + * © Vsevolod Velichko + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#![feature(macro_rules)] +extern crate getopts; +extern crate libc; +extern crate debug; + +use getopts::{optflag, optopt, getopts, usage}; +use c_types::{get_pw_from_args, get_group}; +use libc::funcs::posix88::unistd::{execvp, setuid, setgid}; + +#[path = "../common/util.rs"] mod util; +#[path = "../common/c_types.rs"] mod c_types; + +extern { + fn chroot(path: *libc::c_char) -> libc::c_int; + fn setgroups(size: libc::c_int, list: *libc::uid_t) -> libc::c_int; +} + +static NAME : &'static str = "chroot"; +static VERSION : &'static str = "1.0.0"; + +#[allow(dead_code)] +fn main () { std::os::set_exit_status(uumain(std::os::args())); } + +pub fn uumain(args: Vec) -> int { + let program = args.get(0); + + let options = [ + optopt("u", "user", "User (ID or name) to switch before running the program", "USER"), + optopt("g", "group", "Group (ID or name) to switch to", "GROUP"), + optopt("G", "groups", "Comma-separated list of groups to switch to", "GROUP1,GROUP2…"), + optopt("", "userspec", "Colon-separated user and group to switch to. \ + Same as -u USER -g GROUP. \ + Userspec has higher preference than -u and/or -g", "USER:GROUP"), + optflag("h", "help", "Show help"), + optflag("V", "version", "Show program's version") + ]; + + let opts = match getopts(args.tail(), options) { + Ok(m) => {m} + Err(f) => { + println!("{}", f); + help_menu(program.as_slice(), options); + return 1 + } + }; + + if opts.opt_present("V") { version(); return 0 } + if opts.opt_present("h") { help_menu(program.as_slice(), options); return 0 } + + if opts.free.len() == 0 { + println!("Missing operand: NEWROOT"); + println!("Try `{:s} --help` for more information.", program.as_slice()); + return 1 + } + + let defaultShell : &'static str = "/bin/sh"; + let defaultOption : &'static str = "-i"; + let userShell = std::os::getenv("SHELL"); + + let newroot = Path::new(opts.free.get(0).as_slice()); + if !newroot.is_dir() { + crash!(1, "cannot change root directory to `{}`: no such directory", newroot.display()); + } + + let command : Vec<&str> = match opts.free.len() { + 1 => { + let shell : &str = match userShell { + None => {defaultShell} + Some(ref s) => {s.as_slice()} + }; + vec!(shell, defaultOption) + } + _ => { opts.free.slice(1, opts.free.len()).iter().map(|x| x.as_slice()).collect() } + }; + + set_context(&newroot, &opts); + + unsafe { + let executable = command.get(0).as_slice().to_c_str().unwrap(); + let mut commandParts : Vec<*i8> = command.iter().map(|x| x.to_c_str().unwrap()).collect(); + commandParts.push(std::ptr::null()); + execvp(executable as *libc::c_char, commandParts.as_ptr() as **libc::c_char) as int + } +} + +fn set_context(root: &Path, options: &getopts::Matches) { + let userspecStr = options.opt_str("userspec"); + let userStr = options.opt_str("user").unwrap_or_default(); + let groupStr = options.opt_str("group").unwrap_or_default(); + let groupsStr = options.opt_str("groups").unwrap_or_default(); + let userspec = match userspecStr { + Some(ref u) => { + let s : Vec<&str> = u.as_slice().split(':').collect(); + if s.len() != 2 { + crash!(1, "invalid userspec: `{:s}`", u.as_slice()) + }; + s + } + None => { Vec::new() } + }; + let user = if userspec.is_empty() { userStr.as_slice() } else { userspec.get(0).as_slice() }; + let group = if userspec.is_empty() { groupStr.as_slice() } else { userspec.get(1).as_slice() }; + + enter_chroot(root); + + set_groups(groupsStr.as_slice()); + set_main_group(group); + set_user(user); +} + +fn enter_chroot(root: &Path) { + let rootStr = root.display(); + if !std::os::change_dir(root) { + crash!(1, "cannot chdir to {}", rootStr) + }; + let err = unsafe { + chroot(".".to_c_str().unwrap() as *libc::c_char) + }; + if err != 0 { + crash!(1, "cannot chroot to {}: {:s}", rootStr, strerror(err).as_slice()) + }; +} + +fn set_main_group(group: &str) { + if !group.is_empty() { + let group_id = match get_group(group) { + None => { crash!(1, "no such group: {}", group) } + Some(g) => g.gr_gid + }; + let err = unsafe { setgid(group_id) }; + if err != 0 { + crash!(1, "cannot set gid to {:u}: {:s}", group_id, strerror(err).as_slice()) + } + } +} + +fn set_groups(groups: &str) { + if !groups.is_empty() { + let groupsVec : Vec = FromIterator::from_iter( + groups.split(',').map( + |x| match get_group(x) { + None => { crash!(1, "no such group: {}", x) } + Some(g) => { g.gr_gid } + }) + ); + let err = unsafe { + setgroups(groupsVec.len() as libc::c_int, + groupsVec.as_slice().as_ptr() as *libc::uid_t) + }; + if err != 0 { + crash!(1, "cannot set groups: {:s}", strerror(err).as_slice()) + } + } +} + +fn set_user(user: &str) { + if !user.is_empty() { + let user_id = get_pw_from_args(&vec!(String::from_str(user))).unwrap().pw_uid; + let err = unsafe { setuid(user_id as libc::uid_t) }; + if err != 0 { + crash!(1, "cannot set user to {:s}: {:s}", user, strerror(err).as_slice()) + } + } +} + +fn strerror(errno: i32) -> String { + unsafe { + let err = libc::funcs::c95::string::strerror(errno); + std::str::raw::from_c_str(err) + } +} + +fn version() { + println!("{} v{}", NAME, VERSION) +} + +fn help_menu(program: &str, options: &[getopts::OptGroup]) { + version(); + println!("Usage:"); + println!(" {:s} [OPTION]… NEWROOT [COMMAND [ARG]…]", program); + println!(""); + print!("{:s}", usage( + "Run COMMAND with root directory set to NEWROOT.\n\ + If COMMAND is not specified, it defaults to '${SHELL} -i'. \ + If ${SHELL} is not set, /bin/sh is used.", options)) +} From 301256d0f029ccc638434aa616f6a9cd3c9b81af Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Mon, 16 Jun 2014 19:38:25 +0400 Subject: [PATCH 3/8] Remove chroot from todo list --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a8529ab0b..498344e9e 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,6 @@ To do - chgrp - chmod - chown -- chroot - copy - cp (not much done) - csplit From eebcdbddb1ecafafab9eca4a12940dde34b0ba42 Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Mon, 16 Jun 2014 19:45:11 +0400 Subject: [PATCH 4/8] Remove unused debug crate import --- chroot/chroot.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/chroot/chroot.rs b/chroot/chroot.rs index cff607eaa..ad1325d87 100644 --- a/chroot/chroot.rs +++ b/chroot/chroot.rs @@ -11,7 +11,6 @@ #![feature(macro_rules)] extern crate getopts; extern crate libc; -extern crate debug; use getopts::{optflag, optopt, getopts, usage}; use c_types::{get_pw_from_args, get_group}; From c7e0e5aa5e4c7806ca13d5fe135ee4b4b17afaea Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Tue, 17 Jun 2014 02:03:23 +0400 Subject: [PATCH 5/8] Code style fixes --- chroot/chroot.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/chroot/chroot.rs b/chroot/chroot.rs index ad1325d87..2b280f070 100644 --- a/chroot/chroot.rs +++ b/chroot/chroot.rs @@ -24,8 +24,8 @@ extern { fn setgroups(size: libc::c_int, list: *libc::uid_t) -> libc::c_int; } -static NAME : &'static str = "chroot"; -static VERSION : &'static str = "1.0.0"; +static NAME: &'static str = "chroot"; +static VERSION: &'static str = "1.0.0"; #[allow(dead_code)] fn main () { std::os::set_exit_status(uumain(std::os::args())); } @@ -62,8 +62,8 @@ pub fn uumain(args: Vec) -> int { return 1 } - let defaultShell : &'static str = "/bin/sh"; - let defaultOption : &'static str = "-i"; + let defaultShell: &'static str = "/bin/sh"; + let defaultOption: &'static str = "-i"; let userShell = std::os::getenv("SHELL"); let newroot = Path::new(opts.free.get(0).as_slice()); @@ -71,9 +71,9 @@ pub fn uumain(args: Vec) -> int { crash!(1, "cannot change root directory to `{}`: no such directory", newroot.display()); } - let command : Vec<&str> = match opts.free.len() { + let command: Vec<&str> = match opts.free.len() { 1 => { - let shell : &str = match userShell { + let shell: &str = match userShell { None => {defaultShell} Some(ref s) => {s.as_slice()} }; @@ -86,7 +86,7 @@ pub fn uumain(args: Vec) -> int { unsafe { let executable = command.get(0).as_slice().to_c_str().unwrap(); - let mut commandParts : Vec<*i8> = command.iter().map(|x| x.to_c_str().unwrap()).collect(); + let mut commandParts: Vec<*i8> = command.iter().map(|x| x.to_c_str().unwrap()).collect(); commandParts.push(std::ptr::null()); execvp(executable as *libc::c_char, commandParts.as_ptr() as **libc::c_char) as int } @@ -99,7 +99,7 @@ fn set_context(root: &Path, options: &getopts::Matches) { let groupsStr = options.opt_str("groups").unwrap_or_default(); let userspec = match userspecStr { Some(ref u) => { - let s : Vec<&str> = u.as_slice().split(':').collect(); + let s: Vec<&str> = u.as_slice().split(':').collect(); if s.len() != 2 { crash!(1, "invalid userspec: `{:s}`", u.as_slice()) }; @@ -145,7 +145,7 @@ fn set_main_group(group: &str) { fn set_groups(groups: &str) { if !groups.is_empty() { - let groupsVec : Vec = FromIterator::from_iter( + let groupsVec: Vec = FromIterator::from_iter( groups.split(',').map( |x| match get_group(x) { None => { crash!(1, "no such group: {}", x) } From fcd05aca8dd559fef7f81cb38b07288af8ea12d2 Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Tue, 17 Jun 2014 02:07:16 +0400 Subject: [PATCH 6/8] Use gid_t instead of uid_t for groups --- chroot/chroot.rs | 6 +++--- common/c_types.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chroot/chroot.rs b/chroot/chroot.rs index 2b280f070..454f2e10f 100644 --- a/chroot/chroot.rs +++ b/chroot/chroot.rs @@ -21,7 +21,7 @@ use libc::funcs::posix88::unistd::{execvp, setuid, setgid}; extern { fn chroot(path: *libc::c_char) -> libc::c_int; - fn setgroups(size: libc::c_int, list: *libc::uid_t) -> libc::c_int; + fn setgroups(size: libc::c_int, list: *libc::gid_t) -> libc::c_int; } static NAME: &'static str = "chroot"; @@ -145,7 +145,7 @@ fn set_main_group(group: &str) { fn set_groups(groups: &str) { if !groups.is_empty() { - let groupsVec: Vec = FromIterator::from_iter( + let groupsVec: Vec = FromIterator::from_iter( groups.split(',').map( |x| match get_group(x) { None => { crash!(1, "no such group: {}", x) } @@ -154,7 +154,7 @@ fn set_groups(groups: &str) { ); let err = unsafe { setgroups(groupsVec.len() as libc::c_int, - groupsVec.as_slice().as_ptr() as *libc::uid_t) + groupsVec.as_slice().as_ptr() as *libc::gid_t) }; if err != 0 { crash!(1, "cannot set groups: {:s}", strerror(err).as_slice()) diff --git a/common/c_types.rs b/common/c_types.rs index 5de352d53..e4a891722 100644 --- a/common/c_types.rs +++ b/common/c_types.rs @@ -124,7 +124,7 @@ pub fn get_pw_from_args(free: &Vec) -> Option { pub fn get_group(groupname: &str) -> Option { let group = if groupname.chars().all(|c| c.is_digit()) { - unsafe { getgrgid(from_str::(groupname).unwrap()) } + unsafe { getgrgid(from_str::(groupname).unwrap()) } } else { unsafe { getgrnam(groupname.to_c_str().unwrap() as *c_char) } }; From 46c1a53d5a0048d5d16f7b9098a73a945852eb7c Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Tue, 17 Jun 2014 02:08:55 +0400 Subject: [PATCH 7/8] =?UTF-8?q?Use=20(c)=20rather=20than=20=C2=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chroot/chroot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chroot/chroot.rs b/chroot/chroot.rs index 454f2e10f..7827ea71b 100644 --- a/chroot/chroot.rs +++ b/chroot/chroot.rs @@ -2,7 +2,7 @@ /* * This file is part of the uutils coreutils package. * - * © Vsevolod Velichko + * (c) Vsevolod Velichko * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. From fd6975c86c84009a6a4b6c48cb527af9e214c634 Mon Sep 17 00:00:00 2001 From: Vsevolod Velichko Date: Tue, 17 Jun 2014 02:28:40 +0400 Subject: [PATCH 8/8] Remove redundant braces, show_error for getopt parse error --- chroot/chroot.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/chroot/chroot.rs b/chroot/chroot.rs index 7827ea71b..cf73610d8 100644 --- a/chroot/chroot.rs +++ b/chroot/chroot.rs @@ -45,9 +45,9 @@ pub fn uumain(args: Vec) -> int { ]; let opts = match getopts(args.tail(), options) { - Ok(m) => {m} + Ok(m) => m, Err(f) => { - println!("{}", f); + show_error!("{}", f); help_menu(program.as_slice(), options); return 1 } @@ -79,7 +79,7 @@ pub fn uumain(args: Vec) -> int { }; vec!(shell, defaultOption) } - _ => { opts.free.slice(1, opts.free.len()).iter().map(|x| x.as_slice()).collect() } + _ => opts.free.slice(1, opts.free.len()).iter().map(|x| x.as_slice()).collect() }; set_context(&newroot, &opts); @@ -105,7 +105,7 @@ fn set_context(root: &Path, options: &getopts::Matches) { }; s } - None => { Vec::new() } + None => Vec::new() }; let user = if userspec.is_empty() { userStr.as_slice() } else { userspec.get(0).as_slice() }; let group = if userspec.is_empty() { groupStr.as_slice() } else { userspec.get(1).as_slice() }; @@ -133,7 +133,7 @@ fn enter_chroot(root: &Path) { fn set_main_group(group: &str) { if !group.is_empty() { let group_id = match get_group(group) { - None => { crash!(1, "no such group: {}", group) } + None => crash!(1, "no such group: {}", group), Some(g) => g.gr_gid }; let err = unsafe { setgid(group_id) }; @@ -148,8 +148,8 @@ fn set_groups(groups: &str) { let groupsVec: Vec = FromIterator::from_iter( groups.split(',').map( |x| match get_group(x) { - None => { crash!(1, "no such group: {}", x) } - Some(g) => { g.gr_gid } + None => crash!(1, "no such group: {}", x), + Some(g) => g.gr_gid }) ); let err = unsafe {