1#![cfg_attr(docsrs, feature(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_time_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)] #![allow(mismatched_lifetime_syntaxes)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
47#![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
51#![allow(clippy::print_stderr)]
53#![allow(clippy::print_stdout)]
54
55mod subcommands;
56
57macro_rules! semipublic_mod {
64 {
65 $(
66 $( #[$meta:meta] )*
67 $dflt_vis:vis mod $name:ident ;
68 )*
69 } => {
70 $(
71 cfg_if::cfg_if! {
72 if #[cfg(feature="experimental-api")] {
73 $( #[$meta])*
74 pub mod $name;
75 } else {
76 $( #[$meta])*
77 $dflt_vis mod $name;
78 }
79 }
80 )*
81 }
82}
83
84macro_rules! semipublic_use {
88 {
89 $dflt_vis:vis use $($tok:tt)+
90 } => {
91 cfg_if::cfg_if! {
92 if #[cfg(feature="experimental-api")] {
93 pub use $($tok)+
94 } else {
95 $dflt_vis use $($tok)+
96 }
97 }
98 }
99}
100
101semipublic_mod! {
102 mod cfg;
103 #[cfg(feature = "dns-proxy")]
104 mod dns;
105 mod exit;
106 mod logging;
107 #[cfg(feature="onion-service-service")]
108 mod onion_proxy;
109 mod process;
110 mod reload_cfg;
111 mod proxy;
112}
113
114cfg_if::cfg_if! {
115 if #[cfg(all(feature="experimental-api", feature="rpc"))] {
116 pub mod rpc;
117 } else if #[cfg(all(feature="experimental-api", not(feature="rpc")))] {
118 #[path = "rpc_stub.rs"]
119 pub mod rpc;
120 } else if #[cfg(feature = "rpc")] {
121 mod rpc;
122 } else {
123 #[path = "rpc_stub.rs"]
124 mod rpc;
125 }
126}
127
128use std::ffi::OsString;
129use std::fmt::Write;
130
131semipublic_use! {
132 use cfg::{
133 ARTI_EXAMPLE_CONFIG, ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig,
134 ArtiConfig, ArtiConfigBuilder, ProxyConfig, ProxyConfigBuilder, SystemConfig,
135 SystemConfigBuilder,
136 };
137}
138semipublic_use! {
139 use logging::{LoggingConfig, LoggingConfigBuilder};
140}
141
142use arti_client::TorClient;
143use arti_client::config::default_config_files;
144use safelog::with_safe_logging_suppressed;
145use tor_config::ConfigurationSources;
146use tor_config::mistrust::BuilderExt as _;
147use tor_rtcompat::ToplevelRuntime;
148
149use anyhow::{Context, Error, Result};
150use clap::{Arg, ArgAction, Command, value_parser};
151#[allow(unused_imports)]
152use tracing::{error, info, instrument, warn};
153
154#[cfg(any(
155 feature = "hsc",
156 feature = "onion-service-service",
157 feature = "onion-service-cli-extra",
158))]
159use clap::Subcommand as _;
160
161#[cfg(feature = "experimental-api")]
162#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
163pub use subcommands::proxy::run_proxy;
164
165#[allow(dead_code)]
167#[cfg(feature = "rustls-base")]
168fn rustls_crypto_provider() -> rustls_crate::crypto::CryptoProvider {
169 cfg_if::cfg_if! {
170 if #[cfg(feature = "rustls-aws-lc-rs")] {
171 rustls_crate::crypto::aws_lc_rs::default_provider()
172 } else if #[cfg(feature = "rustls-ring")]{
173 rustls_crate::crypto::ring::default_provider()
174 } else {
175 compile_error!(r#"When building with rustls, you must select "rustls-aws-lc-rs" or "rustls-ring". \
176 "rustls" is an alias for one of these."#);
177 }
179 }
180}
181
182fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
184 cfg_if::cfg_if! {
185 if #[cfg(all(feature="tokio", feature="native-tls"))] {
186 use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
187 } else if #[cfg(all(feature="tokio", feature="rustls-base"))] {
188 use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
189 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
192 rustls_crypto_provider()
193 );
194 } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
195 use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
196 } else if #[cfg(all(feature="async-std", feature="rustls-base"))] {
197 use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
198 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
201 rustls_crypto_provider()
202 );
203 } else {
204 compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
205 }
206 }
207 ChosenRuntime::create()
208}
209
210fn list_enabled_features() -> &'static [&'static str] {
212 &[
216 #[cfg(feature = "journald")]
217 "journald",
218 #[cfg(any(feature = "static-sqlite", feature = "static"))]
219 "static-sqlite",
220 #[cfg(any(feature = "static-native-tls", feature = "static"))]
221 "static-native-tls",
222 ]
223}
224
225#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
238#[allow(clippy::cognitive_complexity)]
239#[instrument(skip_all, level = "trace")]
240fn main_main<I, T>(cli_args: I) -> Result<()>
241where
242 I: IntoIterator<Item = T>,
243 T: Into<std::ffi::OsString> + Clone,
244{
245 let mut config_file_help = "Specify which config file(s) to read.".to_string();
249 if let Ok(default) = default_config_files() {
250 write!(config_file_help, " Defaults to {:?}", default).expect("Can't write to string");
254 }
255
256 let runtime = create_runtime()?;
257
258 tor_log_ratelim::install_runtime(runtime.clone())
260 .context("Failed to initialize tor-log-ratelim")?;
261
262 let features = list_enabled_features();
264 let long_version = format!(
265 "{}\nusing runtime: {:?}\noptional features: {}",
266 env!("CARGO_PKG_VERSION"),
267 runtime,
268 if features.is_empty() {
269 "<none>".into()
270 } else {
271 features.join(", ")
272 }
273 );
274
275 let clap_app = Command::new("Arti")
276 .override_usage("arti <SUBCOMMAND> [OPTIONS]")
282 .arg(
283 Arg::new("config-files")
284 .short('c')
285 .long("config")
286 .action(ArgAction::Set)
287 .value_name("FILE")
288 .value_parser(value_parser!(OsString))
289 .action(ArgAction::Append)
290 .global(true)
292 .help(config_file_help),
293 )
294 .arg(
295 Arg::new("option")
296 .short('o')
297 .action(ArgAction::Set)
298 .value_name("KEY=VALUE")
299 .action(ArgAction::Append)
300 .global(true)
301 .help("Override config file parameters, using TOML-like syntax."),
302 )
303 .arg(
304 Arg::new("loglevel")
305 .short('l')
306 .long("log-level")
307 .global(true)
308 .action(ArgAction::Set)
309 .value_name("LEVEL")
310 .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
311 )
312 .arg(
313 Arg::new("disable-fs-permission-checks")
314 .long("disable-fs-permission-checks")
315 .global(true)
316 .action(ArgAction::SetTrue)
317 .help("Don't check permissions on the files we use."),
318 )
319 .subcommand(
320 Command::new("proxy")
321 .about(
322 "Run Arti in proxy mode, proxying connections through the Tor network.",
323 )
324 .arg(
325 Arg::new("socks-port")
327 .short('p')
328 .action(ArgAction::Set)
329 .value_name("PORT")
330 .value_parser(clap::value_parser!(u16))
331 .help(r#"Localhost port to listen on for SOCKS connections (0 means "disabled"; overrides addresses in the config if specified)."#)
332 )
333 .arg(
334 Arg::new("dns-port")
335 .short('d')
336 .action(ArgAction::Set)
337 .value_name("PORT")
338 .value_parser(clap::value_parser!(u16))
339 .help(r#"Localhost port to listen on for DNS requests (0 means "disabled"; overrides addresses in the config if specified)."#)
340 )
341 )
342 .subcommand_required(true)
343 .arg_required_else_help(true);
344
345 cfg_if::cfg_if! {
349 if #[cfg(feature = "onion-service-service")] {
350 let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
351 }
352 }
353
354 cfg_if::cfg_if! {
355 if #[cfg(feature = "hsc")] {
356 let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
357 }
358 }
359
360 cfg_if::cfg_if! {
361 if #[cfg(feature = "onion-service-cli-extra")] {
362 let clap_app = subcommands::keys::KeysSubcommands::augment_subcommands(clap_app);
363 let clap_app = subcommands::raw::RawSubcommands::augment_subcommands(clap_app);
364 }
365 }
366
367 let clap_app = clap_app
368 .version(env!("CARGO_PKG_VERSION"))
369 .long_version(long_version)
370 .author("The Tor Project Developers")
371 .about("A Rust Tor implementation.");
372
373 let pre_config_logging_writer = || {
382 eprint!("arti:");
384 std::io::stderr()
385 };
386 let pre_config_logging = tracing_subscriber::fmt()
387 .without_time()
388 .with_writer(pre_config_logging_writer)
389 .finish();
390 let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
391 let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
392 let matches = clap_app.try_get_matches_from(cli_args)?;
393
394 let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
395
396 let cfg_mistrust = if fs_mistrust_disabled {
399 fs_mistrust::Mistrust::new_dangerously_trust_everyone()
400 } else {
401 fs_mistrust::MistrustBuilder::default()
402 .build_for_arti()
403 .expect("Could not construct default fs-mistrust")
404 };
405
406 let mut override_options: Vec<String> = matches
407 .get_many::<String>("option")
408 .unwrap_or_default()
409 .cloned()
410 .collect();
411 if fs_mistrust_disabled {
412 override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
413 }
414
415 let cfg_sources = {
416 let mut cfg_sources = ConfigurationSources::try_from_cmdline(
417 || default_config_files().context("identify default config file locations"),
418 matches
419 .get_many::<OsString>("config-files")
420 .unwrap_or_default(),
421 override_options,
422 )?;
423 cfg_sources.set_mistrust(cfg_mistrust);
424 cfg_sources
425 };
426
427 let cfg = cfg_sources.load()?;
428 let (config, client_config) =
429 tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
430
431 let log_mistrust = client_config.fs_mistrust().clone();
432
433 Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
434 })?;
435 let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
438
439 let _log_guards = logging::setup_logging(
440 config.logging(),
441 &log_mistrust,
442 client_config.as_ref(),
443 matches.get_one::<String>("loglevel").map(|s| s.as_str()),
444 )?;
445
446 if !config.application().allow_running_as_root {
447 process::exit_if_root();
448 }
449
450 #[cfg(feature = "harden")]
451 if !config.application().permit_debugging {
452 if let Err(e) = process::enable_process_hardening() {
453 error!(
454 "Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true."
455 );
456 return Err(e);
457 }
458 }
459
460 if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
462 return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
463 }
464
465 cfg_if::cfg_if! {
467 if #[cfg(feature = "onion-service-cli-extra")] {
468 if let Some(keys_matches) = matches.subcommand_matches("keys") {
469 return subcommands::keys::run(runtime, keys_matches, &config, &client_config);
470 } else if let Some(raw_matches) = matches.subcommand_matches("keys-raw") {
471 return subcommands::raw::run(runtime, raw_matches, &client_config);
472 }
473 }
474 }
475
476 cfg_if::cfg_if! {
478 if #[cfg(feature = "onion-service-service")] {
479 if let Some(hss_matches) = matches.subcommand_matches("hss") {
480 return subcommands::hss::run(runtime, hss_matches, &config, &client_config);
481 }
482 }
483 }
484
485 cfg_if::cfg_if! {
487 if #[cfg(feature = "hsc")] {
488 if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
489 return subcommands::hsc::run(runtime, hsc_matches, &client_config);
490 }
491 }
492 }
493
494 panic!("Subcommand added to clap subcommand list, but not yet implemented");
495}
496
497pub fn main() {
523 match main_main(std::env::args_os()) {
524 Ok(()) => {}
525 Err(e) => {
526 use arti_client::HintableError;
527 if let Some(hint) = e.hint() {
528 info!("{}", hint);
529 }
530
531 match e.downcast_ref::<clap::Error>() {
532 Some(clap_err) => clap_err.exit(),
533 None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
534 }
535 }
536 }
537}