Files
muscl/src/core/common.rs
h7x4 222941509d
All checks were successful
Build and test / check (push) Successful in 1m41s
Build and test / test (push) Successful in 3m5s
Build and test / check-license (push) Successful in 5m56s
Build and test / docs (push) Successful in 5m29s
Build and test / build (push) Successful in 3m9s
core: check suid/sgid dynamically instead of checking file
2025-12-04 20:29:44 +09:00

141 lines
3.7 KiB
Rust

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 <projects@pvv.ntnu.no>\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<String>,
}
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<Vec<LibcGroup>> {
// 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<Vec<LibcGroup>> {
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::<Vec<LibcGroup>>();
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<bool> {
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<bool> {
Ok(false)
}
impl UnixUser {
pub fn from_uid(uid: u32) -> anyhow::Result<Self> {
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<Self> {
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<bool> {
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);
}
}