diff --git a/src/bin/proxmox-backup-client.rs b/src/bin/proxmox-backup-client.rs index 30b538ec..4b1da740 100644 --- a/src/bin/proxmox-backup-client.rs +++ b/src/bin/proxmox-backup-client.rs @@ -167,6 +167,7 @@ fn connect(server: &str, userid: &str) -> Result { let options = HttpClientOptions::new() .interactive(true) + .fingerprint_cache(true) .ticket_cache(true); HttpClient::new(server, userid, options) @@ -1472,10 +1473,9 @@ async fn status(param: Value) -> Result { // like get, but simply ignore errors and return Null instead async fn try_get(repo: &BackupRepository, url: &str) -> Value { - let options = HttpClientOptions::new() - .verify_cert(false) // fixme: set verify to true, but howto handle fingerprint ?? .interactive(false) + .fingerprint_cache(true) .ticket_cache(true); let client = match HttpClient::new(repo.host(), repo.user(), options) { diff --git a/src/client/http_client.rs b/src/client/http_client.rs index 3d5c2de7..1ea1f815 100644 --- a/src/client/http_client.rs +++ b/src/client/http_client.rs @@ -35,6 +35,7 @@ pub struct HttpClientOptions { fingerprint: Option, interactive: bool, ticket_cache: bool, + fingerprint_cache: bool, verify_cert: bool, } @@ -46,6 +47,7 @@ impl HttpClientOptions { fingerprint: None, interactive: false, ticket_cache: false, + fingerprint_cache: false, verify_cert: true, } } @@ -70,6 +72,11 @@ impl HttpClientOptions { self } + pub fn fingerprint_cache(mut self, fingerprint_cache: bool) -> Self { + self.fingerprint_cache = fingerprint_cache; + self + } + pub fn verify_cert(mut self, verify_cert: bool) -> Self { self.verify_cert = verify_cert; self @@ -106,6 +113,69 @@ pub fn delete_ticket_info(server: &str, username: &str) -> Result<(), Error> { Ok(()) } +fn store_fingerprint(server: &str, fingerprint: &str) -> Result<(), Error> { + + let base = BaseDirectories::with_prefix("proxmox-backup")?; + + // usually ~/.config/proxmox-backup/fingerprints + let path = base.place_config_file("fingerprints")?; + + let raw = match std::fs::read_to_string(&path) { + Ok(v) => v, + Err(err) => { + if err.kind() == std::io::ErrorKind::NotFound { + String::new() + } else { + bail!("unable to read fingerprints from {:?} - {}", path, err); + } + } + }; + + let mut result = String::new(); + + raw.split('\n').for_each(|line| { + let items: Vec = line.split_whitespace().map(String::from).collect(); + if items.len() == 2 { + if &items[0] == server { + // found, add later with new fingerprint + } else { + result.push_str(line); + result.push('\n'); + } + } + }); + + result.push_str(server); + result.push(' '); + result.push_str(fingerprint); + result.push('\n'); + + replace_file(path, result.as_bytes(), CreateOptions::new())?; + + Ok(()) +} + +fn load_fingerprint(server: &str) -> Option { + + let base = BaseDirectories::with_prefix("proxmox-backup").ok()?; + + // usually ~/.config/proxmox-backup/fingerprints + let path = base.place_config_file("fingerprints").ok()?; + + let raw = std::fs::read_to_string(&path).ok()?; + + for line in raw.split('\n') { + let items: Vec = line.split_whitespace().map(String::from).collect(); + if items.len() == 2 { + if &items[0] == server { + return Some(items[1].clone()); + } + } + } + + None +} + fn store_ticket_info(server: &str, username: &str, ticket: &str, token: &str) -> Result<(), Error> { let base = BaseDirectories::with_prefix("proxmox-backup")?; @@ -169,11 +239,18 @@ 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); + } + let client = Self::build_client( - options.fingerprint.clone(), + server.to_string(), + fingerprint, options.interactive, verified_fingerprint.clone(), options.verify_cert, + options.fingerprint_cache, ); let password = options.password.take(); @@ -243,9 +320,11 @@ 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; } @@ -285,7 +364,11 @@ impl HttpClient { match std::io::stdin().read_exact(&mut buf) { Ok(()) => { if buf[0] == b'y' || buf[0] == b'Y' { - println!("TRUST {}", fp_string); + if fingerprint_cache { + if let Err(err) = store_fingerprint(&server, &fp_string) { + eprintln!("{}", err); + } + } *verified_fingerprint.lock().unwrap() = Some(fp_string); return true; } else if buf[0] == b'n' || buf[0] == b'N' { @@ -302,17 +385,21 @@ impl HttpClient { } 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, fingerprint.clone(), interactive, verified_fingerprint.clone()) + 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);