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 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 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 pid = context.process.pid;
108
109 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}