1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![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
#![allow(clippy::cognitive_complexity)] // See arti#2556
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
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

            
52
// TODO #1645 (either remove this, or decide to have it everywhere)
53
#![cfg_attr(not(all(feature = "full", feature = "experimental")), allow(unused))]
54
// Overrides specific to this crate:
55
#![allow(clippy::print_stderr)]
56
#![allow(clippy::print_stdout)]
57

            
58
mod subcommands;
59

            
60
/// Helper:
61
/// Declare a series of modules as public if experimental_api is set,
62
/// and as non-public otherwise.
63
//
64
// TODO: We'd like to use visibility::make(pub) here, but it doesn't
65
// work on modules.
66
macro_rules! semipublic_mod {
67
    {
68
        $(
69
            $( #[$meta:meta] )*
70
            $dflt_vis:vis mod $name:ident ;
71
        )*
72
    }  => {
73
        $(
74
            cfg_if::cfg_if! {
75
                if #[cfg(feature="experimental-api")] {
76
                   $( #[$meta])*
77
                   pub mod $name;
78
                } else {
79
                   $( #[$meta])*
80
                   $dflt_vis mod $name;
81
                }
82
            }
83
         )*
84
    }
85
}
86

            
87
/// Helper:
88
/// Import a set of items as public if experimental_api is set,
89
/// and as non-public otherwise.
90
macro_rules! semipublic_use {
91
    {
92
        $dflt_vis:vis use $($tok:tt)+
93
    } => {
94
        cfg_if::cfg_if! {
95
            if #[cfg(feature="experimental-api")] {
96
                pub use $($tok)+
97
            } else {
98
                $dflt_vis use $($tok)+
99
            }
100
        }
101
    }
102
}
103

            
104
semipublic_mod! {
105
    mod cfg;
106
    #[cfg(feature = "dns-proxy")]
107
    mod dns;
108
    mod exit;
109
    mod logging;
110
    #[cfg(feature="onion-service-service")]
111
    mod onion_proxy;
112
    mod process;
113
    mod reload_cfg;
114
    mod proxy;
115
}
116

            
117
cfg_if::cfg_if! {
118
    if #[cfg(all(feature="experimental-api", feature="rpc"))] {
119
        pub mod rpc;
120
    } else if #[cfg(all(feature="experimental-api", not(feature="rpc")))] {
121
        #[path = "rpc_stub.rs"]
122
        pub mod rpc;
123
    } else if #[cfg(feature = "rpc")] {
124
        mod rpc;
125
    } else {
126
        #[path = "rpc_stub.rs"]
127
        mod rpc;
128
    }
129
}
130

            
131
use std::ffi::OsString;
132
use std::fmt::Write;
133

            
134
semipublic_use! {
135
    use cfg::{
136
        ARTI_EXAMPLE_CONFIG, ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig,
137
        ArtiConfig, ArtiConfigBuilder, ProxyConfig, ProxyConfigBuilder, SystemConfig,
138
        SystemConfigBuilder,
139
    };
140
}
141
semipublic_use! {
142
    use logging::{LoggingConfig, LoggingConfigBuilder};
143
}
144

            
145
use arti_client::TorClient;
146
use arti_client::config::default_config_files;
147
use safelog::with_safe_logging_suppressed;
148
use tor_config::ConfigurationSources;
149
use tor_config::mistrust::BuilderExt as _;
150
use tor_rtcompat::ToplevelRuntime;
151

            
152
use anyhow::{Context, Error, Result};
153
use clap::{Arg, ArgAction, Command, value_parser};
154
#[allow(unused_imports)]
155
use tracing::{error, info, instrument, warn};
156

            
157
#[cfg(any(
158
    feature = "hsc",
159
    feature = "onion-service-service",
160
    feature = "onion-service-cli-extra",
