1
//! A relay binary use to join the Tor network to relay anonymous communication.
2
//!
3
//! NOTE: This binary is still highly experimental as in in active development, not stable and
4
//! without any type of guarantee of running or even working.
5

            
6
// @@ begin lint list maintained by maint/add_warning @@
7
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
8
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
9
#![warn(missing_docs)]
10
#![warn(noop_method_call)]
11
#![warn(unreachable_pub)]
12
#![warn(clippy::all)]
13
#![deny(clippy::await_holding_lock)]
14
#![deny(clippy::cargo_common_metadata)]
15
#![deny(clippy::cast_lossless)]
16
#![deny(clippy::checked_conversions)]
17
#![warn(clippy::cognitive_complexity)]
18
#![deny(clippy::debug_assert_with_mut_call)]
19
#![deny(clippy::exhaustive_enums)]
20
#![deny(clippy::exhaustive_structs)]
21
#![deny(clippy::expl_impl_clone_on_copy)]
22
#![deny(clippy::fallible_impl_from)]
23
#![deny(clippy::implicit_clone)]
24
#![deny(clippy::large_stack_arrays)]
25
#![warn(clippy::manual_ok_or)]
26
#![deny(clippy::missing_docs_in_private_items)]
27
#![warn(clippy::needless_borrow)]
28
#![warn(clippy::needless_pass_by_value)]
29
#![warn(clippy::option_option)]
30
#![deny(clippy::print_stderr)]
31
#![deny(clippy::print_stdout)]
32
#![warn(clippy::rc_buffer)]
33
#![deny(clippy::ref_option_ref)]
34
#![warn(clippy::semicolon_if_nothing_returned)]
35
#![warn(clippy::trait_duplication_in_bounds)]
36
#![deny(clippy::unchecked_duration_subtraction)]
37
#![deny(clippy::unnecessary_wraps)]
38
#![warn(clippy::unseparated_literal_suffix)]
39
#![deny(clippy::unwrap_used)]
40
#![deny(clippy::mod_module_files)]
41
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
42
#![allow(clippy::uninlined_format_args)]
43
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
44
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
45
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
46
#![allow(clippy::needless_lifetimes)] // See arti#1765
47
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
48
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
49

            
50
mod cli;
51
mod config;
52
mod err;
53
mod relay;
54

            
55
use std::io::IsTerminal as _;
56

            
57
use anyhow::Context;
58
use clap::Parser;
59
use safelog::with_safe_logging_suppressed;
60
use tor_rtcompat::{PreferredRuntime, Runtime};
61
use tracing_subscriber::filter::EnvFilter;
62
use tracing_subscriber::util::SubscriberInitExt;
63
use tracing_subscriber::FmtSubscriber;
64

            
65
use crate::config::{base_resolver, TorRelayConfig, DEFAULT_LOG_LEVEL};
66
use crate::relay::TorRelay;
67

            
68
fn main() {
69
    // Will exit if '--help' used or there's a parse error.
70
    let cli = cli::Cli::parse();
71

            
72
    if let Err(e) = main_main(cli) {
73
        // TODO: Use arti_client's `HintableError` here (see `arti::main`)?
74
        // TODO: Why do we suppress safe logging, and squash the anyhow result into a single line?
75
        // TODO: Do we want to log the error?
76
        with_safe_logging_suppressed(|| tor_error::report_and_exit::<_, ()>(e));
77
    }
78
}
79

            
80
/// The real main without the error formatting.
81
fn main_main(cli: cli::Cli) -> anyhow::Result<()> {
82
    // Register a basic stderr logger until we have enough info to configure the main logger.
83
    // Unlike arti, we enable timestamps for this pre-config logger.
84
    // TODO: Consider using timestamps with reduced-granularity (see `LogPrecision`).
85
    let level: tracing::metadata::Level = cli
86
        .global
87
        .log_level
88
        .map(Into::into)
89
        .unwrap_or(DEFAULT_LOG_LEVEL);
90
    let filter = EnvFilter::builder()
91
        .with_default_directive(level.into())
92
        .parse("")
93
        .expect("empty filter directive should be trivially parsable");
94
    #[allow(clippy::print_stderr)]
95
    FmtSubscriber::builder()
96
        .with_env_filter(filter)
97
        .with_ansi(std::io::stderr().is_terminal())
98
        .with_writer(|| {
99
            eprint!("arti-relay: ");
100
            std::io::stderr()
101
        })
102
        .finish()
103
        .init();
104

            
105
    match cli.command {
106
        #[allow(clippy::print_stdout)]
107
        cli::Commands::BuildInfo => {
108
            println!("Version: {}", env!("CARGO_PKG_VERSION"));
109
            // these are set by our build script
110
            println!("Features: {}", env!("BUILD_FEATURES"));
111
            println!("Profile: {}", env!("BUILD_PROFILE"));
112
            println!("Debug: {}", env!("BUILD_DEBUG"));
113
            println!("Optimization level: {}", env!("BUILD_OPT_LEVEL"));
114
            println!("Rust version: {}", env!("BUILD_RUSTC_VERSION"));
115
            println!("Target triple: {}", env!("BUILD_TARGET"));
116
            println!("Host triple: {}", env!("BUILD_HOST"));
117
        }
118
        cli::Commands::Run(args) => start_relay(args, cli.global)?,
119
    }
120

            
121
    Ok(())
122
}
123

            
124
/// Initialize and start the relay.
125
// Pass by value so that we don't need to clone fields, which keeps the code simpler.
126
#[allow(clippy::needless_pass_by_value)]
127
fn start_relay(_args: cli::RunArgs, global_args: cli::GlobalArgs) -> anyhow::Result<()> {
128
    let runtime = init_runtime().context("Failed to initialize the runtime")?;
129

            
130
    let mut cfg_sources = global_args
131
        .config()
132
        .context("Failed to get configuration sources")?;
133

            
134
    // A Mistrust object to use for loading our configuration.
135
    // Elsewhere, we use the value _from_ the configuration.
136
    let cfg_mistrust = if global_args.disable_fs_permission_checks {
137
        fs_mistrust::Mistrust::new_dangerously_trust_everyone()
138
    } else {
139
        fs_mistrust::MistrustBuilder::default()
140
            // By default, a `Mistrust` checks an environment variable.
141
            // We do not (at the moment) want this behaviour for relays:
142
            // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2699#note_3147502
143
            .ignore_environment()
144
            .build()
145
            .expect("default fs-mistrust should be buildable")
146
    };
147

            
148
    cfg_sources.set_mistrust(cfg_mistrust);
149

            
150
    let cfg = cfg_sources
151
        .load()
152
        .context("Failed to load configuration sources")?;
153
    let config =
154
        tor_config::resolve::<TorRelayConfig>(cfg).context("Failed to resolve configuration")?;
155

            
156
    // TODO: Configure a proper logger, not just a simple stderr logger.
157
    // TODO: We may want this to be the global logger, but if we use arti's `setup_logging` in the
158
    // future, it returns a `LogGuards` which we'd have no way of holding on to until the
159
    // application exits (see https://gitlab.torproject.org/tpo/core/arti/-/issues/1791).
160
    let filter = EnvFilter::builder()
161
        .parse(&config.logging.console)
162
        .with_context(|| {
163
            format!(
164
                "Failed to parse console logging directive {:?}",
165
                config.logging.console,
166
            )
167
        })?;
168
    #[allow(clippy::print_stderr)]
169
    let logger = tracing_subscriber::FmtSubscriber::builder()
170
        .with_env_filter(filter)
171
        .with_ansi(std::io::stderr().is_terminal())
172
        .with_writer(|| {
173
            eprint!("arti-relay: ");
174
            std::io::stderr()
175
        })
176
        .finish();
177
    let logger = tracing::Dispatch::new(logger);
178

            
179
    tracing::dispatcher::with_default(&logger, || {
180
        let path_resolver = base_resolver();
181
        let relay =
182
            TorRelay::new(runtime, &config, path_resolver).context("Failed to initialize relay")?;
183
        run_relay(relay)
184
    })?;
185

            
186
    Ok(())
187
}
188

            
189
/// Run the relay.
190
#[allow(clippy::unnecessary_wraps)] // TODO: not implemented yet; remove me
191
fn run_relay<R: Runtime>(_relay: TorRelay<R>) -> anyhow::Result<()> {
192
    Ok(())
193
}
194

            
195
/// Initialize a runtime.
196
///
197
/// Any commands that need a runtime should call this so that we use a consistent runtime.
198
fn init_runtime() -> std::io::Result<impl Runtime> {
199
    // Use the tokio runtime from tor_rtcompat unless we later find a reason to use tokio directly;
200
    // see https://gitlab.torproject.org/tpo/core/arti/-/work_items/1744
201
    PreferredRuntime::create()
202
}