use anyhow::Context; use indoc::indoc; use nix::unistd::{Group as LibcGroup, User as LibcUser}; #[cfg(not(target_os = "macos"))] use std::ffi::CString; use std::fmt; pub const DEFAULT_CONFIG_PATH: &str = "/etc/muscl/config.toml"; pub const DEFAULT_SOCKET_PATH: &str = "/run/muscl/muscl.sock"; pub const ASCII_BANNER: &str = indoc! { r#" __ ____ ___ __ ____________/ / / __ `__ \/ / / / ___/ ___/ / / / / / / / /_/ (__ ) /__/ / /_/ /_/ /_/\__,_/____/\___/_/ "# }; pub const KIND_REGARDS: &str = concat!( "Hacked together by yours truly, Programvareverkstedet \n", "If you experience any bugs or turbulence, please give us a heads up :)", ); #[derive(Debug, Clone)] pub struct UnixUser { pub username: String, pub groups: Vec, } impl fmt::Display for UnixUser { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.username) } } // TODO: these functions are somewhat critical, and should have integration tests #[cfg(target_os = "macos")] fn get_unix_groups(_user: &LibcUser) -> anyhow::Result> { // Return an empty list on macOS since there is no `getgrouplist` function Ok(vec![]) } #[cfg(not(target_os = "macos"))] fn get_unix_groups(user: &LibcUser) -> anyhow::Result> { let user_cstr = CString::new(user.name.as_bytes()).context("Failed to convert username to CStr")?; let groups = nix::unistd::getgrouplist(&user_cstr, user.gid)? .iter() .filter_map(|gid| match LibcGroup::from_gid(*gid) { Ok(Some(group)) => Some(group), Ok(None) => None, Err(e) => { tracing::warn!( "Failed to look up group with GID {}: {}\nIgnoring...", gid, e ); None } }) .collect::>(); Ok(groups) } /// Check if the current executable is running in SUID/SGID mode #[cfg(feature = "suid-sgid-mode")] pub fn executing_in_suid_sgid_mode() -> anyhow::Result { let euid = nix::unistd::geteuid(); let uid = nix::unistd::getuid(); let egid = nix::unistd::getegid(); let gid = nix::unistd::getgid(); Ok(euid != uid || egid != gid) } #[cfg(not(feature = "suid-sgid-mode"))] #[inline] pub fn executing_in_suid_sgid_mode() -> anyhow::Result { Ok(false) } impl UnixUser { pub fn from_uid(uid: u32) -> anyhow::Result { let libc_uid = nix::unistd::Uid::from_raw(uid); let libc_user = LibcUser::from_uid(libc_uid) .context("Failed to look up your UNIX username")? .ok_or(anyhow::anyhow!("Failed to look up your UNIX username"))?; let groups = get_unix_groups(&libc_user)?; Ok(UnixUser { username: libc_user.name, groups: groups.iter().map(|g| g.name.to_owned()).collect(), }) } pub fn from_enviroment() -> anyhow::Result { let libc_uid = nix::unistd::getuid(); UnixUser::from_uid(libc_uid.as_raw()) } } #[inline] pub(crate) fn yn(b: bool) -> &'static str { if b { "Y" } else { "N" } } #[inline] pub(crate) fn rev_yn(s: &str) -> Option { match s.to_lowercase().as_str() { "y" => Some(true), "n" => Some(false), _ => None, } } #[cfg(test)] mod test { use super::*; #[test] fn test_yn() { assert_eq!(yn(true), "Y"); assert_eq!(yn(false), "N"); } #[test] fn test_rev_yn() { assert_eq!(rev_yn("Y"), Some(true)); assert_eq!(rev_yn("y"), Some(true)); assert_eq!(rev_yn("N"), Some(false)); assert_eq!(rev_yn("n"), Some(false)); assert_eq!(rev_yn("X"), None); } }