1
#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_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)] // 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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
45

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

            
52
pub mod cfg;
53
pub mod logging;
54
#[cfg(not(feature = "onion-service-service"))]
55
mod onion_proxy_disabled;
56

            
57
mod subcommands;
58

            
59
/// Helper:
60
/// Declare a series of modules as public if experimental_api is set,
61
/// and as non-public otherwise.
62
//
63
// TODO: We'd like to use visibility::make(pub) here, but it doesn't
64
// work on modules.
65
macro_rules! semipublic_mod {
66
    {
67
        $(
68
            $( #[$meta:meta] )*
69
            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
                   mod $name;
79
                }
80
            }
81
         )*
82
    }
83
}
84

            
85
semipublic_mod! {
86
    #[cfg(feature = "dns-proxy")]
87
    mod dns;
88
    mod exit;
89
    #[cfg(feature="onion-service-service")]
90
    mod onion_proxy;
91
    mod process;
92
    mod reload_cfg;
93
    mod socks;
94
}
95
#[cfg_attr(not(feature = "rpc"), path = "rpc_stub.rs")]
96
mod rpc;
97

            
98
use std::ffi::OsString;
99
use std::fmt::Write;
100

            
101
pub use cfg::{
102
    ApplicationConfig, ApplicationConfigBuilder, ArtiCombinedConfig, ArtiConfig, ArtiConfigBuilder,
103
    ProxyConfig, ProxyConfigBuilder, SystemConfig, SystemConfigBuilder, ARTI_EXAMPLE_CONFIG,
104
};
105
pub use logging::{LoggingConfig, LoggingConfigBuilder};
106

            
107
use arti_client::config::default_config_files;
108
use arti_client::TorClient;
109
use safelog::with_safe_logging_suppressed;
110
use tor_config::mistrust::BuilderExt as _;
111
use tor_config::ConfigurationSources;
112
use tor_rtcompat::ToplevelRuntime;
113

            
114
use anyhow::{Context, Error, Result};
115
use clap::{value_parser, Arg, ArgAction, Command};
116
#[allow(unused_imports)]
117
use tracing::{error, info, warn};
118

            
119
#[cfg(any(feature = "hsc", feature = "onion-service-service"))]
120
use clap::Subcommand as _;
121

            
122
#[cfg(feature = "experimental-api")]
123
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-api")))]
124
pub use subcommands::proxy::run_proxy as run;
125

            
126
/// Create a runtime for Arti to use.
127
72
fn create_runtime() -> std::io::Result<impl ToplevelRuntime> {
128
    cfg_if::cfg_if! {
129
        if #[cfg(all(feature="tokio", feature="native-tls"))] {
130
            use tor_rtcompat::tokio::TokioNativeTlsRuntime as ChosenRuntime;
131
        } else if #[cfg(all(feature="tokio", feature="rustls"))] {
132
            use tor_rtcompat::tokio::TokioRustlsRuntime as ChosenRuntime;
133
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
134
                rustls_crate::crypto::ring::default_provider(),
135

            
136
            );
137
        } else if #[cfg(all(feature="async-std", feature="native-tls"))] {
138
            use tor_rtcompat::async_std::AsyncStdNativeTlsRuntime as ChosenRuntime;
139
        } else if #[cfg(all(feature="async-std", feature="rustls"))] {
140
            use tor_rtcompat::async_std::AsyncStdRustlsRuntime as ChosenRuntime;
141
            let _idempotent_ignore = rustls_crate::crypto::CryptoProvider::install_default(
142
                rustls_crate::crypto::ring::default_provider(),
143
            );
144
        } else {
145
            compile_error!("You must configure both an async runtime and a TLS stack. See doc/TROUBLESHOOTING.md for more.");
146
        }
147
    }
148
72
    ChosenRuntime::create()
