212 lines
7.1 KiB
Rust
212 lines
7.1 KiB
Rust
mod cli;
|
|
mod config;
|
|
mod error;
|
|
mod proxy;
|
|
mod router;
|
|
mod security;
|
|
mod status;
|
|
mod tor;
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::Ordering;
|
|
|
|
use clap::Parser;
|
|
use tracing::{error, info};
|
|
|
|
use crate::cli::{Cli, Command};
|
|
use crate::config::{AppConfig, LogTarget, SecurityMode};
|
|
use crate::proxy::socks5::Socks5Server;
|
|
use crate::tor::engine::TorEngine;
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
match cli.command {
|
|
Command::Start { config, i_know_what_im_doing } => {
|
|
let cfg = match AppConfig::load(&config) {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
eprintln!("Error loading config: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
init_logging(&cfg);
|
|
|
|
// Security startup validation
|
|
if let Err(e) = security::validate_startup(&cfg, i_know_what_im_doing) {
|
|
error!("{e}");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
// Reduced security runtime gate
|
|
if cfg.security.mode == SecurityMode::Reduced && !i_know_what_im_doing {
|
|
error!(
|
|
"security.mode = \"reduced\" requires --i-know-what-im-doing flag. Aborting."
|
|
);
|
|
std::process::exit(1);
|
|
}
|
|
|
|
let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
|
|
rt.block_on(async move {
|
|
run_daemon(cfg).await;
|
|
});
|
|
}
|
|
|
|
Command::ConfigCheck {
|
|
config,
|
|
test_client_ip,
|
|
test_onion,
|
|
} => {
|
|
let cfg = match AppConfig::load(&config) {
|
|
Ok(c) => {
|
|
println!("Config OK: {}", config.display());
|
|
c
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Config ERROR: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
println!(" mode: {:?}", cfg.mode);
|
|
println!(" trust_domain: {:?}", cfg.trust_domain);
|
|
println!(" security: {:?}", cfg.security.mode);
|
|
println!(" socks5: {}", cfg.proxy.socks5_listen);
|
|
println!(" auth: {}", if cfg.proxy.auth.enabled { "enabled" } else { "disabled" });
|
|
println!(" onion profile: {:?}", cfg.tor.profiles.default_onion);
|
|
println!(" clearnet profile: {:?}", cfg.tor.profiles.default_clearnet);
|
|
println!();
|
|
|
|
security::dry_run_acl(
|
|
&cfg,
|
|
test_client_ip.as_deref(),
|
|
test_onion.as_deref(),
|
|
);
|
|
}
|
|
|
|
Command::Status { json, data_dir } => {
|
|
let dir = data_dir.unwrap_or_else(|| std::path::PathBuf::from("/var/lib/onion-transit/arti"));
|
|
match status::read_status_file(&dir) {
|
|
Some(s) => {
|
|
if json {
|
|
println!("{}", serde_json::to_string_pretty(&s).unwrap());
|
|
} else {
|
|
println!("Onion-Transit v{}", s.version);
|
|
println!(" uptime: {}s", s.uptime_secs);
|
|
println!(" active streams: {}", s.active_streams);
|
|
println!(" total streams: {}", s.total_streams);
|
|
println!(" bootstrap attempts: {}", s.bootstrap_attempts);
|
|
}
|
|
}
|
|
None => {
|
|
if json {
|
|
let status = serde_json::json!({
|
|
"error": "no running daemon found",
|
|
"status_file": status::status_file_path(&dir).display().to_string(),
|
|
});
|
|
println!("{}", serde_json::to_string_pretty(&status).unwrap());
|
|
} else {
|
|
eprintln!(
|
|
"No running daemon found. Status file: {}",
|
|
status::status_file_path(&dir).display()
|
|
);
|
|
eprintln!("Is onion-transit running with data_dir = {}?", dir.display());
|
|
}
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initialize tracing based on config.
|
|
fn init_logging(config: &AppConfig) {
|
|
use tracing_subscriber::{fmt, EnvFilter};
|
|
|
|
let filter = EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| EnvFilter::new(&config.logging.level));
|
|
|
|
match config.logging.target {
|
|
LogTarget::Stdout => {
|
|
fmt()
|
|
.with_env_filter(filter)
|
|
.with_target(false)
|
|
.init();
|
|
}
|
|
LogTarget::File => {
|
|
if let Some(ref path) = config.logging.file_path {
|
|
let dir = path.parent().unwrap_or(std::path::Path::new("."));
|
|
let filename = path
|
|
.file_name()
|
|
.unwrap_or(std::ffi::OsStr::new("onion-transit.log"));
|
|
let file_appender = tracing_appender::rolling::never(dir, filename);
|
|
fmt()
|
|
.with_env_filter(filter)
|
|
.with_writer(file_appender)
|
|
.with_ansi(false)
|
|
.init();
|
|
} else {
|
|
// Fallback to stdout if no file path configured
|
|
fmt().with_env_filter(filter).init();
|
|
}
|
|
}
|
|
LogTarget::Syslog => {
|
|
// Syslog: use stdout with JSON format as a reasonable approximation
|
|
// that integrates well with journald.
|
|
fmt()
|
|
.with_env_filter(filter)
|
|
.json()
|
|
.init();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Run the main daemon: bootstrap Tor, start SOCKS5 proxy, handle signals.
|
|
async fn run_daemon(config: AppConfig) {
|
|
let config = Arc::new(config);
|
|
|
|
// Bootstrap Arti
|
|
info!("Starting Onion-Transit daemon...");
|
|
let engine = match TorEngine::bootstrap(&config.tor).await {
|
|
Ok(e) => Arc::new(e),
|
|
Err(e) => {
|
|
error!("Failed to bootstrap Tor: {e}");
|
|
std::process::exit(1);
|
|
}
|
|
};
|
|
|
|
info!(
|
|
active_streams = engine.metrics.active_streams.load(Ordering::Relaxed),
|
|
total_streams = engine.metrics.total_streams.load(Ordering::Relaxed),
|
|
"Tor engine ready"
|
|
);
|
|
|
|
// Start status writer (writes status file every 5 seconds)
|
|
let start_time = std::time::Instant::now();
|
|
let status_metrics = Arc::clone(&engine.metrics);
|
|
let status_data_dir = config.tor.data_dir.clone();
|
|
tokio::spawn(async move {
|
|
status::status_writer_task(status_metrics, status_data_dir, start_time, 5).await;
|
|
});
|
|
|
|
// Start SOCKS5 proxy
|
|
let socks_server = Socks5Server::new(Arc::clone(&config), Arc::clone(&engine));
|
|
|
|
tokio::select! {
|
|
result = socks_server.run() => {
|
|
if let Err(e) = result {
|
|
error!("SOCKS5 server error: {e}");
|
|
}
|
|
}
|
|
_ = tokio::signal::ctrl_c() => {
|
|
info!("Received Ctrl+C, shutting down...");
|
|
}
|
|
}
|
|
|
|
info!(
|
|
total_streams = engine.metrics.total_streams.load(Ordering::Relaxed),
|
|
"Onion-Transit daemon stopped."
|
|
);
|
|
}
|