diff --git a/src-tauri/src/conn_pool/connection.rs b/src-tauri/src/conn_pool/connection.rs index 1a6cd474..6cf5161f 100644 --- a/src-tauri/src/conn_pool/connection.rs +++ b/src-tauri/src/conn_pool/connection.rs @@ -1,6 +1,7 @@ use std::fmt::{Debug, Formatter}; use std::io::Read; use std::ops::{Deref, DerefMut}; +use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::Duration; @@ -13,7 +14,7 @@ use crate::device_manager::Device; use crate::error::Error; impl DeviceConnection { - pub(crate) fn new(device: Device) -> Result { + pub(crate) fn new(device: Device, ssh_dir: Option<&Path>) -> Result { let kex = vec![ "curve25519-sha256", "curve25519-sha256@libssh.org", @@ -72,7 +73,7 @@ impl DeviceConnection { if let Some(private_key) = &device.private_key { let passphrase = device.valid_passphrase(); - let priv_key_content = private_key.content()?; + let priv_key_content = private_key.content(ssh_dir)?; let priv_key = SshKey::from_privkey_base64(&priv_key_content, passphrase.as_deref())?; if session.userauth_publickey(None, &priv_key)? != AuthStatus::Success { diff --git a/src-tauri/src/conn_pool/mod.rs b/src-tauri/src/conn_pool/mod.rs index d8ff282d..fb067bda 100644 --- a/src-tauri/src/conn_pool/mod.rs +++ b/src-tauri/src/conn_pool/mod.rs @@ -2,6 +2,7 @@ use crate::device_manager::Device; use crate::error::Error; use libssh_rs::Session; use r2d2::{Pool, PooledConnection}; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use uuid::Uuid; @@ -37,4 +38,5 @@ pub struct DeviceConnectionPool { pub struct DeviceConnectionManager { device: Device, + ssh_dir: Option, } diff --git a/src-tauri/src/conn_pool/pool.rs b/src-tauri/src/conn_pool/pool.rs index 91521695..e4695911 100644 --- a/src-tauri/src/conn_pool/pool.rs +++ b/src-tauri/src/conn_pool/pool.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -11,7 +12,7 @@ use crate::device_manager::Device; use crate::error::Error; impl DeviceConnectionPool { - pub fn new(device: Device) -> DeviceConnectionPool { + pub fn new(device: Device, ssh_dir: Option) -> DeviceConnectionPool { let last_error = Arc::>>::default(); let inner = Pool::::builder() .min_idle(Some(0)) @@ -20,7 +21,7 @@ impl DeviceConnectionPool { .error_handler(Box::new(DeviceConnectionErrorHandler { last_error: last_error.clone(), })) - .build_unchecked(DeviceConnectionManager { device }); + .build_unchecked(DeviceConnectionManager { device, ssh_dir }); return DeviceConnectionPool { inner, last_error }; } @@ -45,7 +46,7 @@ impl ManageConnection for DeviceConnectionManager { type Error = Error; fn connect(&self) -> Result { - return DeviceConnection::new(self.device.clone()); + return DeviceConnection::new(self.device.clone(), self.ssh_dir.as_deref()); } fn is_valid(&self, _: &mut Self::Connection) -> Result<(), Self::Error> { diff --git a/src-tauri/src/device_manager/io.rs b/src-tauri/src/device_manager/io.rs index 5e90b99e..cafe3634 100644 --- a/src-tauri/src/device_manager/io.rs +++ b/src-tauri/src/device_manager/io.rs @@ -1,10 +1,9 @@ +use std::{env, fs}; use std::fs::{create_dir_all, File}; use std::io::{BufReader, BufWriter, ErrorKind}; use std::path::PathBuf; -use std::{env, fs}; use serde_json::Value; -use tauri::api::path::home_dir; use crate::device_manager::Device; use crate::error::Error; @@ -62,18 +61,6 @@ pub(crate) async fn write(devices: Vec) -> Result<(), Error> { .expect("critical failure in app::io::write task"); } -pub(crate) fn ssh_dir() -> Option { - return home_dir().map(|d| d.join(".ssh")); -} - -pub(crate) fn ensure_ssh_dir() -> Result { - let dir = ssh_dir().ok_or_else(|| Error::bad_config())?; - if !dir.exists() { - create_dir_all(dir.clone())?; - } - return Ok(dir); -} - #[cfg(target_family = "windows")] fn devices_file_path() -> Result { let home = env::var("APPDATA") diff --git a/src-tauri/src/device_manager/manager.rs b/src-tauri/src/device_manager/manager.rs index e2dcd0ca..f49fb547 100644 --- a/src-tauri/src/device_manager/manager.rs +++ b/src-tauri/src/device_manager/manager.rs @@ -1,13 +1,15 @@ use std::fs; -use std::path::Path; +use std::fs::create_dir_all; +use std::path::{Path, PathBuf}; use libssh_rs::SshKey; use tokio::fs::{remove_file, File}; use tokio::io::AsyncWriteExt; -use crate::device_manager::io::{ensure_ssh_dir, read, ssh_dir, write}; +use crate::device_manager::io::{read, write}; use crate::device_manager::{Device, DeviceManager, PrivateKey}; use crate::error::Error; +use crate::ssh_dir::{GetSshDir, SetSshDir}; impl DeviceManager { pub async fn list(&self) -> Result, Error> { @@ -40,7 +42,7 @@ impl DeviceManager { let path = Path::new(name); if path.is_absolute() { let name = String::from( - pathdiff::diff_paths(path, ensure_ssh_dir()?) + pathdiff::diff_paths(path, self.ensure_ssh_dir()?) .ok_or(Error::NotFound)? .to_string_lossy(), ); @@ -49,7 +51,7 @@ impl DeviceManager { } PrivateKey::Data { data } => { let name = key.name(device.valid_passphrase())?; - let key_path = ensure_ssh_dir()?.join(&name); + let key_path = self.ensure_ssh_dir()?.join(&name); let mut file = File::create(key_path).await?; file.write(data.as_bytes()).await?; device.private_key = Some(PrivateKey::Path { name }); @@ -80,7 +82,7 @@ impl DeviceManager { if !name.starts_with("webos_") { continue; } - let key_path = ensure_ssh_dir()?.join(name); + let key_path = self.ensure_ssh_dir()?.join(name); remove_file(key_path).await?; } } @@ -114,8 +116,7 @@ impl DeviceManager { let ssh_key_path = if name_path.is_absolute() { name_path.to_path_buf() } else { - let ssh_dir = ssh_dir().ok_or_else(|| Error::bad_config())?; - fs::canonicalize(ssh_dir.join(name))? + fs::canonicalize(self.ensure_ssh_dir()?)? }; return match SshKey::from_privkey_file(ssh_key_path.to_str().unwrap(), Some(passphrase)) { Ok(_) => Ok(()), @@ -127,3 +128,14 @@ impl DeviceManager { }; } } +impl GetSshDir for DeviceManager { + fn get_ssh_dir(&self) -> Option { + return self.ssh_dir.lock().unwrap().clone(); + } +} + +impl SetSshDir for DeviceManager { + fn set_ssh_dir(&self, dir: PathBuf) { + *self.ssh_dir.lock().unwrap() = Some(dir); + } +} diff --git a/src-tauri/src/device_manager/mod.rs b/src-tauri/src/device_manager/mod.rs index d4667f8f..d4ac41a8 100644 --- a/src-tauri/src/device_manager/mod.rs +++ b/src-tauri/src/device_manager/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::sync::Mutex; use serde::{Deserialize, Serialize}; @@ -13,18 +14,12 @@ pub struct DeviceSessionToken { pub id: Option, } +#[derive(Default)] pub struct DeviceManager { + ssh_dir: Mutex>, devices: Mutex>, } -impl Default for DeviceManager { - fn default() -> Self { - return DeviceManager { - devices: Mutex::new(Vec::new()), - }; - } -} - #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(untagged)] pub enum PrivateKey { diff --git a/src-tauri/src/device_manager/privkey.rs b/src-tauri/src/device_manager/privkey.rs index 51e50318..0b078881 100644 --- a/src-tauri/src/device_manager/privkey.rs +++ b/src-tauri/src/device_manager/privkey.rs @@ -1,16 +1,17 @@ -use libssh_rs::{PublicKeyHashType, SshKey}; use std::io::Read; +use std::path::Path; + +use libssh_rs::{PublicKeyHashType, SshKey}; -use crate::device_manager::io::ssh_dir; use crate::device_manager::PrivateKey; use crate::error::Error; impl PrivateKey { - pub fn content(&self) -> Result { + pub fn content(&self, ssh_dir: Option<&Path>) -> Result { return match self { PrivateKey::Path { name } => { let mut secret_file = - std::fs::File::open(ssh_dir().ok_or(Error::bad_config())?.join(name))?; + std::fs::File::open(ssh_dir.ok_or(Error::bad_config())?.join(name))?; let mut secret = String::new(); secret_file.read_to_string(&mut secret)?; Ok(secret) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 89eaeea0..6141c924 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,14 +1,17 @@ extern crate core; -use log::LevelFilter; -use tauri::Manager; +use std::path::PathBuf; +use log::LevelFilter; use native_dialog::{MessageDialog, MessageType}; +use tauri::api::path::home_dir; +use tauri::{AppHandle, Manager, RunEvent, Runtime}; use crate::device_manager::DeviceManager; use crate::session_manager::SessionManager; use crate::shell_manager::ShellManager; use crate::spawn_manager::SpawnManager; +use crate::ssh_dir::{GetSshDir, SetSshDir}; mod conn_pool; mod device_manager; @@ -19,6 +22,7 @@ mod remote_files; mod session_manager; mod shell_manager; mod spawn_manager; +mod ssh_dir; //#[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -50,7 +54,20 @@ pub fn run() { let spawns = wnd.state::(); spawns.clear(); }) - .run(tauri::generate_context!()); + .build(tauri::generate_context!()) + .and_then(|app| { + app.run(|app, event| match event { + RunEvent::Ready => { + if let Some(ssh_dir) = app.get_ssh_dir() { + app.state::().set_ssh_dir(ssh_dir.clone()); + app.state::().set_ssh_dir(ssh_dir.clone()); + app.state::().set_ssh_dir(ssh_dir.clone()); + } + } + _ => {} + }); + return Ok(()); + }); if let Err(e) = result { #[cfg(windows)] if let tauri::Error::Runtime(ref e) = e { @@ -72,3 +89,11 @@ pub fn run() { .expect("Unexpected error occurred while processing unexpected error :("); } } + +impl GetSshDir for AppHandle { + fn get_ssh_dir(&self) -> Option { + return home_dir() + .or_else(|| self.path_resolver().app_data_dir()) + .map(|d| d.join(".ssh")); + } +} diff --git a/src-tauri/src/plugins/device.rs b/src-tauri/src/plugins/device.rs index 553d9f82..66e38a84 100644 --- a/src-tauri/src/plugins/device.rs +++ b/src-tauri/src/plugins/device.rs @@ -1,11 +1,12 @@ -use tauri::State; use tauri::{ plugin::{Builder, TauriPlugin}, Runtime, }; +use tauri::{AppHandle, State}; use crate::device_manager::{Device, DeviceManager}; use crate::error::Error; +use crate::ssh_dir::GetSshDir; #[tauri::command] async fn list(manager: State<'_, DeviceManager>) -> Result, Error> { @@ -57,11 +58,11 @@ async fn localkey_verify( } #[tauri::command] -async fn privkey_read(device: Device) -> Result { +async fn privkey_read(app: AppHandle, device: Device) -> Result { return Ok(device .private_key .ok_or_else(|| Error::bad_config())? - .content()?); + .content(app.get_ssh_dir().as_deref())?); } /// Initializes the plugin. diff --git a/src-tauri/src/session_manager/manager.rs b/src-tauri/src/session_manager/manager.rs index 5ac42d71..09230459 100644 --- a/src-tauri/src/session_manager/manager.rs +++ b/src-tauri/src/session_manager/manager.rs @@ -1,9 +1,11 @@ +use std::path::PathBuf; use std::sync::{Arc, Condvar, Mutex}; use crate::conn_pool::{DeviceConnectionPool, ManagedDeviceConnection}; use crate::device_manager::Device; use crate::error::Error; use crate::session_manager::{Proc, SessionManager}; +use crate::ssh_dir::{GetSshDir, SetSshDir}; impl SessionManager { pub fn session(&self, device: Device) -> Result { @@ -36,14 +38,14 @@ impl SessionManager { command: String::from(command), callback: Mutex::default(), ready: Arc::new((Mutex::default(), Condvar::new())), - sender:Mutex::default(), + sender: Mutex::default(), interrupted: Mutex::new(false), }; } fn pool(&self, device: Device) -> DeviceConnectionPool { if device.new { - return DeviceConnectionPool::new(device); + return DeviceConnectionPool::new(device, self.get_ssh_dir()); } if let Some(p) = self .pools @@ -54,7 +56,7 @@ impl SessionManager { return p.clone(); } let key = device.name.clone(); - let pool = DeviceConnectionPool::new(device); + let pool = DeviceConnectionPool::new(device, self.get_ssh_dir()); self.pools .lock() .expect("Failed to lock SessionManager::pools") @@ -62,3 +64,15 @@ impl SessionManager { return pool; } } + +impl GetSshDir for SessionManager { + fn get_ssh_dir(&self) -> Option { + return self.ssh_dir.lock().unwrap().clone(); + } +} + +impl SetSshDir for SessionManager { + fn set_ssh_dir(&self, dir: PathBuf) { + *self.ssh_dir.lock().unwrap() = Some(dir); + } +} diff --git a/src-tauri/src/session_manager/mod.rs b/src-tauri/src/session_manager/mod.rs index c09f6335..17e24206 100644 --- a/src-tauri/src/session_manager/mod.rs +++ b/src-tauri/src/session_manager/mod.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::PathBuf; use std::sync::mpsc::Sender; use std::sync::{Arc, Condvar, Mutex}; @@ -12,6 +13,7 @@ mod proc; #[derive(Default)] pub struct SessionManager { + ssh_dir: Mutex>, pools: Mutex>, } diff --git a/src-tauri/src/shell_manager/manager.rs b/src-tauri/src/shell_manager/manager.rs index 3bbc4fd6..5dcdbbf2 100644 --- a/src-tauri/src/shell_manager/manager.rs +++ b/src-tauri/src/shell_manager/manager.rs @@ -1,12 +1,21 @@ +use std::path::PathBuf; use std::sync::Arc; use crate::device_manager::Device; use crate::error::Error; use crate::shell_manager::{Shell, ShellInfo, ShellManager, ShellToken}; +use crate::ssh_dir::{GetSshDir, SetSshDir}; impl ShellManager { pub fn open(&self, device: Device, rows: u16, cols: u16, dumb: bool) -> Arc { - let shell = Arc::new(Shell::new(device, !dumb, rows, cols, self.shells.clone())); + let shell = Arc::new(Shell::new( + device, + self.get_ssh_dir().as_deref(), + !dumb, + rows, + cols, + self.shells.clone(), + )); self.shells .lock() .unwrap() @@ -39,3 +48,15 @@ impl ShellManager { return list; } } + +impl GetSshDir for ShellManager { + fn get_ssh_dir(&self) -> Option { + return self.ssh_dir.lock().unwrap().clone(); + } +} + +impl SetSshDir for ShellManager { + fn set_ssh_dir(&self, dir: PathBuf) { + *self.ssh_dir.lock().unwrap() = Some(dir); + } +} diff --git a/src-tauri/src/shell_manager/mod.rs b/src-tauri/src/shell_manager/mod.rs index a22f37b4..658fd2c4 100644 --- a/src-tauri/src/shell_manager/mod.rs +++ b/src-tauri/src/shell_manager/mod.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use std::time::Instant; @@ -9,6 +10,7 @@ use vt100::Parser; use crate::device_manager::Device; use crate::error::Error; use crate::shell_manager::shell::ShellsMap; +use crate::ssh_dir::GetSshDir; pub(crate) mod manager; pub(crate) mod shell; @@ -17,12 +19,14 @@ pub(crate) mod token; #[derive(Default)] pub struct ShellManager { pub(crate) shells: Arc>, + ssh_dir: Mutex>, } pub struct Shell { pub token: ShellToken, created_at: Instant, device: Device, + ssh_dir: Option, pub(crate) has_pty: Mutex>, pub(crate) closed: Mutex>, pub(crate) sender: Mutex>>, diff --git a/src-tauri/src/shell_manager/shell.rs b/src-tauri/src/shell_manager/shell.rs index 1151bb3b..59eda126 100644 --- a/src-tauri/src/shell_manager/shell.rs +++ b/src-tauri/src/shell_manager/shell.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::io::Write; -use std::sync::{Arc, Mutex}; +use std::path::Path; use std::sync::mpsc::channel; +use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use std::time::{Duration, Instant}; @@ -84,6 +85,7 @@ impl Shell { pub(crate) fn new( device: Device, + ssh_dir: Option<&Path>, wants_pty: bool, rows: u16, cols: u16, @@ -93,6 +95,7 @@ impl Shell { token: ShellToken::new(), created_at: Instant::now(), device, + ssh_dir: ssh_dir.map(|p| p.to_path_buf()), has_pty: Mutex::new(if !wants_pty { Some(false) } else { None }), closed: Mutex::default(), sender: Mutex::default(), @@ -134,7 +137,7 @@ impl Shell { fn worker(&self) -> Result { let (sender, receiver) = channel::(); - let connection = DeviceConnection::new(self.device.clone())?; + let connection = DeviceConnection::new(self.device.clone(), self.ssh_dir.as_deref())?; let channel = connection.new_channel()?; channel.open_session()?; let (rows, cols) = self.parser.lock().unwrap().screen().size(); diff --git a/src-tauri/src/ssh_dir/mod.rs b/src-tauri/src/ssh_dir/mod.rs new file mode 100644 index 00000000..893f7aa2 --- /dev/null +++ b/src-tauri/src/ssh_dir/mod.rs @@ -0,0 +1,21 @@ +use crate::error::Error; +use std::fs::create_dir_all; +use std::path::PathBuf; + +pub trait GetSshDir { + fn get_ssh_dir(&self) -> Option; + + fn ensure_ssh_dir(&self) -> Result { + let Some(dir) = self.get_ssh_dir() else { + return Err(Error::bad_config()); + }; + if !dir.exists() { + create_dir_all(&dir)?; + } + return Ok(dir); + } +} + +pub trait SetSshDir { + fn set_ssh_dir(&self, dir: PathBuf); +}