From 418305653c117b9fc5875d10dc15f144e2732ce3 Mon Sep 17 00:00:00 2001 From: sisungo Date: Fri, 11 Sep 2026 09:04:08 +0000 Subject: [PATCH] feat: add command login Signed-off-by: sisungo --- bin/account/src/authenticate.rs | 4 - bin/account/src/login.rs | 109 +++++++++++++++++++++++++++ bin/account/src/main.rs | 6 +- bin/account/src/su.rs | 3 +- daemon/accountd/Cargo.toml | 2 +- daemon/accountd/share/auth_method.sb | 1 - daemon/accountd/src/api.rs | 18 ++--- lib/semios_account/src/protocol.rs | 2 +- misc/init.example.json | 21 ++++++ 9 files changed, 147 insertions(+), 19 deletions(-) create mode 100644 bin/account/src/login.rs delete mode 100644 daemon/accountd/share/auth_method.sb create mode 100644 misc/init.example.json diff --git a/bin/account/src/authenticate.rs b/bin/account/src/authenticate.rs index 8a56509..a8abe2e 100644 --- a/bin/account/src/authenticate.rs +++ b/bin/account/src/authenticate.rs @@ -1,10 +1,6 @@ use crate::{client, util::uuid_by_one_user_filter}; use anyhow::anyhow; use clap::Parser; -use semios_account::{ - auth::{AuthCli, AuthenticateArgs, UserAuthMethodFlags}, - protocol::{GetAuthMethodPathArgs, GetUserAuthMethodsArgs}, -}; #[derive(Debug, Clone, Parser)] pub struct Cli { diff --git a/bin/account/src/login.rs b/bin/account/src/login.rs new file mode 100644 index 0000000..6ede217 --- /dev/null +++ b/bin/account/src/login.rs @@ -0,0 +1,109 @@ +use crate::client; +use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL}; +use std::os::unix::process::CommandExt; + +#[derive(Debug)] +struct Cli { + preserve_environment: bool, + user: Option, + host: Option, + force: bool, +} +impl Cli { + const USAGE: &str = "usage: login [-p] [-h host] [-f] [name]"; + + fn parse() -> Self { + let mut args = std::env::args().skip(1); + let mut this = Self { + preserve_environment: false, + user: None, + host: None, + force: false, + }; + + while let Some(i) = args.next() { + if i == "-f" { + this.force = true; + } else if i == "-p" { + this.preserve_environment = true; + } else if i == "-h" { + this.host = Some(args.next().unwrap_or_else(|| Self::parse_error())); + } else if !i.starts_with("-") { + this.user = Some(i); + } else { + Self::parse_error() + } + } + + this + } + + fn parse_error() -> ! { + 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!("login: Service error: {err}"); + std::process::exit(1); + } + }; + + loop { + let user = cli.user.clone().unwrap_or_else(ask_login); + let Ok(user_info) = client.get_user_info(GetUserInfoArgs::Username(user.clone())) else { + eprintln!("\nLogin incorrect"); + 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(_) => { + eprintln!("\nLogin incorrect"); + continue; + } + }; + let Some(uid) = user_info.host_uid else { + eprintln!("\nThis user is not configured to login on this host."); + continue; + }; + let Some(shell) = user_info.defaults.get(CLI_LOGIN_SHELL) else { + eprintln!("\nThis user has no login shell."); + continue; + }; + let home_dir = user_info.home_directory.unwrap_or_else(|| "/".into()); + + if !crate::has_root_privs() { + eprintln!("login: the `login` binary is only available with root privileges"); + continue; + } + + // Set environment variables + if !cli.preserve_environment { + // 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", &user); + std::env::set_var("SHELL", &shell); + std::env::set_var("LOGNAME", &user); + } + } + + let err = std::process::Command::new(shell).uid(uid).exec(); + eprintln!("login: exec error: {err}"); + } +} + +fn ask_login() -> String { + eprint!("login: "); + let mut login = String::with_capacity(128); + _ = std::io::stdin().read_line(&mut login); + login.trim().into() +} diff --git a/bin/account/src/main.rs b/bin/account/src/main.rs index c208415..4905bae 100644 --- a/bin/account/src/main.rs +++ b/bin/account/src/main.rs @@ -2,6 +2,7 @@ mod authenticate; mod create_group; mod create_user; mod info; +mod login; mod su; mod update_auth; mod util; @@ -31,7 +32,10 @@ fn main() { // Invoke non-default utility if required if progname() == "su" { su::main(); - std::process::exit(0); + return; + } else if progname() == "login" { + login::main(); + return; } // Invoke the default `account` utility diff --git a/bin/account/src/su.rs b/bin/account/src/su.rs index b0e3469..b19ef68 100644 --- a/bin/account/src/su.rs +++ b/bin/account/src/su.rs @@ -1,6 +1,5 @@ -use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL}; - use crate::client; +use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL}; use std::{ffi::OsString, os::unix::process::CommandExt}; #[derive(Debug)] diff --git a/daemon/accountd/Cargo.toml b/daemon/accountd/Cargo.toml index bcb20d9..e770521 100644 --- a/daemon/accountd/Cargo.toml +++ b/daemon/accountd/Cargo.toml @@ -12,7 +12,7 @@ anyhow = "1" cfg-if = "1" bitflags = "2" clap = { workspace = true } -rusqlite = "0.39" +rusqlite = { version = "0.39", features = ["bundled"] } rustc-hash = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/daemon/accountd/share/auth_method.sb b/daemon/accountd/share/auth_method.sb deleted file mode 100644 index 8875ab4..0000000 --- a/daemon/accountd/share/auth_method.sb +++ /dev/null @@ -1 +0,0 @@ -// Sandbox rules for running an authentication method. diff --git a/daemon/accountd/src/api.rs b/daemon/accountd/src/api.rs index 9bb7ae2..c6cd502 100644 --- a/daemon/accountd/src/api.rs +++ b/daemon/accountd/src/api.rs @@ -154,7 +154,7 @@ impl Session { async fn list_user( state: Arc, - caller: Caller, + _caller: Caller, args: ListUserArgs, ) -> Result, Error> { Ok(state.local_db.list_user(args.start, args.len)) @@ -162,7 +162,7 @@ async fn list_user( async fn list_group( state: Arc, - caller: Caller, + _caller: Caller, args: ListGroupArgs, ) -> Result, Error> { Ok(state.local_db.list_group(args.start, args.len)) @@ -170,7 +170,7 @@ async fn list_group( async fn get_user_info( state: Arc, - caller: Caller, + _caller: Caller, args: GetUserInfoArgs, ) -> Result { let uuid = match args { @@ -189,7 +189,7 @@ async fn get_user_info( async fn get_group_info( state: Arc, - caller: Caller, + _caller: Caller, args: GetGroupInfoArgs, ) -> Result { let uuid = match args { @@ -279,7 +279,7 @@ async fn create_group( async fn remove_user( state: Arc, caller: Caller, - args: RemoveUserArgs, + _args: RemoveUserArgs, ) -> Result<(), Error> { require_secure_tags(&state, caller, &[SecureTag::RemoveUser]).await?; todo!(); @@ -321,7 +321,7 @@ async fn set_secret( async fn get_user_auth_methods( state: Arc, - caller: Caller, + _caller: Caller, args: GetUserAuthMethodsArgs, ) -> Result, Error> { require_secure_tags(&state, Caller::User(args.user), &[SecureTag::Login]).await?; @@ -330,7 +330,7 @@ async fn get_user_auth_methods( async fn user_add_auth_method( state: Arc, - caller: Caller, + _caller: Caller, args: UserAddAuthMethodArgs, ) -> Result<(), Error> { require_secure_tags(&state, Caller::User(args.user), &[SecureTag::Login]).await?; @@ -355,7 +355,7 @@ async fn user_add_auth_method( async fn get_auth_method_path( state: Arc, - caller: Caller, + _caller: Caller, args: GetAuthMethodPathArgs, ) -> Result { Ok(state @@ -368,7 +368,7 @@ async fn get_auth_method_path( // ==- Helpers -== async fn require_same_user( - state: &AppState, + _state: &AppState, caller: Caller, requested_user: Uuid, ) -> Result<(), Error> { diff --git a/lib/semios_account/src/protocol.rs b/lib/semios_account/src/protocol.rs index 9b62453..379a933 100644 --- a/lib/semios_account/src/protocol.rs +++ b/lib/semios_account/src/protocol.rs @@ -1,6 +1,6 @@ use crate::{ error::Error, - record::{GroupInfo, HostGid, HostUid, SecureTag}, + record::{HostGid, HostUid, SecureTag}, secret::{MasterKeyProvision, Secret}, }; use cfg_if::cfg_if; diff --git a/misc/init.example.json b/misc/init.example.json new file mode 100644 index 0000000..9032671 --- /dev/null +++ b/misc/init.example.json @@ -0,0 +1,21 @@ +[ + { + "on_failure": "terminate", + "request": { + "method": "CreateUser", + "params": { + "username": "root", + "fullname": "System Administrator", + "host_uid": { "manual": 0 }, + "description": "UNIX system administrator", + "secure_tags": ["Login", "CreateUser", "RemoveUser", "ReadSecret", "WriteSecret"], + "extra_records": {}, + "defaults": { + "CliLoginShell": "/bin/sh" + }, + "home_directory": "/home/root", + "groups": [] + } + } + } +]