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}