161
))]
162
use clap::Subcommand as _;
163

            
164
#[cfg(feature = "experimental-api")]
165
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
166
pub use subcommands::proxy::run_proxy;
167

            
168
/// Return an appropriate CryptoProvider for use with rustls.
169
#[allow(dead_code)]
170
#[cfg(feature = "rustls-base")]
171
fn rustls_crypto_provider() -> rustls_crate::crypto::CryptoProvider {
172
    cfg_if::cfg_if! {
173
        if #[cfg(feature = "rustls-aws-lc-rs")] {
174
            rustls_crate::crypto::aws_lc_rs::default_provider()
175
        } else if #[cfg(feature = "rustls-ring")]{
176
            rustls_crate::crypto::ring::default_provider()
177
        } else {
178
            compile_error!(r#"When building with rustls, you must select "rustls-aws-lc-rs" or "rustls-ring". \
179
                              "rustls" is an alias for one of these."#);
180
            // The rustls feature is just an alias for "rustls-aws-lc-rs".
181
        }
182
    }
183
}
184

            
185
/// Create a runtime for Arti to use.
186
275
fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
187
    cfg_if::cfg_if! {
188
        if #[cfg(all(feature="tokio", feature="native-tls"))] {
189
            use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
190
        } else if #[cfg(all(feature="tokio", feature="rustls-base"))] {
191
            use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
192
            // Note: See comments in tor_rtcompat::impls::rustls::RustlsProvider
193
            // about choice of default crypto provider.
194
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
195
                rustls_crypto_provider()
196
            );
197
        } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
198
            use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
199
        } else if #[cfg(all(feature="async-std", feature="rustls-base"))] {
200
            use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
201
            // Note: See comments in tor_rtcompat::impls::rustls::RustlsProvider
202
            // about choice of default crypto provider.
203
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
204
                rustls_crypto_provider()
205
            );
206
        } else {
207
            compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
208
        }
209
    }
210
275
    ChosenRuntime::create()
211
275
}
212

            
213
/// Return a (non-exhaustive) array of enabled Cargo features, for version printing purposes.
214
275
fn list_enabled_features() -> &'static [&'static str] {
215
    // HACK(eta): We can't get this directly, so we just do this awful hack instead.
216
    // Note that we only list features that aren't about the runtime used, since that already
217
    // gets printed separately.
218
275
    &[
219
275
        #[cfg(feature = "journald")]
220
275
        "journald",
221
275
        #[cfg(any(feature = "static-sqlite", feature = "static"))]
222
275
        "static-sqlite",
223
275
        #[cfg(any(feature = "static-native-tls", feature = "static"))]
224
275
        "static-native-tls",
225
275
    ]
226
275
}
227

            
228
/// Inner function, to handle a set of CLI arguments and return a single
229
/// `Result<()>` for convenient handling.
230
///
231
/// # ⚠️ Warning! ⚠️
232
///
233
/// If your program needs to call this function, you are setting yourself up for
234
/// some serious maintenance headaches.  See discussion on [`main`] and please
235
/// reach out to help us build you a better API.
236
///
237
/// # Panics
238
///
239
/// Currently, might panic if wrong arguments are specified.
240
275
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
241
275
#[instrument(skip_all, level = "trace")]
242
275
fn main_main<I, T>(cli_args: I) -> Result<()>
243
275
where
244
275
    I: IntoIterator<Item = T>,
245
275
    T: Into<std::ffi::OsString> + Clone,
