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
#![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)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
#![allow(clippy::collapsible_if)] // See arti#2342
46
#![deny(clippy::unused_async)]
47
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48

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

            
55
mod subcommands;
56

            
57
/// Helper:
58
/// Declare a series of modules as public if experimental_api is set,
59
/// and as non-public otherwise.
60
//
61
// TODO: We'd like to use visibility::make(pub) here, but it doesn't
62
// work on modules.
63
macro_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

            
84
/// Helper:
85
/// Import a set of items as public if experimental_api is set,
86
/// and as non-public otherwise.
87
macro_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

            
101
semipublic_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

            
114
cfg_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

            
128
use std::ffi::OsString;
129
use std::fmt::Write;
130

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

            
142
use arti_client::TorClient;
143
use arti_client::config::default_config_files;
144
use safelog::with_safe_logging_suppressed;
145
use tor_config::ConfigurationSources;
146
use tor_config::mistrust::BuilderExt as _;
147
use tor_rtcompat::ToplevelRuntime;
148

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

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

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

            
165
/// Create a runtime for Arti to use.
166
354
fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
167
    cfg_if::cfg_if! {
168
        if #[cfg(all(feature="tokio", feature="native-tls"))] {
169
            use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
170
        } else if #[cfg(all(feature="tokio", feature="rustls"))] {
171
            use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
172
            // Note: See comments in tor_rtcompate::impls::rustls::RustlsProvider
173
            // about choice of default crypto provider.
174
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
175
                rustls_crate::crypto::ring::default_provider(),
176

            
177
            );
178
        } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
179
            use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
180
        } else if #[cfg(all(feature="async-std", feature="rustls"))] {
181
            use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
182
            // Note: See comments in tor_rtcompate::impls::rustls::RustlsProvider
183
            // about choice of default crypto provider.
184
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
185
                rustls_crate::crypto::ring::default_provider(),
186
            );
187
        } else {
188
            compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
189
        }
190
    }
191
354
    ChosenRuntime::create()
192
354
}
193

            
194
/// Return a (non-exhaustive) array of enabled Cargo features, for version printing purposes.
195
354
fn list_enabled_features() -> &'static [&'static str] {
196
    // HACK(eta): We can't get this directly, so we just do this awful hack instead.
197
    // Note that we only list features that aren't about the runtime used, since that already
198
    // gets printed separately.
199
354
    &[
200
354
        #[cfg(feature = "journald")]
201
354
        "journald",
202
354
        #[cfg(any(feature = "static-sqlite", feature = "static"))]
203
354
        "static-sqlite",
204
354
        #[cfg(any(feature = "static-native-tls", feature = "static"))]
205
354
        "static-native-tls",
206
354
    ]
207
354
}
208

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

            
240
354
    let runtime = create_runtime()?;
241

            
242
    // Configure tor-log-ratelim early before we begin logging.
243
354
    tor_log_ratelim::install_runtime(runtime.clone())
244
354
        .context("Failed to initialze tor-log-ratelim")?;
245

            
246
    // Use the runtime's `Debug` impl to describe it for the version string.
247
354
    let features = list_enabled_features();
248
354
    let long_version = format!(
249
354
        "{}\nusing runtime: {:?}\noptional features: {}",
250
        env!("CARGO_PKG_VERSION"),
251
        runtime,
252
354
        if features.is_empty() {
253
            "<none>".into()
254
        } else {
255
354
            features.join(", ")
256
        }
257
    );
258

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

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

            
336
    cfg_if::cfg_if! {
337
        if #[cfg(feature = "onion-service-service")] {
338
354
            let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
339
        }
340
    }
341

            
342
    cfg_if::cfg_if! {
343
        if #[cfg(feature = "hsc")] {
344
354
            let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
345
        }
346
    }
347

            
348
    cfg_if::cfg_if! {
349
        if #[cfg(feature = "onion-service-cli-extra")] {
350
354
            let clap_app = subcommands::keys::KeysSubcommands::augment_subcommands(clap_app);
351
354
            let clap_app = subcommands::raw::RawSubcommands::augment_subcommands(clap_app);
352
        }
353
    }
354

            
355
    // Tracing doesn't log anything when there is no subscriber set.  But we want to see
356
    // logging messages from config parsing etc.  We can't set the global default subscriber
357
    // because we can only set it once.  The other ways involve a closure.  So we have a
358
    // closure for all the startup code which runs *before* we set the logging properly.
359
    //
360
    // There is no cooked way to print our program name, so we do it like this.  This
361
    // closure is called to "make" a "Writer" for each message, so it runs at the right time:
362
    // before each message.
363
354
    let pre_config_logging_writer = || {
364
        // Weirdly, with .without_time(), tracing produces messages with a leading space.
365
132
        eprint!("arti:");
366
132
        std::io::stderr()
367
132
    };
368
354
    let pre_config_logging = tracing_subscriber::fmt()
369
354
        .without_time()
370
354
        .with_writer(pre_config_logging_writer)
371
354
        .finish();
372
354
    let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
373
354
    let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
374
354
        let matches = clap_app.try_get_matches_from(cli_args)?;
375

            
376
306
        let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
377

            
378
        // A Mistrust object to use for loading our configuration.  Elsewhere, we
379
        // use the value _from_ the configuration.
