@@ -21,25 +21,9 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
let mut client = client()?;
|
||||
let user = uuid_by_one_user_filter(&mut client, &cli.user)
|
||||
.ok_or_else(|| anyhow!("user \"{}\" not found", cli.user))?;
|
||||
let available_methods = client.get_user_auth_methods(GetUserAuthMethodsArgs { user })?;
|
||||
let main_method = available_methods
|
||||
.iter()
|
||||
.find(|x| x.flags.contains(UserAuthMethodFlags::MAIN));
|
||||
let Some(method) = cli.method.or(main_method.map(|x| x.name.clone())) else {
|
||||
return Err(anyhow!("no auth method available"));
|
||||
};
|
||||
if !available_methods.iter().any(|x| x.name == method) {
|
||||
return Err(anyhow!("unregistered auth method {method}"));
|
||||
let ret = crate::util::authenticate(&mut client, user, cli.method, &cli.mode)?;
|
||||
if !ret {
|
||||
std::process::exit(1);
|
||||
}
|
||||
let executable = client.get_auth_method_path(GetAuthMethodPathArgs { name: method })?;
|
||||
let args = AuthCli::Authenticate(AuthenticateArgs {
|
||||
user_uuid: user,
|
||||
mode: cli.mode,
|
||||
})
|
||||
.compose();
|
||||
std::process::Command::new(executable)
|
||||
.args(args)
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+24
-3
@@ -2,6 +2,7 @@ mod authenticate;
|
||||
mod create_group;
|
||||
mod create_user;
|
||||
mod info;
|
||||
mod su;
|
||||
mod update_auth;
|
||||
mod util;
|
||||
|
||||
@@ -27,8 +28,14 @@ pub enum Cli {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
// Invoke non-default utility if required
|
||||
if progname() == "su" {
|
||||
su::main();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// Invoke the default `account` utility
|
||||
let cli = Cli::parse();
|
||||
let result = match cli {
|
||||
Cli::Info(cli) => info::main(cli),
|
||||
Cli::CreateUser(cli) => create_user::main(cli),
|
||||
@@ -36,15 +43,29 @@ fn main() {
|
||||
Cli::Authenticate(cli) => authenticate::main(cli),
|
||||
Cli::UpdateAuth(cli) => update_auth::main(cli),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
eprintln!("error: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn progname() -> String {
|
||||
std::env::args()
|
||||
.next()
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| "account")
|
||||
.split('/')
|
||||
.last()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn has_root_privs() -> bool {
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
|
||||
fn is_suid_mode() -> bool {
|
||||
unsafe { libc::getuid() != libc::geteuid() && libc::geteuid() == 0 }
|
||||
unsafe { libc::getuid() != libc::geteuid() && has_root_privs() }
|
||||
}
|
||||
|
||||
fn client() -> std::io::Result<Client> {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL};
|
||||
|
||||
use crate::client;
|
||||
use std::{ffi::OsString, os::unix::process::CommandExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Cli {
|
||||
login: bool,
|
||||
preserve_environment: bool,
|
||||
fast: bool,
|
||||
user: String,
|
||||
command: Vec<OsString>,
|
||||
}
|
||||
impl Cli {
|
||||
const USAGE: &str = "usage: su [options] [-] [login [args]]";
|
||||
|
||||
fn parse() -> Self {
|
||||
let mut this = Self {
|
||||
login: false,
|
||||
preserve_environment: false,
|
||||
fast: true,
|
||||
user: "root".into(),
|
||||
command: Vec::new(),
|
||||
};
|
||||
|
||||
let mut positional = false;
|
||||
|
||||
for i in std::env::args_os().skip(1) {
|
||||
let bytes = i.as_encoded_bytes();
|
||||
if positional {
|
||||
this.command.push(i);
|
||||
continue;
|
||||
}
|
||||
if bytes == b"--login" {
|
||||
this.login = true;
|
||||
} else if bytes == b"--preserve-environment" {
|
||||
this.preserve_environment = true;
|
||||
} else if bytes == b"--fast" {
|
||||
this.fast = true;
|
||||
} else if bytes.starts_with(b"--") {
|
||||
Self::parse_error(bytes);
|
||||
} else if bytes.starts_with(b"-") {
|
||||
if i == "-" {
|
||||
this.login = true;
|
||||
continue;
|
||||
}
|
||||
for &byte in &bytes[1..] {
|
||||
match byte {
|
||||
b'l' => {
|
||||
this.login = true;
|
||||
}
|
||||
b'm' | b'p' => {
|
||||
this.preserve_environment = true;
|
||||
}
|
||||
b'f' => {
|
||||
this.fast = true;
|
||||
}
|
||||
_ => Self::parse_error(&[byte]),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
debug_assert!(!positional);
|
||||
positional = true;
|
||||
this.user = String::from_utf8_lossy(bytes).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn parse_error(bytes: &[u8]) -> ! {
|
||||
eprintln!("su: illegal option -- {}", String::from_utf8_lossy(bytes));
|
||||
eprintln!("{}", Self::USAGE);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
let cli = Cli::parse();
|
||||
let mut client = match client() {
|
||||
Ok(val) => val,
|
||||
Err(err) => {
|
||||
eprintln!("su: Service error: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let Ok(user_info) = client.get_user_info(GetUserInfoArgs::Username(cli.user.clone())) else {
|
||||
eprintln!("su: user {} does not exist", cli.user);
|
||||
std::process::exit(1);
|
||||
};
|
||||
let auth_result =
|
||||
crate::util::authenticate(&mut client, user_info.uuid, Some("password".into()), "CLI");
|
||||
match auth_result {
|
||||
Ok(true) => (),
|
||||
Ok(false) => std::process::exit(1),
|
||||
Err(err) => {
|
||||
eprintln!("su: cannot authenticate user: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let Some(uid) = user_info.host_uid else {
|
||||
eprintln!("su: user {} is not configured on this host", cli.user);
|
||||
std::process::exit(1);
|
||||
};
|
||||
let Some(shell) = user_info.defaults.get(CLI_LOGIN_SHELL) else {
|
||||
eprintln!("su: user {} has no login shell", cli.user);
|
||||
std::process::exit(1);
|
||||
};
|
||||
let home_dir = user_info.home_directory.unwrap_or_else(|| "/".into());
|
||||
|
||||
if !crate::has_root_privs() {
|
||||
eprintln!("su: the `su` binary is only available with root privileges");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
if !cli.preserve_environment || cli.login {
|
||||
// SAFETY: The program is single-threaded, so no data race is possible here.
|
||||
unsafe {
|
||||
std::env::set_var("HOME", &home_dir);
|
||||
std::env::set_var("USER", &cli.user);
|
||||
std::env::set_var("SHELL", &shell);
|
||||
std::env::set_var("LOGNAME", &cli.user);
|
||||
}
|
||||
}
|
||||
|
||||
let err = std::process::Command::new(shell)
|
||||
.uid(uid)
|
||||
.args(cli.command)
|
||||
.exec();
|
||||
|
||||
eprintln!("su: exec error: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
+32
-1
@@ -1,6 +1,8 @@
|
||||
use anyhow::anyhow;
|
||||
use semios_account::{
|
||||
auth::{AuthCli, AuthenticateArgs, UserAuthMethodFlags},
|
||||
client::Client,
|
||||
protocol::{GetGroupInfoArgs, GetUserInfoArgs},
|
||||
protocol::{GetAuthMethodPathArgs, GetGroupInfoArgs, GetUserAuthMethodsArgs, GetUserInfoArgs},
|
||||
record::HostUid,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
@@ -68,3 +70,32 @@ pub fn uuid_by_one_group_filter(client: &mut Client, filter: &str) -> Option<Uui
|
||||
|
||||
Some(client.get_group_info(args).ok()?.uuid)
|
||||
}
|
||||
|
||||
pub fn authenticate(
|
||||
client: &mut Client,
|
||||
user: Uuid,
|
||||
method: Option<String>,
|
||||
mode: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let available_methods = client.get_user_auth_methods(GetUserAuthMethodsArgs { user })?;
|
||||
let main_method = available_methods
|
||||
.iter()
|
||||
.find(|x| x.flags.contains(UserAuthMethodFlags::MAIN));
|
||||
let Some(method) = method.or(main_method.map(|x| x.name.clone())) else {
|
||||
return Err(anyhow!("no auth method available"));
|
||||
};
|
||||
if !available_methods.iter().any(|x| x.name == method) {
|
||||
return Err(anyhow!("unregistered auth method {method}"));
|
||||
}
|
||||
let executable = client.get_auth_method_path(GetAuthMethodPathArgs { name: method })?;
|
||||
let args = AuthCli::Authenticate(AuthenticateArgs {
|
||||
user_uuid: user,
|
||||
mode: mode.to_string(),
|
||||
})
|
||||
.compose();
|
||||
let wait_status = std::process::Command::new(executable)
|
||||
.args(args)
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
Ok(wait_status.success())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user