246
{
247
    // We describe a default here, rather than using `default()`, because the
248
    // correct behavior is different depending on whether the filename is given
249
    // explicitly or not.
250
275
    let mut config_file_help = "Specify which config file(s) to read.".to_string();
251
275
    if let Ok(default) = default_config_files() {
252
275
        // If we couldn't resolve the default config file, then too bad.  If something
253
275
        // actually tries to use it, it will produce an error, but don't fail here
254
275
        // just for that reason.
255
275
        write!(config_file_help, " Defaults to {:?}", default).expect("Can't write to string");
256
275
    }
257

            
258
275
    let runtime = create_runtime()?;
259

            
260
    // Configure tor-log-ratelim early before we begin logging.
261
275
    tor_log_ratelim::install_runtime(runtime.clone())
262
275
        .context("Failed to initialize tor-log-ratelim")?;
263

            
264
    // Use the runtime's `Debug` impl to describe it for the version string.
265
275
    let features = list_enabled_features();
266
275
    let long_version = format!(
267
        "{}\nusing runtime: {:?}\noptional features: {}",
268
        env!("CARGO_PKG_VERSION"),
269
        runtime,
270
275
        if features.is_empty() {
271
            "<none>".into()
272
        } else {
273
275
            features.join(", ")
274
        }
275
    );
276

            
277
275
    let clap_app = Command::new("Arti")
278
            // HACK(eta): clap generates "arti [OPTIONS] <SUBCOMMAND>" for this usage string by
279
            //            default, but then fails to parse options properly if you do put them
280
            //            before the subcommand.
281
            //            We just declare all options as `global` and then require them to be
282
            //            put after the subcommand, hence this new usage string.
283
275
            .override_usage("arti <SUBCOMMAND> [OPTIONS]")
284
275
            .arg(
285
275
                Arg::new("config-files")
286
275
                    .short('c')
287
275
                    .long("config")
288
275
                    .action(ArgAction::Set)
289
275
                    .value_name("FILE")
290
275
                    .value_parser(value_parser!(OsString))
291
275
                    .action(ArgAction::Append)
292
                    // NOTE: don't forget the `global` flag on all arguments declared at this level!
293
275
                    .global(true)
294
275
                    .help(config_file_help),
295
            )
296
275
            .arg(
297
275
                Arg::new("option")
298
275
                    .short('o')
299
275
                    .action(ArgAction::Set)
300
275
                    .value_name("KEY=VALUE")
301
275
                    .action(ArgAction::Append)
302
275
                    .global(true)
303
275
                    .help("Override config file parameters, using TOML-like syntax."),
304
            )
305
275
            .arg(
306
275
                Arg::new("loglevel")
307
275
                    .short('l')
308
275
                    .long("log-level")
309
275
                    .global(true)
310
275
                    .action(ArgAction::Set)
311
275
                    .value_name("LEVEL")
312
275
                    .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
313
            )
314
275
            .arg(
315
275
                Arg::new("disable-fs-permission-checks")
316
275
                    .long("disable-fs-permission-checks")
317
275
                    .global(true)
318
275
                    .action(ArgAction::SetTrue)
319
275
                    .help("Don't check permissions on the files we use."),
320
            )
321
275
            .subcommand(
322
275
                Command::new("proxy")
323
275
                    .about(
324
                        "Run Arti in proxy mode, proxying connections through the Tor network.",
325
                    )
326
275
                    .arg(
327
                        // TODO: Allow a proxy-port alias for this too, once http-connect is stable.
328
275
                        Arg::new("socks-port")
329
275
                            .short('p')
330
275
                            .action(ArgAction::Set)
331
275
                            .value_name("PORT")
332
275
                            .value_parser(clap::value_parser!(u16))
333
275
                            .help(r#"Localhost port to listen on for SOCKS connections (0 means "disabled"; overrides addresses in the config if specified)."#)
334
                    )
335
275
                    .arg(
336
275
                        Arg::new("dns-port")
337
275
                            .short('d')
338
275
                            .action(ArgAction::Set)
339
275
                            .value_name("PORT")
340
275
                            .value_parser(clap::value_parser!(u16))
341
275
                            .help(r#"Localhost port to listen on for DNS requests (0 means "disabled"; overrides addresses in the config if specified)."#)
342
                    )
343
            )
344
275
            .subcommand_required(true)
345
275
            .arg_required_else_help(true);
346

            
347
    // When adding a subcommand, it may be necessary to add an entry in
348
    // `maint/check-cli-help`, to the function `help_arg`.
349

            
350
    cfg_if::cfg_if! {
351
        if #[cfg(feature = "onion-service-service")] {
352
275
            let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
353
        }
354
    }
355

            
356
    cfg_if::cfg_if! {
357
        if #[cfg(feature = "hsc")] {
358
275
            let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
359
        }
360
    }
361

            
362
    cfg_if::cfg_if! {
363
        if #[cfg(feature = "onion-service-cli-extra")] {
364
275
            let clap_app = subcommands::keys::KeysSubcommands::augment_subcommands(clap_app);
365
275
            let clap_app = subcommands::raw::RawSubcommands::augment_subcommands(clap_app);
366
        }
367
    }
