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

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

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

            
12
pub const ASCII_BANNER: &str = indoc! {
13
  r"
14
                                __
15
     ____ ___  __  ____________/ /
16
    / __ `__ \/ / / / ___/ ___/ /
17
   / / / / / / /_/ (__  ) /__/ /
18
  /_/ /_/ /_/\__,_/____/\___/_/
19
  "
20
};
21

            
22
pub const KIND_REGARDS: &str = concat!(
23
    "Hacked together by yours truly, Programvareverkstedet <projects@pvv.ntnu.no>\n",
24
    "If you experience any bugs or turbulence, please give us a heads up :)",
25
);
26

            
27
/// TODO: store and display UID
28
#[derive(Debug, Clone)]
29
pub struct UnixUser {
30
    pub username: String,
31
    pub groups: Vec<String>,
32
}
33

            
34
impl fmt::Display for UnixUser {
35
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36
        f.write_str(&self.username)
37
    }
38
}
39

            
40
// TODO: these functions are somewhat critical, and should have integration tests
41

            
42
#[cfg(target_os = "macos")]
43
fn get_unix_groups(_user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
44
    // Return an empty list on macOS since there is no `getgrouplist` function
45
    Ok(vec![])
46
}
47

            
48
#[cfg(not(target_os = "macos"))]
49
fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
50
    let user_cstr =
51
        CString::new(user.name.as_bytes()).context("Failed to convert username to CStr")?;
52
    let groups = nix::unistd::getgrouplist(&user_cstr, user.gid)?
53
        .iter()
54
        .filter_map(|gid| match LibcGroup::from_gid(*gid) {
55
            Ok(Some(group)) => Some(group),
56
            Ok(None) => None,
57
            Err(e) => {
58
                tracing::warn!(
59
                    "Failed to look up group with GID {}: {}\nIgnoring...",
60
                    gid,
61
                    e
62
                );
63
                None
64
            }
65
        })
66
        .collect::<Vec<LibcGroup>>();
67

            
68
    Ok(groups)
69
}
70

            
71
/// Check if the current executable is running in SUID/SGID mode
72
#[cfg(feature = "suid-sgid-mode")]
73
pub fn executing_in_suid_sgid_mode() -> anyhow::Result<bool> {
74
    let euid = nix::unistd::geteuid();
75
    let uid = nix::unistd::getuid();
76
    let egid = nix::unistd::getegid();
77
    let gid = nix::unistd::getgid();
78

            
79
    Ok(euid != uid || egid != gid)
80
}
81

            
82
#[cfg(not(feature = "suid-sgid-mode"))]
83
#[inline]
84
pub fn executing_in_suid_sgid_mode() -> anyhow::Result<bool> {
85
    Ok(false)
86
}
87

            
88
impl UnixUser {
89
    pub fn from_uid(uid: u32) -> anyhow::Result<Self> {
90
        let libc_uid = nix::unistd::Uid::from_raw(uid);
91
        let libc_user = LibcUser::from_uid(libc_uid)
92
            .context("Failed to look up your UNIX username")?
93
            .ok_or(anyhow::anyhow!("Failed to look up your UNIX username"))?;
94

            
95
        let groups = get_unix_groups(&libc_user)?;
96

            
97
        Ok(UnixUser {
98
            username: libc_user.name,
99
            groups: groups.iter().map(|g| g.name.clone()).collect(),
100
        })
101
    }
102

            
103
    // pub fn from_environment() -> anyhow::Result<Self> {
104
    //     let libc_uid = nix::unistd::getuid();
105
    //     UnixUser::from_uid(libc_uid.as_raw())
106
    // }
107
}
108

            
109
#[inline]
110
68
pub(crate) fn yn(b: bool) -> &'static str {
111
68
    if b { "Y" } else { "N" }
112
68
}
113

            
114
#[inline]
115
27
pub(crate) fn rev_yn(s: &str) -> Option<bool> {
116
27
    match s.to_lowercase().as_str() {
117
27
        "y" => Some(true),
118
14
        "n" => Some(false),
119
1
        _ => None,
120
    }
121
27
}
122

            
123
#[cfg(test)]
124
mod test {
125
    use super::*;
126

            
127
    #[test]
128
    fn test_yn() {
129
        assert_eq!(yn(true), "Y");
130
        assert_eq!(yn(false), "N");
131
    }
132

            
133
    #[test]
134
    fn test_rev_yn() {
135
        assert_eq!(rev_yn("Y"), Some(true));
136
        assert_eq!(rev_yn("y"), Some(true));
137
        assert_eq!(rev_yn("N"), Some(false));
138
        assert_eq!(rev_yn("n"), Some(false));
139
        assert_eq!(rev_yn("X"), None);
140
    }
141
}