mysqladm/core/
common.rs

1use anyhow::Context;
2use nix::unistd::{Group as LibcGroup, User as LibcUser};
3
4#[cfg(not(target_os = "macos"))]
5use std::ffi::CString;
6
7pub const DEFAULT_CONFIG_PATH: &str = "/etc/mysqladm/config.toml";
8pub const DEFAULT_SOCKET_PATH: &str = "/run/mysqladm/mysqladm.sock";
9
10pub 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")]
18fn 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"))]
24fn 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")]
50pub 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]
67pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
68    Ok(false)
69}
70
71impl 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]
93pub(crate) fn yn(b: bool) -> &'static str {
94    if b { "Y" } else { "N" }
95}
96
97#[inline]
98pub(crate) fn rev_yn(s: &str) -> Option<bool> {
99    match s.to_lowercase().as_str() {
100        "y" => Some(true),
101        "n" => Some(false),
102        _ => None,
103    }
104}
105
106#[cfg(test)]
107mod 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}