use crate::common::error::Error;
use crate::exec::{ExitReason, RunOptions};
use crate::log::user_warn;
use crate::pam::{CLIConverser, PamContext, PamError, PamErrorType};
use std::{env, process};
use cli::{SuAction, SuOptions};
use context::SuContext;
mod cli;
mod context;
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn authenticate(user: &str, login: bool) -> Result<PamContext<CLIConverser>, Error> {
let context = if login { "su-l" } else { "su" };
let use_stdin = true;
let mut pam = PamContext::builder_cli("su", use_stdin, Default::default())
.target_user(user)
.service_name(context)
.build()?;
pam.mark_silent(true);
pam.mark_allow_null_auth_token(false);
pam.set_user(user)?;
let mut max_tries = 3;
let mut current_try = 0;
loop {
current_try += 1;
match pam.authenticate() {
Ok(_) => break,
Err(PamError::Pam(PamErrorType::MaxTries, _)) => {
return Err(Error::MaxAuthAttempts(current_try));
}
Err(PamError::Pam(PamErrorType::AuthError, _)) => {
max_tries -= 1;
if max_tries == 0 {
return Err(Error::MaxAuthAttempts(current_try));
} else {
user_warn!("Authentication failed, try again.");
}
}
Err(e) => {
return Err(e.into());
}
}
}
pam.validate_account_or_change_auth_token()?;
pam.open_session()?;
Ok(pam)
}
fn run(options: SuOptions) -> Result<(), Error> {
let context = SuContext::from_env(options)?;
let mut pam = authenticate(&context.user().name, context.is_login())?;
let environment = context.environment.clone();
let pid = context.process.pid;
let (reason, emulate_default_handler) = crate::exec::run_command(context, environment)?;
let _ = pam.close_session();
emulate_default_handler();
match reason {
ExitReason::Code(code) => process::exit(code),
ExitReason::Signal(signal) => {
crate::system::kill(pid, signal)?;
}
}
Ok(())
}
pub fn main() {
let su_options = SuOptions::from_env().unwrap();
match su_options.action {
SuAction::Help => {
println!("Usage: su [options] [-] [<user> [<argument>...]]");
std::process::exit(0);
}
SuAction::Version => {
eprintln!("su-rs {VERSION}");
std::process::exit(0);
}
SuAction::Run => {
if let Err(error) = run(su_options) {
eprintln!("{error}");
std::process::exit(1);
}
}
};
}