149
72
}
150

            
151
/// Return a (non-exhaustive) array of enabled Cargo features, for version printing purposes.
152
72
fn list_enabled_features() -> &'static [&'static str] {
153
72
    // HACK(eta): We can't get this directly, so we just do this awful hack instead.
154
72
    // Note that we only list features that aren't about the runtime used, since that already
155
72
    // gets printed separately.
156
72
    &[
157
72
        #[cfg(feature = "journald")]
158
72
        "journald",
159
72
        #[cfg(any(feature = "static-sqlite", feature = "static"))]
160
72
        "static-sqlite",
161
72
        #[cfg(any(feature = "static-native-tls", feature = "static"))]
162
72
        "static-native-tls",
163
72
    ]
164
72
}
165

            
166
/// Inner function, to handle a set of CLI arguments and return a single
167
/// `Result<()>` for convenient handling.
168
///
169
/// # ⚠️ Warning! ⚠️
170
///
171
/// If your program needs to call this function, you are setting yourself up for
172
/// some serious maintenance headaches.  See discussion on [`main`] and please
173
/// reach out to help us build you a better API.
174
///
175
/// # Panics
176
///
177
/// Currently, might panic if wrong arguments are specified.
178
72
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
179
72
#[allow(clippy::cognitive_complexity)]
180
72
fn main_main<I, T>(cli_args: I) -> Result<()>
181
72
where
182
72
    I: IntoIterator<Item = T>,
183
72
    T: Into<std::ffi::OsString> + Clone,
184
72
{
185
72
    // We describe a default here, rather than using `default()`, because the
186
72
    // correct behavior is different depending on whether the filename is given
187
72
    // explicitly or not.
188
72
    let mut config_file_help = "Specify which config file(s) to read.".to_string();
189
72
    if let Ok(default) = default_config_files() {
190
72
        // If we couldn't resolve the default config file, then too bad.  If something
191
72
        // actually tries to use it, it will produce an error, but don't fail here
192
72
        // just for that reason.
193
72
        write!(config_file_help, " Defaults to {:?}", default).expect("Can't write to string");
194
72
    }
195

            
196
    // We create the runtime now so that we can use its `Debug` impl to describe it for
197
    // the version string.
198
72
    let runtime = create_runtime()?;
199
72
    let features = list_enabled_features();
200
72
    let long_version = format!(
201
72
        "{}\nusing runtime: {:?}\noptional features: {}",
202
72
        env!("CARGO_PKG_VERSION"),
203
72
        runtime,
204
72
        if features.is_empty() {
205
            "<none>".into()
206
        } else {
207
72
            features.join(", ")
208
        }
209
    );
210

            
211
72
    let clap_app = Command::new("Arti")
212
72
            .version(env!("CARGO_PKG_VERSION"))
213
72
            .long_version(long_version)
214
72
            .author("The Tor Project Developers")
215
72
            .about("A Rust Tor implementation.")
216
72
            // HACK(eta): clap generates "arti [OPTIONS] <SUBCOMMAND>" for this usage string by
217
72
            //            default, but then fails to parse options properly if you do put them
218
72
            //            before the subcommand.
219
72
            //            We just declare all options as `global` and then require them to be
220
72
            //            put after the subcommand, hence this new usage string.
221
72
            .override_usage("arti <SUBCOMMAND> [OPTIONS]")
222
72
            .arg(
223
72
                Arg::new("config-files")
224
72
                    .short('c')
225
72
                    .long("config")
226
72
                    .action(ArgAction::Set)
227
72
                    .value_name("FILE")
228
72
                    .value_parser(value_parser!(OsString))
229
72
                    .action(ArgAction::Append)
230
72
                    // NOTE: don't forget the `global` flag on all arguments declared at this level!
231
72
                    .global(true)
232
72
                    .help(config_file_help),
233
72
            )
234
72
            .arg(
235
72
                Arg::new("option")
236
72
                    .short('o')
237
72
                    .action(ArgAction::Set)
238
72
                    .value_name("KEY=VALUE")
239
72
                    .action(ArgAction::Append)
240
72
                    .global(true)
241
72
                    .help("Override config file parameters, using TOML-like syntax."),
242
72
            )
243
72
            .arg(
244
72
                Arg::new("loglevel")
245
72
                    .short('l')
246
72
                    .long("log-level")
247
72
                    .global(true)
248
72
                    .action(ArgAction::Set)
249
72
                    .value_name("LEVEL")
250
72
                    .help("Override the log level (usually one of 'trace', 'debug', 'info', 'warn', 'error')."),
251
72
            )
252
72
            .arg(
253
72
                Arg::new("disable-fs-permission-checks")
254
72
                    .long("disable-fs-permission-checks")
255
72
                    .global(true)
256
72
                    .action(ArgAction::SetTrue)
257
72
                    .help("Don't check permissions on the files we use."),
258
72
            )
259
72
            .subcommand(
260
72
                Command::new("proxy")
261
72
                    .about(
262
72
                        "Run Arti in SOCKS proxy mode, proxying connections through the Tor network.",
263
72
                    )
264
72
                    .arg(
265
72
                        Arg::new("socks-port")
266
72
                            .short('p')
267
72
                            .action(ArgAction::Set)
268
72
                            .value_name("PORT")
269
72
                            .help("Port to listen on for SOCKS connections (overrides the port in the config if specified).")
270
72
                    )
271
72
                    .arg(
272
72
                        Arg::new("dns-port")
273
72
                            .short('d')
274
72
                            .action(ArgAction::Set)
275
72
                            .value_name("PORT")
276
72
                            .help("Port to listen on for DNS request (overrides the port in the config if specified).")
277
72
                    )
278
72
            )
279
72
            .subcommand_required(true)
280
72
            .arg_required_else_help(true);
281
72

            
282
72
    // When adding a subcommand, it may be necessary to add an entry in
283
72
    // `maint/check-cli-help`, to the function `help_arg`.
284
72

            
285
72
    cfg_if::cfg_if! {
286
72
        if #[cfg(feature = "onion-service-service")] {
287
72
            let clap_app = subcommands::hss::HssSubcommands::augment_subcommands(clap_app);
288
72
        }
289
72
    }