368

            
369
275
    let clap_app = clap_app
370
275
        .version(env!("CARGO_PKG_VERSION"))
371
275
        .long_version(long_version)
372
275
        .author("The Tor Project Developers")
373
275
        .about("A Rust Tor implementation.");
374

            
375
    // Tracing doesn't log anything when there is no subscriber set.  But we want to see
376
    // logging messages from config parsing etc.  We can't set the global default subscriber
377
    // because we can only set it once.  The other ways involve a closure.  So we have a
378
    // closure for all the startup code which runs *before* we set the logging properly.
379
    //
380
    // There is no cooked way to print our program name, so we do it like this.  This
381
    // closure is called to "make" a "Writer" for each message, so it runs at the right time:
382
    // before each message.
383
275
    let pre_config_logging_writer = || {
384
        // Weirdly, with .without_time(), tracing produces messages with a leading space.
385
85
        eprint!("arti:");
386
85
        std::io::stderr()
387
85
    };
388
275
    let pre_config_logging = tracing_subscriber::fmt()
389
275
        .without_time()
390
275
        .with_writer(pre_config_logging_writer)
391
275
        .finish();
392
275
    let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
393
275
    let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
394
275
        let matches = clap_app.try_get_matches_from(cli_args)?;
395

            
396
230
        let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
397

            
398
        // A Mistrust object to use for loading our configuration.  Elsewhere, we
399
        // use the value _from_ the configuration.
400
230
        let cfg_mistrust = if fs_mistrust_disabled {
401
            fs_mistrust::Mistrust::new_dangerously_trust_everyone()
402
        } else {
403
230
            fs_mistrust::MistrustBuilder::default()
404
230
                .build_for_arti()
405
230
                .expect("Could not construct default fs-mistrust")
406
        };
407

            
408
230
        let mut override_options: Vec<String> = matches
409
230
            .get_many::<String>("option")
410
230
            .unwrap_or_default()
411
230
            .cloned()
412
230
            .collect();
413
230
        if fs_mistrust_disabled {
414
            override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
415
230
        }
416

            
417
230
        let cfg_sources = {
418
230
            let mut cfg_sources = ConfigurationSources::try_from_cmdline(
419
                || default_config_files().context("identify default config file locations"),
420
230
                matches
421
230
                    .get_many::<OsString>("config-files")
422
230
                    .unwrap_or_default(),
423
230
                override_options,
424
            )?;
425
230
            cfg_sources.set_mistrust(cfg_mistrust);
426
230
            cfg_sources
427
        };
428

            
429
230
        let cfg = cfg_sources.load()?;
430
230
        let (config, client_config) =
431
230
            tor_config::resolve::<ArtiCombinedConfig>(&cfg).context("read configuration")?;
432

            
433
230
        let log_mistrust = client_config.fs_mistrust().clone();
434

            
435
230
        Ok::<_, Error>((
436
230
            matches,
437
230
            cfg_sources,
438
230
            cfg,
439
230
            config,
440
230
            client_config,
441
230
            log_mistrust,
442
230
        ))
443
275
    })?;
444
    // Sadly I don't seem to be able to persuade rustfmt to format the two lists of
445
    // variable names identically.
446
230
    let (matches, cfg_sources, loaded_cfg_tree, config, client_config, log_mistrust) =
447
230
        pre_config_logging_ret;
