src/client/http_client.rs: new struct HttpClientOptions
This commit is contained in:
		| @ -256,6 +256,7 @@ pub async fn pull_group( | ||||
|     list.sort_unstable_by(|a, b| a.backup_time.cmp(&b.backup_time)); | ||||
|  | ||||
|     let auth_info = client.login().await?; | ||||
|     let fingerprint = client.fingerprint(); | ||||
|  | ||||
|     let last_sync = tgt_store.last_successful_backup(group)?; | ||||
|  | ||||
| @ -269,11 +270,11 @@ pub async fn pull_group( | ||||
|             if last_sync_time > backup_time { continue; } | ||||
|         } | ||||
|  | ||||
|         let new_client = HttpClient::new( | ||||
|             src_repo.host(), | ||||
|             src_repo.user(), | ||||
|             Some(auth_info.ticket.clone()) | ||||
|         )?; | ||||
|         let options = HttpClientOptions::new() | ||||
|             .password(Some(auth_info.ticket.clone())) | ||||
|             .fingerprint(fingerprint.clone()); | ||||
|  | ||||
|         let new_client = HttpClient::new(src_repo.host(), src_repo.user(), options)?; | ||||
|  | ||||
|         let reader = BackupReader::start( | ||||
|             new_client, | ||||
| @ -406,7 +407,11 @@ async fn pull ( | ||||
|     let (remote_config, _digest) = remote::config()?; | ||||
|     let remote: remote::Remote = remote_config.lookup("remote", &remote)?; | ||||
|  | ||||
|     let client = HttpClient::new(&remote.host, &remote.userid, Some(remote.password.clone()))?; | ||||
|     let options = HttpClientOptions::new() | ||||
|         .password(Some(remote.password.clone())) | ||||
|         .fingerprint(remote.fingerprint.clone()); | ||||
|  | ||||
|     let client = HttpClient::new(&remote.host, &remote.userid, options)?; | ||||
|     let _auth_info = client.login() // make sure we can auth | ||||
|         .await | ||||
|         .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?; | ||||
|  | ||||
| @ -4,7 +4,7 @@ use failure::*; | ||||
|  | ||||
| use chrono::{DateTime, Utc}; | ||||
|  | ||||
| use proxmox_backup::client::{HttpClient, BackupReader}; | ||||
| use proxmox_backup::client::{HttpClient, HttpClientOptions, BackupReader}; | ||||
|  | ||||
| pub struct DummyWriter { | ||||
|     bytes: usize, | ||||
| @ -29,7 +29,11 @@ async fn run() -> Result<(), Error> { | ||||
|  | ||||
|     let username = "root@pam"; | ||||
|  | ||||
|     let client = HttpClient::new(host, username, None)?; | ||||
|     let options = HttpClientOptions::new() | ||||
|         .interactive(true) | ||||
|         .ticket_cache(true); | ||||
|  | ||||
|     let client = HttpClient::new(host, username, options)?; | ||||
|  | ||||
|     let backup_time = "2019-06-28T10:49:48Z".parse::<DateTime<Utc>>()?; | ||||
|  | ||||
|  | ||||
| @ -163,6 +163,15 @@ fn complete_repository(_arg: &str, _param: &HashMap<String, String>) -> Vec<Stri | ||||
|     result | ||||
| } | ||||
|  | ||||
| fn connect(server: &str, userid: &str) -> Result<HttpClient, Error> { | ||||
|  | ||||
|     let options = HttpClientOptions::new() | ||||
|         .interactive(true) | ||||
|         .ticket_cache(true); | ||||
|  | ||||
|     HttpClient::new(server, userid, options) | ||||
| } | ||||
|  | ||||
| async fn view_task_result( | ||||
|     client: HttpClient, | ||||
|     result: Value, | ||||
| @ -317,7 +326,7 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/groups", repo.store()); | ||||
|  | ||||
| @ -411,7 +420,7 @@ async fn list_snapshots(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let group = if let Some(path) = param["group"].as_str() { | ||||
|         Some(BackupGroup::parse(path)?) | ||||
| @ -473,7 +482,7 @@ async fn forget_snapshots(param: Value) -> Result<Value, Error> { | ||||
|     let path = tools::required_string_param(¶m, "snapshot")?; | ||||
|     let snapshot = BackupDir::parse(path)?; | ||||
|  | ||||
|     let mut client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let mut client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store()); | ||||
|  | ||||
| @ -503,7 +512,7 @@ async fn api_login(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|     client.login().await?; | ||||
|  | ||||
|     record_repository(&repo); | ||||
| @ -563,7 +572,7 @@ async fn dump_catalog(param: Value) -> Result<Value, Error> { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let client = BackupReader::start( | ||||
|         client, | ||||
| @ -633,7 +642,7 @@ async fn list_snapshot_files(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/files", repo.store()); | ||||
|  | ||||
| @ -682,7 +691,7 @@ async fn start_garbage_collection(param: Value) -> Result<Value, Error> { | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | ||||
|  | ||||
|     let mut client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let mut client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/gc", repo.store()); | ||||
|  | ||||
| @ -903,7 +912,7 @@ async fn create_backup( | ||||
|  | ||||
|     let backup_time = Utc.timestamp(backup_time_opt.unwrap_or_else(|| Utc::now().timestamp()), 0); | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|     record_repository(&repo); | ||||
|  | ||||
|     println!("Starting backup: {}/{}/{}", backup_type, backup_id, BackupDir::backup_time_to_string(backup_time)); | ||||
| @ -1161,7 +1170,7 @@ async fn restore(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let archive_name = tools::required_string_param(¶m, "archive-name")?; | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     record_repository(&repo); | ||||
|  | ||||
| @ -1328,7 +1337,7 @@ async fn upload_log(param: Value) -> Result<Value, Error> { | ||||
|     let snapshot = tools::required_string_param(¶m, "snapshot")?; | ||||
|     let snapshot = BackupDir::parse(snapshot)?; | ||||
|  | ||||
|     let mut client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let mut client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let keyfile = param["keyfile"].as_str().map(PathBuf::from); | ||||
|  | ||||
| @ -1388,7 +1397,7 @@ async fn prune(mut param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|  | ||||
|     let mut client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let mut client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/prune", repo.store()); | ||||
|  | ||||
| @ -1433,7 +1442,7 @@ async fn status(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/admin/datastore/{}/status", repo.store()); | ||||
|  | ||||
| @ -1463,7 +1472,13 @@ async fn status(param: Value) -> Result<Value, Error> { | ||||
| // like get, but simply ignore errors and return Null instead | ||||
| async fn try_get(repo: &BackupRepository, url: &str) -> Value { | ||||
|  | ||||
|     let client = match HttpClient::new(repo.host(), repo.user(), None) { | ||||
|  | ||||
|     let options = HttpClientOptions::new() | ||||
|         .verify_cert(false) // fixme: set verify to true, but howto handle fingerprint ?? | ||||
|         .interactive(false) | ||||
|         .ticket_cache(true); | ||||
|  | ||||
|     let client = match HttpClient::new(repo.host(), repo.user(), options) { | ||||
|         Ok(v) => v, | ||||
|         _ => return Value::Null, | ||||
|     }; | ||||
| @ -1914,7 +1929,7 @@ async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> { | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let archive_name = tools::required_string_param(¶m, "archive-name")?; | ||||
|     let target = tools::required_string_param(¶m, "target")?; | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     record_repository(&repo); | ||||
|  | ||||
| @ -2024,7 +2039,7 @@ async fn mount_do(param: Value, pipe: Option<RawFd>) -> Result<Value, Error> { | ||||
| /// Shell to interactively inspect and restore snapshots. | ||||
| async fn catalog_shell(param: Value) -> Result<(), Error> { | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|     let path = tools::required_string_param(¶m, "snapshot")?; | ||||
|     let archive_name = tools::required_string_param(¶m, "archive-name")?; | ||||
|  | ||||
| @ -2159,7 +2174,7 @@ async fn task_list(param: Value) -> Result<Value, Error> { | ||||
|  | ||||
|     let output_format = param["output-format"].as_str().unwrap_or("text").to_owned(); | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let limit = param["limit"].as_u64().unwrap_or(50) as usize; | ||||
|  | ||||
| @ -2208,7 +2223,7 @@ async fn task_log(param: Value) -> Result<Value, Error> { | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let upid =  tools::required_string_param(¶m, "upid")?; | ||||
|  | ||||
|     let client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     display_task_log(client, upid, true).await?; | ||||
|  | ||||
| @ -2234,7 +2249,7 @@ async fn task_stop(param: Value) -> Result<Value, Error> { | ||||
|     let repo = extract_repository_from_value(¶m)?; | ||||
|     let upid_str =  tools::required_string_param(¶m, "upid")?; | ||||
|  | ||||
|     let mut client = HttpClient::new(repo.host(), repo.user(), None)?; | ||||
|     let mut client = connect(repo.host(), repo.user())?; | ||||
|  | ||||
|     let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str); | ||||
|     let _ = client.delete(&path, None).await?; | ||||
|  | ||||
| @ -34,11 +34,16 @@ fn connect() -> Result<HttpClient, Error> { | ||||
|  | ||||
|     let uid = nix::unistd::Uid::current(); | ||||
|  | ||||
|     let mut options = HttpClientOptions::new() | ||||
|         .verify_cert(false); // not required for connection to localhost | ||||
|  | ||||
|     let client = if uid.is_root()  { | ||||
|         let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?; | ||||
|         HttpClient::new("localhost", "root@pam", Some(ticket))? | ||||
|         options = options.password(Some(ticket)); | ||||
|         HttpClient::new("localhost", "root@pam", options)? | ||||
|     } else { | ||||
|         HttpClient::new("localhost", "root@pam", None)? | ||||
|         options = options.ticket_cache(true).interactive(true); | ||||
|         HttpClient::new("localhost", "root@pam", options)? | ||||
|     }; | ||||
|  | ||||
|     Ok(client) | ||||
| @ -473,12 +478,14 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String | ||||
|  | ||||
|         let remote: Remote = remote_config.lookup("remote", &remote)?; | ||||
|  | ||||
|         let options = HttpClientOptions::new() | ||||
|             .password(Some(remote.password.clone())) | ||||
|             .fingerprint(remote.fingerprint.clone()); | ||||
|  | ||||
|         let client = HttpClient::new( | ||||
|             &remote.host, | ||||
|             &remote.userid, | ||||
|             Some(remote.password), | ||||
|             remote.fingerprint, | ||||
|             false, | ||||
|             options, | ||||
|         )?; | ||||
|  | ||||
|         let mut rt = tokio::runtime::Runtime::new().unwrap(); | ||||
|  | ||||
| @ -9,7 +9,11 @@ async fn upload_speed() -> Result<usize, Error> { | ||||
|  | ||||
|     let username = "root@pam"; | ||||
|  | ||||
|     let client = HttpClient::new(host, username, None)?; | ||||
|     let options = HttpClientOptions::new() | ||||
|         .interactive(true) | ||||
|         .ticket_cache(true); | ||||
|  | ||||
|     let client = HttpClient::new(host, username, options)?; | ||||
|  | ||||
|     let backup_time = chrono::Utc::now(); | ||||
|  | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| use std::io::Write; | ||||
| use std::task::{Context, Poll}; | ||||
| use std::sync::{Arc, Mutex}; | ||||
|  | ||||
| use chrono::Utc; | ||||
| use failure::*; | ||||
| @ -9,7 +10,7 @@ use http::header::HeaderValue; | ||||
| use http::{Request, Response}; | ||||
| use hyper::Body; | ||||
| use hyper::client::{Client, HttpConnector}; | ||||
| use openssl::ssl::{SslConnector, SslMethod}; | ||||
| use openssl::{ssl::{SslConnector, SslMethod}, x509::X509StoreContextRef}; | ||||
| use serde_json::{json, Value}; | ||||
| use percent_encoding::percent_encode; | ||||
| use xdg::BaseDirectories; | ||||
| @ -29,11 +30,59 @@ pub struct AuthInfo { | ||||
|     pub token: String, | ||||
| } | ||||
|  | ||||
| pub struct HttpClientOptions { | ||||
|     password: Option<String>, | ||||
|     fingerprint: Option<String>, | ||||
|     interactive: bool, | ||||
|     ticket_cache: bool, | ||||
|     verify_cert: bool, | ||||
| } | ||||
|  | ||||
| impl HttpClientOptions { | ||||
|  | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             password: None, | ||||
|             fingerprint: None, | ||||
|             interactive: false, | ||||
|             ticket_cache: false, | ||||
|             verify_cert: true, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn password(mut self, password: Option<String>) -> Self { | ||||
|         self.password = password; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn fingerprint(mut self, fingerprint: Option<String>) -> Self { | ||||
|         self.fingerprint = fingerprint; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn interactive(mut self, interactive: bool) -> Self { | ||||
|         self.interactive = interactive; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn ticket_cache(mut self, ticket_cache: bool) -> Self { | ||||
|         self.ticket_cache = ticket_cache; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn verify_cert(mut self, verify_cert: bool) -> Self { | ||||
|         self.verify_cert = verify_cert; | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// HTTP(S) API client | ||||
| pub struct HttpClient { | ||||
|     client: Client<HttpsConnector>, | ||||
|     server: String, | ||||
|     fingerprint: Arc<Mutex<Option<String>>>, | ||||
|     auth: BroadcastFuture<AuthInfo>, | ||||
|     _options: HttpClientOptions, | ||||
| } | ||||
|  | ||||
| /// Delete stored ticket data (logout) | ||||
| @ -116,23 +165,47 @@ fn load_ticket_info(server: &str, username: &str) -> Option<(String, String)> { | ||||
|  | ||||
| impl HttpClient { | ||||
|  | ||||
|     pub fn new(server: &str, username: &str, password: Option<String>) -> Result<Self, Error> { | ||||
|         let client = Self::build_client(); | ||||
|     pub fn new(server: &str, username: &str, mut options: HttpClientOptions) -> Result<Self, Error> { | ||||
|  | ||||
|         let verified_fingerprint = Arc::new(Mutex::new(None)); | ||||
|  | ||||
|         let client = Self::build_client( | ||||
|             options.fingerprint.clone(), | ||||
|             options.interactive, | ||||
|             verified_fingerprint.clone(), | ||||
|             options.verify_cert, | ||||
|         ); | ||||
|  | ||||
|         let password = options.password.take(); | ||||
|  | ||||
|         let password = if let Some(password) = password { | ||||
|             password | ||||
|         } else if let Some((ticket, _token)) = load_ticket_info(server, username) { | ||||
|             ticket | ||||
|         } else { | ||||
|             Self::get_password(&username)? | ||||
|             let mut ticket_info = None; | ||||
|             if options.ticket_cache { | ||||
|                 ticket_info = load_ticket_info(server, username); | ||||
|             } | ||||
|             if let Some((ticket, _token)) = ticket_info { | ||||
|                 ticket | ||||
|             } else { | ||||
|                 Self::get_password(&username, options.interactive)? | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let login_future = Self::credentials(client.clone(), server.to_owned(), username.to_owned(), password); | ||||
|         let login_future = Self::credentials( | ||||
|             client.clone(), | ||||
|             server.to_owned(), | ||||
|             username.to_owned(), | ||||
|             password, | ||||
|             options.ticket_cache, | ||||
|         ); | ||||
|  | ||||
|         Ok(Self { | ||||
|             client, | ||||
|             server: String::from(server), | ||||
|             fingerprint: verified_fingerprint, | ||||
|             auth: BroadcastFuture::new(Box::new(login_future)), | ||||
|             _options: options, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -144,7 +217,12 @@ impl HttpClient { | ||||
|         self.auth.listen().await | ||||
|     } | ||||
|  | ||||
|     fn get_password(_username: &str) -> Result<String, Error> { | ||||
|     /// Returns the optional fingerprint passed to the new() constructor. | ||||
|     pub fn fingerprint(&self) -> Option<String> { | ||||
|         (*self.fingerprint.lock().unwrap()).clone() | ||||
|     } | ||||
|  | ||||
|     fn get_password(_username: &str, interactive: bool) -> Result<String, Error> { | ||||
|         use std::env::VarError::*; | ||||
|         match std::env::var("PBS_PASSWORD") { | ||||
|             Ok(p) => return Ok(p), | ||||
| @ -155,18 +233,90 @@ impl HttpClient { | ||||
|         } | ||||
|  | ||||
|         // If we're on a TTY, query the user for a password | ||||
|         if tty::stdin_isatty() { | ||||
|         if interactive && tty::stdin_isatty() { | ||||
|             return Ok(String::from_utf8(tty::read_password("Password: ")?)?); | ||||
|         } | ||||
|  | ||||
|         bail!("no password input mechanism available"); | ||||
|     } | ||||
|  | ||||
|     fn build_client() -> Client<HttpsConnector> { | ||||
|     fn verify_callback( | ||||
|         valid: bool, ctx: | ||||
|         &mut X509StoreContextRef, | ||||
|         expected_fingerprint: Option<String>, | ||||
|         interactive: bool, | ||||
|         verified_fingerprint: Arc<Mutex<Option<String>>>, | ||||
|     ) -> bool { | ||||
|         if valid { return true; } | ||||
|  | ||||
|         let cert = match ctx.current_cert() { | ||||
|             Some(cert) => cert, | ||||
|             None => return false, | ||||
|         }; | ||||
|  | ||||
|         let depth = ctx.error_depth(); | ||||
|         if depth != 0 { return false; } | ||||
|  | ||||
|         let fp = match cert.digest(openssl::hash::MessageDigest::sha256()) { | ||||
|             Ok(fp) => fp, | ||||
|             Err(_) => return false, // 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()) | ||||
|             .collect::<Vec<&str>>().join(":"); | ||||
|  | ||||
|         if let Some(expected_fingerprint) = expected_fingerprint { | ||||
|             if expected_fingerprint == fp_string { | ||||
|                 *verified_fingerprint.lock().unwrap() = Some(fp_string); | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // If we're on a TTY, query the user | ||||
|         if interactive && tty::stdin_isatty() { | ||||
|             println!("fingerprint: {}", fp_string); | ||||
|             loop { | ||||
|                 print!("Want to trust? (y/n): "); | ||||
|                 let _ = std::io::stdout().flush(); | ||||
|                 let mut buf = [0u8; 1]; | ||||
|                 use std::io::Read; | ||||
|                 match std::io::stdin().read_exact(&mut buf) { | ||||
|                     Ok(()) => { | ||||
|                         if buf[0] == b'y' || buf[0] == b'Y' { | ||||
|                             println!("TRUST {}", fp_string); | ||||
|                             *verified_fingerprint.lock().unwrap() = Some(fp_string); | ||||
|                            return true; | ||||
|                         } else if buf[0] == b'n' || buf[0] == b'N' { | ||||
|                             return false; | ||||
|                         } | ||||
|                     } | ||||
|                     Err(_) => { | ||||
|                         return false; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         false | ||||
|     } | ||||
|  | ||||
|     fn build_client( | ||||
|         fingerprint: Option<String>, | ||||
|         interactive: bool, | ||||
|         verified_fingerprint: Arc<Mutex<Option<String>>>, | ||||
|         verify_cert: bool, | ||||
|     ) -> Client<HttpsConnector> { | ||||
|  | ||||
|         let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap(); | ||||
|  | ||||
|         ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE); // fixme! | ||||
|         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()) | ||||
|             }); | ||||
|         } 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! | ||||
| @ -339,6 +489,7 @@ impl HttpClient { | ||||
|         server: String, | ||||
|         username: String, | ||||
|         password: String, | ||||
|         use_ticket_cache: bool, | ||||
|     ) -> Result<AuthInfo, Error> { | ||||
|         let data = json!({ "username": username, "password": password }); | ||||
|         let req = Self::request_builder(&server, "POST", "/api2/json/access/ticket", Some(data)).unwrap(); | ||||
| @ -349,7 +500,9 @@ impl HttpClient { | ||||
|             token: cred["data"]["CSRFPreventionToken"].as_str().unwrap().to_owned(), | ||||
|         }; | ||||
|  | ||||
|         let _ = store_ticket_info(&server, &auth.username, &auth.ticket, &auth.token); | ||||
|         if use_ticket_cache { | ||||
|             let _ = store_ticket_info(&server, &auth.username, &auth.ticket, &auth.token); | ||||
|         } | ||||
|  | ||||
|         Ok(auth) | ||||
|     } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user