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