#[cfg(feature = "serde")]
mod serde_support;
use crate::Error;
use once_cell::sync::Lazy;
use std::{
ffi::{OsStr, OsString},
sync::Mutex,
};
static CACHE: Lazy<Mutex<users::UsersCache>> = Lazy::new(|| Mutex::new(users::UsersCache::new()));
fn get_self_named_gid_impl<U: users::Groups + users::Users>(userdb: &U) -> Option<u32> {
let username = get_own_username(userdb)?;
let group = userdb.get_group_by_name(username.as_os_str())?;
if cur_groups().contains(&group.gid()) {
Some(group.gid())
} else {
None
}
}
fn get_own_username<U: users::Users>(userdb: &U) -> Option<OsString> {
let my_uid = userdb.get_current_uid();
if let Some(username) = std::env::var_os("USER") {
if let Some(passwd) = userdb.get_user_by_name(username.as_os_str()) {
if passwd.uid() == my_uid {
return Some(username);
}
}
}
if let Some(passwd) = userdb.get_user_by_uid(my_uid) {
if passwd.uid() == my_uid {
return Some(passwd.name().to_owned());
}
}
None
}
fn cur_groups() -> Vec<u32> {
let n_groups = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
if n_groups <= 0 {
return Vec::new();
}
let mut buf: Vec<users::gid_t> = vec![0; n_groups as usize];
let n_groups2 = unsafe { libc::getgroups(buf.len() as i32, buf.as_mut_ptr()) };
if n_groups2 <= 0 {
return Vec::new();
}
if n_groups2 < n_groups {
buf.resize(n_groups2 as usize, 0);
}
let cur_gid = users::get_current_gid();
if !buf.contains(&cur_gid) {
buf.push(cur_gid);
}
buf
}
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[non_exhaustive]
pub enum TrustedUser {
None,
#[educe(Default)]
Current,
Id(u32),
Name(OsString),
}
impl From<u32> for TrustedUser {
fn from(val: u32) -> Self {
TrustedUser::Id(val)
}
}
impl From<OsString> for TrustedUser {
fn from(val: OsString) -> Self {
TrustedUser::Name(val)
}
}
impl From<&OsStr> for TrustedUser {
fn from(val: &OsStr) -> Self {
val.to_owned().into()
}
}
impl From<String> for TrustedUser {
fn from(val: String) -> Self {
OsString::from(val).into()
}
}
impl From<&str> for TrustedUser {
fn from(val: &str) -> Self {
val.to_owned().into()
}
}
impl TrustedUser {
pub(crate) fn get_uid(&self) -> Result<Option<u32>, Error> {
let userdb = CACHE.lock().expect("poisoned lock");
self.get_uid_impl(&*userdb)
}
fn get_uid_impl<U: users::Users>(&self, userdb: &U) -> Result<Option<u32>, Error> {
match self {
TrustedUser::None => Ok(None),
TrustedUser::Current => Ok(Some(userdb.get_current_uid())),
TrustedUser::Id(id) => Ok(Some(*id)),
TrustedUser::Name(name) => userdb
.get_user_by_name(&name)
.map(|u| Some(u.uid()))
.ok_or_else(|| Error::NoSuchUser(name.to_string_lossy().into_owned())),
}
}
}
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[non_exhaustive]
pub enum TrustedGroup {
None,
#[educe(Default)]
SelfNamed,
Id(u32),
Name(OsString),
}
impl From<u32> for TrustedGroup {
fn from(val: u32) -> Self {
TrustedGroup::Id(val)
}
}
impl From<OsString> for TrustedGroup {
fn from(val: OsString) -> TrustedGroup {
TrustedGroup::Name(val)
}
}
impl From<&OsStr> for TrustedGroup {
fn from(val: &OsStr) -> TrustedGroup {
val.to_owned().into()
}
}
impl From<String> for TrustedGroup {
fn from(val: String) -> TrustedGroup {
OsString::from(val).into()
}
}
impl From<&str> for TrustedGroup {
fn from(val: &str) -> TrustedGroup {
val.to_owned().into()
}
}
impl TrustedGroup {
pub(crate) fn get_gid(&self) -> Result<Option<u32>, Error> {
let userdb = CACHE.lock().expect("poisoned lock");
self.get_gid_impl(&*userdb)
}
fn get_gid_impl<U: users::Users + users::Groups>(
&self,
userdb: &U,
) -> Result<Option<u32>, Error> {
match self {
TrustedGroup::None => Ok(None),
TrustedGroup::SelfNamed => Ok(get_self_named_gid_impl(userdb)),
TrustedGroup::Id(id) => Ok(Some(*id)),
TrustedGroup::Name(name) => userdb
.get_group_by_name(&name)
.map(|u| Some(u.gid()))
.ok_or_else(|| Error::NoSuchGroup(name.to_string_lossy().into_owned())),
}
}
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use users::mock::{Group, MockUsers, User};
#[test]
fn groups() {
let groups = cur_groups();
let cur_gid = users::get_current_gid();
if groups.is_empty() {
return;
}
assert!(groups.contains(&cur_gid));
}
#[test]
fn username_real() {
let cache = CACHE.lock().expect("poisoned lock");
let uname = get_own_username(&*cache).expect("Running on a misconfigured host");
let user = users::get_user_by_name(uname.as_os_str()).unwrap();
assert_eq!(user.name(), uname);
assert_eq!(user.uid(), users::get_current_uid());
}
#[test]
fn username_from_env() {
let username = if let Some(username) = std::env::var_os("USER") {
username
} else {
return;
};
let username_s = if let Some(u) = username.to_str() {
u
} else {
return;
};
let other_name = format!("{}2", username_s);
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, username_s, 413));
db.add_user(User::new(999, &other_name, 999));
let found = get_own_username(&db);
assert_eq!(found.as_ref(), Some(&username));
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(999, username_s, 999));
db.add_user(User::new(413, &other_name, 413));
let found = get_own_username(&db);
assert_eq!(found, Some(OsString::from(other_name.clone())));
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(999413, &other_name, 999));
let found = get_own_username(&db);
assert!(found.is_none());
}
#[test]
fn username_ignoring_env() {
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, "aranea", 413413));
db.add_user(User::new(415, "notyouru!sername", 413413));
let found = get_own_username(&db);
assert_eq!(found, Some(OsString::from("aranea")));
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(999413, "notyourn!ame", 999));
let found = get_own_username(&db);
assert!(found.is_none());
}
#[test]
fn selfnamed() {
let cur_groups = cur_groups();
if cur_groups.is_empty() {
return;
}
let not_our_gid = (1..65536)
.find(|n| !cur_groups.contains(n))
.expect("We are somehow in all groups 1..65535!");
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, "aranea", 413413));
db.add_group(Group::new(413413, "serket"));
let found = get_self_named_gid_impl(&db);
assert!(found.is_none());
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, "aranea", 413413));
db.add_group(Group::new(not_our_gid, "aranea"));
let found = get_self_named_gid_impl(&db);
assert!(found.is_none());
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, "aranea", 413413));
db.add_group(Group::new(cur_groups[0], "aranea"));
let found = get_self_named_gid_impl(&db);
assert_eq!(found, Some(cur_groups[0]));
}
#[test]
fn lookup_id() {
let mut db = MockUsers::with_current_uid(413);
db.add_user(User::new(413, "aranea", 413413));
db.add_group(Group::new(33, "nepeta"));
assert_eq!(TrustedUser::None.get_uid_impl(&db).unwrap(), None);
assert_eq!(TrustedUser::Current.get_uid_impl(&db).unwrap(), Some(413));
assert_eq!(TrustedUser::Id(413).get_uid_impl(&db).unwrap(), Some(413));
assert_eq!(
TrustedUser::Name("aranea".into())
.get_uid_impl(&db)
.unwrap(),
Some(413)
);
assert!(TrustedUser::Name("ac".into()).get_uid_impl(&db).is_err());
assert_eq!(TrustedGroup::None.get_gid_impl(&db).unwrap(), None);
assert_eq!(TrustedGroup::Id(33).get_gid_impl(&db).unwrap(), Some(33));
assert_eq!(
TrustedGroup::Name("nepeta".into())
.get_gid_impl(&db)
.unwrap(),
Some(33)
);
assert!(TrustedGroup::Name("ac".into()).get_gid_impl(&db).is_err());
}
}