From 330582a142e30a80fe5f62af043955ca7373b7e7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 18 May 2022 14:36:24 -0400 Subject: [PATCH] fs-mistrust: Add Group and User types. This will help make the actual configuration more serializable, I hope. --- Cargo.lock | 1 + crates/fs-mistrust/Cargo.toml | 1 + crates/fs-mistrust/src/err.rs | 14 ++- crates/fs-mistrust/src/lib.rs | 19 +++- crates/fs-mistrust/src/user.rs | 186 +++++++++++++++++++++++++++++---- 5 files changed, 197 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54513e8d9..e3c2c2636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1201,6 +1201,7 @@ version = "0.1.0" dependencies = [ "anyhow", "derive_builder_fork_arti", + "educe", "libc", "once_cell", "tempfile", diff --git a/crates/fs-mistrust/Cargo.toml b/crates/fs-mistrust/Cargo.toml index 621800885..210cf9f84 100644 --- a/crates/fs-mistrust/Cargo.toml +++ b/crates/fs-mistrust/Cargo.toml @@ -15,6 +15,7 @@ default = ["walkdir"] [dependencies] derive_builder = { version = "0.11", package = "derive_builder_fork_arti" } +educe = "0.4.6" thiserror = "1" walkdir = { version = "2", optional = true } diff --git a/crates/fs-mistrust/src/err.rs b/crates/fs-mistrust/src/err.rs index f4693690b..e5cab4094 100644 --- a/crates/fs-mistrust/src/err.rs +++ b/crates/fs-mistrust/src/err.rs @@ -100,6 +100,14 @@ pub enum Error { /// [`Mistrust`](crate::Mistrust). #[error("Missing field: {0}")] MissingField(#[from] derive_builder::UninitializedFieldError), + + /// A group that we were configured to trust could not be found. + #[error("No such group: {0}")] + NoSuchGroup(String), + + /// A user that we were configured to trust could not be found. + #[error("No such user: {0}")] + NoSuchUser(String), } impl Error { @@ -140,6 +148,8 @@ impl Error { Error::Content(e) => return e.path(), Error::Listing(e) => return e.path(), Error::MissingField(_) => return None, + Error::NoSuchGroup(_) => return None, + Error::NoSuchUser(_) => return None, } .as_path(), ) @@ -163,7 +173,9 @@ impl Error { | Error::InvalidSubdirectory | Error::Io(_, _) | Error::NoTempFile(_) - | Error::MissingField(_) => false, + | Error::MissingField(_) + | Error::NoSuchGroup(_) + | Error::NoSuchUser(_) => false, Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()), Error::Content(err) => err.is_bad_permission(), diff --git a/crates/fs-mistrust/src/lib.rs b/crates/fs-mistrust/src/lib.rs index 94053d132..472b00929 100644 --- a/crates/fs-mistrust/src/lib.rs +++ b/crates/fs-mistrust/src/lib.rs @@ -295,6 +295,9 @@ pub use err::Error; /// A result type as returned by this crate pub type Result = std::result::Result; +#[cfg(target_family = "unix")] +pub use user::{TrustedGroup, TrustedUser}; + /// Configuration for verifying that a file or directory is really "private". /// /// By default, we mistrust everything that we can: we assume that every @@ -329,12 +332,18 @@ pub struct Mistrust { /// What user ID do we trust by default (if any?) #[cfg(target_family = "unix")] - #[builder(setter(strip_option), default = "user::this_uid()")] + #[builder( + setter(into), + field(type = "TrustedUser", build = "self.trust_user.get_uid()?") + )] trust_user: Option, /// What group ID do we trust by default (if any?) #[cfg(target_family = "unix")] - #[builder(setter(strip_option), default = "user::get_self_named_gid()")] + #[builder( + setter(into), + field(type = "TrustedGroup", build = "self.trust_group.get_gid()?") + )] trust_group: Option, } @@ -362,8 +371,8 @@ impl MistrustBuilder { /// This option disables the default group-trust behavior as well. #[cfg(target_family = "unix")] pub fn trust_admin_only(&mut self) -> &mut Self { - self.trust_user = Some(None); - self.trust_group = Some(None); + self.trust_user = TrustedUser::None; + self.trust_group = TrustedGroup::None; self } @@ -377,7 +386,7 @@ impl MistrustBuilder { /// world-writable objects respectively. #[cfg(target_family = "unix")] pub fn trust_no_group_id(&mut self) -> &mut Self { - self.trust_group = Some(None); + self.trust_group = TrustedGroup::None; self } diff --git a/crates/fs-mistrust/src/user.rs b/crates/fs-mistrust/src/user.rs index c4c77da3b..9a6902bf1 100644 --- a/crates/fs-mistrust/src/user.rs +++ b/crates/fs-mistrust/src/user.rs @@ -1,7 +1,11 @@ //! Code to inspect user db information on unix. +use crate::Error; use once_cell::sync::Lazy; -use std::{ffi::OsString, sync::Mutex}; +use std::{ + ffi::{OsStr, OsString}, + sync::Mutex, +}; /// Cached values of user db entries we've looked up. /// @@ -10,23 +14,6 @@ use std::{ffi::OsString, sync::Mutex}; /// Though this type has interior mutability, it isn't Sync, so we need to add a mutex. static CACHE: Lazy> = Lazy::new(|| Mutex::new(users::UsersCache::new())); -/// Return the UID for this user. -/// -/// The return type is funny here, to please derive_builder. -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn this_uid() -> Option { - Some(unsafe { libc::getuid() }) -} - -/// Look for a group with the same name as our username. -/// -/// If there is one, and we belong to it, return its gid. Otherwise -/// return None. -pub(crate) fn get_self_named_gid() -> Option { - let cache = CACHE.lock().expect("Poisoned lock"); - get_self_named_gid_impl(&*cache) -} - /// Like get_self_named_gid(), but use a provided user database. fn get_self_named_gid_impl(userdb: &U) -> Option { let username = get_own_username(userdb)?; @@ -92,6 +79,141 @@ fn cur_groups() -> Vec { buf } +/// A user that we can be configured to trust. +#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)] +#[educe(Default)] +#[non_exhaustive] +pub enum TrustedUser { + /// We won't treat any user as trusted. + None, + /// Treat the current user as trusted. + #[educe(Default)] + Current, + /// Treat the user with a particular UID as trusted. + Id(u32), + /// Treat a user with a particular name as trusted. + /// + /// If there is no such user, we'll report an error. + Name(OsString), +} + +impl From for TrustedUser { + fn from(val: u32) -> Self { + TrustedUser::Id(val) + } +} +impl From 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 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 { + /// Try to convert this `User` into an optional UID. + pub(crate) fn get_uid(&self) -> Result, Error> { + let userdb = CACHE.lock().expect("poisoned lock"); + self.get_uid_impl(&*userdb) + } + /// As `get_uid`, but take a userdb. + fn get_uid_impl(&self, userdb: &U) -> Result, 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())), + } + } +} + +/// A group that we can be configured to trust. +#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)] +#[educe(Default)] +#[non_exhaustive] +pub enum TrustedGroup { + /// We won't treat any group as trusted + None, + /// We'll treat any group with same name as the current user as trusted. + /// + /// If there is no such group, we trust no group. + /// + /// (This is the default.) + #[educe(Default)] + SelfNamed, + /// We'll treat a specific group ID as trusted. + Id(u32), + /// We'll treat a group with a specific name as trusted. + /// + /// If there is no such group, we'll report an error. + Name(OsString), +} + +impl From for TrustedGroup { + fn from(val: u32) -> Self { + TrustedGroup::Id(val) + } +} +impl From 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 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 { + /// Try to convert this `Group` into an optional GID. + pub(crate) fn get_gid(&self) -> Result, Error> { + let userdb = CACHE.lock().expect("poisoned lock"); + self.get_gid_impl(&*userdb) + } + /// Like `get_gid`, but take a user db as an argument. + fn get_gid_impl( + &self, + userdb: &U, + ) -> Result, 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)] @@ -212,4 +334,32 @@ mod test { 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()); + } }