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}