use nix::{
libc::{gid_t, uid_t},
unistd::{self, Gid, Uid},
};
use std::{env, ffi::CString, str::FromStr};
#[derive(Clone, Debug)]
pub struct User(pub unistd::User);
impl User {
pub fn from_uid(uid: Uid) -> Result<Self, String> {
let user = unistd::User::from_uid(uid)
.map_err(|err| format!("user with ID ({uid}): {err}"))?
.ok_or_else(|| format!("user with ID ({uid}) does not exist"))?;
Ok(Self(user))
}
pub fn from_name(name: &str) -> Result<Self, String> {
let user = unistd::User::from_name(name)
.map_err(|err| format!("user '{name}': {err}"))?
.ok_or_else(|| format!("user '{name}' does not exist"))?;
Ok(Self(user))
}
pub unsafe fn set_env(&self) {
unsafe { env::set_var("USER", &self.0.name) };
unsafe { env::set_var("HOME", &self.0.dir) };
unsafe { env::set_var("SHELL", &self.0.shell) };
}
}
impl FromStr for User {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<uid_t>().ok() {
Some(uid) => Self::from_uid(uid.into()),
None => Self::from_name(s),
}
}
}
#[derive(Clone, Debug)]
pub struct Group(pub unistd::Group);
impl Group {
pub fn from_gid(gid: Gid) -> Result<Self, String> {
let group = unistd::Group::from_gid(gid)
.map_err(|err| format!("group with ID ({gid}): {err}"))?
.ok_or_else(|| format!("group with ID ({gid}) does not exist"))?;
Ok(Self(group))
}
pub fn from_name(name: &str) -> Result<Self, String> {
let group = unistd::Group::from_name(name)
.map_err(|err| format!("group '{name}': {err}"))?
.ok_or_else(|| format!("group '{name}' does not exist"))?;
Ok(Self(group))
}
}
impl FromStr for Group {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<gid_t>().ok() {
Some(gid) => Self::from_gid(gid.into()),
None => Self::from_name(s),
}
}
}
#[derive(Clone, Debug)]
pub struct Privileges {
pub user: User,
pub group: Group,
}
impl Privileges {
pub fn drop_privileges(&self) -> Result<(), String> {
let user = &self.user.0;
let group = &self.group.0;
let name = CString::new(user.name.as_str())
.expect("user names can only contain valid ASCII characters");
unistd::initgroups(&name, group.gid).map_err(|err| {
format!(
"failed to set supplementary group list for user '{}': {err}",
user.name
)
})?;
unistd::setgid(group.gid).map_err(|err| {
format!("failed to set group to '{}': {err}", group.name)
})?;
unistd::setuid(user.uid).map_err(|err| {
format!("failed to set user to '{}': {err}", user.name)
})?;
Ok(())
}
pub unsafe fn set_env(&self) {
unsafe { self.user.set_env() };
}
}
impl FromStr for Privileges {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut values = s.splitn(2, ':');
let user: User = values.next().unwrap().parse()?;
let group: Group = if let Some(group) = values.next() {
group.parse()?
} else {
Group::from_gid(user.0.gid)?
};
Ok(Self { user, group })
}
}