signstar_config/
utils.rs

1//! Utilities for signstar-config.
2use std::path::PathBuf;
3
4use nethsm_config::{ExtendedUserMapping, SystemUserId};
5use nix::unistd::{User, geteuid};
6use which::which;
7
8/// An error that may occur when using signstar-config utils.
9#[derive(Debug, thiserror::Error)]
10pub enum Error {
11    /// An executable that is supposed to be called, is not found.
12    #[error("Unable to to find executable \"{command}\"")]
13    ExecutableNotFound {
14        /// The executable that could not be found.
15        command: String,
16        /// The source error.
17        source: which::Error,
18    },
19
20    /// An [`ExtendedUserMapping`] does not provide a system user.
21    #[error("The user mapping does not provide a system user:\n{0}")]
22    MappingSystemUserGet(String),
23
24    /// There is no data about a system user.
25    #[error("Data for system user {user} is missing")]
26    SystemUserData {
27        /// The user identifier for which data is missing.
28        user: NameOrUid,
29    },
30
31    /// Unable to lookup system user data (by name or from process EUID).
32    #[error(
33        "Unable to lookup data for system user {}:\n{source}",
34        match user {
35            NameOrUid::Name(name) => format!("user {name}"),
36            NameOrUid::Uid(uid) => format!("uid {uid}"),
37        }
38    )]
39    SystemUserLookup {
40        /// The user identifier for which data could not be looked up.
41        user: NameOrUid,
42        /// The source error.
43        source: nix::errno::Errno,
44    },
45
46    /// The calling user does not match the targeted system user.
47    #[error(
48        "The targeted system user {target_user} is not the currently calling system user {current_user}."
49    )]
50    SystemUserMismatch {
51        /// The system user that is the target of the operation.
52        target_user: String,
53        /// The currently calling system user.
54        current_user: String,
55    },
56
57    /// The current user is an unprivileged user, but should be root.
58    #[error("The command requires running as root, but running as \"{user}\"")]
59    SystemUserNotRoot {
60        /// The system user that is used instead of `root`.
61        user: String,
62    },
63
64    /// The current user is root, but should be an unprivileged user.
65    #[error("The command must not be run as root, but running as \"root\"")]
66    SystemUserRoot,
67}
68
69/// A name or uid of a system user on a host
70#[derive(Debug, strum::Display)]
71pub enum NameOrUid {
72    /// The name of the system user.
73    Name(SystemUserId),
74    /// The ID of the system user.
75    Uid(nix::unistd::Uid),
76}
77
78/// Returns the path to a `command`.
79///
80/// Searches for an executable in `$PATH` of the current environment and returns the first one
81/// found.
82///
83/// # Errors
84///
85/// Returns an error if no executable matches the provided `command`.
86pub(crate) fn get_command(command: &str) -> Result<PathBuf, Error> {
87    which(command).map_err(|source| Error::ExecutableNotFound {
88        command: command.to_string(),
89        source,
90    })
91}
92
93/// Fails if not running as root.
94///
95/// Evaluates the effective user ID.
96///
97/// # Errors
98///
99/// Returns an error if the effective user ID is not that of root.
100pub(crate) fn fail_if_not_root(user: &User) -> Result<(), Error> {
101    if !user.uid.is_root() {
102        return Err(Error::SystemUserNotRoot {
103            user: user.name.clone(),
104        });
105    }
106    Ok(())
107}
108
109/// Fails if running as root.
110///
111/// Evaluates the effective user ID.
112///
113/// # Errors
114///
115/// Returns an error if the effective user ID is that of root.
116pub(crate) fn fail_if_root(user: &User) -> Result<(), Error> {
117    if user.uid.is_root() {
118        return Err(Error::SystemUserRoot);
119    }
120    Ok(())
121}
122
123/// Returns the [`User`] associated with the current process.
124///
125/// Retrieves user data of the system based on the effective user ID of the current process.
126///
127/// # Errors
128///
129/// Returns an error if
130/// - no user data can be derived from the current process
131/// - no user data can be found on the system, associated with the ID of the user of the current
132///   process.
133pub(crate) fn get_current_system_user() -> Result<User, Error> {
134    let euid = geteuid();
135    let Some(user) = User::from_uid(euid).map_err(|source| Error::SystemUserLookup {
136        user: NameOrUid::Uid(euid),
137        source,
138    })?
139    else {
140        return Err(Error::SystemUserData {
141            user: NameOrUid::Uid(euid),
142        });
143    };
144    Ok(user)
145}
146
147/// Checks whether the current system user is the targeted user.
148///
149/// Compares two [`User`] instances and fails if they are not the same.
150///
151/// # Errors
152///
153/// Returns an error if the current system user is not the targeted user.
154pub(crate) fn match_current_system_user(
155    current_user: &User,
156    target_user: &User,
157) -> Result<(), Error> {
158    if current_user != target_user {
159        return Err(Error::SystemUserMismatch {
160            target_user: target_user.name.clone(),
161            current_user: current_user.name.clone(),
162        });
163    }
164    Ok(())
165}
166
167/// Returns a [`SystemUserId`] and matching Unix system [`User`] associated with it.
168///
169/// # Errors
170///
171/// Returns an error if
172/// - there is no [`SystemUserId`] in the mapping,
173/// - or no [`User`] data can be retrieved from a found [`SystemUserId`].
174pub(crate) fn get_system_user_pair(
175    mapping: &ExtendedUserMapping,
176) -> Result<(SystemUserId, User), Error> {
177    // retrieve the targeted system user from the mapping
178    let Some(system_user) = mapping.get_user_mapping().get_system_user() else {
179        return Err(Error::MappingSystemUserGet(format!(
180            "{:?}",
181            mapping.get_user_mapping()
182        )));
183    };
184
185    // retrieve the actual user data on the system
186    let Some(user) =
187        User::from_name(system_user.as_ref()).map_err(|source| Error::SystemUserLookup {
188            user: NameOrUid::Name(system_user.clone()),
189            source,
190        })?
191    else {
192        return Err(Error::SystemUserData {
193            user: NameOrUid::Name(system_user.clone()),
194        });
195    };
196
197    Ok((system_user.clone(), user))
198}