290
72

            
291
72
    cfg_if::cfg_if! {
292
72
        if #[cfg(feature = "hsc")] {
293
72
            let clap_app = subcommands::hsc::HscSubcommands::augment_subcommands(clap_app);
294
72
        }
295
72
    }
296
72

            
297
72
    // Tracing doesn't log anything when there is no subscriber set.  But we want to see
298
72
    // logging messages from config parsing etc.  We can't set the global default subscriber
299
72
    // because we can only set it once.  The other ways involve a closure.  So we have a
300
72
    // closure for all the startup code which runs *before* we set the logging properly.
301
72
    //
302
72
    // There is no cooked way to print our program name, so we do it like this.  This
303
72
    // closure is called to "make" a "Writer" for each message, so it runs at the right time:
304
72
    // before each message.
305
72
    let pre_config_logging_writer = || {
306
        // Weirdly, with .without_time(), tracing produces messages with a leading space.
307
        eprint!("arti:");
308
        std::io::stderr()
309
    };
310
72
    let pre_config_logging = tracing_subscriber::fmt()
311
72
        .without_time()
312
72
        .with_writer(pre_config_logging_writer)
313
72
        .finish();
314
72
    let pre_config_logging = tracing::Dispatch::new(pre_config_logging);
315
72
    let pre_config_logging_ret = tracing::dispatcher::with_default(&pre_config_logging, || {
316
72
        let matches = clap_app.try_get_matches_from(cli_args)?;
317

            
318
54
        let fs_mistrust_disabled = matches.get_flag("disable-fs-permission-checks");
319

            
320
        // A Mistrust object to use for loading our configuration.  Elsewhere, we
321
        // use the value _from_ the configuration.
322
54
        let cfg_mistrust = if fs_mistrust_disabled {
323
            fs_mistrust::Mistrust::new_dangerously_trust_everyone()
324
        } else {
325
54
            fs_mistrust::MistrustBuilder::default()
326
54
                .build_for_arti()
327
54
                .expect("Could not construct default fs-mistrust")
328
        };
329

            
330
54
        let mut override_options: Vec<String> = matches
331
54
            .get_many::<String>("option")
332
54
            .unwrap_or_default()
333
54
            .cloned()
334
54
            .collect();
335
54
        if fs_mistrust_disabled {
336
            override_options.push("storage.permissions.dangerously_trust_everyone=true".to_owned());
337
54
        }
338

            
339
54
        let cfg_sources = {
340
54
            let mut cfg_sources = ConfigurationSources::try_from_cmdline(
341
54
                || default_config_files().context("identify default config file locations"),
342
54
                matches
343
54
                    .get_many::<OsString>("config-files")
344
54
                    .unwrap_or_default(),
345
54
                override_options,
346
54
            )?;
347
54
            cfg_sources.set_mistrust(cfg_mistrust);
348
54
            cfg_sources
349
        };
350

            
351
54
        let cfg = cfg_sources.load()?;
352
54
        let (config, client_config) =
353
54
            tor_config::resolve::<ArtiCombinedConfig>(cfg).context("read configuration")?;
354

            
355
54
        let log_mistrust = client_config.fs_mistrust().clone();
356
54

            
357
54
        Ok::<_, Error>((matches, cfg_sources, config, client_config, log_mistrust))
358
72
    })?;
