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)] #![deny(clippy::unused_async)]
46#![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
50#![allow(clippy::print_stderr)]
52#![allow(clippy::print_stdout)]
53
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 $dflt_vis:vis 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 $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#[cfg_attr(not(feature = "rpc"), path = "rpc_stub.rs")]
115mod rpc;
116
117use std::ffi::OsString;
118use std::fmt::Write;
119
120semipublic_use! {
121 use cfg::{
122 ARTI_EXAMPLE_CONFIG, ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig,
123 ArtiConfig, ArtiConfigBuilder, ProxyConfig, ProxyConfigBuilder, SystemConfig,
124 SystemConfigBuilder,
125 };
126}
127semipublic_use! {
128 use logging::{LoggingConfig, LoggingConfigBuilder};
129}
130
131use arti_client::TorClient;
132use arti_client::config::default_config_files;
133use safelog::with_safe_logging_suppressed;
134use tor_config::ConfigurationSources;
135use tor_config::mistrust::BuilderExt as _;
136use tor_rtcompat::ToplevelRuntime;
137
138use anyhow::{Context, Error, Result};
139use clap::{Arg, ArgAction, Command, value_parser};
140#[allow(unused_imports)]
141use tracing::{error, info, instrument, warn};
142
143#[cfg(any(
144 feature = "hsc",
145 feature = "onion-service-service",
146 feature = "onion-service-cli-extra",
147))]
148use clap::Subcommand as _;
149
150#[cfg(feature = "experimental-api")]
151#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
152pub use subcommands::proxy::run_proxy;
153
154fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
156 cfg_if::cfg_if! {
157 if #[cfg(all(feature="tokio", feature="native-tls"))] {
158 use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
159 } else if #[cfg(all(feature="tokio", feature="rustls"))] {
160 use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
161 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
164 rustls_crate::crypto::ring::default_provider(),
165
166 );
167 } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
168 use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
169 } else if #[cfg(all(feature="async-std", feature="rustls"))] {
170 use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
171 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
174 rustls_crate::crypto::ring::default_provider(),
175 );
176 } else {
177 compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
178 }
179 }
180 ChosenRuntime::create()
181}
182
183fn list_enabled_features() -> &'static [&'static str] {
185 &[
189 #[cfg(feature = "journald")]
190 "journald",
191 #[cfg(any(feature = "static-sqlite", feature = "static"))]
192 "static-sqlite",
193 #[cfg(any(feature = "static-native-tls", feature = "static"))]
194 "static-native-tls",
195 ]
196}
197
198#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
211#[allow(clippy::cognitive_complexity)]
212#[instrument(skip_all, level = "trace")]
213fn main_main<I, T>(cli_args: I) -> Result<()>
214where
215 I: IntoIterator<Item = T>,
216 T: Into<std::ffi::OsString> + Clone,
217{
218 let mut config_file_help = "Specify which config file(s) to read.".to_string();
222 if let Ok(default) = default_config_files() {
223 write!(config_file_help, " Defaults to {:?}", default).expect("Can't write to string");
227 }
228
229 let runtime = create_runtime()?;
230
231 tor_log_ratelim::install_runtime(runtime.clone())
233 .context("Failed to initialze tor-log-ratelim")?;
234
235 let features = list_enabled_features();
237 let long_version = format!(
238 "{}\nusing runtime: {:?}\noptional features: {}",
239 env!("CARGO_PKG_VERSION"),
240 runtime,
241 if features.is_empty() {
242 "<none>".into()
243 } else {
244 features.join(", ")
245 }
246 );
247
248 let clap_app = Command::new("Arti")
249 .version(env!("CARGO_PKG_VERSION"))
250 .long_version(long_version)
251 .author("The Tor Project Developers")
252 .about("A Rust Tor implementation.")
253 .override_usage("arti <SUBCOMMAND> [OPTIONS]")
259 .arg(
260 Arg::new("config-files")
261 .short('c')
262 .long("config")
263 .action(ArgAction::Set)
264 .value_name("FILE")
265 .value_parser(value_parser!(OsString))
266 .action(ArgAction::Append)
267 .global(true)
269 .help(config_file_help),
270 )
271 .arg(
272 Arg::new("option")
273 .short('o')
274 .action(ArgAction::Set)
275 .value_name("KEY=VALUE")
276 .action(ArgAction::Append)
277 .global(true)
278 .help("Override config file parameters, using TOML-like syntax."),
279 )
280 .arg(
281 Arg::new("loglevel")
282 .short('l')
283 .long("log-level")
284 .global(true)
285 .action(ArgAction::Set)
286 .value_name("LEVEL")
287 .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
288 )
289 .arg(
290 Arg::new("disable-fs-permission-checks")
291 .long("disable-fs-permission-checks")
292 .global(true)
293 .action(ArgAction::SetTrue)
294 .help("Don't check permissions on the files we use."),
295 )
296 .subcommand(
297 Command::new("proxy")
298 .about(
299 "Run Arti in proxy mode, proxying connections through the Tor network.",
300 )
301 .arg(
302 Arg::new("socks-port")
304 .short('p')
305 .action(ArgAction::Set)
306 .value_name("PORT")
307 .value_parser(clap::value_parser!(u16))
308 .help(r#"Localhost port to listen on for SOCKS connections (0 means "disabled"; overrides addresses in the config if specified)."#)
309 )
310 .arg(
311 Arg::new("dns-port")
312 .short('d')
313 .action(ArgAction::Set)
314 .value_name("PORT")
315 .value_parser(clap::value_parser!(u16))
316 .help(r#"Localhost port to listen on for DNS requests (0 means "disabled"; overrides addresses in the config if specified)."#)
317 )
318 )
319 .subcommand_required(true)
320 .arg_required_else_help(true);
321
322 cfg_if::cfg_if! {
326 if #[cfg(feature = "onion-service-service")] {
327 let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
328 }
329 }
330
331 cfg_if::cfg_if! {
332 if #[cfg(feature = "hsc")] {
333 let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
334 }
335 }
336
337 cfg_if::cfg_if! {
338 if #[cfg(feature = "onion-service-cli-extra")] {
339 let clap_app = subcommands::keys::KeysSubcommands::augment_subcommands(clap_app);
340 let clap_app = subcommands::raw::RawSubcommands::augment_subcommands(clap_app);
341 }
342 }
343
344 let pre_config_logging_writer = || {
353 eprint!("arti:");
355 std::io::stderr()
356 };
357 let pre_config_logging = tracing_subscriber::fmt()
358 .without_time()
359 .with_writer(pre_config_logging_writer)
360 .finish();
361 let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
362 let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
363 let matches = clap_app.try_get_matches_from(cli_args)?;
364
365 let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
366
367 let cfg_mistrust = if fs_mistrust_disabled {
370 fs_mistrust::Mistrust::new_dangerously_trust_everyone()
371 } else {
372 fs_mistrust::MistrustBuilder::default()
373 .build_for_arti()
374 .expect("Could not construct default fs-mistrust")
375 };
376
377 let mut override_options: Vec<String> = matches
378 .get_many::<String>("option")
379 .unwrap_or_default()
380 .cloned()
381 .collect();
382 if fs_mistrust_disabled {
383 override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
384 }
385
386 let cfg_sources = {
387 let mut cfg_sources = ConfigurationSources::try_from_cmdline(
388 || default_config_files().context("identify default config file locations"),
389 matches
390 .get_many::<OsString>("config-files")
391 .unwrap_or_default(),
392 override_options,
393 )?;
394 cfg_sources.set_mistrust(cfg_mistrust);
395 cfg_sources
396 };
397
398 let cfg = cfg_sources.load()?;
399 let (config, client_config) =
400 tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
401
402 let log_mistrust = client_config.fs_mistrust().clone();
403
404 Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
405 })?;
406 let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
409
410 let _log_guards = logging::setup_logging(
411 config.logging(),
412 &log_mistrust,
413 client_config.as_ref(),
414 matches.get_one::<String>("loglevel").map(|s| s.as_str()),
415 )?;
416
417 if !config.application().allow_running_as_root {
418 process::exit_if_root();
419 }
420
421 #[cfg(feature = "harden")]
422 if !config.application().permit_debugging {
423 if let Err(e) = process::enable_process_hardening() {
424 error!(
425 "Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true."
426 );
427 return Err(e);
428 }
429 }
430
431 if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
433 return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
434 }
435
436 cfg_if::cfg_if! {
438 if #[cfg(feature = "onion-service-cli-extra")] {
439 if let Some(keys_matches) = matches.subcommand_matches("keys") {
440 return subcommands::keys::run(runtime, keys_matches, &config, &client_config);
441 } else if let Some(raw_matches) = matches.subcommand_matches("keys-raw") {
442 return subcommands::raw::run(runtime, raw_matches, &client_config);
443 }
444 }
445 }
446
447 cfg_if::cfg_if! {
449 if #[cfg(feature = "onion-service-service")] {
450 if let Some(hss_matches) = matches.subcommand_matches("hss") {
451 return subcommands::hss::run(runtime, hss_matches, &config, &client_config);
452 }
453 }
454 }
455
456 cfg_if::cfg_if! {
458 if #[cfg(feature = "hsc")] {
459 if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
460 return subcommands::hsc::run(runtime, hsc_matches, &client_config);
461 }
462 }
463 }
464
465 panic!("Subcommand added to clap subcommand list, but not yet implemented");
466}
467
468pub fn main() {
494 match main_main(std::env::args_os()) {
495 Ok(()) => {}
496 Err(e) => {
497 use arti_client::HintableError;
498 if let Some(hint) = e.hint() {
499 info!("{}", hint);
500 }
501
502 match e.downcast_ref::<clap::Error>() {
503 Some(clap_err) => clap_err.exit(),
504 None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
505 }
506 }
507 }
508}