change_user_run/user.rs
1//! User creation.
2
3use std::{path::Path, process::Command};
4
5use log::debug;
6
7use crate::{Error, get_command};
8
9/// The default shell used for new users (`/usr/bin/bash`).
10pub const DEFAULT_SHELL: &str = "/usr/bin/bash";
11
12/// Creates a set of users using [useradd] and unlocks them using [usermod].
13///
14/// Optionally, the base directory for the home directory of all users and the shell can be
15/// provided. By default [`DEFAULT_SHELL`] is used as the user's shell.
16///
17/// # Note
18///
19/// User creation is a privileged action which requires calling this function as root.
20///
21/// # Errors
22///
23/// Returns an error if
24///
25/// - the [useradd] or [usermod] commands cannot be found,
26/// - the [useradd] command cannot be executed,
27/// - a user and/or its home cannot be created,
28/// - the [usermod] command cannot be executed,
29/// - or unlocking a created user fails.
30///
31/// # Examples
32///
33/// ```no_run
34/// use std::path::Path;
35///
36/// use change_user_run::create_users;
37///
38/// # fn main() -> testresult::TestResult {
39/// // Create a single user in the default location with the default shell.
40/// create_users(&["testuser"], None, None)?;
41///
42/// // Create a set of users, with home directories under a custom directory, using a custom shell.
43/// create_users(
44/// &["testuser"],
45/// Some(&Path::new("/var/lib/custom")),
46/// Some(&Path::new("/usr/bin/fish")),
47/// )?;
48/// # Ok(())
49/// # }
50/// ```
51///
52/// [useradd]: https://man.archlinux.org/man/useradd.8
53/// [usermod]: https://man.archlinux.org/man/usermod.8
54pub fn create_users(
55 users: &[&str],
56 home_base_dir: Option<&Path>,
57 shell: Option<&Path>,
58) -> Result<(), Error> {
59 let useradd_command = get_command("useradd")?;
60 let usermod_command = get_command("usermod")?;
61
62 debug!("Creating users: {}", users.join(", "));
63
64 for user in users {
65 debug!("Creating user: {user}");
66
67 // Create the user and its home.
68 let mut command = Command::new(&useradd_command);
69 let command = command
70 .arg("--create-home")
71 .arg("--user-group")
72 .arg("--shell")
73 .arg(shell.unwrap_or(Path::new(DEFAULT_SHELL)));
74 let command = if let Some(path) = home_base_dir.as_ref() {
75 command.arg("--base-dir").arg(path)
76 } else {
77 command
78 };
79 let command = command.arg(user);
80
81 let command_output = command.output().map_err(|source| Error::CommandExec {
82 command: format!("{command:?}"),
83 source,
84 })?;
85 if !command_output.status.success() {
86 return Err(crate::Error::CommandNonZero {
87 command: format!("{command:?}"),
88 exit_status: command_output.status,
89 stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
90 });
91 }
92
93 // unlock the user
94 let mut command = Command::new(&usermod_command);
95 command.arg("--unlock");
96 command.arg(user);
97 let command_output = command.output().map_err(|source| Error::CommandExec {
98 command: format!("{command:?}"),
99 source,
100 })?;
101 if !command_output.status.success() {
102 return Err(Error::CommandNonZero {
103 command: format!("{command:?}"),
104 exit_status: command_output.status,
105 stderr: String::from_utf8_lossy(&command_output.stderr).into_owned(),
106 });
107 }
108 }
109
110 Ok(())
111}