signstar_config/
utils.rs

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