use std::{collections::BTreeSet, path::PathBuf, sync::Arc};
use anyhow::Result;
use getset::{CopyGetters, Getters, Setters};
use libmoshpit::{
AlgorithmList, DiffMode, DisplayPreference, FileLayer, KEY_ALGORITHM_X25519, KexConfig,
KexMode, KeyPair, supported_algorithms,
};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use uuid::Uuid;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub(crate) struct AlgorithmPreferences {
#[serde(default)]
pub(crate) kex: Option<Vec<String>>,
#[serde(default)]
pub(crate) aead: Option<Vec<String>>,
#[serde(default)]
pub(crate) mac: Option<Vec<String>>,
#[serde(default)]
pub(crate) kdf: Option<Vec<String>>,
}
impl AlgorithmPreferences {
fn into_algorithm_list(self) -> AlgorithmList {
let defaults = supported_algorithms();
AlgorithmList {
kex: self.kex.unwrap_or(defaults.kex),
aead: self.aead.unwrap_or(defaults.aead),
mac: self.mac.unwrap_or(defaults.mac),
kdf: self.kdf.unwrap_or(defaults.kdf),
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
pub(crate) struct ClientTracing {
#[getset(get = "pub(crate)")]
file: FileLayer,
}
#[derive(Clone, CopyGetters, Debug, Deserialize, Eq, Getters, PartialEq, Serialize, Setters)]
pub(crate) struct Config {
#[serde(skip_deserializing)]
#[getset(get_copy = "pub(crate)")]
mode: KexMode,
#[serde(skip_deserializing)]
#[getset(get = "pub(crate)", set = "pub(crate)")]
user: String,
#[getset(get = "pub(crate)")]
tracing: ClientTracing,
#[getset(get_copy = "pub(crate)")]
server_port: u16,
#[getset(get = "pub(crate)")]
server_destination: String,
#[getset(get = "pub(crate)")]
private_key_path: Option<String>,
#[getset(get = "pub(crate)")]
public_key_path: Option<String>,
#[serde(skip)]
#[getset(get_copy = "pub(crate)", set = "pub(crate)")]
resume_session_uuid: Option<Uuid>,
#[serde(default = "Config::default_max_reconnect_backoff_secs")]
#[getset(get_copy = "pub(crate)")]
max_reconnect_backoff_secs: u64,
#[serde(default)]
#[getset(get_copy = "pub(crate)")]
predict: DisplayPreference,
#[serde(default)]
#[getset(get_copy = "pub(crate)")]
nat_warmup: bool,
#[serde(default = "Config::default_nat_warmup_count")]
#[getset(get_copy = "pub(crate)")]
nat_warmup_count: u32,
#[serde(default)]
#[getset(get_copy = "pub(crate)")]
diff_mode: DiffMode,
#[serde(default)]
preferred_algorithms: AlgorithmPreferences,
#[serde(default = "Config::default_send_env")]
#[getset(get = "pub(crate)")]
send_env: Vec<String>,
#[serde(default)]
#[getset(get = "pub(crate)")]
send_path: Vec<String>,
}
impl Config {
fn default_max_reconnect_backoff_secs() -> u64 {
3600
}
fn default_nat_warmup_count() -> u32 {
3
}
fn default_send_env() -> Vec<String> {
vec!["LANG".into(), "LC_*".into(), "TZ".into()]
}
fn load_key_paths(&self) -> Result<(PathBuf, PathBuf)> {
let (default_private_key_path, default_pub_key_ext) =
KeyPair::default_key_path_ext(self.mode, KEY_ALGORITHM_X25519)?;
let private_key_path = self
.private_key_path
.as_ref()
.map_or(default_private_key_path, PathBuf::from);
let public_key_path = self.public_key_path.as_ref().map_or(
private_key_path.with_extension(default_pub_key_ext),
PathBuf::from,
);
Ok((private_key_path, public_key_path))
}
}
impl Default for Config {
fn default() -> Self {
Config {
mode: KexMode::Client,
user: String::new(),
tracing: ClientTracing::default(),
server_port: 60001,
server_destination: String::new(),
private_key_path: None,
public_key_path: None,
resume_session_uuid: None,
max_reconnect_backoff_secs: Self::default_max_reconnect_backoff_secs(),
predict: DisplayPreference::default(),
nat_warmup: false,
nat_warmup_count: Self::default_nat_warmup_count(),
diff_mode: DiffMode::default(),
preferred_algorithms: AlgorithmPreferences::default(),
send_env: Self::default_send_env(),
send_path: Vec::new(),
}
}
}
impl KexConfig for Config {
fn mode(&self) -> KexMode {
self.mode
}
fn port_pool(&self) -> Option<Arc<Mutex<BTreeSet<u16>>>> {
None
}
fn key_pair_paths(&self) -> Result<(PathBuf, PathBuf)> {
self.load_key_paths()
}
fn user(&self) -> Option<String> {
self.user.clone().into()
}
fn resume_session_uuid(&self) -> Option<Uuid> {
self.resume_session_uuid
}
fn server_id(&self) -> Option<String> {
Some(self.server_destination().clone())
}
fn diff_mode(&self) -> DiffMode {
self.diff_mode
}
fn preferred_algorithms(&self) -> AlgorithmList {
self.preferred_algorithms.clone().into_algorithm_list()
}
fn send_env(&self) -> Vec<String> {
self.send_env.clone()
}
fn send_path(&self) -> Vec<String> {
self.send_path.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.mode(), KexMode::Client);
assert_eq!(config.server_port(), 60001);
assert_eq!(config.server_destination(), "");
assert_eq!(config.private_key_path(), &None);
assert_eq!(config.public_key_path(), &None);
assert_eq!(config.resume_session_uuid(), None);
assert_eq!(config.max_reconnect_backoff_secs(), 3600);
assert_eq!(config.predict(), DisplayPreference::default());
}
#[test]
fn test_kex_config_impl() {
let mut config = Config::default();
let _ = config.set_user("testuser".to_string());
let uuid = Uuid::new_v4();
let _ = config.set_resume_session_uuid(Some(uuid));
assert_eq!(KexConfig::mode(&config), KexMode::Client);
assert!(KexConfig::port_pool(&config).is_none());
assert_eq!(KexConfig::user(&config).unwrap(), "testuser");
assert_eq!(KexConfig::resume_session_uuid(&config), Some(uuid));
}
#[test]
fn test_load_key_paths() -> Result<()> {
let config = Config::default();
let (priv_path, pub_path) = config.load_key_paths()?;
assert!(priv_path.to_string_lossy().contains("id_x25519"));
assert!(pub_path.to_string_lossy().contains("id_x25519.pub"));
let config = Config {
private_key_path: Some("/tmp/my_priv".to_string()),
public_key_path: Some("/tmp/my_pub".to_string()),
..Config::default()
};
let (priv_path, pub_path) = config.load_key_paths()?;
assert_eq!(priv_path, PathBuf::from("/tmp/my_priv"));
assert_eq!(pub_path, PathBuf::from("/tmp/my_pub"));
Ok(())
}
}