mirror of https://github.com/chipsenkbeil/distant
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
9.9 KiB
Rust
309 lines
9.9 KiB
Rust
use crate::config::NetworkConfig;
|
|
use async_trait::async_trait;
|
|
use distant_core::net::client::{Client as NetClient, ClientConfig, ReconnectStrategy};
|
|
use distant_core::net::common::authentication::msg::*;
|
|
use distant_core::net::common::authentication::{
|
|
AuthHandler, AuthMethodHandler, PromptAuthMethodHandler, SingleAuthHandler,
|
|
};
|
|
use distant_core::net::manager::ManagerClient;
|
|
use log::*;
|
|
use std::io;
|
|
use std::time::Duration;
|
|
|
|
mod msg;
|
|
pub use msg::*;
|
|
|
|
pub struct Client<T> {
|
|
network: NetworkConfig,
|
|
auth_handler: T,
|
|
}
|
|
|
|
impl Client<()> {
|
|
pub fn new(network: NetworkConfig) -> Self {
|
|
Self {
|
|
network,
|
|
auth_handler: (),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Client<T> {
|
|
pub fn using_json_auth_handler(self) -> Client<JsonAuthHandler> {
|
|
Client {
|
|
network: self.network,
|
|
auth_handler: JsonAuthHandler::default(),
|
|
}
|
|
}
|
|
|
|
pub fn using_prompt_auth_handler(self) -> Client<PromptAuthHandler> {
|
|
Client {
|
|
network: self.network,
|
|
auth_handler: PromptAuthHandler::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: AuthHandler + Clone> Client<T> {
|
|
/// Connect to the manager listening on the socket or windows pipe based on
|
|
/// the [`NetworkConfig`] provided to the client earlier. Will return a new instance
|
|
/// of the [`ManagerClient`] upon successful connection
|
|
pub async fn connect(self) -> anyhow::Result<ManagerClient> {
|
|
let client = self.connect_impl().await?;
|
|
client.on_connection_change(|state| debug!("Client is now {state}"));
|
|
Ok(client)
|
|
}
|
|
|
|
async fn connect_impl(self) -> anyhow::Result<ManagerClient> {
|
|
#[cfg(unix)]
|
|
{
|
|
let mut maybe_client = None;
|
|
let mut error: Option<anyhow::Error> = None;
|
|
for path in self.network.to_unix_socket_path_candidates() {
|
|
match NetClient::unix_socket(path)
|
|
.auth_handler(self.auth_handler.clone())
|
|
.config(ClientConfig {
|
|
reconnect_strategy: ReconnectStrategy::ExponentialBackoff {
|
|
base: Duration::from_secs(1),
|
|
factor: 2.0,
|
|
max_duration: Some(Duration::from_secs(10)),
|
|
max_retries: None,
|
|
timeout: None,
|
|
},
|
|
..Default::default()
|
|
})
|
|
.connect()
|
|
.await
|
|
{
|
|
Ok(client) => {
|
|
info!("Connected to unix socket @ {:?}", path);
|
|
maybe_client = Some(client);
|
|
break;
|
|
}
|
|
Err(x) => {
|
|
let err = anyhow::Error::new(x)
|
|
.context(format!("Failed to connect to unix socket {:?}", path));
|
|
if let Some(x) = error {
|
|
error = Some(x.context(err));
|
|
} else {
|
|
error = Some(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(maybe_client.ok_or_else(|| {
|
|
error.unwrap_or_else(|| anyhow::anyhow!("No unix socket candidate available"))
|
|
})?)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
let mut maybe_client = None;
|
|
let mut error: Option<anyhow::Error> = None;
|
|
for name in self.network.to_windows_pipe_name_candidates() {
|
|
match NetClient::local_windows_pipe(name)
|
|
.auth_handler(self.auth_handler.clone())
|
|
.config(ClientConfig {
|
|
reconnect_strategy: ReconnectStrategy::ExponentialBackoff {
|
|
base: Duration::from_secs(1),
|
|
factor: 2.0,
|
|
max_duration: Some(Duration::from_secs(10)),
|
|
max_retries: None,
|
|
timeout: None,
|
|
},
|
|
..Default::default()
|
|
})
|
|
.connect()
|
|
.await
|
|
{
|
|
Ok(client) => {
|
|
info!("Connected to named windows pipe @ {:?}", name);
|
|
maybe_client = Some(client);
|
|
break;
|
|
}
|
|
Err(x) => {
|
|
let err = anyhow::Error::new(x)
|
|
.context(format!("Failed to connect to windows pipe {:?}", name));
|
|
if let Some(x) = error {
|
|
error = Some(x.context(err));
|
|
} else {
|
|
error = Some(err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(maybe_client.ok_or_else(|| {
|
|
error.unwrap_or_else(|| anyhow::anyhow!("No windows pipe candidate available"))
|
|
})?)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Implementation of [`AuthHandler`] that communicates over JSON.
|
|
#[derive(Clone)]
|
|
pub struct JsonAuthHandler {
|
|
tx: MsgSender,
|
|
rx: MsgReceiver,
|
|
}
|
|
|
|
impl JsonAuthHandler {
|
|
pub fn new(tx: MsgSender, rx: MsgReceiver) -> Self {
|
|
Self { tx, rx }
|
|
}
|
|
}
|
|
|
|
impl Default for JsonAuthHandler {
|
|
fn default() -> Self {
|
|
Self::new(MsgSender::from_stdout(), MsgReceiver::from_stdin())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthHandler for JsonAuthHandler {
|
|
async fn on_initialization(
|
|
&mut self,
|
|
initialization: Initialization,
|
|
) -> io::Result<InitializationResponse> {
|
|
self.tx
|
|
.send_blocking(&Authentication::Initialization(initialization))?;
|
|
let response = self.rx.recv_blocking::<AuthenticationResponse>()?;
|
|
|
|
match response {
|
|
AuthenticationResponse::Initialization(x) => Ok(x),
|
|
x => Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Unexpected response: {x:?}"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn on_start_method(&mut self, start_method: StartMethod) -> io::Result<()> {
|
|
self.tx
|
|
.send_blocking(&Authentication::StartMethod(start_method))?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn on_finished(&mut self) -> io::Result<()> {
|
|
self.tx.send_blocking(&Authentication::Finished)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthMethodHandler for JsonAuthHandler {
|
|
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<ChallengeResponse> {
|
|
self.tx
|
|
.send_blocking(&Authentication::Challenge(challenge))?;
|
|
let response = self.rx.recv_blocking::<AuthenticationResponse>()?;
|
|
|
|
match response {
|
|
AuthenticationResponse::Challenge(x) => Ok(x),
|
|
x => Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Unexpected response: {x:?}"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn on_verification(
|
|
&mut self,
|
|
verification: Verification,
|
|
) -> io::Result<VerificationResponse> {
|
|
self.tx
|
|
.send_blocking(&Authentication::Verification(verification))?;
|
|
let response = self.rx.recv_blocking::<AuthenticationResponse>()?;
|
|
|
|
match response {
|
|
AuthenticationResponse::Verification(x) => Ok(x),
|
|
x => Err(io::Error::new(
|
|
io::ErrorKind::InvalidData,
|
|
format!("Unexpected response: {x:?}"),
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn on_info(&mut self, info: Info) -> io::Result<()> {
|
|
self.tx.send_blocking(&Authentication::Info(info))?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn on_error(&mut self, error: Error) -> io::Result<()> {
|
|
self.tx.send_blocking(&Authentication::Error(error))?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Implementation of [`AuthHandler`] that uses prompts to perform authentication requests and
|
|
/// notification of different information.
|
|
pub struct PromptAuthHandler(Box<dyn AuthHandler>);
|
|
|
|
impl PromptAuthHandler {
|
|
pub fn new() -> Self {
|
|
Self(Box::new(SingleAuthHandler::new(
|
|
PromptAuthMethodHandler::new(
|
|
|prompt: &str| {
|
|
eprintln!("{prompt}");
|
|
let mut line = String::new();
|
|
std::io::stdin().read_line(&mut line)?;
|
|
Ok(line)
|
|
},
|
|
|prompt: &str| rpassword::prompt_password(prompt),
|
|
),
|
|
)))
|
|
}
|
|
}
|
|
|
|
impl Clone for PromptAuthHandler {
|
|
/// Clones a new copy of the handler.
|
|
///
|
|
/// ### Note
|
|
///
|
|
/// This is a hack so we can use this handler elsewhere. Because this handler only has a new
|
|
/// method that creates a new instance, we treat it like a clone and just create an entirely
|
|
/// new prompt auth handler since there is no actual state to clone.
|
|
fn clone(&self) -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthHandler for PromptAuthHandler {
|
|
async fn on_initialization(
|
|
&mut self,
|
|
initialization: Initialization,
|
|
) -> io::Result<InitializationResponse> {
|
|
self.0.on_initialization(initialization).await
|
|
}
|
|
|
|
async fn on_start_method(&mut self, start_method: StartMethod) -> io::Result<()> {
|
|
self.0.on_start_method(start_method).await
|
|
}
|
|
|
|
async fn on_finished(&mut self) -> io::Result<()> {
|
|
self.0.on_finished().await
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl AuthMethodHandler for PromptAuthHandler {
|
|
async fn on_challenge(&mut self, challenge: Challenge) -> io::Result<ChallengeResponse> {
|
|
self.0.on_challenge(challenge).await
|
|
}
|
|
|
|
async fn on_verification(
|
|
&mut self,
|
|
verification: Verification,
|
|
) -> io::Result<VerificationResponse> {
|
|
self.0.on_verification(verification).await
|
|
}
|
|
|
|
async fn on_info(&mut self, info: Info) -> io::Result<()> {
|
|
self.0.on_info(info).await
|
|
}
|
|
|
|
async fn on_error(&mut self, error: Error) -> io::Result<()> {
|
|
self.0.on_error(error).await
|
|
}
|
|
}
|