From 7d5c1f7ab495448aedc93a27e12e2ce86b8a06c9 Mon Sep 17 00:00:00 2001 From: Laurence Tratt Date: Sat, 19 Oct 2024 09:51:53 +0100 Subject: [PATCH] Add the ability to print out server info. In particular, this allows the HTTPS public key to be printed so that one can verify that the browser is actually connecting to the expected server. For example: ``` $ cargo run info pizauth version 1.0.5: cache directory: /tmp/runtime-ltratt/pizauth config file: /home/ltratt/.config/pizauth.conf server running: HTTP port: 7470 HTTPS port: 24004 HTTPS public key: 04:70:57:B0:9A:B4:1C:F9:CB:2E:33:CB:71:D9:B1:7C:5C:6E:84:8F:25:66:F4:C8:1E:3F:41:F8:34:8D:2A:6A:3F:26:D9:A6:57:27:D0:B0:93:09:5B:50:70:D9:DD:1A:A0:DE:33:04:E1:A8:FB:C6:30:D2:92:B1:26:11:E5:75:B9 ``` Note this requires a bump to the JSON info version. --- src/main.rs | 26 +++++++++++++++++++++++--- src/server/mod.rs | 38 ++++++++++++++++++++++++++++++++++++-- src/server/state.rs | 11 +++++++++-- src/user_sender.rs | 14 ++++++++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) 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" };