359
    // Sadly I don't seem to be able to persuade rustfmt to format the two lists of
360
    // variable names identically.
361
54
    let (matches, cfg_sources, config, client_config, log_mistrust) = pre_config_logging_ret;
362

            
363
54
    let _log_guards = logging::setup_logging(
364
54
        config.logging(),
365
54
        &log_mistrust,
366
54
        client_config.as_ref(),
367
54
        matches.get_one::<String>("loglevel").map(|s| s.as_str()),
368
54
    )?;
369

            
370
54
    if !config.application().allow_running_as_root {
371
        process::exit_if_root();
372
54
    }
373

            
374
    #[cfg(feature = "harden")]
375
54
    if !config.application().permit_debugging {
376
54
        if let Err(e) = process::enable_process_hardening() {
377
            error!("Encountered a problem while enabling hardening. To disable this feature, set application.permit_debugging to true.");
378
            return Err(e);
379
54
        }
380
    }
381

            
382
    // Check for the "proxy" subcommand.
383
54
    if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
384
        return subcommands::proxy::run(runtime, proxy_matches, cfg_sources, config, client_config);
385
54
    }
386

            
387
    // Check for the optional "hss" subcommand.
388
    cfg_if::cfg_if! {
389
        if #[cfg(feature = "onion-service-service")] {
390
54
            if let Some(hss_matches) = matches.subcommand_matches("hss") {
391
18
                return subcommands::hss::run(hss_matches, &config, &client_config);
392
36
            }
393
        }
394
    }
395

            
396
    // Check for the optional "hsc" subcommand.
397
    cfg_if::cfg_if! {
398
        if #[cfg(feature = "hsc")] {
399
36
            if let Some(hsc_matches) = matches.subcommand_matches("hsc") {
400
36
                return subcommands::hsc::run(runtime, hsc_matches, &client_config);
401
            }
402
        }
403
    }
404

            
405
    panic!("Subcommand added to clap subcommand list, but not yet implemented");
406
72
}
407

            
408
/// Main program, callable directly from a binary crate's `main`
409
///
410
/// This function behaves the same as `main_main()`, except:
411
///   * It takes command-line arguments from `std::env::args_os` rather than
412
///     from an argument.
413
///   * It exits the process with an appropriate error code on error.
414
///
415
/// # ⚠️ Warning ⚠️
416
///
417
/// Calling this function, or the related experimental function `main_main`, is
418
/// probably a bad idea for your code.  It means that you are invoking Arti as
419
/// if from the command line, but keeping it embedded inside your process. Doing
420
/// this will block your process take over handling for several signal types,
421
/// possibly disable debugger attachment, and a lot more junk that a library
422
/// really has no business doing for you.  It is not designed to run in this
423
/// way, and may give you strange results.
424
///
425
/// If the functionality you want is available in [`arti_client`] crate, or from
426
/// a *non*-experimental API in this crate, it would be better for you to use
427
/// that API instead.
428
///
429
/// Alternatively, if you _do_ need some underlying function from the `arti`
430
/// crate, it would be better for all of us if you had a stable interface to that
431
/// function. Please reach out to the Arti developers, so we can work together
432
/// to get you the stable API you need.
433
72
pub fn main() {
434
72
    match main_main(std::env::args_os()) {
435
36
        Ok(()) => {}
436
36
        Err(e) => {
437
            use arti_client::HintableError;
438
36
            if let Some(hint) = e.hint() {
439
                info!("{}", hint);
440
36
            }
441

            
442
36
            match e.downcast_ref::<clap::Error>() {
443
18
                Some(clap_err) => clap_err.exit(),
444
21
                None => with_safe_logging_suppressed(|| tor_error::report_and_exit(e)),
445
            }
446
        }
447
    }
448
54
}