diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 4b1da740..aa08ff06 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -166,6 +166,7 @@ fn complete_repository(_arg: &str, _param: &HashMap) -> Vec Result { let options = HttpClientOptions::new() + .prefix(Some("proxmox-backup".to_string())) .interactive(true) .fingerprint_cache(true) .ticket_cache(true); @@ -536,7 +537,7 @@ fn api_logout(param: Value) -> Result { let repo = extract_repository_from_value(¶m)?; - delete_ticket_info(repo.host(), repo.user())?; + delete_ticket_info("proxmox-backup", repo.host(), repo.user())?; Ok(Value::Null) } @@ -1474,6 +1475,7 @@ async fn status(param: Value) -> Result { async fn try_get(repo: &BackupRepository, url: &str) -> Value { let options = HttpClientOptions::new() + .prefix(Some("proxmox-backup".to_string())) .interactive(false) .fingerprint_cache(true) .ticket_cache(true); diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index 5a28bdf5..271e9ae9 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -35,6 +35,7 @@ fn connect() -> Result { let uid = nix::unistd::Uid::current(); let mut options = HttpClientOptions::new() + .prefix(Some("proxmox-backup".to_string())) .verify_cert(false); // not required for connection to localhost let client = if uid.is_root() { diff --git a/src/client/http_client.rs b/src/client/http_client.rs index 1ea1f815..af14d41f 100644 --- a/src/client/http_client.rs +++ b/src/client/http_client.rs @@ -31,6 +31,7 @@ pub struct AuthInfo { } pub struct HttpClientOptions { + prefix: Option, password: Option, fingerprint: Option, interactive: bool, @@ -43,6 +44,7 @@ impl HttpClientOptions { pub fn new() -> Self { Self { + prefix: None, password: None, fingerprint: None, interactive: false, @@ -52,6 +54,11 @@ impl HttpClientOptions { } } + pub fn prefix(mut self, prefix: Option) -> Self { + self.prefix = prefix; + self + } + pub fn password(mut self, password: Option) -> Self { self.password = password; self @@ -93,9 +100,9 @@ pub struct HttpClient { } /// Delete stored ticket data (logout) -pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> { +pub fn delete_ticket_info(prefix: &str, server: &str, username: &str) -> Result<(), Error> { - let base = BaseDirectories::with_prefix("proxmox-backup")?; + let base = BaseDirectories::with_prefix(prefix)?; // usually /run/user//... let path = base.place_runtime_file("tickets")?; @@ -113,11 +120,11 @@ pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> { Ok(()) } -fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> { +fn store_fingerprint(prefix: &str, server: &str, fingerprint: &str) -> Result<(), Error> { - let base = BaseDirectories::with_prefix("proxmox-backup")?; + let base = BaseDirectories::with_prefix(prefix)?; - // usually ~/.config/proxmox-backup/fingerprints + // usually ~/.config//fingerprints let path = base.place_config_file("fingerprints")?; let raw = match std::fs::read_to_string(&path) { @@ -155,11 +162,11 @@ fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> { Ok(()) } -fn load_fingerprint(server: &str) -> Option { +fn load_fingerprint(prefix: &str, server: &str) -> Option { - let base = BaseDirectories::with_prefix("proxmox-backup").ok()?; + let base = BaseDirectories::with_prefix(prefix).ok()?; - // usually ~/.config/proxmox-backup/fingerprints + // usually ~/.config//fingerprints let path = base.place_config_file("fingerprints").ok()?; let raw = std::fs::read_to_string(&path).ok()?; @@ -176,9 +183,9 @@ fn load_fingerprint(server: &str) -> Option { None } -fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> { +fn store_ticket_info(prefix: &str, server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> { - let base = BaseDirectories::with_prefix("proxmox-backup")?; + let base = BaseDirectories::with_prefix(prefix)?; // usually /run/user//... let path = base.place_runtime_file("tickets")?; @@ -212,8 +219,8 @@ fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Ok(()) } -fn load_ticket_info(server: &str, username: &str) -> Option<(String, String)> { - let base = BaseDirectories::with_prefix("proxmox-backup").ok()?; +fn load_ticket_info(prefix: &str, server: &str, username: &str) -> Option<(String, String)> { + let base = BaseDirectories::with_prefix(prefix).ok()?; // usually /run/user//... let path = base.place_runtime_file("tickets").ok()?; @@ -240,27 +247,58 @@ impl HttpClient { let verified_fingerprint = Arc::new(Mutex::new(None)); let mut fingerprint = options.fingerprint.take(); - if options.fingerprint_cache && fingerprint.is_none() { - fingerprint = load_fingerprint(server); + if options.fingerprint_cache && fingerprint.is_none() && options.prefix.is_some() { + fingerprint = load_fingerprint(options.prefix.as_ref().unwrap(), server); } - let client = Self::build_client( - server.to_string(), - fingerprint, - options.interactive, - verified_fingerprint.clone(), - options.verify_cert, - options.fingerprint_cache, - ); + let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap(); + + if options.verify_cert { + let server = server.to_string(); + let verified_fingerprint = verified_fingerprint.clone(); + let interactive = options.interactive; + let fingerprint_cache = options.fingerprint_cache; + let prefix = options.prefix.clone(); + ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| { + let (valid, fingerprint) = Self::verify_callback(valid, ctx, fingerprint.clone(), interactive); + if valid { + if let Some(fingerprint) = fingerprint { + if fingerprint_cache && prefix.is_some() { + if let Err(err) = store_fingerprint( + prefix.as_ref().unwrap(), &server, &fingerprint) { + eprintln!("{}", err); + } + } + *verified_fingerprint.lock().unwrap() = Some(fingerprint); + } + } + valid + }); + } else { + ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); + } + + let mut httpc = hyper::client::HttpConnector::new(); + httpc.set_nodelay(true); // important for h2 download performance! + httpc.set_recv_buffer_size(Some(1024*1024)); //important for h2 download performance! + httpc.enforce_http(false); // we want https... + + let https = HttpsConnector::with_connector(httpc, ssl_connector_builder.build()); + + let client = Client::builder() + //.http2_initial_stream_window_size( (1 << 31) - 2) + //.http2_initial_connection_window_size( (1 << 31) - 2) + .build::<_, Body>(https); let password = options.password.take(); + let use_ticket_cache = options.ticket_cache && options.prefix.is_some(); let password = if let Some(password) = password { password } else { let mut ticket_info = None; - if options.ticket_cache { - ticket_info = load_ticket_info(server, username); + if use_ticket_cache { + ticket_info = load_ticket_info(options.prefix.as_ref().unwrap(), server, username); } if let Some((ticket, _token)) = ticket_info { ticket @@ -274,8 +312,18 @@ impl HttpClient { server.to_owned(), username.to_owned(), password, - options.ticket_cache, - ); + ).map_ok({ + let server = server.to_string(); + let prefix = options.prefix.clone(); + + move |auth| { + if use_ticket_cache & &prefix.is_some() { + let _ = store_ticket_info(prefix.as_ref().unwrap(), &server, &auth.username, &auth.ticket, &auth.token); + } + + auth + } + }); Ok(Self { client, @@ -320,25 +368,22 @@ impl HttpClient { fn verify_callback( valid: bool, ctx: &mut X509StoreContextRef, - server: String, expected_fingerprint: Option, interactive: bool, - verified_fingerprint: Arc>>, - fingerprint_cache: bool, - ) -> bool { - if valid { return true; } + ) -> (bool, Option) { + if valid { return (true, None); } let cert = match ctx.current_cert() { Some(cert) => cert, - None => return false, + None => return (false, None), }; let depth = ctx.error_depth(); - if depth != 0 { return false; } + if depth != 0 { return (false, None); } let fp = match cert.digest(openssl::hash::MessageDigest::sha256()) { Ok(fp) => fp, - Err(_) => return false, // should not happen + Err(_) => return (false, None), // should not happen }; let fp_string = proxmox::tools::digest_to_hex(&fp); let fp_string = fp_string.as_bytes().chunks(2).map(|v| std::str::from_utf8(v).unwrap()) @@ -346,10 +391,9 @@ impl HttpClient { if let Some(expected_fingerprint) = expected_fingerprint { if expected_fingerprint == fp_string { - *verified_fingerprint.lock().unwrap() = Some(fp_string); - return true; + return (true, Some(fp_string)); } else { - return false; + return (false, None); } } @@ -364,58 +408,18 @@ impl HttpClient { match std::io::stdin().read_exact(&mut buf) { Ok(()) => { if buf[0] == b'y' || buf[0] == b'Y' { - if fingerprint_cache { - if let Err(err) = store_fingerprint(&server, &fp_string) { - eprintln!("{}", err); - } - } - *verified_fingerprint.lock().unwrap() = Some(fp_string); - return true; + return (true, Some(fp_string)); } else if buf[0] == b'n' || buf[0] == b'N' { - return false; + return (false, None); } } Err(_) => { - return false; + return (false, None); } } } } - false - } - - fn build_client( - server: String, - fingerprint: Option, - interactive: bool, - verified_fingerprint: Arc>>, - verify_cert: bool, - fingerprint_cache: bool, - ) -> Client { - - let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap(); - - if verify_cert { - ssl_connector_builder.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, move |valid, ctx| { - Self::verify_callback( - valid, ctx, server.clone(), fingerprint.clone(), interactive, - verified_fingerprint.clone(), fingerprint_cache) - }); - } else { - ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); - } - - let mut httpc = hyper::client::HttpConnector::new(); - httpc.set_nodelay(true); // important for h2 download performance! - httpc.set_recv_buffer_size(Some(1024*1024)); //important for h2 download performance! - httpc.enforce_http(false); // we want https... - - let https = HttpsConnector::with_connector(httpc, ssl_connector_builder.build()); - - Client::builder() - //.http2_initial_stream_window_size( (1 << 31) - 2) - //.http2_initial_connection_window_size( (1 << 31) - 2) - .build::<_, Body>(https) + (false, None) } pub async fn request(&self, mut req: Request) -> Result { @@ -576,7 +580,6 @@ impl HttpClient { server: String, username: String, password: String, - use_ticket_cache: bool, ) -> Result { let data = json!({ "username": username, "password": password }); let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap(); @@ -587,10 +590,6 @@ impl HttpClient { token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(), }; - if use_ticket_cache { - let _ = store_ticket_info(&server, &auth.username, &auth.ticket, &auth.token); - } - Ok(auth) }