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 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 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 Ok(_) => break,
60
61 Err(PamError::Pam(PamErrorType::MaxTries)) => {
63 return Err(Error::MaxAuthAttempts(current_try));
64 }
65
66 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 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 let context = SuContext::from_env(options)?;
92
93 let mut pam: PamContext = authenticate(
95 &context.requesting_user.name,
96 &context.user.name,
97 context.options.login,
98 )?;
99
100 let mut environment = context.environment.clone();
105 environment.extend(pam.env()?);
106
107 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}