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)] #![allow(mismatched_lifetime_syntaxes)] #![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
49#![allow(clippy::print_stderr)]
51#![allow(clippy::print_stdout)]
52
53pub mod cfg;
54pub mod logging;
55#[cfg(not(feature = "onion-service-service"))]
56mod onion_proxy_disabled;
57
58mod subcommands;
59
60macro_rules! semipublic_mod {
67 {
68 $(
69 $( #[$meta:meta] )*
70 mod $name:ident ;
71 )*
72 } => {
73 $(
74 $( #[$meta])*
75 cfg_if::cfg_if! {
76 if #[cfg(feature="experimental-api")] {
77 pub mod $name;
78 } else {
79 mod $name;
80 }
81 }
82 )*
83 }
84}
85
86semipublic_mod! {
87 #[cfg(feature = "dns-proxy")]
88 mod dns;
89 mod exit;
90 #[cfg(feature="onion-service-service")]
91 mod onion_proxy;
92 mod process;
93 mod reload_cfg;
94 mod socks;
95}
96#[cfg_attr(not(feature = "rpc"), path = "rpc_stub.rs")]
97mod rpc;
98
99use std::ffi::OsString;
100use std::fmt::Write;
101
102pub use cfg::{
103 ARTI_EXAMPLE_CONFIG, ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig,
104 ArtiConfig, ArtiConfigBuilder, ProxyConfig, ProxyConfigBuilder, SystemConfig,
105 SystemConfigBuilder,
106};
107pub use logging::{LoggingConfig, LoggingConfigBuilder};
108
109use arti_client::TorClient;
110use arti_client::config::default_config_files;
111use safelog::with_safe_logging_suppressed;
112use tor_config::ConfigurationSources;
113use tor_config::mistrust::BuilderExt as _;
114use tor_rtcompat::ToplevelRuntime;
115
116use anyhow::{Context, Error, Result};
117use clap::{Arg, ArgAction, Command, value_parser};
118#[allow(unused_imports)]
119use tracing::{error, info, warn};
120
121#[cfg(any(
122 feature = "hsc",
123 feature = "onion-service-service",
124 feature = "onion-service-cli-extra",
125))]
126use clap::Subcommand as _;
127
128#[cfg(feature = "experimental-api")]
129#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
130pub use subcommands::proxy::run_proxy as run;
131
132fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
134 cfg_if::cfg_if! {
135 if #[cfg(all(feature="tokio", feature="native-tls"))] {
136 use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
137 } else if #[cfg(all(feature="tokio", feature="rustls"))] {
138 use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
139 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
142 rustls_crate::crypto::ring::default_provider(),
143
144 );
145 } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
146 use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
147 } else if #[cfg(all(feature="async-std", feature="rustls"))] {
148 use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
149 let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
152 rustls_crate::crypto::ring::default_provider(),
153 );
154 } else {
155 compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
156 }
157 }
158 ChosenRuntime::create()
159}
160
161fn list_enabled_features() -> &'static [&'static str] {
163 &[
167 #[cfg(feature = "journald")]
168 "journald",
169 #[cfg(any(feature = "static-sqlite", feature = "static"))]
170 "static-sqlite",
171 #[cfg(any(feature = "static-native-tls", feature = "static"))]
172 "static-native-tls",
173 ]
174}
175
176#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
189#[allow(clippy::cognitive_complexity)]
190fn main_main<I, T>(cli_args: I) -> Result<()>
191where
192 I: IntoIterator<Item = T>,
193 T: Into<std::ffi::OsString> + Clone,
194{
195 let mut config_file_help = "Specify which config file(s) to read.".to_string();
199 if let Ok(default) = default_config_files() {
200 write!(config_file_help, " Defaults to {:?}", default).expect("Can't write to string");
204 }
205
206 let runtime = create_runtime()?;
209 let features = list_enabled_features();
210 let long_version = format!(
211 "{}\nusing runtime: {:?}\noptional features: {}",
212 env!("CARGO_PKG_VERSION"),
213 runtime,
214 if features.is_empty() {
215 "<none>".into()
216 } else {
217 features.join(", ")
218 }
219 );
220
221 let clap_app = Command::new("Arti")
222 .version(env!("CARGO_PKG_VERSION"))
223 .long_version(long_version)
224 .author("The Tor Project Developers")
225 .about("A Rust Tor implementation.")
226 .override_usage("arti <SUBCOMMAND> [OPTIONS]")
232 .arg(
233 Arg::new("config-files")
234 .short('c')
235 .long("config")
236 .action(ArgAction::Set)
237 .value_name("FILE")
238 .value_parser(value_parser!(OsString))
239 .action(ArgAction::Append)
240 .global(true)
242 .help(config_file_help),
243 )
244 .arg(
245 Arg::new("option")
246 .short('o')
247 .action(ArgAction::Set)
248 .value_name("KEY=VALUE")
249 .action(ArgAction::Append)
250 .global(true)
251 .help("Override config file parameters, using TOML-like syntax."),
252 )
253 .arg(
254 Arg::new("loglevel")
255 .short('l')
256 .long("log-level")
257 .global(true)
258 .action(ArgAction::Set)
259 .value_name("LEVEL")
260 .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
261 )
262 .arg(
263 Arg::new("disable-fs-permission-checks")
264 .long("disable-fs-permission-checks")
265 .global(true)
266 .action(ArgAction::SetTrue)
267 .help("Don't check permissions on the files we use."),
268 )
269 .subcommand(
270 Command::new("proxy")
271 .about(
272 "Run Arti in SOCKS proxy mode, proxying connections through the Tor network.",
273 )
274 .arg(
275 Arg::new("socks-port")
276 .short('p')
277 .action(ArgAction::Set)
278 .value_name("PORT")
279 .help("Port to listen on for SOCKS connections (overrides the port in the config if specified).")
280 )
281 .arg(
282 Arg::new("dns-port")
283 .short('d')
284 .action(ArgAction::Set)
285 .value_name("PORT")
286 .help("Port to listen on for DNS request (overrides the port in the config if specified).")
287 )
288 )
289 .subcommand_required(true)
290 .arg_required_else_help(true);
291
292 cfg_if::cfg_if! {
296 if #[cfg(feature = "onion-service-service")] {
297 let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
298 }
299 }
300
301 cfg_if::cfg_if! {
302 if #[cfg(feature = "hsc")] {
303 let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
304 }
305 }
306
307 cfg_if::cfg_if! {
308 if #[cfg(feature = "onion-service-cli-extra")] {
309 let clap_app = subcommands::keys::KeysSubcommands::augment_subcommands(clap_app);
310 let clap_app = subcommands::raw::RawSubcommands::augment_subcommands(clap_app);
311 }
312 }
313
314 let pre_config_logging_writer = || {
323 eprint!("arti:");
325 std::io::stderr()
326 };
327 let pre_config_logging = tracing_subscriber::fmt()
328 .without_time()
329 .with_writer(pre_config_logging_writer)
330 .finish();
331 let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
332 let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
333 let matches = clap_app.try_get_matches_from(cli_args)?;
334
335 let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
336
337 let cfg_mistrust = if fs_mistrust_disabled {
340 fs_mistrust::Mistrust::new_dangerously_trust_everyone()
341 } else {
342 fs_mistrust::MistrustBuilder::default()
343 .build_for_arti()
344 .expect("Could not construct default fs-mistrust")
345 };
346
347 let mut override_options: Vec<String> = matches
348 .get_many::<String>("option")
349 .unwrap_or_default()
350 .cloned()
351 .collect();
352 if fs_mistrust_disabled {
353 override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
354 }
355
356 let cfg_sources = {
357 let mut cfg_sources = ConfigurationSources::try_from_cmdline(
358 || default_config_files().context("identify default config file locations"),
359 matches
360 .get_many::<OsString>("config-files")
361 .unwrap_or_default(),
362 override_options,
363 )?;
364 cfg_sources.set_mistrust(cfg_mistrust);
365 cfg_sources
366 };
367
368 let cfg = cfg_sources.load()?;
369 let (config, client_config) =
370 tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
371
372 let log_mistrust = client_config.fs_mistrust().clone();
373
374 Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
375 })?;
376 let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
379
380 let _log_guards = logging::setup_logging(
381 config.logging(),
382 &log_mistrust,
383 client_config.as_ref(),
384 matches.get_one::<String>("loglevel").map(|s| s.as_str()),
385 )?;
386
387 if !config.application().allow_running_as_root {
388 process::exit_if_root();
389 }
390
391 #[cfg(feature = "harden")]
392 if !config.application().permit_debugging {
393 if let Err(e) = process::enable_process_hardening() {
394 error!(
395 "Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true."
396 );
397 return Err(e);
398 }
399 }
400
401 if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
403 return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
404 }
405
406 cfg_if::cfg_if! {
408 if #[cfg(feature = "onion-service-cli-extra")] {
409 if let Some(keys_matches) = matches.subcommand_matches("keys") {
410 return subcommands::keys::run(runtime, keys_matches, &config, &client_config);
411 } else if let Some(raw_matches) = matches.subcommand_matches("keys-raw") {
412 return subcommands::raw::run(runtime, raw_matches, &client_config);
413 }
414 }
415 }
416
417 cfg_if::cfg_if! {
419 if #[cfg(feature = "onion-service-service")] {
420 if let Some(hss_matches) = matches.subcommand_matches("hss") {
421 return subcommands::hss::run(runtime, hss_matches, &config, &client_config);
422 }
423 }
424 }
425
426 cfg_if::cfg_if! {
428 if #[cfg(feature = "hsc")] {
429 if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
430 return subcommands::hsc::run(runtime, hsc_matches, &client_config);
431 }
432 }
433 }
434
435 panic!("Subcommand added to clap subcommand list, but not yet implemented");
436}
437
438pub fn main() {
464 match main_main(std::env::args_os()) {
465 Ok(()) => {}
466 Err(e) => {
467 use arti_client::HintableError;
468 if let Some(hint) = e.hint() {
469 info!("{}", hint);
470 }
471
472 match e.downcast_ref::<clap::Error>() {
473 Some(clap_err) => clap_err.exit(),
474 None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
475 }
476 }
477 }
478}