use once_cell::sync::Lazy;
use std::env;
pub struct NetworkConfig {
pub default_port: u16,
pub default_qssh_port: u16,
pub connect_timeout_secs: u64,
pub read_timeout_secs: u64,
pub keepalive_interval_secs: u64,
pub max_packet_size: usize,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
default_port: env::var("QSSH_DEFAULT_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(22),
default_qssh_port: env::var("QSSH_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(22222),
connect_timeout_secs: env::var("QSSH_CONNECT_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(30),
read_timeout_secs: env::var("QSSH_READ_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(120),
keepalive_interval_secs: env::var("QSSH_KEEPALIVE_INTERVAL")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(60),
max_packet_size: env::var("QSSH_MAX_PACKET_SIZE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(65536),
}
}
}
pub struct BufferConfig {
pub default_buffer_size: usize,
pub small_buffer_size: usize,
pub large_buffer_size: usize,
pub channel_window_size: u32,
pub channel_max_packet: u32,
}
impl Default for BufferConfig {
fn default() -> Self {
Self {
default_buffer_size: env::var("QSSH_BUFFER_SIZE")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(8192),
small_buffer_size: env::var("QSSH_SMALL_BUFFER")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1024),
large_buffer_size: env::var("QSSH_LARGE_BUFFER")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(65536),
channel_window_size: env::var("QSSH_CHANNEL_WINDOW")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1024 * 1024),
channel_max_packet: env::var("QSSH_CHANNEL_MAX_PACKET")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(32768),
}
}
}
pub struct SecurityConfig {
pub max_auth_attempts: u32,
pub auth_timeout_secs: u64,
pub key_rotation_interval_secs: u64,
pub session_timeout_secs: u64,
pub max_connections: usize,
pub rate_limit_per_minute: u32,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
max_auth_attempts: env::var("QSSH_MAX_AUTH_ATTEMPTS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(3),
auth_timeout_secs: env::var("QSSH_AUTH_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(60),
key_rotation_interval_secs: env::var("QSSH_KEY_ROTATION")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(3600),
session_timeout_secs: env::var("QSSH_SESSION_TIMEOUT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(86400), max_connections: env::var("QSSH_MAX_CONNECTIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(100),
rate_limit_per_minute: env::var("QSSH_RATE_LIMIT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(60),
}
}
}
pub struct CryptoConfig {
pub default_algorithm: &'static str,
pub kdf_iterations: u32,
pub salt_length: usize,
pub nonce_length: usize,
pub tag_length: usize,
}
impl Default for CryptoConfig {
fn default() -> Self {
Self {
default_algorithm: "falcon512",
kdf_iterations: env::var("QSSH_KDF_ITERATIONS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(100_000),
salt_length: 16,
nonce_length: 12,
tag_length: 16,
}
}
}
pub struct X11Config {
pub base_display_port: u16,
pub max_displays: u32,
pub default_display: u32,
pub auth_timeout_secs: u64,
}
impl Default for X11Config {
fn default() -> Self {
Self {
base_display_port: env::var("QSSH_X11_BASE_PORT")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(6000),
max_displays: env::var("QSSH_X11_MAX_DISPLAYS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(100),
default_display: env::var("DISPLAY")
.ok()
.and_then(|s| {
s.split(':')
.nth(1)
.and_then(|part| part.split('.').next())
.and_then(|num| num.parse().ok())
})
.unwrap_or(0),
auth_timeout_secs: 60,
}
}
}
pub struct TerminalConfig {
pub default_term: &'static str,
pub default_rows: u16,
pub default_cols: u16,
pub default_pixel_width: u16,
pub default_pixel_height: u16,
}
impl Default for TerminalConfig {
fn default() -> Self {
Self {
default_term: "xterm-256color",
default_rows: env::var("LINES")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(24),
default_cols: env::var("COLUMNS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(80),
default_pixel_width: 640,
default_pixel_height: 480,
}
}
}
pub struct PathConfig {
pub config_dir: String,
pub known_hosts_file: String,
pub authorized_keys_file: String,
pub identity_file: String,
pub temp_dir: String,
pub log_dir: String,
}
impl Default for PathConfig {
fn default() -> Self {
let home = env::var("HOME").unwrap_or_else(|_| ".".to_string());
let config_dir = env::var("QSSH_CONFIG_DIR")
.unwrap_or_else(|_| format!("{}/.qssh", home));
Self {
config_dir: config_dir.clone(),
known_hosts_file: env::var("QSSH_KNOWN_HOSTS")
.unwrap_or_else(|_| format!("{}/known_hosts", config_dir)),
authorized_keys_file: env::var("QSSH_AUTHORIZED_KEYS")
.unwrap_or_else(|_| format!("{}/authorized_keys", config_dir)),
identity_file: env::var("QSSH_IDENTITY")
.unwrap_or_else(|_| format!("{}/id_qssh", config_dir)),
temp_dir: env::var("TMPDIR")
.or_else(|_| env::var("TEMP"))
.or_else(|_| env::var("TMP"))
.unwrap_or_else(|_| "/tmp".to_string()),
log_dir: env::var("QSSH_LOG_DIR")
.unwrap_or_else(|_| format!("{}/logs", config_dir)),
}
}
}
pub static NETWORK: Lazy<NetworkConfig> = Lazy::new(NetworkConfig::default);
pub static BUFFER: Lazy<BufferConfig> = Lazy::new(BufferConfig::default);
pub static SECURITY: Lazy<SecurityConfig> = Lazy::new(SecurityConfig::default);
pub static CRYPTO: Lazy<CryptoConfig> = Lazy::new(CryptoConfig::default);
pub static X11: Lazy<X11Config> = Lazy::new(X11Config::default);
pub static TERMINAL: Lazy<TerminalConfig> = Lazy::new(TerminalConfig::default);
pub static PATHS: Lazy<PathConfig> = Lazy::new(PathConfig::default);
pub mod protocol {
pub const VERSION: &str = "QSSH-2.0";
pub const MIN_PACKET_SIZE: usize = 16;
pub const MAX_PACKET_SIZE: usize = 35000;
pub const PADDING_MULTIPLE: usize = 8;
}
pub mod msg_codes {
pub const DISCONNECT: u8 = 1;
pub const IGNORE: u8 = 2;
pub const UNIMPLEMENTED: u8 = 3;
pub const DEBUG: u8 = 4;
pub const SERVICE_REQUEST: u8 = 5;
pub const SERVICE_ACCEPT: u8 = 6;
pub const KEXINIT: u8 = 20;
pub const NEWKEYS: u8 = 21;
pub const USERAUTH_REQUEST: u8 = 50;
pub const USERAUTH_FAILURE: u8 = 51;
pub const USERAUTH_SUCCESS: u8 = 52;
pub const USERAUTH_BANNER: u8 = 53;
pub const CHANNEL_OPEN: u8 = 90;
pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91;
pub const CHANNEL_OPEN_FAILURE: u8 = 92;
pub const CHANNEL_WINDOW_ADJUST: u8 = 93;
pub const CHANNEL_DATA: u8 = 94;
pub const CHANNEL_EXTENDED_DATA: u8 = 95;
pub const CHANNEL_EOF: u8 = 96;
pub const CHANNEL_CLOSE: u8 = 97;
pub const CHANNEL_REQUEST: u8 = 98;
pub const CHANNEL_SUCCESS: u8 = 99;
pub const CHANNEL_FAILURE: u8 = 100;
}
pub fn get_config_value<T: std::str::FromStr>(key: &str, default: T) -> T {
env::var(key)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_config() {
assert_eq!(NETWORK.default_qssh_port, 22222);
assert!(NETWORK.connect_timeout_secs > 0);
}
#[test]
fn test_buffer_config() {
assert!(BUFFER.default_buffer_size > 0);
assert!(BUFFER.large_buffer_size > BUFFER.default_buffer_size);
}
#[test]
fn test_security_config() {
assert!(SECURITY.max_auth_attempts > 0);
assert!(SECURITY.key_rotation_interval_secs > 0);
}
}