diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 64fa2ac..da9e472 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -102,7 +102,7 @@ pub(crate) fn default_fake_cert_len() -> usize { } pub(crate) fn default_tls_front_dir() -> String { - "/etc/telemt/tlsfront".to_string() + "tlsfront".to_string() } pub(crate) fn default_replay_check_len() -> usize { @@ -568,7 +568,7 @@ pub(crate) fn default_beobachten_flush_secs() -> u64 { } pub(crate) fn default_beobachten_file() -> String { - "/etc/telemt/beobachten.txt".to_string() + "beobachten.txt".to_string() } pub(crate) fn default_tls_new_session_tickets() -> u8 { diff --git a/src/maestro/helpers.rs b/src/maestro/helpers.rs index b888fb4..01b0d46 100644 --- a/src/maestro/helpers.rs +++ b/src/maestro/helpers.rs @@ -1,6 +1,6 @@ #![allow(clippy::items_after_test_module)] -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::time::Duration; use tokio::sync::watch; @@ -17,7 +17,7 @@ use crate::transport::middle_proxy::{ pub(crate) fn resolve_runtime_config_path( config_path_cli: &str, - startup_cwd: &std::path::Path, + startup_cwd: &Path, config_path_explicit: bool, ) -> PathBuf { if config_path_explicit { @@ -46,6 +46,39 @@ pub(crate) fn resolve_runtime_config_path( startup_cwd.join("config.toml") } +pub(crate) fn resolve_runtime_base_dir( + config_path: &Path, + startup_cwd: &Path, + config_path_explicit: bool, + data_path: Option<&Path>, +) -> PathBuf { + if let Some(path) = data_path { + return normalize_runtime_dir(path, startup_cwd); + } + + if startup_cwd != Path::new("/") { + return normalize_runtime_dir(startup_cwd, startup_cwd); + } + + if config_path_explicit + && let Some(parent) = config_path.parent() + && !parent.as_os_str().is_empty() + { + return normalize_runtime_dir(parent, startup_cwd); + } + + PathBuf::from("/etc/telemt") +} + +fn normalize_runtime_dir(path: &Path, startup_cwd: &Path) -> PathBuf { + let absolute = if path.is_absolute() { + path.to_path_buf() + } else { + startup_cwd.join(path) + }; + absolute.canonicalize().unwrap_or(absolute) +} + /// Parsed CLI arguments. pub(crate) struct CliArgs { pub config_path: String, @@ -231,9 +264,11 @@ fn print_help() { #[cfg(test)] mod tests { + use std::path::{Path, PathBuf}; + use super::{ expected_handshake_close_description, is_expected_handshake_eof, peer_close_description, - resolve_runtime_config_path, + resolve_runtime_base_dir, resolve_runtime_config_path, }; use crate::error::{ProxyError, StreamError}; @@ -304,6 +339,92 @@ mod tests { let _ = std::fs::remove_dir(&startup_cwd); } + #[test] + fn resolve_runtime_base_dir_prefers_cli_data_path() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let startup_cwd = std::env::temp_dir().join(format!("telemt_runtime_base_cwd_{nonce}")); + let data_path = std::env::temp_dir().join(format!("telemt_runtime_base_data_{nonce}")); + std::fs::create_dir_all(&startup_cwd).unwrap(); + std::fs::create_dir_all(&data_path).unwrap(); + + let resolved = resolve_runtime_base_dir( + &startup_cwd.join("config.toml"), + &startup_cwd, + true, + Some(&data_path), + ); + assert_eq!(resolved, data_path.canonicalize().unwrap()); + + let _ = std::fs::remove_dir(&data_path); + let _ = std::fs::remove_dir(&startup_cwd); + } + + #[test] + fn resolve_runtime_base_dir_uses_working_directory_before_explicit_config_parent() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let startup_cwd = std::env::temp_dir().join(format!("telemt_runtime_base_start_{nonce}")); + let config_dir = std::env::temp_dir().join(format!("telemt_runtime_base_cfg_{nonce}")); + std::fs::create_dir_all(&startup_cwd).unwrap(); + std::fs::create_dir_all(&config_dir).unwrap(); + + let resolved = + resolve_runtime_base_dir(&config_dir.join("telemt.toml"), &startup_cwd, true, None); + assert_eq!(resolved, startup_cwd.canonicalize().unwrap()); + + let _ = std::fs::remove_dir(&config_dir); + let _ = std::fs::remove_dir(&startup_cwd); + } + + #[test] + fn resolve_runtime_base_dir_uses_explicit_config_parent_from_root() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let config_dir = std::env::temp_dir().join(format!("telemt_runtime_base_root_cfg_{nonce}")); + std::fs::create_dir_all(&config_dir).unwrap(); + + let resolved = + resolve_runtime_base_dir(&config_dir.join("telemt.toml"), Path::new("/"), true, None); + assert_eq!(resolved, config_dir.canonicalize().unwrap()); + + let _ = std::fs::remove_dir(&config_dir); + } + + #[test] + fn resolve_runtime_base_dir_uses_systemd_working_directory_before_etc() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let startup_cwd = + std::env::temp_dir().join(format!("telemt_runtime_base_systemd_{nonce}")); + std::fs::create_dir_all(&startup_cwd).unwrap(); + + let resolved = + resolve_runtime_base_dir(&startup_cwd.join("config.toml"), &startup_cwd, false, None); + assert_eq!(resolved, startup_cwd.canonicalize().unwrap()); + + let _ = std::fs::remove_dir(&startup_cwd); + } + + #[test] + fn resolve_runtime_base_dir_falls_back_to_etc_from_root() { + let resolved = resolve_runtime_base_dir( + Path::new("/etc/telemt/config.toml"), + Path::new("/"), + false, + None, + ); + assert_eq!(resolved, PathBuf::from("/etc/telemt")); + } + #[test] fn expected_handshake_eof_matches_connection_reset() { let err = ProxyError::Io(std::io::Error::from(std::io::ErrorKind::ConnectionReset)); diff --git a/src/maestro/mod.rs b/src/maestro/mod.rs index 4c5b98a..3cce3dc 100644 --- a/src/maestro/mod.rs +++ b/src/maestro/mod.rs @@ -47,7 +47,7 @@ use crate::stats::{ReplayChecker, Stats}; use crate::stream::BufferPool; use crate::transport::UpstreamManager; use crate::transport::middle_proxy::MePool; -use helpers::{parse_cli, resolve_runtime_config_path}; +use helpers::{parse_cli, resolve_runtime_base_dir, resolve_runtime_config_path}; #[cfg(unix)] use crate::daemon::{DaemonOptions, PidFile, drop_privileges}; @@ -112,8 +112,51 @@ async fn run_telemt_core( std::process::exit(1); } }; + if let Some(ref data_path) = data_path + && !data_path.is_absolute() + { + eprintln!( + "[telemt] data_path must be absolute: {}", + data_path.display() + ); + std::process::exit(1); + } let mut config_path = resolve_runtime_config_path(&config_path_cli, &startup_cwd, config_path_explicit); + let runtime_base_dir = resolve_runtime_base_dir( + &config_path, + &startup_cwd, + config_path_explicit, + data_path.as_deref(), + ); + + if !runtime_base_dir.exists() + && let Err(e) = std::fs::create_dir_all(&runtime_base_dir) + { + eprintln!( + "[telemt] Can't create runtime directory {}: {}", + runtime_base_dir.display(), + e + ); + std::process::exit(1); + } + + if !runtime_base_dir.is_dir() { + eprintln!( + "[telemt] Runtime path exists but is not a directory: {}", + runtime_base_dir.display() + ); + std::process::exit(1); + } + + if let Err(e) = std::env::set_current_dir(&runtime_base_dir) { + eprintln!( + "[telemt] Can't use runtime directory {}: {}", + runtime_base_dir.display(), + e + ); + std::process::exit(1); + } let mut config = match ProxyConfig::load(&config_path) { Ok(c) => c, @@ -156,16 +199,15 @@ async fn run_telemt_core( ); } } else { - let system_dir = std::path::Path::new("/etc/telemt"); - let system_config_path = system_dir.join("telemt.toml"); - let startup_config_path = startup_cwd.join("config.toml"); + let runtime_config_path = runtime_base_dir.join("telemt.toml"); + let fallback_config_path = runtime_base_dir.join("config.toml"); let mut persisted = false; if let Some(serialized) = serialized.as_ref() { - match std::fs::create_dir_all(system_dir) { - Ok(()) => match std::fs::write(&system_config_path, serialized) { + match std::fs::create_dir_all(&runtime_base_dir) { + Ok(()) => match std::fs::write(&runtime_config_path, serialized) { Ok(()) => { - config_path = system_config_path; + config_path = runtime_config_path; eprintln!( "[telemt] Created default config at {}", config_path.display() @@ -175,7 +217,7 @@ async fn run_telemt_core( Err(write_error) => { eprintln!( "[telemt] Warning: failed to write default config at {}: {}", - system_config_path.display(), + runtime_config_path.display(), write_error ); } @@ -183,16 +225,16 @@ async fn run_telemt_core( Err(create_error) => { eprintln!( "[telemt] Warning: failed to create {}: {}", - system_dir.display(), + runtime_base_dir.display(), create_error ); } } if !persisted { - match std::fs::write(&startup_config_path, serialized) { + match std::fs::write(&fallback_config_path, serialized) { Ok(()) => { - config_path = startup_config_path; + config_path = fallback_config_path; eprintln!( "[telemt] Created default config at {}", config_path.display() @@ -202,7 +244,7 @@ async fn run_telemt_core( Err(write_error) => { eprintln!( "[telemt] Warning: failed to write default config at {}: {}", - startup_config_path.display(), + fallback_config_path.display(), write_error ); }