#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
#![doc = include_str!("../README.md")]
#![cfg_attr(not(ci_arti_stable), allow(renamed_and_removed_lints))]
#![cfg_attr(not(ci_arti_nightly), allow(unknown_lints))]
#![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
pub mod cfg;
pub mod logging;
#[cfg(all(feature = "experimental-api", feature = "dns-proxy"))]
pub mod dns;
#[cfg(feature = "experimental-api")]
pub mod exit;
#[cfg(feature = "experimental-api")]
pub mod process;
#[cfg(feature = "experimental-api")]
pub mod reload_cfg;
#[cfg(feature = "experimental-api")]
pub mod socks;
#[cfg(all(not(feature = "experimental-api"), feature = "dns-proxy"))]
mod dns;
#[cfg(not(feature = "experimental-api"))]
mod exit;
#[cfg(not(feature = "experimental-api"))]
mod process;
#[cfg(not(feature = "experimental-api"))]
mod reload_cfg;
#[cfg(not(feature = "experimental-api"))]
mod socks;
#[cfg(feature = "rpc")]
mod rpc;
use std::ffi::OsString;
use std::fmt::Write;
pub use cfg::{
ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig, ArtiConfig, ArtiConfigBuilder,
ProxyConfig, ProxyConfigBuilder, SystemConfig, SystemConfigBuilder, ARTI_EXAMPLE_CONFIG,
};
pub use logging::{LoggingConfig, LoggingConfigBuilder};
use arti_client::config::default_config_files;
use arti_client::{TorClient, TorClientConfig};
use safelog::with_safe_logging_suppressed;
use tor_config::ConfigurationSources;
use tor_rtcompat::{BlockOn, Runtime};
use anyhow::{Context, Error, Result};
use clap::{value_parser, Arg, ArgAction, Command};
#[allow(unused_imports)]
use tracing::{error, info, warn};
type PinnedFuture<T> = std::pin::Pin<Box<dyn futures::Future<Output = T>>>;
fn create_runtime() -> std::io::Result<impl Runtime> {
cfg_if::cfg_if! {
if #[cfg(feature="rpc")] {
use tor_rtcompat::PreferredRuntime as ChosenRuntime;
} else if #[cfg(all(feature="tokio", feature="native-tls"))] {
use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
} else if #[cfg(all(feature="tokio", feature="rustls"))] {
use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
} else if #[cfg(all(feature="async-std", feature="native-tls"))] {
use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
} else if #[cfg(all(feature="async-std", feature="rustls"))] {
use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
} else {
compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
}
}
ChosenRuntime::create()
}
fn list_enabled_features() -> &'static [&'static str] {
&[
#[cfg(feature = "journald")]
"journald",
#[cfg(any(feature = "static-sqlite", feature = "static"))]
"static-sqlite",
#[cfg(any(feature = "static-native-tls", feature = "static"))]
"static-native-tls",
]
}
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
async fn run<R: Runtime>(
runtime: R,
socks_port: u16,
dns_port: u16,
config_sources: ConfigurationSources,
arti_config: ArtiConfig,
client_config: TorClientConfig,
) -> Result<()> {
use arti_client::BootstrapBehavior::OnDemand;
use futures::FutureExt;
#[cfg(feature = "rpc")]
let rpc_path = {
if let Some(path) = &arti_config.rpc().rpc_listen {
let path = path.path()?;
let parent = path
.parent()
.ok_or(anyhow::anyhow!("No parent directory for rpc_listen path?"))?;
client_config
.fs_mistrust()
.verifier()
.make_secure_dir(parent)?;
if path.exists() {
std::fs::remove_file(&path)?;
}
Some(path)
} else {
None
}
};
let client_builder = TorClient::with_runtime(runtime.clone())
.config(client_config)
.bootstrap_behavior(OnDemand);
let client = client_builder.create_unbootstrapped()?;
reload_cfg::watch_for_config_changes(config_sources, arti_config, client.clone())?;
#[cfg(all(feature = "rpc", feature = "tokio"))]
let rpc_mgr = {
if let Some(listen_path) = rpc_path {
Some(rpc::launch_rpc_listener(
&runtime,
listen_path,
client.clone(),
)?)
} else {
None
}
};
let mut proxy: Vec<PinnedFuture<(Result<()>, &str)>> = Vec::new();
if socks_port != 0 {
let runtime = runtime.clone();
let client = client.isolated_client();
proxy.push(Box::pin(async move {
let res = socks::run_socks_proxy(
runtime,
client,
socks_port,
#[cfg(all(feature = "rpc", feature = "tokio"))]
rpc_mgr,
)
.await;
(res, "SOCKS")
}));
}
#[cfg(feature = "dns-proxy")]
if dns_port != 0 {
let runtime = runtime.clone();
let client = client.isolated_client();
proxy.push(Box::pin(async move {
let res = dns::run_dns_resolver(runtime, client, dns_port).await;
(res, "DNS")
}));
}
#[cfg(not(feature = "dns-proxy"))]
if dns_port != 0 {
warn!("Tried to specify a DNS proxy port, but Arti was built without dns-proxy support.");
return Ok(());
}
if proxy.is_empty() {
warn!("No proxy port set; specify -p PORT (for `socks_port`) or -d PORT (for `dns_port`). Alternatively, use the `socks_port` or `dns_port` configuration option.");
return Ok(());
}
let proxy = futures::future::select_all(proxy).map(|(finished, _index, _others)| finished);
futures::select!(
r = exit::wait_for_ctrl_c().fuse()
=> r.context("waiting for termination signal"),
r = proxy.fuse()
=> r.0.context(format!("{} proxy failure", r.1)),
r = async {
client.bootstrap().await?;
info!("Sufficiently bootstrapped; system SOCKS now functional.");
futures::future::pending::<Result<()>>().await
}.fuse()
=> r.context("bootstrap"),
)
}
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
fn main_main<I, T>(cli_args: I) -> Result<()>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
let mut config_file_help = "Specify which config file(s) to read.".to_string();
if let Ok(default) = default_config_files() {
write!(config_file_help, " Defaults to {:?}", default).unwrap();
}
let runtime = create_runtime()?;
let features = list_enabled_features();
let long_version = format!(
"{}\nusing runtime: {:?}\noptional features: {}",
env!("CARGO_PKG_VERSION"),
runtime,
if features.is_empty() {
"<none>".into()
} else {
features.join(", ")
}
);
let clap_app =
Command::new("Arti")
.version(env!("CARGO_PKG_VERSION"))
.long_version(&long_version as &str)
.author("The Tor Project Developers")
.about("A Rust Tor implementation.")
.override_usage("arti <SUBCOMMAND> [OPTIONS]")
.arg(
Arg::new("config-files")
.short('c')
.long("config")
.action(ArgAction::Set)
.value_name("FILE")
.value_parser(value_parser!(OsString))
.action(ArgAction::Append)
.global(true)
.help(config_file_help.as_str()),
)
.arg(
Arg::new("option")
.short('o')
.action(ArgAction::Set)
.value_name("KEY=VALUE")
.action(ArgAction::Append)
.global(true)
.help("Override config file parameters, using TOML-like syntax."),
)
.arg(
Arg::new("loglevel")
.short('l')
.long("log-level")
.global(true)
.action(ArgAction::Set)
.value_name("LEVEL")
.help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
)
.arg(
Arg::new("disable-fs-permission-checks")
.long("disable-fs-permission-checks")
.global(true)
.action(ArgAction::SetTrue)
.help("Don't check permissions on the files we use."),
)
.subcommand(
Command::new("proxy")
.about(
"Run Arti in SOCKS proxy mode, proxying connections through the Tor network.",
)
.arg(
Arg::new("socks-port")
.short('p')
.action(ArgAction::Set)
.value_name("PORT")
.help("Port to listen on for SOCKS connections (overrides the port in the config if specified).")
)
.arg(
Arg::new("dns-port")
.short('d')
.action(ArgAction::Set)
.value_name("PORT")
.help("Port to listen on for DNS request (overrides the port in the config if specified).")
)
)
.subcommand_required(true)
.arg_required_else_help(true);
let pre_config_logging_writer = || {
eprint!("arti:");
std::io::stderr()
};
let pre_config_logging = tracing_subscriber::fmt()
.without_time()
.with_writer(pre_config_logging_writer)
.finish();
let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
let matches = clap_app.try_get_matches_from(cli_args)?;
let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
let cfg_mistrust = if fs_mistrust_disabled {
fs_mistrust::Mistrust::new_dangerously_trust_everyone()
} else {
fs_mistrust::MistrustBuilder::default()
.controlled_by_env_var(arti_client::config::FS_PERMISSIONS_CHECKS_DISABLE_VAR)
.build()
.expect("Could not construct default fs-mistrust")
};
let mut override_options: Vec<String> = matches
.get_many::<String>("option")
.unwrap_or_default()
.cloned()
.collect();
if fs_mistrust_disabled {
override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
}
let cfg_sources = {
let mut cfg_sources = ConfigurationSources::try_from_cmdline(
|| default_config_files().context("identify default config file locations"),
matches
.get_many::<OsString>("config-files")
.unwrap_or_default(),
override_options,
)?;
cfg_sources.set_mistrust(cfg_mistrust);
cfg_sources
};
let cfg = cfg_sources.load()?;
let (config, client_config) =
tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
let log_mistrust = client_config.fs_mistrust().clone();
Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
})?;
let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
let _log_guards = logging::setup_logging(
config.logging(),
&log_mistrust,
matches.get_one::<String>("loglevel").map(|s| s.as_str()),
)?;
if !config.application().allow_running_as_root {
process::exit_if_root();
}
#[cfg(feature = "harden")]
if !config.application().permit_debugging {
if let Err(e) = process::enable_process_hardening() {
error!("Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true.");
return Err(e);
}
}
if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
let socks_port = match (
proxy_matches.get_one::<String>("socks-port"),
config.proxy().socks_listen.localhost_port_legacy()?,
) {
(Some(p), _) => p.parse().expect("Invalid port specified"),
(None, Some(s)) => s,
(None, None) => 0,
};
let dns_port = match (
proxy_matches.get_one::<String>("dns-port"),
config.proxy().dns_listen.localhost_port_legacy()?,
) {
(Some(p), _) => p.parse().expect("Invalid port specified"),
(None, Some(s)) => s,
(None, None) => 0,
};
info!(
"Starting Arti {} in SOCKS proxy mode on port {}...",
env!("CARGO_PKG_VERSION"),
socks_port
);
process::use_max_file_limit(&config);
let rt_copy = runtime.clone();
rt_copy.block_on(run(
runtime,
socks_port,
dns_port,
cfg_sources,
config,
client_config,
))?;
Ok(())
} else {
panic!("Subcommand added to clap subcommand list, but not yet implemented")
}
}
pub fn main() {
match main_main(std::env::args_os()) {
Ok(()) => {}
Err(e) => {
if let Some(arti_err) = e.downcast_ref::<arti_client::Error>() {
if let Some(hint) = arti_err.hint() {
info!("{}", hint);
}
}
match e.downcast_ref::<clap::Error>() {
Some(clap_err) => clap_err.exit(),
None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
}
}
}
}