Skip to main content

sudo_rs/su/
mod.rs

1#![forbid(unsafe_code)]
2
3use crate::common::error::Error;
4use crate::log::user_warn;
5use crate::pam::{PamContext, PamError, PamErrorType};
6use crate::system::term::current_tty_name;
7
8use std::env;
9
10use cli::SuAction;
11use context::SuContext;
12use help::{USAGE_MSG, long_help_message};
13
14use self::cli::SuRunOptions;
15
16mod cli;
17mod context;
18mod help;
19
20const DEFAULT_USER: &str = "root";
21const VERSION: &str = env!("CARGO_PKG_VERSION");
22
23fn authenticate(requesting_user: &str, user: &str, login: bool) -> Result<PamContext, Error> {
24    // FIXME make it configurable by the packager
25    let context = if login && cfg!(target_os = "linux") {
26        "su-l"
27    } else {
28        "su"
29    };
30    let use_stdin = true;
31    let mut pam = PamContext::new_cli(
32        "su",
33        context,
34        false,
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    // run command and return corresponding exit code
108    let command_exit_reason = crate::exec::run_command(context.as_run_options(), environment);
109
110    pam.close_session();
111
112    match command_exit_reason?.exit_process()? {}
113}
114
115pub fn main() {
116    crate::log::SudoLogger::new("su: ").into_global_logger();
117
118    let action = match SuAction::from_env() {
119        Ok(action) => action,
120        Err(error) => {
121            println_ignore_io_error!("su: {error}\n{USAGE_MSG}");
122            std::process::exit(1);
123        }
124    };
125
126    match action {
127        SuAction::Help(_) => {
128            println_ignore_io_error!("{}", long_help_message());
129            std::process::exit(0);
130        }
131        SuAction::Version(_) => {
132            eprintln_ignore_io_error!("su-rs {VERSION}");
133            std::process::exit(0);
134        }
135        SuAction::Run(options) => match run(options) {
136            Err(Error::CommandNotFound(c)) => {
137                eprintln_ignore_io_error!("su: {}", Error::CommandNotFound(c));
138                std::process::exit(127);
139            }
140            Err(Error::InvalidCommand(c)) => {
141                eprintln_ignore_io_error!("su: {}", Error::InvalidCommand(c));
142                std::process::exit(126);
143            }
144            Err(error) => {
145                eprintln_ignore_io_error!("su: {error}");
146                std::process::exit(1);
147            }
148            _ => {}
149        },
150    };
151}