~goorzhel/radm

radm/src/system.rs -rw-r--r-- 3.7 KiB
d11d708e — Antonio Gurgel 0.6.4 a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! Low-level interfaces.
//!
//! These functions aren't associated with [User](crate::user::User), to emphasize that they
//! run while `radm` still has root privileges.

use std::{
    env::{set_var, var, VarError},
    ffi::{CString, OsStr},
    fmt::Display,
    fs::{create_dir_all, set_permissions, Permissions},
    os::unix::prelude::PermissionsExt,
    path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use log::{debug, warn};
use nix::{
    sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal},
    unistd::{chdir, chown, getgid, getgrouplist, gethostname, getuid, setgid, setgroups, setuid},
};

use crate::user::User;

/// Gets the hostname.
pub fn hostname() -> Result<String> {
    let mut buf = [0u8; 128];
    let hostname = gethostname(&mut buf).context("Couldn't call gethostname")?;
    let hostname = hostname.to_str().context("Hostname isn't valid UTF-8")?;
    Ok(hostname.to_owned())
}

/// Sets `radm`'s privileges and working directory to those of the authenticated user.
pub fn drop_privileges(user: &User) -> Result<()> {
    if !getuid().is_root() {
        return Ok(());
    }

    let username = CString::new(user.name.clone())?;
    let uid = user.uid;
    let gid = user.gid;

    let gids = getgrouplist(&username, gid).context("Couldn't getgroups()")?;
    setgroups(&gids).context("Couldn't setgroups()")?;
    setgid(gid).context("Couldn't setgid()")?;
    setuid(uid).context("Couldn't setuid()")?;
    chdir(Path::new(&user.dir)).context("Couldn't chdir() to homedir")?;

    // Some extra paranoia
    match getuid().is_root() || u32::from(getgid()) == 0 {
        true => panic!("Dropped privileges but somehow still have root"),
        false => Ok(()),
    }
}

/// Reads environment variable `$XDG_RUNTIME_DIR` and ensures a directory with
/// the correct perms exists at the path.
pub fn prepare_xdg_runtime(user: &User) -> Result<()> {
    let uid = user.uid;
    let gid = user.gid;

    let path: PathBuf = var("XDG_RUNTIME_DIR").unwrap().into();
    // unwrap: $XDG_RUNTIME_DIR should already be set by User::load_environment.

    let run_user = path.parent();
    if run_user != Some(Path::new("/run/user")) {
        warn!(
            "XDG_RUNTIME_DIR ({:?}) isn't a child of `/run/user`. Dropping privileges to prevent abuse.",
            path
        );
        drop_privileges(user).context("Couldn't drop privileges")?;
    }

    create_dir_all(&path).context("Couldn't create XDG_RUNTIME_DIR")?;
    chown(&path, Some(uid), Some(gid)).context("Couldn't chown XDG_RUNTIME_DIR")?;
    set_permissions(&path, Permissions::from_mode(0o700))
        .context("Couldn't chmod XDG_RUNTIME_DIR")?;
    Ok(())
}

/// Reads an environment variable; if unset, sets it to the given default.
pub fn env_var_or_set_default<K: AsRef<OsStr> + Display, D: AsRef<OsStr> + Display>(
    key: K,
    default: D,
) -> String {
    match var(&key) {
        Ok(v) => v,
        Err(e) => {
            match e {
                VarError::NotPresent => debug!("Defaulting {} to {}", &key, default),
                VarError::NotUnicode(_) => warn!(
                    "Found value for {} but it wasn't valid Unicode; defaulting to {}",
                    &key, default
                ),
            }
            set_var(key, &default);
            default.to_string()
        }
    }
}

/// Toggles SIGINT and SIGQUIT.
///
/// Compare [util-linux](https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/tree/login-utils/login.c?h=v2.38#n1315).
pub fn toggle_signals(handler: SigHandler) -> Result<()> {
    let mask = SigSet::empty();
    let flags = SaFlags::empty();

    let action = SigAction::new(handler, flags, mask);
    unsafe {
        sigaction(Signal::SIGINT, &action)?;
        sigaction(Signal::SIGQUIT, &action)?;
    }

    Ok(())
}