448

            
449
230
    let _log_guards = logging::setup_logging(
450
230
        config.logging(),
451
230
        &log_mistrust,
452
230
        client_config.as_ref(),
453
230
        matches.get_one::<String>("loglevel").map(|s| s.as_str()),
454
    )?;
455

            
456
230
    if !config.application().allow_running_as_root {
457
        process::exit_if_root();
458
230
    }
459

            
460
    #[cfg(feature = "harden")]
461
230
    if !config.application().permit_debugging {
462
230
        if let Err(e) = process::enable_process_hardening() {
463
            error!(
464
                "Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true."
465
            );
466
            return Err(e.into());
467
230
        }
468
    }
469

            
470
    // Check for the "proxy" subcommand.
471
230
    if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
472
        return subcommands::proxy::run(
473
            runtime,
474
            proxy_matches,
475
            cfg_sources,
476
            loaded_cfg_tree,
477
            config,
478
            client_config,
479
        );
480
230
    }
481

            
482
    // Check for the optional "keys" and "keys-raw" subcommand.
483
    cfg_if::cfg_if! {
484
        if #[cfg(feature = "onion-service-cli-extra")] {
485
230
            if let Some(keys_matches) = matches.subcommand_matches("keys") {
486
25
                return subcommands::keys::run(runtime, keys_matches, &config, &client_config);
487
205
            } else if let Some(raw_matches) = matches.subcommand_matches("keys-raw") {
488
                return subcommands::raw::run(runtime, raw_matches, &client_config);
489
205
            }
490
        }
491
    }
492

            
493
    // Check for the optional "hss" subcommand.
494
    cfg_if::cfg_if! {
495
        if #[cfg(feature = "onion-service-service")] {
496
205
            if let Some(hss_matches) = matches.subcommand_matches("hss") {
497
95
                return subcommands::hss::run(runtime, hss_matches, &config, &client_config);
498
110
            }
499
        }
500
    }
501

            
502
    // Check for the optional "hsc" subcommand.
503
    cfg_if::cfg_if! {
504
        if #[cfg(feature = "hsc")] {
505
110
            if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
506
110
                return subcommands::hsc::run(runtime, hsc_matches, &client_config);
507
            }
508
        }
509
    }
510

            
511
    panic!("Subcommand added to clap subcommand list, but not yet implemented");
512
275
}
513

            
514
/// Main program, callable directly from a binary crate's `main`
515
///
516
/// This function behaves the same as `main_main()`, except:
517
///   * It takes command-line arguments from `std::env::args_os` rather than
518
///     from an argument.
519
///   * It exits the process with an appropriate error code on error.
520
///
521
/// # ⚠️ Warning ⚠️
522
///
523
/// Calling this function, or the related experimental function `main_main`, is
524
/// probably a bad idea for your code.  It means that you are invoking Arti as
525
/// if from the command line, but keeping it embedded inside your process. Doing
526
/// this will block your process take over handling for several signal types,
527
/// possibly disable debugger attachment, and a lot more junk that a library
528
/// really has no business doing for you.  It is not designed to run in this
529
/// way, and may give you strange results.
530
///
531
/// If the functionality you want is available in [`arti_client`] crate, or from
532
/// a *non*-experimental API in this crate, it would be better for you to use
533
/// that API instead.
534
///
535
/// Alternatively, if you _do_ need some underlying function from the `arti`
536
/// crate, it would be better for all of us if you had a stable interface to that
537
/// function. Please reach out to the Arti developers, so we can work together
538
/// to get you the stable API you need.
539
275
pub fn main() {
540
275
    match main_main(std::env::args_os()) {
541
150
        Ok(()) => {}
542
125
        Err(e) => {
543
            use arti_client::HintableError;
544
125
            if let Some(hint) = e.hint() {
545
                info!("{}", hint);
546
125
            }
547

            
548
125
            match e.downcast_ref::<clap::Error>() {
549
45
                Some(clap_err) => clap_err.exit(),
550
80
                None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
551
            }
552
        }
553
    }
554
230
}