1#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_duration_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
48#![allow(clippy::print_stderr)]
50#![allow(clippy::print_stdout)]
51
52pub mod cfg;
53pub mod logging;
54#[cfg(not(feature = "onion-service-service"))]
55mod onion_proxy_disabled;
56
57mod subcommands;
58
59macro_rules! semipublic_mod {
66 {
67 $(
68 $( #[$meta:meta] )*
69 mod $name:ident ;
70 )*
71 } => {
72 $(
73 $( #[$meta])*
74 cfg_if::cfg_if! {
75 if #[cfg(feature="experimental-api")] {
76 pub mod $name;
77 } else {
78 mod $name;
79 }
80 }
81 )*
82 }
83}
84
85semipublic_mod! {
86 #[cfg(feature = "dns-proxy")]
87 mod dns;
88 mod exit;
89 #[cfg(feature="onion-service-service")]
90 mod onion_proxy;
91 mod process;
92 mod reload_cfg;
93 mod socks;
94}
95#[cfg_attr(not(feature = "rpc"), path = "rpc_stub.rs")]
96mod rpc;
97
98use std::ffi::OsString;
99use std::fmt::Write;
100
101pub use cfg::{
102 ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig, ArtiConfig, ArtiConfigBuilder,
103 ProxyConfig, ProxyConfigBuilder, SystemConfig, SystemConfigBuilder, ARTI_EXAMPLE_CONFIG,
104};
105pub use logging::{LoggingConfig, LoggingConfigBuilder};
106
107use arti_client::config::default_config_files;
108use arti_client::TorClient;
109use safelog::with_safe_logging_suppressed;
110use tor_config::mistrust::BuilderExt as _;
111use tor_config::ConfigurationSources;
112use tor_rtcompat::ToplevelRuntime;
113
114use anyhow::{Context, Error, Result};
115use clap::{value_parser, Arg, ArgAction, Command};
116#[allow(unused_imports)]
117use tracing::{error, info, warn};
118
119#[cfg(any(feature = "hsc", feature = "onion-service-service"))]
120use clap::Subcommand as _;
121
122#[cfg(feature = "experimental-api")]
123#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
124pub use subcommands::proxy::run_proxy as run;
125
126fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
128 cfg_if::cfg_if! {
129 if #[cfg(all(feature="tokio", feature="native-tls"))] {
130 use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
131 } else if #[cfg(all(feature="tokio", feature="rustls"))] {
132 use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
133 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
134 rustls_crate::crypto::ring::default_provider(),
135
136 );
137 } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
138 use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
139 } else if #[cfg(all(feature="async-std", feature="rustls"))] {
140 use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
141 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
142 rustls_crate::crypto::ring::default_provider(),
143 );
144 } else {
145 compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
146 }
147 }
148 ChosenRuntime::create()
149}
150
151fn list_enabled_features() -> &'static [&'static str] {
153 &[
157 #[cfg(feature = "journald")]
158 "journald",
159 #[cfg(any(feature = "static-sqlite", feature = "static"))]
160 "static-sqlite",
161 #[cfg(any(feature = "static-native-tls", feature = "static"))]
162 "static-native-tls",
163 ]
164}
165
166#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
179#[allow(clippy::cognitive_complexity)]
180fn main_main<I, T>(cli_args: I) -> Result<()>
181where
182 I: IntoIterator<Item = T>,
183 T: Into<std::ffi::OsString> + Clone,
184{
185 let mut config_file_help = "Specify which config file(s) to read.".to_string();
189 if let Ok(default) = default_config_files() {
190 write!(config_file_help, " Defaults to {:?}", default).unwrap();
194 }
195
196 let runtime = create_runtime()?;
199 let features = list_enabled_features();
200 let long_version = format!(
201 "{}\nusing runtime: {:?}\noptional features: {}",
202 env!("CARGO_PKG_VERSION"),
203 runtime,
204 if features.is_empty() {
205 "<none>".into()
206 } else {
207 features.join(", ")
208 }
209 );
210
211 let clap_app = Command::new("Arti")
212 .version(env!("CARGO_PKG_VERSION"))
213 .long_version(long_version)
214 .author("The Tor Project Developers")
215 .about("A Rust Tor implementation.")
216 .override_usage("arti <SUBCOMMAND> [OPTIONS]")
222 .arg(
223 Arg::new("config-files")
224 .short('c')
225 .long("config")
226 .action(ArgAction::Set)
227 .value_name("FILE")
228 .value_parser(value_parser!(OsString))
229 .action(ArgAction::Append)
230 .global(true)
232 .help(config_file_help),
233 )
234 .arg(
235 Arg::new("option")
236 .short('o')
237 .action(ArgAction::Set)
238 .value_name("KEY=VALUE")
239 .action(ArgAction::Append)
240 .global(true)
241 .help("Override config file parameters, using TOML-like syntax."),
242 )
243 .arg(
244 Arg::new("loglevel")
245 .short('l')
246 .long("log-level")
247 .global(true)
248 .action(ArgAction::Set)
249 .value_name("LEVEL")
250 .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
251 )
252 .arg(
253 Arg::new("disable-fs-permission-checks")
254 .long("disable-fs-permission-checks")
255 .global(true)
256 .action(ArgAction::SetTrue)
257 .help("Don't check permissions on the files we use."),
258 )
259 .subcommand(
260 Command::new("proxy")
261 .about(
262 "Run Arti in SOCKS proxy mode, proxying connections through the Tor network.",
263 )
264 .arg(
265 Arg::new("socks-port")
266 .short('p')
267 .action(ArgAction::Set)
268 .value_name("PORT")
269 .help("Port to listen on for SOCKS connections (overrides the port in the config if specified).")
270 )
271 .arg(
272 Arg::new("dns-port")
273 .short('d')
274 .action(ArgAction::Set)
275 .value_name("PORT")
276 .help("Port to listen on for DNS request (overrides the port in the config if specified).")
277 )
278 )
279 .subcommand_required(true)
280 .arg_required_else_help(true);
281
282 cfg_if::cfg_if! {
286 if #[cfg(feature = "onion-service-service")] {
287 let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
288 }
289 }
290
291 cfg_if::cfg_if! {
292 if #[cfg(feature = "hsc")] {
293 let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
294 }
295 }
296
297 let pre_config_logging_writer = || {
306 eprint!("arti:");
308 std::io::stderr()
309 };
310 let pre_config_logging = tracing_subscriber::fmt()
311 .without_time()
312 .with_writer(pre_config_logging_writer)
313 .finish();
314 let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
315 let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
316 let matches = clap_app.try_get_matches_from(cli_args)?;
317
318 let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
319
320 let cfg_mistrust = if fs_mistrust_disabled {
323 fs_mistrust::Mistrust::new_dangerously_trust_everyone()
324 } else {
325 fs_mistrust::MistrustBuilder::default()
326 .build_for_arti()
327 .expect("Could not construct default fs-mistrust")
328 };
329
330 let mut override_options: Vec<String> = matches
331 .get_many::<String>("option")
332 .unwrap_or_default()
333 .cloned()
334 .collect();
335 if fs_mistrust_disabled {
336 override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
337 }
338
339 let cfg_sources = {
340 let mut cfg_sources = ConfigurationSources::try_from_cmdline(
341 || default_config_files().context("identify default config file locations"),
342 matches
343 .get_many::<OsString>("config-files")
344 .unwrap_or_default(),
345 override_options,
346 )?;
347 cfg_sources.set_mistrust(cfg_mistrust);
348 cfg_sources
349 };
350
351 let cfg = cfg_sources.load()?;
352 let (config, client_config) =
353 tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
354
355 let log_mistrust = client_config.fs_mistrust().clone();
356
357 Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
358 })?;
359 let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
362
363 let _log_guards = logging::setup_logging(
364 config.logging(),
365 &log_mistrust,
366 client_config.as_ref(),
367 matches.get_one::<String>("loglevel").map(|s| s.as_str()),
368 )?;
369
370 if !config.application().allow_running_as_root {
371 process::exit_if_root();
372 }
373
374 #[cfg(feature = "harden")]
375 if !config.application().permit_debugging {
376 if let Err(e) = process::enable_process_hardening() {
377 error!("Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true.");
378 return Err(e);
379 }
380 }
381
382 if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
384 return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
385 }
386
387 cfg_if::cfg_if! {
389 if #[cfg(feature = "onion-service-service")] {
390 if let Some(hss_matches) = matches.subcommand_matches("hss") {
391 return subcommands::hss::run(hss_matches, &config, &client_config);
392 }
393 }
394 }
395
396 cfg_if::cfg_if! {
398 if #[cfg(feature = "hsc")] {
399 if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
400 return subcommands::hsc::run(runtime, hsc_matches, &client_config);
401 }
402 }
403 }
404
405 panic!("Subcommand added to clap subcommand list, but not yet implemented");
406}
407
408pub fn main() {
434 match main_main(std::env::args_os()) {
435 Ok(()) => {}
436 Err(e) => {
437 use arti_client::HintableError;
438 if let Some(hint) = e.hint() {
439 info!("{}", hint);
440 }
441
442 match e.downcast_ref::<clap::Error>() {
443 Some(clap_err) => clap_err.exit(),
444 None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
445 }
446 }
447 }
448}