diff --git a/src/main.rs b/src/main.rs index a49270d..4dc8b49 100644 --- a/src/main.rs +++ b/src/main.rs @@ -175,6 +175,7 @@ fn main() { if matches.opt_present("h") || !matches.free.is_empty() { usage(); } + let svj = user_sender::server_info(&cache_path).ok(); let progname = progname(); let cache_path = cache_path .to_str() @@ -185,16 +186,35 @@ fn main() { .unwrap_or(""); let ver = env!("CARGO_PKG_VERSION"); if matches.opt_present("j") { - let j = json!({ + let mut j = json!({ "cache_directory": cache_path, "config_file": conf_path, "executed_as": progname, - "info_format_version": 1, + "info_format_version": 2, "pizauth_version": ver }); + let svj = match svj { + Some(x) => json!({ "server_running": true, "server_info": x}), + None => json!({ "server_running": false }), + }; + j.as_object_mut() + .unwrap() + .extend(svj.as_object().unwrap().clone()); println!("{}", j); } else { - println!("{progname} version {ver}:\n cache directory: {cache_path}\n config file: {conf_path}") + println!("{progname} version {ver}:\n cache directory: {cache_path}\n config file: {conf_path}"); + if let Some(svj) = svj { + println!( + "server running:\n HTTP port: {}\n HTTPS port: {}", + svj["http_port"].as_str().unwrap(), + svj["https_port"].as_str().unwrap() + ); + if let Some(x) = svj.get("https_pub_key") { + println!(" HTTPS public key: {}", x.as_str().unwrap()); + } + } else { + println!("server not running"); + } } } "refresh" => { diff --git a/src/server/mod.rs b/src/server/mod.rs index 5e836a0..3c0cfa2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,6 +6,7 @@ mod request_token; mod state; use std::{ + collections::HashMap, error::Error, io::{Read, Write}, os::unix::net::{UnixListener, UnixStream}, @@ -27,6 +28,7 @@ use eventer::{Eventer, TokenEvent}; use notifier::Notifier; use refresher::Refresher; use request_token::request_token; +use serde_json::json; use state::{AccountId, AuthenticatorState, CTGuard, TokenState}; /// Length of the PKCE code verifier in bytes. @@ -83,6 +85,28 @@ fn request(pstate: Arc, mut stream: UnixStream) -> Result<() stream.write_all(&pstate.dump()?)?; return Ok(()); } + "info" if rest.is_empty() => { + let mut m = HashMap::new(); + m.insert( + "http_port", + match pstate.http_port { + Some(x) => x.to_string(), + None => "none".to_string(), + }, + ); + m.insert( + "https_port", + match pstate.https_port { + Some(x) => x.to_string(), + None => "none".to_string(), + }, + ); + if let Some(x) = &pstate.https_pub_key { + m.insert("https_pub_key", x.clone()); + } + stream.write_all(json!(m).to_string().as_bytes())?; + return Ok(()); + } "reload" if rest.is_empty() => { match Config::from_path(&pstate.conf_path) { Ok(new_conf) => { @@ -311,7 +335,7 @@ pub fn server(conf_path: PathBuf, conf: Config, cache_path: &Path) -> Result<(), Some((x, y)) => (Some(x), Some(y)), None => (None, None), }; - let (https_port, https_state, certificate) = match http_server::https_server_setup(&conf)? { + let (https_port, https_state, certified_key) = match http_server::https_server_setup(&conf)? { Some((x, y, z)) => (Some(x), Some(y), Some(z)), None => (None, None, None), }; @@ -321,11 +345,21 @@ pub fn server(conf_path: PathBuf, conf: Config, cache_path: &Path) -> Result<(), let notifier = Arc::new(Notifier::new()?); let refresher = Refresher::new(); + let pub_key_str = certified_key.as_ref().map(|x| { + x.key_pair + .public_key_raw() + .iter() + .map(|x| format!("{x:02X}")) + .collect::>() + .join(":") + }); + let pstate = Arc::new(AuthenticatorState::new( conf_path, conf, http_port, https_port, + pub_key_str, Arc::clone(&eventer), Arc::clone(¬ifier), Arc::clone(&refresher), @@ -334,7 +368,7 @@ pub fn server(conf_path: PathBuf, conf: Config, cache_path: &Path) -> Result<(), if let Some(x) = http_state { http_server::http_server(Arc::clone(&pstate), x)?; } - if let (Some(x), Some(y)) = (https_state, certificate) { + if let (Some(x), Some(y)) = (https_state, certified_key) { http_server::https_server(Arc::clone(&pstate), x, y)?; } eventer.eventer(Arc::clone(&pstate))?; diff --git a/src/server/state.rs b/src/server/state.rs index a25d18e..6955cbb 100644 --- a/src/server/state.rs +++ b/src/server/state.rs @@ -45,10 +45,12 @@ pub struct AuthenticatorState { /// The "global lock" protecting the config and current [TokenState]s. Can only be accessed via /// [AuthenticatorState::ct_lock]. locked_state: Mutex, - /// port of the HTTP server required by OAuth. + /// Port of the HTTP server required by OAuth. pub http_port: Option, - /// port of the HTTPS server required by OAuth. + /// Port of the HTTPS server required by OAuth. pub https_port: Option, + /// If an HTTPS server is running, its raw public key formatted in hex with each byte separated by `:`. + pub https_pub_key: Option, pub eventer: Arc, pub notifier: Arc, pub refresher: Arc, @@ -60,6 +62,7 @@ impl AuthenticatorState { conf: Config, http_port: Option, https_port: Option, + https_pub_key: Option, eventer: Arc, notifier: Arc, refresher: Arc, @@ -69,6 +72,7 @@ impl AuthenticatorState { locked_state: Mutex::new(LockedState::new(conf)), http_port, https_port, + https_pub_key, eventer, notifier, refresher, @@ -594,6 +598,7 @@ mod test { conf, Some(0), Some(0), + Some("".to_string()), eventer, notifier, Refresher::new(), @@ -727,6 +732,7 @@ mod test { conf, Some(0), Some(0), + Some("".to_string()), eventer, notifier, Refresher::new(), @@ -808,6 +814,7 @@ mod test { conf, Some(0), Some(0), + Some("".to_string()), eventer, notifier, Refresher::new(), diff --git a/src/user_sender.rs b/src/user_sender.rs index 51772ac..22d0ab7 100644 --- a/src/user_sender.rs +++ b/src/user_sender.rs @@ -22,6 +22,20 @@ pub fn dump(cache_path: &Path) -> Result, Box> { Ok(buf) } +pub fn server_info(cache_path: &Path) -> Result> { + let sock_path = sock_path(cache_path); + let mut stream = UnixStream::connect(sock_path) + .map_err(|_| "pizauth authenticator not running or not responding")?; + stream + .write_all("info:".as_bytes()) + .map_err(|_| "Socket not writeable")?; + stream.shutdown(Shutdown::Write)?; + + let mut s = String::new(); + stream.read_to_string(&mut s)?; + Ok(serde_json::from_str(&s)?) +} + pub fn refresh(cache_path: &Path, account: &str, with_url: bool) -> Result<(), Box> { let sock_path = sock_path(cache_path); let with_url = if with_url { "withurl" } else { "withouturl" };