2018-10-04 20:45:29 +02:00
|
|
|
use std::env::Args;
|
2019-08-22 13:08:50 +02:00
|
|
|
use std::ffi::CStr;
|
|
|
|
use std::ffi::CString;
|
2018-10-04 20:45:29 +02:00
|
|
|
use std::fs::File;
|
|
|
|
use std::io::BufRead;
|
2019-08-22 13:08:50 +02:00
|
|
|
use std::io::BufReader;
|
|
|
|
use std::io::{Error, ErrorKind};
|
2020-09-14 19:38:13 +02:00
|
|
|
use std::fs;
|
2018-10-04 20:45:29 +02:00
|
|
|
|
|
|
|
extern crate libc;
|
|
|
|
use libc::passwd;
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
struct Entry {
|
|
|
|
users: Vec<String>,
|
|
|
|
dest_user: String,
|
|
|
|
cmd: String,
|
|
|
|
args: String,
|
|
|
|
argv0: String,
|
|
|
|
inherit_envs: Vec<String>,
|
|
|
|
no_new_privs: bool,
|
|
|
|
arbitrary_args: bool,
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
impl Entry {
|
|
|
|
fn new() -> Entry {
|
|
|
|
return Entry {
|
2018-10-04 20:45:29 +02:00
|
|
|
users: Vec::new(),
|
|
|
|
dest_user: String::new(),
|
|
|
|
cmd: String::new(),
|
|
|
|
args: String::new(),
|
|
|
|
argv0: String::new(),
|
|
|
|
inherit_envs: Vec::new(),
|
|
|
|
no_new_privs: true,
|
|
|
|
arbitrary_args: false,
|
2018-11-02 21:48:36 +01:00
|
|
|
};
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
struct Passwd {
|
|
|
|
pw_name: String,
|
|
|
|
pw_passwd: String,
|
|
|
|
pw_uid: libc::uid_t,
|
|
|
|
pw_gid: libc::gid_t,
|
|
|
|
pw_gecos: String,
|
|
|
|
pw_dir: String,
|
|
|
|
pw_shell: String,
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn initgroups(user: &str, group: libc::gid_t) -> std::io::Result<()> {
|
2018-10-04 20:45:29 +02:00
|
|
|
let userarg = CString::new(user);
|
2020-09-14 19:19:20 +02:00
|
|
|
return errnowrapper(unsafe {
|
|
|
|
libc::initgroups(userarg.unwrap().as_ptr(), group)
|
|
|
|
});
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn errnowrapper(ret: libc::c_int) -> std::io::Result<()> {
|
|
|
|
if ret != 0 {
|
|
|
|
return Err(Error::last_os_error());
|
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn setuid(id: libc::uid_t) -> std::io::Result<()> {
|
|
|
|
return errnowrapper(unsafe { libc::setuid(id) });
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn setgid(gid: libc::gid_t) -> std::io::Result<()> {
|
|
|
|
return errnowrapper(unsafe { libc::setgid(gid) });
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn geteuid() -> u32 {
|
|
|
|
unsafe {
|
|
|
|
return libc::geteuid();
|
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn getpwnam(username: &str) -> std::io::Result<Passwd> {
|
|
|
|
fn getstr(str: *mut libc::c_char) -> String {
|
|
|
|
unsafe { CStr::from_ptr(str).to_string_lossy().into_owned() }
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
2020-09-14 19:19:20 +02:00
|
|
|
let username_c = CString::new(username).unwrap();
|
|
|
|
let username_ptr = username_c.as_ptr();
|
|
|
|
let pwnamresult: *mut libc::passwd = unsafe { libc::getpwnam(username_ptr) };
|
|
|
|
if pwnamresult.is_null() {
|
2020-09-14 19:45:58 +02:00
|
|
|
if Error::last_os_error().raw_os_error().unwrap() == 0 {
|
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::NotFound,
|
|
|
|
format!("The username '{}' was not found", username),
|
|
|
|
));
|
|
|
|
}
|
2020-09-14 19:19:20 +02:00
|
|
|
return Err(Error::new(
|
|
|
|
Error::last_os_error().kind(),
|
2020-09-15 20:48:34 +02:00
|
|
|
format!(
|
|
|
|
"Lookup of user {} failed: {}",
|
|
|
|
username,
|
|
|
|
&Error::last_os_error().to_string()
|
|
|
|
),
|
2020-09-14 19:19:20 +02:00
|
|
|
));
|
|
|
|
}
|
2020-07-09 00:11:50 +02:00
|
|
|
unsafe {
|
2018-11-02 21:48:36 +01:00
|
|
|
Ok(Passwd {
|
|
|
|
pw_name: getstr((*pwnamresult).pw_name),
|
|
|
|
pw_passwd: getstr((*pwnamresult).pw_passwd),
|
|
|
|
pw_uid: (*pwnamresult).pw_uid,
|
|
|
|
pw_gid: (*pwnamresult).pw_gid,
|
|
|
|
pw_gecos: getstr((*pwnamresult).pw_gecos),
|
|
|
|
pw_dir: getstr((*pwnamresult).pw_dir),
|
|
|
|
pw_shell: getstr((*pwnamresult).pw_shell),
|
|
|
|
})
|
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn ensure_allowed(userid: libc::uid_t, entry: &Entry) -> std::io::Result<()> {
|
2018-10-04 20:45:29 +02:00
|
|
|
if userid == 0 {
|
|
|
|
return Ok(());
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
for user in &entry.users {
|
|
|
|
let passwd: Passwd = getpwnam(&user)?;
|
|
|
|
if passwd.pw_uid == userid {
|
|
|
|
return Ok(());
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
let passwd: Passwd = getpwnam(&entry.dest_user)?;
|
|
|
|
if passwd.pw_uid == userid {
|
2018-10-04 20:45:29 +02:00
|
|
|
return Ok(());
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::PermissionDenied,
|
|
|
|
"Not allowed to become target user",
|
|
|
|
));
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn usage() {
|
2018-10-04 20:45:29 +02:00
|
|
|
println!("Usage: raou ENRTYNAME");
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
fn add_multi(vec: &mut Vec<String>, val: String) {
|
|
|
|
if val.contains(',') {
|
2018-10-04 20:45:29 +02:00
|
|
|
let splitted = val.split(',');
|
|
|
|
for part in splitted {
|
|
|
|
vec.push(part.to_owned());
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
} else {
|
2018-10-04 20:45:29 +02:00
|
|
|
vec.push(val);
|
|
|
|
}
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
fn assign(entry: &mut Entry, key: &str, value: &str) {
|
2018-10-04 20:45:29 +02:00
|
|
|
let val = value.to_owned();
|
2018-11-02 21:48:36 +01:00
|
|
|
match key {
|
|
|
|
"path" => entry.cmd = val,
|
|
|
|
"user" => add_multi(&mut entry.users, val),
|
|
|
|
"env_vars" => add_multi(&mut entry.inherit_envs, val),
|
|
|
|
"argv0" => entry.argv0 = val,
|
|
|
|
"target_user" => entry.dest_user = val,
|
2019-08-22 13:08:50 +02:00
|
|
|
"allow_args" => entry.arbitrary_args = val == "1" || val == "true",
|
2018-11-02 21:48:36 +01:00
|
|
|
"args" => entry.args = val,
|
2019-08-22 13:08:50 +02:00
|
|
|
"no_new_privs" => entry.no_new_privs = val == "1" || val == "true",
|
2018-11-02 21:48:36 +01:00
|
|
|
_ => {
|
|
|
|
eprintln!("Ignoring invalid key {}", key);
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
fn assign_from_line(entry: &mut Entry, line: &str) {
|
2018-10-04 20:45:29 +02:00
|
|
|
let mut splitted = line.splitn(2, ' ');
|
|
|
|
let key = splitted.next();
|
|
|
|
let value = splitted.next();
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
if !key.is_some() || !value.is_some() {
|
2018-10-04 20:45:29 +02:00
|
|
|
return;
|
2018-11-02 21:48:36 +01:00
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
assign(entry, key.unwrap(), value.unwrap())
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
fn create_entry_from_file(filepath: &str) -> std::io::Result<Entry> {
|
|
|
|
let mut entry: Entry = Entry::new();
|
2018-10-04 20:45:29 +02:00
|
|
|
let f = File::open(filepath)?;
|
|
|
|
|
|
|
|
let bf = BufReader::new(f);
|
|
|
|
|
|
|
|
for line in bf.lines() {
|
|
|
|
assign_from_line(&mut entry, &line.unwrap());
|
|
|
|
}
|
|
|
|
Ok(entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: clearenv does not set errno?
|
2018-11-02 21:48:36 +01:00
|
|
|
fn clearenv() -> std::io::Result<()> {
|
2018-10-04 20:45:29 +02:00
|
|
|
return errnowrapper(unsafe { libc::clearenv() });
|
|
|
|
}
|
|
|
|
//TODO: AsRef for envs?
|
2018-11-02 21:48:36 +01:00
|
|
|
fn setup_environment(passwd: &Passwd, envs: &[String]) -> std::io::Result<()> {
|
2020-09-14 19:19:20 +02:00
|
|
|
let saved_envs: Vec<String> = envs.iter()
|
2018-11-02 21:48:36 +01:00
|
|
|
.map(|s| std::env::var(s).expect("No such var"))
|
|
|
|
.collect();
|
2018-10-04 20:45:29 +02:00
|
|
|
clearenv()?;
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
//TODO: set_var does not have a return val?
|
2018-10-04 20:45:29 +02:00
|
|
|
std::env::set_var("HOME", &passwd.pw_dir);
|
|
|
|
std::env::set_var("USER", &passwd.pw_name);
|
|
|
|
std::env::set_var("LOGNAME", &passwd.pw_name);
|
|
|
|
std::env::set_var("SHELL", &passwd.pw_shell);
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
for (i, item) in saved_envs.iter().enumerate() {
|
2018-10-04 20:45:29 +02:00
|
|
|
std::env::set_var(&envs[i], item);
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn become_user(passwd: &Passwd) -> std::io::Result<()> {
|
2018-10-04 20:45:29 +02:00
|
|
|
initgroups(&(passwd.pw_name), passwd.pw_gid)?;
|
|
|
|
setgid(passwd.pw_gid)?;
|
|
|
|
setuid(passwd.pw_uid)?;
|
|
|
|
std::env::set_current_dir(&passwd.pw_dir)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-08-22 13:08:50 +02:00
|
|
|
fn drop_privs(entry: &Entry) -> std::io::Result<()> {
|
|
|
|
if entry.no_new_privs {
|
|
|
|
errnowrapper(unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) })?;
|
2020-09-14 19:19:20 +02:00
|
|
|
errnowrapper(unsafe {
|
|
|
|
libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
|
|
|
|
})?;
|
2019-08-22 13:08:50 +02:00
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
2018-11-02 21:48:36 +01:00
|
|
|
fn to_cstring<T: AsRef<str>>(s: T) -> *const libc::c_char {
|
2018-10-04 20:45:29 +02:00
|
|
|
return CString::new(s.as_ref()).unwrap().into_raw();
|
|
|
|
}
|
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
fn create_execv_args(entry: &Entry, cmdargs: &Vec<String>) -> Vec<*const libc::c_char> {
|
|
|
|
let mut args: Vec<*const libc::c_char>;
|
|
|
|
if entry.arbitrary_args && cmdargs.len() > 2 {
|
2018-10-04 20:45:29 +02:00
|
|
|
args = cmdargs.iter().skip(2).map(to_cstring).collect();
|
2018-11-02 21:48:36 +01:00
|
|
|
} else {
|
|
|
|
args = entry
|
|
|
|
.args
|
|
|
|
.as_str()
|
|
|
|
.split_whitespace()
|
|
|
|
.map(to_cstring)
|
|
|
|
.collect();
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
if !&entry.argv0.is_empty() {
|
2018-10-04 20:45:29 +02:00
|
|
|
args.insert(0, to_cstring(&entry.argv0));
|
2018-11-02 21:48:36 +01:00
|
|
|
} else {
|
2018-10-04 22:00:14 +02:00
|
|
|
let cmdbegin = &entry.cmd.rfind("/").unwrap() + 1;
|
|
|
|
args.insert(0, to_cstring(&entry.cmd.split_at(cmdbegin).1));
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
args.push(std::ptr::null());
|
|
|
|
return args;
|
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
|
2020-09-14 19:38:13 +02:00
|
|
|
let basedir: String = String::from("/etc/raou.d/");
|
|
|
|
let filepath: String = basedir.to_string() + entryname;
|
2018-10-04 20:45:29 +02:00
|
|
|
|
2020-09-26 18:37:48 +02:00
|
|
|
let realpath = fs::canonicalize(&filepath);
|
|
|
|
match realpath {
|
|
|
|
Ok(p) => {
|
|
|
|
if !p.starts_with(basedir) {
|
|
|
|
return Err(std::io::Error::new(
|
|
|
|
ErrorKind::InvalidInput,
|
|
|
|
"Specified entry is outside base directory",
|
|
|
|
));
|
|
|
|
}
|
2020-09-26 18:42:27 +02:00
|
|
|
if ! p.is_file() {
|
|
|
|
return Err(std::io::Error::new(
|
|
|
|
ErrorKind::Other,
|
|
|
|
"Error: Entry not a file",
|
|
|
|
));
|
|
|
|
}
|
2020-09-26 18:37:48 +02:00
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
if e.kind() == ErrorKind::NotFound {
|
|
|
|
return Err(std::io::Error::new(
|
|
|
|
ErrorKind::NotFound,
|
|
|
|
format!("The entry {} does not exist", entryname),
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
return Err(e);
|
|
|
|
}
|
|
|
|
}
|
2018-10-27 12:49:57 +02:00
|
|
|
}
|
2018-11-02 21:48:36 +01:00
|
|
|
let entry: Entry = create_entry_from_file(&filepath)?;
|
|
|
|
let destuserpasswd: Passwd = getpwnam(&entry.dest_user)?;
|
|
|
|
let currentuser: u32 = geteuid();
|
2018-10-04 20:45:29 +02:00
|
|
|
|
|
|
|
let args = create_execv_args(&entry, &cmdargs);
|
|
|
|
|
|
|
|
ensure_allowed(currentuser, &entry)?;
|
2018-11-02 21:48:36 +01:00
|
|
|
become_user(&destuserpasswd).or_else(|e| {
|
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::PermissionDenied,
|
2020-09-15 20:48:34 +02:00
|
|
|
format!("Failed to switch user: {}", &e.to_string()),
|
2018-11-02 21:48:36 +01:00
|
|
|
));
|
|
|
|
})?;
|
2020-09-14 19:19:20 +02:00
|
|
|
setup_environment(&destuserpasswd, &entry.inherit_envs)
|
|
|
|
.or_else(|e| {
|
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::Other,
|
2020-09-15 20:48:34 +02:00
|
|
|
format!("Environment setup failure: {}", &e.to_string()),
|
2020-09-14 19:19:20 +02:00
|
|
|
));
|
|
|
|
})?;
|
2019-08-22 13:08:50 +02:00
|
|
|
|
|
|
|
drop_privs(&entry).or_else(|e| {
|
2018-11-02 21:48:36 +01:00
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::Other,
|
2020-09-15 20:48:34 +02:00
|
|
|
format!("Failed to drop priviliges: {}", &e.to_string()),
|
2018-11-02 21:48:36 +01:00
|
|
|
));
|
|
|
|
})?;
|
|
|
|
|
|
|
|
unsafe {
|
2020-09-14 19:19:20 +02:00
|
|
|
errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr()))
|
|
|
|
.or_else(|e| {
|
|
|
|
return Err(Error::new(
|
|
|
|
ErrorKind::Other,
|
2020-09-15 20:48:34 +02:00
|
|
|
format!("execv failed: {}", &e.to_string()),
|
2020-09-14 19:19:20 +02:00
|
|
|
));
|
|
|
|
})?;
|
2018-10-27 12:49:57 +02:00
|
|
|
}
|
2018-10-04 20:45:29 +02:00
|
|
|
std::process::exit(0);
|
|
|
|
}
|
|
|
|
fn main() -> Result<(), std::io::Error> {
|
2018-11-02 21:48:36 +01:00
|
|
|
let argv: Args = std::env::args();
|
|
|
|
let cmdargs: Vec<String> = argv.collect();
|
2018-10-04 20:45:29 +02:00
|
|
|
let entryname = cmdargs.get(1);
|
|
|
|
if entryname.is_some() {
|
2018-11-02 21:48:36 +01:00
|
|
|
match exec(&entryname.unwrap(), &cmdargs) {
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("The following error ocurred:");
|
|
|
|
eprintln!("{}", e);
|
2018-10-04 20:45:29 +02:00
|
|
|
|
2018-11-02 21:48:36 +01:00
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
};
|
2018-10-04 20:45:29 +02:00
|
|
|
}
|
|
|
|
usage();
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|