@@ -21,25 +21,9 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
|||||||
let mut client = client()?;
|
let mut client = client()?;
|
||||||
let user = uuid_by_one_user_filter(&mut client, &cli.user)
|
let user = uuid_by_one_user_filter(&mut client, &cli.user)
|
||||||
.ok_or_else(|| anyhow!("user \"{}\" not found", cli.user))?;
|
.ok_or_else(|| anyhow!("user \"{}\" not found", cli.user))?;
|
||||||
let available_methods = client.get_user_auth_methods(GetUserAuthMethodsArgs { user })?;
|
let ret = crate::util::authenticate(&mut client, user, cli.method, &cli.mode)?;
|
||||||
let main_method = available_methods
|
if !ret {
|
||||||
.iter()
|
std::process::exit(1);
|
||||||
.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 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-3
@@ -2,6 +2,7 @@ mod authenticate;
|
|||||||
mod create_group;
|
mod create_group;
|
||||||
mod create_user;
|
mod create_user;
|
||||||
mod info;
|
mod info;
|
||||||
|
mod su;
|
||||||
mod update_auth;
|
mod update_auth;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
@@ -27,8 +28,14 @@ pub enum Cli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
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 {
|
let result = match cli {
|
||||||
Cli::Info(cli) => info::main(cli),
|
Cli::Info(cli) => info::main(cli),
|
||||||
Cli::CreateUser(cli) => create_user::main(cli),
|
Cli::CreateUser(cli) => create_user::main(cli),
|
||||||
@@ -36,15 +43,29 @@ fn main() {
|
|||||||
Cli::Authenticate(cli) => authenticate::main(cli),
|
Cli::Authenticate(cli) => authenticate::main(cli),
|
||||||
Cli::UpdateAuth(cli) => update_auth::main(cli),
|
Cli::UpdateAuth(cli) => update_auth::main(cli),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = result {
|
if let Err(err) = result {
|
||||||
eprintln!("error: {err}");
|
eprintln!("error: {err}");
|
||||||
std::process::exit(1);
|
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 {
|
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> {
|
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::{
|
use semios_account::{
|
||||||
|
auth::{AuthCli, AuthenticateArgs, UserAuthMethodFlags},
|
||||||
client::Client,
|
client::Client,
|
||||||
protocol::{GetGroupInfoArgs, GetUserInfoArgs},
|
protocol::{GetAuthMethodPathArgs, GetGroupInfoArgs, GetUserAuthMethodsArgs, GetUserInfoArgs},
|
||||||
record::HostUid,
|
record::HostUid,
|
||||||
};
|
};
|
||||||
use std::fmt::Display;
|
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)
|
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