use std::{env, path::PathBuf};
use nix::unistd::{Gid, Uid};
use crate::{
errors::*,
sys::{self, PathExt},
};
pub fn home_dir() -> RvResult<PathBuf> {
sys::home_dir()
}
pub fn config_dir() -> RvResult<PathBuf> {
Ok(match env::var("XDG_CONFIG_HOME") {
Ok(x) => PathBuf::from(x),
Err(_) => home_dir()?.mash(".config"),
})
}
pub fn cache_dir() -> RvResult<PathBuf> {
Ok(match env::var("XDG_CACHE_HOME") {
Ok(x) => PathBuf::from(x),
Err(_) => home_dir()?.mash(".cache"),
})
}
pub fn data_dir() -> RvResult<PathBuf> {
Ok(match env::var("XDG_DATA_HOME") {
Ok(x) => PathBuf::from(x),
Err(_) => home_dir()?.mash(".local").mash("share"),
})
}
pub fn state_dir() -> RvResult<PathBuf> {
Ok(match env::var("XDG_STATE_HOME") {
Ok(x) => PathBuf::from(x),
Err(_) => home_dir()?.mash(".local").mash("state"),
})
}
pub fn runtime_dir() -> PathBuf {
match env::var("XDG_RUNTIME_DIR") {
Ok(x) => PathBuf::from(x),
Err(_) => PathBuf::from("/tmp"),
}
}
pub fn sys_data_dirs() -> RvResult<Vec<PathBuf>> {
let default = vec![PathBuf::from("/usr/local/share"), PathBuf::from("/usr/share")];
Ok(match env::var("XDG_DATA_DIRS") {
Ok(x) => {
let paths = sys::parse_paths(x)?;
if paths.is_empty() {
default
} else {
paths
}
},
Err(_) => default,
})
}
pub fn sys_config_dirs() -> RvResult<Vec<PathBuf>> {
let default = vec![PathBuf::from("/etc/xdg")];
Ok(match env::var("XDG_CONFIG_DIRS") {
Ok(x) => {
let paths = sys::parse_paths(x)?;
if paths.is_empty() {
default
} else {
paths
}
},
Err(_) => default,
})
}
pub fn path_dirs() -> RvResult<Vec<PathBuf>> {
sys::parse_paths(env::var("PATH")?)
}
#[derive(Debug, Clone, Default)]
pub struct User {
pub uid: u32, pub gid: u32, pub name: String, pub home: PathBuf, pub shell: PathBuf, pub ruid: u32, pub rgid: u32, pub realname: String, pub realhome: PathBuf, pub realshell: PathBuf, }
impl User {
pub fn is_root(&self) -> bool {
self.uid == 0
}
}
pub fn current() -> RvResult<User> {
let user = from_uid(getuid())?;
Ok(user)
}
pub fn from_uid(uid: u32) -> RvResult<User> {
if let Some(nix_user) = nix::unistd::User::from_uid(Uid::from_raw(uid))? {
let username = nix_user.name;
let uid = nix_user.uid.as_raw();
let gid = nix_user.gid.as_raw();
let userhome = nix_user.dir;
let usershell = nix_user.shell;
let (ruid, rgid) = getrids(uid, gid);
let realuser = if uid != ruid {
from_uid(ruid)?
} else {
User {
uid,
gid,
name: username.to_string(),
home: PathBuf::from(&userhome),
shell: PathBuf::from(&usershell),
..Default::default()
}
};
Ok(User {
uid,
gid,
name: username,
home: PathBuf::from(&userhome),
shell: PathBuf::from(&usershell),
ruid,
rgid,
realname: realuser.name,
realhome: realuser.home,
realshell: realuser.shell,
})
} else {
Err(UserError::does_not_exist_by_id(uid).into())
}
}
pub fn drop_sudo() -> RvResult<()> {
match getuid() {
0 => {
let (ruid, rgid) = getrids(0, 0);
switchuser(ruid, ruid, ruid, rgid, rgid, rgid)
},
_ => Ok(()),
}
}
pub fn getuid() -> u32 {
nix::unistd::getuid().as_raw()
}
pub fn getgid() -> u32 {
nix::unistd::getgid().as_raw()
}
pub fn geteuid() -> u32 {
nix::unistd::geteuid().as_raw()
}
pub fn getegid() -> u32 {
nix::unistd::getegid().as_raw()
}
pub fn getrids(uid: u32, gid: u32) -> (u32, u32) {
match uid {
0 => match (env::var("SUDO_UID"), env::var("SUDO_GID")) {
(Ok(u), Ok(g)) => match (u.parse::<u32>(), g.parse::<u32>()) {
(Ok(u), Ok(g)) => (u, g),
_ => (uid, gid),
},
_ => (uid, gid),
},
_ => (uid, gid),
}
}
pub fn is_root() -> bool {
getuid() == 0
}
pub fn name() -> RvResult<String> {
Ok(current()?.name)
}
pub fn setuid(uid: u32) -> RvResult<()> {
nix::unistd::setuid(Uid::from_raw(uid))?;
Ok(())
}
pub fn seteuid(euid: u32) -> RvResult<()> {
nix::unistd::seteuid(Uid::from_raw(euid))?;
Ok(())
}
pub fn setgid(gid: u32) -> RvResult<()> {
nix::unistd::setgid(Gid::from_raw(gid))?;
Ok(())
}
pub fn setegid(egid: u32) -> RvResult<()> {
nix::unistd::setegid(Gid::from_raw(egid))?;
Ok(())
}
pub fn sudo_up() -> RvResult<()> {
if is_root() {
return Ok(());
}
switchuser(0, 0, 0, 0, 0, 0)
}
pub fn sudo_down() -> RvResult<()> {
if !is_root() {
return Ok(());
}
match getuid() {
0 => {
let (ruid, rgid) = getrids(0, 0);
switchuser(ruid, ruid, 0, rgid, rgid, 0)
},
_ => Ok(()),
}
}
pub fn switchuser(ruid: u32, euid: u32, suid: u32, rgid: u32, egid: u32, sgid: u32) -> RvResult<()> {
nix::unistd::setresgid(Gid::from_raw(rgid), Gid::from_raw(egid), Gid::from_raw(sgid))?;
nix::unistd::setresuid(Uid::from_raw(ruid), Uid::from_raw(euid), Uid::from_raw(suid))?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::{env, path::PathBuf};
use crate::prelude::*;
#[test]
fn test_user_home() {
let home_str = env::var("HOME").unwrap();
let home_path = PathBuf::from(home_str);
let home_dir = home_path.parent().unwrap();
assert_eq!(home_dir.to_path_buf(), user::home_dir().unwrap().dir().unwrap());
}
#[test]
fn test_user_ids() {
assert!(user::sudo_down().is_ok());
assert!(user::drop_sudo().is_ok());
assert!(user::getuid() != 0);
assert!(user::getgid() != 0);
assert!(user::geteuid() != 0);
assert!(user::getegid() != 0);
assert_eq!(user::getrids(user::getuid(), user::getgid()), (user::getuid(), user::getgid()));
assert_eq!(user::is_root(), false);
assert!(user::from_uid(user::getuid()).is_ok());
assert_ne!(user::name().unwrap(), "");
assert!(user::current().is_ok());
assert_eq!(user::current().unwrap().is_root(), false);
}
#[test]
fn test_user_dirs() {
assert!(user::home_dir().is_ok());
assert!(user::config_dir().is_ok());
assert!(user::cache_dir().is_ok());
assert!(user::data_dir().is_ok());
user::runtime_dir();
assert!(user::sys_data_dirs().is_ok());
assert!(user::sys_config_dirs().is_ok());
assert!(user::path_dirs().is_ok());
}
}