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