Skip to main content

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;
8use crate::system::Process;
9
10use std::{env, process};
11
12use cli::SuAction;
13use context::SuContext;
14use help::{long_help_message, USAGE_MSG};
15
16use self::cli::SuRunOptions;
17
18mod cli;
19mod context;
20mod help;
21
22const DEFAULT_USER: &str = "root";
23const VERSION: &str = env!("CARGO_PKG_VERSION");
24
25fn authenticate(requesting_user: &str, user: &str, login: bool) -> Result<PamContext, Error> {
26    // FIXME make it configurable by the packager
27    let context = if login && cfg!(target_os = "linux") {
28        "su-l"
29    } else {
30        "su"
31    };
32    let use_stdin = true;
33    let mut pam = PamContext::new_cli(
34        "su",
35        context,
36        false,
37        use_stdin,
38        false,
39        false,
40        false,
41        None,
42        Some(user),
43    )?;
44    pam.set_requesting_user(requesting_user)?;
45
46    // attempt to set the TTY this session is communicating on
47    if let Ok(pam_tty) = current_tty_name() {
48        pam.set_tty(&pam_tty)?;
49    }
50
51    pam.mark_silent(true);
52    pam.mark_allow_null_auth_token(false);
53
54    let mut max_tries = 3;
55    let mut current_try = 0;
56
57    loop {
58        current_try += 1;
59        match pam.authenticate(user) {
60            // there was no error, so authentication succeeded
61            Ok(_) => break,
62
63            // maxtries was reached, pam does not allow any more tries
64            Err(PamError::Pam(PamErrorType::MaxTries)) => {
65                return Err(Error::MaxAuthAttempts(current_try));
66            }
67
68            // there was an authentication error, we can retry
69            Err(PamError::Pam(PamErrorType::AuthError)) => {
70                max_tries -= 1;
71                if max_tries == 0 {
72                    return Err(Error::MaxAuthAttempts(current_try));
73                } else {
74                    user_warn!("Authentication failed, try again.");
75                }
76            }
77
78            // there was another pam error, return the error
79            Err(e) => {
80                return Err(e.into());
81            }
82        }
83    }
84
85    pam.validate_account_or_change_auth_token()?;
86    pam.open_session()?;
87
88    Ok(pam)
89}
90
91fn run(options: SuRunOptions) -> Result<(), Error> {
92    // lookup user and build context object
93    let context = SuContext::from_env(options)?;
94
95    // authenticate the target user
96    let mut pam: PamContext = authenticate(
97        &context.requesting_user.name,
98        &context.user.name,
99        context.options.login,
100    )?;
101
102    // su in all cases uses PAM (pam_getenvlist(3)) to do the
103    // final environment modification. Command-line options such as
104    // --login and --preserve-environment affect the environment before
105    // it is modified by PAM.
106    let mut environment = context.environment.clone();
107    environment.extend(pam.env()?);
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(Process::process_id(), 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}