use crate::cli::SudoOptions;
use crate::system::{Group, User};
use std::{
env, fs,
os::unix::prelude::MetadataExt,
path::{Path, PathBuf},
str::FromStr,
};
use super::{context::LaunchType, Error};
#[derive(PartialEq, Debug)]
enum NameOrId<'a, T: FromStr> {
Name(&'a str),
Id(T),
}
impl<'a, T: FromStr> NameOrId<'a, T> {
pub fn parse(input: &'a str) -> Option<Self> {
if input.is_empty() {
None
} else if let Some(stripped) = input.strip_prefix('#') {
stripped.parse::<T>().ok().map(|id| Self::Id(id))
} else {
Some(Self::Name(input))
}
}
}
pub fn resolve_current_user() -> Result<User, Error> {
User::real()?.ok_or(Error::UserNotFound("current user".to_string()))
}
type Shell = Option<PathBuf>;
pub(super) fn resolve_launch_and_shell(
sudo_options: &SudoOptions,
current_user: &User,
target_user: &User,
) -> (LaunchType, Shell) {
if sudo_options.login {
(LaunchType::Login, Some(target_user.shell.clone()))
} else if sudo_options.shell {
let shell = env::var("SHELL")
.map(|s| s.into())
.unwrap_or_else(|_| current_user.shell.clone());
(LaunchType::Shell, Some(shell))
} else {
(LaunchType::Direct, None)
}
}
pub(super) fn resolve_target_user_and_group(
target_user_name_or_id: &Option<String>,
target_group_name_or_id: &Option<String>,
current_user: &User,
) -> Result<(User, Group), Error> {
let mut target_user =
match NameOrId::parse(target_user_name_or_id.as_deref().unwrap_or_default()) {
Some(NameOrId::Name(name)) => User::from_name(name)?,
Some(NameOrId::Id(uid)) => User::from_uid(uid)?,
_ => None,
};
let mut target_group =
match NameOrId::parse(target_group_name_or_id.as_deref().unwrap_or_default()) {
Some(NameOrId::Name(name)) => Group::from_name(name)?,
Some(NameOrId::Id(gid)) => Group::from_gid(gid)?,
_ => None,
};
match (&target_user_name_or_id, &target_group_name_or_id) {
(None, Some(_)) => {
target_user = Some(current_user.clone());
}
(Some(_), None) => {
if let Some(user) = &target_user {
target_group = Group::from_gid(user.gid)?;
}
}
(None, None) => {
target_user = User::from_name("root")?;
target_group = Group::from_name("root")?;
}
_ => {}
}
match (target_user, target_group) {
(Some(user), Some(group)) => {
Ok((user, group))
}
(Some(_), None) => Err(Error::GroupNotFound(
target_group_name_or_id
.as_deref()
.unwrap_or_default()
.to_string(),
)),
_ => Err(Error::UserNotFound(
target_user_name_or_id
.as_deref()
.unwrap_or_default()
.to_string(),
)),
}
}
fn is_valid_executable(path: &PathBuf) -> bool {
if path.is_file() {
match fs::metadata(path) {
Ok(meta) => meta.mode() & 0o111 != 0,
_ => false,
}
} else {
false
}
}
pub(super) fn resolve_path(command: &Path, path: &str) -> Option<PathBuf> {
let mut resolve_current_path = false;
path.split(':')
.filter(|&path| {
if path.is_empty() || path == "." {
resolve_current_path = true;
false
} else {
true
}
})
.map(|path| PathBuf::from(path).join(command))
.find(is_valid_executable)
.or_else(|| {
if resolve_current_path {
env::current_dir()
.ok()
.map(|dir| dir.join(command))
.and_then(|path| {
if is_valid_executable(&path) {
Some(path)
} else {
None
}
})
} else {
None
}
})
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::resolve_path;
use super::{resolve_current_user, resolve_target_user_and_group, NameOrId};
#[test]
fn test_resolve_path() {
let path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
assert_eq!(
resolve_path(&PathBuf::from("yes"), path),
Some(PathBuf::from("/usr/bin/yes"))
);
assert_eq!(
resolve_path(&PathBuf::from("whoami"), path),
Some(PathBuf::from("/usr/bin/whoami"))
);
assert_eq!(
resolve_path(&PathBuf::from("env"), path),
Some(PathBuf::from("/usr/bin/env"))
);
assert_eq!(
resolve_path(&PathBuf::from("thisisnotonyourfs"), path),
None
);
assert_eq!(resolve_path(&PathBuf::from("thisisnotonyourfs"), "."), None);
}
#[test]
fn test_name_or_id() {
assert_eq!(NameOrId::<u32>::parse(""), None);
assert_eq!(NameOrId::<u32>::parse("mies"), Some(NameOrId::Name("mies")));
assert_eq!(NameOrId::<u32>::parse("1337"), Some(NameOrId::Name("1337")));
assert_eq!(NameOrId::<u32>::parse("#1337"), Some(NameOrId::Id(1337)));
assert_eq!(NameOrId::<u32>::parse("#-1"), None);
}
#[test]
fn test_resolve_target_user_and_group() {
let current_user = resolve_current_user().unwrap();
let (user, group) = resolve_target_user_and_group(&None, &None, ¤t_user).unwrap();
assert_eq!(user.name, "root");
assert_eq!(group.name, "root");
let result = resolve_target_user_and_group(
&Some("non_existing_ghost".to_string()),
&None,
¤t_user,
);
assert!(result.is_err());
let result = resolve_target_user_and_group(
&None,
&Some("non_existing_ghost".to_string()),
¤t_user,
);
assert!(result.is_err());
let (user, group) =
resolve_target_user_and_group(&None, &Some("root".to_string()), ¤t_user).unwrap();
assert_eq!(user.name, current_user.name);
assert_eq!(group.name, "root");
let (user, group) = resolve_target_user_and_group(
&Some(current_user.name.to_string()),
&None,
¤t_user,
)
.unwrap();
assert_eq!(user.name, current_user.name);
assert_eq!(group.gid, current_user.gid);
}
}