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}