sudo_rs/su/
mod.rs

1#![forbid(unsafe_code)]
2
3use crate::common::error::Error;
4use crate::exec::ExitReason;
5use crate::log::user_warn;
6use crate::pam::{PamContext, PamError, PamErrorType};
7use crate::system::term::current_tty_name;
8
9use std::{env, process};
10
11use cli::SuAction;
12use context::SuContext;
13use help::{long_help_message, USAGE_MSG};
14
15use self::cli::SuRunOptions;
16
17mod cli;
18mod context;
19mod help;
20
21const DEFAULT_USER: &str = "root";
22const VERSION: &str = env!("CARGO_PKG_VERSION");
23
24fn authenticate(requesting_user: &str, user: &str, login: bool) -> Result<PamContext, Error> {
25    // FIXME make it configurable by the packager
26    let context = if login && cfg!(target_os = "linux") {
27        "su-l"
28    } else {
29        "su"
30    };
31    let use_stdin = true;
32    let mut pam = PamContext::new_cli(
33        "su",
34        context,
35        use_stdin,
36        false,
37        false,
38        false,
39        None,
40        Some(user),
41    )?;
42    pam.set_requesting_user(requesting_user)?;
43
44    // attempt to set the TTY this session is communicating on
45    if let Ok(pam_tty) = current_tty_name() {
46        pam.set_tty(&pam_tty)?;
47    }
48
49    pam.mark_silent(true);
50    pam.mark_allow_null_auth_token(false);
51
52    let mut max_tries = 3;
53    let mut current_try = 0;
54
55    loop {
56        current_try += 1;
57        match pam.authenticate(user) {
58            // there was no error, so authentication succeeded
59            Ok(_) => break,
60
61            // maxtries was reached, pam does not allow any more tries
62            Err(PamError::Pam(PamErrorType::MaxTries)) => {
63                return Err(Error::MaxAuthAttempts(current_try));
64            }
65
66            // there was an authentication error, we can retry
67            Err(PamError::Pam(PamErrorType::AuthError)) => {
68                max_tries -= 1;
69                if max_tries == 0 {
70                    return Err(Error::MaxAuthAttempts(current_try));
71                } else {
72                    user_warn!("Authentication failed, try again.");
73                }
74            }
75
76            // there was another pam error, return the error
77            Err(e) => {
78                return Err(e.into());
79            }
80        }
81    }
82
83    pam.validate_account_or_change_auth_token()?;
84    pam.open_session()?;
85
86    Ok(pam)
87}
88
89fn run(options: SuRunOptions) -> Result<(), Error> {
90    // lookup user and build context object
91    let context = SuContext::from_env(options)?;
92
93    // authenticate the target user
94    let mut pam: PamContext = authenticate(
95        &context.requesting_user.name,
96        &context.user.name,
97        context.options.login,
98    )?;
99
100    // su in all cases uses PAM (pam_getenvlist(3)) to do the
101    // final environment modification. Command-line options such as
102    // --login and --preserve-environment affect the environment before
103    // it is modified by PAM.
104    let mut environment = context.environment.clone();
105    environment.extend(pam.env()?);
106
107    let pid = context.process.pid;
108
109    // run command and return corresponding exit code
110    let command_exit_reason = crate::exec::run_command(context.as_run_options(), environment);
111
112    pam.close_session();
113
114    match command_exit_reason? {
115        ExitReason::Code(code) => process::exit(code),
116        ExitReason::Signal(signal) => {
117            crate::system::kill(pid, signal)?;
118        }
119    }
120
121    Ok(())
122}
123
124pub fn main() {
125    crate::log::SudoLogger::new("su: ").into_global_logger();
126
127    let action = match SuAction::from_env() {
128        Ok(action) => action,
129        Err(error) => {
130            println_ignore_io_error!("su: {error}\n{USAGE_MSG}");
131            std::process::exit(1);
132        }
133    };
134
135    match action {
136        SuAction::Help(_) => {
137            println_ignore_io_error!("{}", long_help_message());
138            std::process::exit(0);
139        }
140        SuAction::Version(_) => {
141            eprintln_ignore_io_error!("su-rs {VERSION}");
142            std::process::exit(0);
143        }
144        SuAction::Run(options) => match run(options) {
145            Err(Error::CommandNotFound(c)) => {
146                eprintln_ignore_io_error!("su: {}", Error::CommandNotFound(c));
147                std::process::exit(127);
148            }
149            Err(Error::InvalidCommand(c)) => {
150                eprintln_ignore_io_error!("su: {}", Error::InvalidCommand(c));
151                std::process::exit(126);
152            }
153            Err(error) => {
154                eprintln_ignore_io_error!("su: {error}");
155                std::process::exit(1);
156            }
157            _ => {}
158        },
159    };
160}