1
use anyhow::Context;
2
use nix::unistd::{Group as LibcGroup, User as LibcUser};
3

            
4
#[cfg(not(target_os = "macos"))]
5
use std::ffi::CString;
6

            
7
pub const DEFAULT_CONFIG_PATH: &str = "/etc/muscl/config.toml";
8
pub const DEFAULT_SOCKET_PATH: &str = "/run/muscl/muscl.sock";
9

            
10
pub struct UnixUser {
11
    pub username: String,
12
    pub groups: Vec<String>,
13
}
14

            
15
// TODO: these functions are somewhat critical, and should have integration tests
16

            
17
#[cfg(target_os = "macos")]
18
fn get_unix_groups(_user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
19
    // Return an empty list on macOS since there is no `getgrouplist` function
20
    Ok(vec![])
21
}
22

            
23
#[cfg(not(target_os = "macos"))]
24
fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
25
    let user_cstr =
26
        CString::new(user.name.as_bytes()).context("Failed to convert username to CStr")?;
27
    let groups = nix::unistd::getgrouplist(&user_cstr, user.gid)?
28
        .iter()
29
        .filter_map(|gid| match LibcGroup::from_gid(*gid) {
30
            Ok(Some(group)) => Some(group),
31
            Ok(None) => None,
32
            Err(e) => {
33
                log::warn!(
34
                    "Failed to look up group with GID {}: {}\nIgnoring...",
35
                    gid,
36
                    e
37
                );
38
                None
39
            }
40
        })
41
        .collect::<Vec<LibcGroup>>();
42

            
43
    Ok(groups)
44
}
45

            
46
/// Check if the current executable is SUID or SGID.
47
///
48
/// If the check fails, an error is returned.
49
#[cfg(feature = "suid-sgid-mode")]
50
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
51
    use std::{fs, os::unix::fs::PermissionsExt};
52
    let result = std::env::current_exe()
53
        .context("Failed to get current executable path")
54
        .and_then(|executable| {
55
            fs::metadata(executable).context("Failed to get executable metadata")
56
        })
57
        .context("Failed to check SUID/SGID bits on executable")
58
        .map(|metadata| {
59
            let mode = metadata.permissions().mode();
60
            mode & 0o4000 != 0 || mode & 0o2000 != 0
61
        })?;
62
    Ok(result)
63
}
64

            
65
#[cfg(not(feature = "suid-sgid-mode"))]
66
#[inline]
67
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
68
    Ok(false)
69
}
70

            
71
impl UnixUser {
72
    pub fn from_uid(uid: u32) -> anyhow::Result<Self> {
73
        let libc_uid = nix::unistd::Uid::from_raw(uid);
74
        let libc_user = LibcUser::from_uid(libc_uid)
75
            .context("Failed to look up your UNIX username")?
76
            .ok_or(anyhow::anyhow!("Failed to look up your UNIX username"))?;
77

            
78
        let groups = get_unix_groups(&libc_user)?;
79

            
80
        Ok(UnixUser {
81
            username: libc_user.name,
82
            groups: groups.iter().map(|g| g.name.to_owned()).collect(),
83
        })
84
    }
85

            
86
    pub fn from_enviroment() -> anyhow::Result<Self> {
87
        let libc_uid = nix::unistd::getuid();
88
        UnixUser::from_uid(libc_uid.as_raw())
89
    }
90
}
91

            
92
#[inline]
93
35
pub(crate) fn yn(b: bool) -> &'static str {
94
35
    if b { "Y" } else { "N" }
95
35
}
96

            
97
#[inline]
98
27
pub(crate) fn rev_yn(s: &str) -> Option<bool> {
99
27
    match s.to_lowercase().as_str() {
100
27
        "y" => Some(true),
101
14
        "n" => Some(false),
102
1
        _ => None,
103
    }
104
27
}
105

            
106
#[cfg(test)]
107
mod test {
108
    use super::*;
109

            
110
    #[test]
111
    fn test_yn() {
112
        assert_eq!(yn(true), "Y");
113
        assert_eq!(yn(false), "N");
114
    }
115

            
116
    #[test]
117
    fn test_rev_yn() {
118
        assert_eq!(rev_yn("Y"), Some(true));
119
        assert_eq!(rev_yn("y"), Some(true));
120
        assert_eq!(rev_yn("N"), Some(false));
121
        assert_eq!(rev_yn("n"), Some(false));
122
        assert_eq!(rev_yn("X"), None);
123
    }
124
}