380
306
        let cfg_mistrust = if fs_mistrust_disabled {
381
            fs_mistrust::Mistrust::new_dangerously_trust_everyone()
382
        } else {
383
306
            fs_mistrust::MistrustBuilder::default()
384
306
                .build_for_arti()
385
306
                .expect("Could not construct default fs-mistrust")
386
        };
387

            
388
306
        let mut override_options: Vec<String> = matches
389
306
            .get_many::<String>("option")
390
306
            .unwrap_or_default()
391
306
            .cloned()
392
306
            .collect();
393
306
        if fs_mistrust_disabled {
394
            override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
395
306
        }
396

            
397
306
        let cfg_sources = {
398
306
            let mut cfg_sources = ConfigurationSources::try_from_cmdline(
399
                || default_config_files().context("identify default config file locations"),
400
306
                matches
401
306
                    .get_many::<OsString>("config-files")
402
306
                    .unwrap_or_default(),
403
306
                override_options,
404
            )?;
405
306
            cfg_sources.set_mistrust(cfg_mistrust);
406
306
            cfg_sources
407
        };
408

            
409
306
        let cfg = cfg_sources.load()?;
410
306
        let (config, client_config) =
411
306
            tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
412

            
413
306
        let log_mistrust = client_config.fs_mistrust().clone();
414

            
415
306
        Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
416
354
    })?;
417
    // Sadly I don't seem to be able to persuade rustfmt to format the two lists of
418
    // variable names identically.
419
306
    let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
420

            
421
306
    let _log_guards = logging::setup_logging(
422
306
        config.logging(),
423
306
        &log_mistrust,
424
306
        client_config.as_ref(),
425
306
        matches.get_one::<String>("loglevel").map(|s| s.as_str()),
426
    )?;
427

            
428
306
    if !config.application().allow_running_as_root {
429
        process::exit_if_root();
430
306
    }
431

            
432
    #[cfg(feature = "harden")]
433
306
    if !config.application().permit_debugging {
434
306
        if let Err(e) = process::enable_process_hardening() {
435
            error!(
436
                "Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true."
437
            );
438
            return Err(e);
439
306
        }
440
    }
441

            
442
    // Check for the "proxy" subcommand.
443
306
    if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
444
        return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
445
306
    }
446

            
447
    // Check for the optional "keys" and "keys-raw" subcommand.
448
    cfg_if::cfg_if! {
449
        if #[cfg(feature = "onion-service-cli-extra")] {
450
306
            if let Some(keys_matches) = matches.subcommand_matches("keys") {
451
60
                return subcommands::keys::run(runtime, keys_matches, &config, &client_config);
452
246
            } else if let Some(raw_matches) = matches.subcommand_matches("keys-raw") {
453
                return subcommands::raw::run(runtime, raw_matches, &client_config);
454
246
            }
455
        }
456
    }
457

            
458
    // Check for the optional "hss" subcommand.
459
    cfg_if::cfg_if! {
460
        if #[cfg(feature = "onion-service-service")] {
461
246
            if let Some(hss_matches) = matches.subcommand_matches("hss") {
462
114
                return subcommands::hss::run(runtime, hss_matches, &config, &client_config);
463
132
            }
464
        }
465
    }
466

            
467
    // Check for the optional "hsc" subcommand.
468
    cfg_if::cfg_if! {
469
        if #[cfg(feature = "hsc")] {
470
132
            if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
471
132
                return subcommands::hsc::run(runtime, hsc_matches, &client_config);
472
            }
473
        }
474
    }
475

            
476
    panic!("Subcommand added to clap subcommand list, but not yet implemented");
477
354
}
478

            
479
/// Main program, callable directly from a binary crate's `main`
480
///
481
/// This function behaves the same as `main_main()`, except:
482
///   * It takes command-line arguments from `std::env::args_os` rather than
483
///     from an argument.
484
///   * It exits the process with an appropriate error code on error.
485
///
486
/// # ⚠️ Warning ⚠️
487
///
488
/// Calling this function, or the related experimental function `main_main`, is
489
/// probably a bad idea for your code.  It means that you are invoking Arti as
490
/// if from the command line, but keeping it embedded inside your process. Doing
491
/// this will block your process take over handling for several signal types,
492
/// possibly disable debugger attachment, and a lot more junk that a library
493
/// really has no business doing for you.  It is not designed to run in this
494
/// way, and may give you strange results.
495
///
496
/// If the functionality you want is available in [`arti_client`] crate, or from
497
/// a *non*-experimental API in this crate, it would be better for you to use
498
/// that API instead.
499
///
500
/// Alternatively, if you _do_ need some underlying function from the `arti`
501
/// crate, it would be better for all of us if you had a stable interface to that
502
/// function. Please reach out to the Arti developers, so we can work together
503
/// to get you the stable API you need.
504
354
pub fn main() {
505
354
    match main_main(std::env::args_os()) {
506
210
        Ok(()) => {}
507
144
        Err(e) => {
508
            use arti_client::HintableError;
509
144
            if let Some(hint) = e.hint() {
510
                info!("{}", hint);
511
144
            }
512

            
513
144
            match e.downcast_ref::<clap::Error>() {
514
48
                Some(clap_err) => clap_err.exit(),
515
112
                None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
516
            }
517
        }
518
    }
519
306
}