1
//! A relay binary used to join the Tor network to relay anonymous communication.
2
//!
3
//! NOTE: This binary is still highly experimental as in active development, not stable and
4
//! without any type of guarantee of running or even working.
5
//!
6
//! ## Error handling
7
//!
8
//! We return [`anyhow::Error`] for functions whose errors will always result in an exit and don't
9
//! need to be handled individually.
10
//! When we do need to handle errors, functions should return a more comprehensive error type (for
11
//! example one created with `thiserror`).
12

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

            
59
mod cli;
60
mod client;
61
mod config;
62
mod keys;
63
mod relay;
64
mod tasks;
65
mod util;
66

            
67
use std::io::IsTerminal as _;
68
use std::time::SystemTime;
69

            
70
use anyhow::Context;
71
use base64ct::Base64Unpadded;
72
use base64ct::Encoding as _;
73
use clap::Parser;
74
use futures::FutureExt;
75
use safelog::with_safe_logging_suppressed;
76
use tor_basic_utils::iter_join;
77
use tor_error::warn_report;
78
use tor_keymgr::KeyMgr;
79
use tor_keymgr::KeySpecifierPattern as _;
80
use tor_relay_crypto::pk::RelayNtorKeypair;
81
use tor_relay_crypto::pk::{RelayIdentityKeypair, RelayIdentityRsaKeypair};
82
use tor_rtcompat::SpawnExt;
83
use tor_rtcompat::tokio::TokioRustlsRuntime;
84
use tor_rtcompat::{Runtime, ToplevelRuntime};
85
use tracing::{debug, info, trace};
86
use tracing_subscriber::FmtSubscriber;
87
use tracing_subscriber::filter::EnvFilter;
88
use tracing_subscriber::util::SubscriberInitExt;
89

            
90
use crate::config::{DEFAULT_LOG_LEVEL, TorRelayConfig, base_resolver};
91
use crate::keys::{
92
    RelayIdentityKeypairSpecifier, RelayIdentityRsaKeypairSpecifier, RelayNtorKeypairSpecifier,
93
    RelayNtorKeypairSpecifierPattern,
94
};
95
use crate::relay::InertTorRelay;
96

            
97
fn main() {
98
    // Will exit if '--help' used or there's a parse error.
99
    let cli = cli::Cli::parse();
100

            
101
    if let Err(e) = main_main(cli) {
102
        // TODO: Use arti_client's `HintableError` here (see `arti::main`)?
103
        // TODO: Why do we suppress safe logging?
104
        // TODO: Do we want to log the error?
105
        // We use anyhow's error formatting here rather than `tor_error::report_and_exit` since the
106
        // latter seems to omit some error info and anyhow's error formatting is nicer.
107
        #[allow(clippy::print_stderr)]
108
        with_safe_logging_suppressed(|| {
109
            eprintln!("Error: {e:?}");
110
            // The 127 is copied from `tor_error::report_and_exit`.
111
            // It's unclear why 127 was chosen there.
112
            std::process::exit(127);
113
        });
114
    }
115
}
116

            
117
/// The real main without the error formatting.
118
fn main_main(cli: cli::Cli) -> anyhow::Result<()> {
119
    // Register a basic stderr logger until we have enough info to configure the main logger.
120
    // Unlike arti, we enable timestamps for this pre-config logger.
121
    // TODO: Consider using timestamps with reduced-granularity (see `LogPrecision`).
122
    let level: tracing::metadata::Level = cli
123
        .global
124
        .log_level
125
        .map(Into::into)
126
        .unwrap_or(DEFAULT_LOG_LEVEL);
127
    let filter = EnvFilter::builder()
128
        .with_default_directive(level.into())
129
        .parse("")
130
        .expect("empty filter directive should be trivially parsable");
131
    FmtSubscriber::builder()
132
        .with_env_filter(filter)
133
        .with_ansi(std::io::stderr().is_terminal())
134
        .with_writer(std::io::stderr)
135
        .finish()
136
        .init();
137

            
138
    match cli.command {
139
        #[allow(clippy::print_stdout)]
140
        cli::Commands::BuildInfo => {
141
            println!("Version: {}", env!("CARGO_PKG_VERSION"));
142
            // these are set by our build script
143
            println!("Features: {}", env!("BUILD_FEATURES"));
144
            println!("Profile: {}", env!("BUILD_PROFILE"));
145
            println!("Debug: {}", env!("BUILD_DEBUG"));
146
            println!("Optimization level: {}", env!("BUILD_OPT_LEVEL"));
147
            println!("Rust version: {}", env!("BUILD_RUSTC_VERSION"));
148
            println!("Target triple: {}", env!("BUILD_TARGET"));
149
            println!("Host triple: {}", env!("BUILD_HOST"));
150
        }
151
        cli::Commands::Run(args) => start_relay(args, cli.global)?,
152
    }
153

            
154
    Ok(())
155
}
156

            
157
/// Initialize and start the relay.
158
// Pass by value so that we don't need to clone fields, which keeps the code simpler.
159
#[allow(clippy::needless_pass_by_value)]
160
fn start_relay(_args: cli::RunArgs, global_args: cli::GlobalArgs) -> anyhow::Result<()> {
161
    // TODO: Warn (or exit?) if running as root; see 'arti::process::running_as_root()'.
162

            
163
    let mut cfg_sources = global_args
164
        .config()
165
        .context("Failed to get configuration sources")?;
166

            
167
    debug!(
168
        "Using override options: {}",
169
        iter_join(", ", cfg_sources.options()),
170
    );
171

            
172
    // A Mistrust object to use for loading our configuration.
173
    // Elsewhere, we use the value _from_ the configuration.
174
    let cfg_mistrust = if global_args.disable_fs_permission_checks {
175
        fs_mistrust::Mistrust::new_dangerously_trust_everyone()
176
    } else {
177
        fs_mistrust::MistrustBuilder::default()
178
            // By default, a `Mistrust` checks an environment variable.
179
            // We do not (at the moment) want this behaviour for relays:
180
            // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2699#note_3147502
181
            .ignore_environment()
182
            .build()
183
            .expect("default fs-mistrust should be buildable")
184
    };
185

            
186
    cfg_sources.set_mistrust(cfg_mistrust);
187

            
188
    let cfg = cfg_sources
189
        .load()
190
        .context("Failed to load configuration sources")?;
191
    let config =
192
        tor_config::resolve::<TorRelayConfig>(cfg).context("Failed to resolve configuration")?;
193

            
194
    // TODO: Configure a proper logger, not just a simple stderr logger.
195
    // TODO: We may want this to be the global logger, but if we use arti's `setup_logging` in the
196
    // future, it returns a `LogGuards` which we'd have no way of holding on to until the
197
    // application exits (see https://gitlab.torproject.org/tpo/core/arti/-/issues/1791).
198
    let filter = EnvFilter::builder()
199
        .parse(&config.logging.console)
200
        .with_context(|| {
201
            format!(
202
                "Failed to parse console logging directive {:?}",
203
                config.logging.console,
204
            )
205
        })?;
206
    let logger = tracing_subscriber::FmtSubscriber::builder()
207
        .with_env_filter(filter)
208
        .with_ansi(std::io::stderr().is_terminal())
209
        .with_writer(std::io::stderr)
210
        .finish();
211
    let logger = tracing::Dispatch::new(logger);
212

            
213
    // Disable safe logging if requested.
214
    // This guard will be dropped at the end of this function,
215
    // which means we effectively re-enable safe logging once this function returns.
216
    // TODO: Do we want this guard behaviour?
217
    // I think it would be better to enable safe-logging forever?
218
    let _safelog_guard = if config.logging.log_sensitive_information {
219
        match safelog::disable_safe_logging() {
220
            Ok(guard) => Some(guard),
221
            Err(e) => {
222
                // We don't need to propagate this error;
223
                // it isn't the end of the world if we were unable to disable safe logging.
224
                warn_report!(e, "Unable to disable safe logging");
225
                None
226
            }
227
        }
228
    } else {
229
        None
230
    };
231

            
232
    tracing::dispatcher::with_default(&logger, || {
233
        let runtime = init_runtime().context("Failed to initialize the runtime")?;
234

            
235
        // Configure tor-log-ratelim early before we begin logging.
236
        tor_log_ratelim::install_runtime(runtime.clone())
237
            .context("Failed to initialize tor-log-ratelim")?;
238

            
239
        let path_resolver = base_resolver();
240
        let relay =
241
            InertTorRelay::new(config, path_resolver).context("Failed to initialize the relay")?;
242

            
243
        match mainloop(&runtime, run_relay(runtime.clone(), relay))? {
244
            MainloopStatus::Finished(Err(e)) => Err(e),
245
            MainloopStatus::CtrlC => {
246
                info!("Received a ctrl-c; stopping the relay");
247
                Ok(())
248
            }
249
        }
250
    })
251
}
252

            
253
/// A helper to drive a future using a runtime.
254
///
255
/// This calls `block_on` on the runtime.
256
/// The future will be cancelled on a ctrl-c event.
257
fn mainloop<T: Send + 'static>(
258
    runtime: &impl ToplevelRuntime,
259
    fut: impl Future<Output = T> + Send + 'static,
260
) -> anyhow::Result<MainloopStatus<T>> {
261
    trace!("Starting runtime");
262

            
263
    let rv = runtime.block_on(async {
264
        // Code running in 'block_on' runs slower than in a task (in tokio at least),
265
        // so the future is run on a task.
266
        let mut handle = runtime
267
            .spawn_with_handle(fut)
268
            .context("Failed to spawn task")?
269
            .fuse();
270

            
271
        futures::select!(
272
            // Signal handler is registered on the first poll.
273
            res = tokio::signal::ctrl_c().fuse() => {
274
                let () = res.context("Failed to listen for ctrl-c event")?;
275
                trace!("Received a ctrl-c");
276
                // Dropping the handle will cancel the task, so we do that explicitly here.
277
                drop(handle);
278
                Ok(MainloopStatus::CtrlC)
279
            }
280
            x = handle => Ok(MainloopStatus::Finished(x)),
281
        )
282
    });
283

            
284
    trace!("Finished runtime");
285
    rv
286
}
287

            
288
/// Run the relay.
289
///
290
/// This blocks until the relay stops.
291
async fn run_relay<R: Runtime>(
292
    runtime: R,
293
    inert_relay: InertTorRelay,
294
) -> anyhow::Result<void::Void> {
295
    let relay = inert_relay
296
        .init(runtime)
297
        .await
298
        .context("Failed to bootstrap")?;
299

            
300
    // TODO: This is mostly useful for debugging.
301
    // We might want to remove this in the future, or move this somewhere else.
302
    log_public_keys(relay.keymgr()).context("Failed to log public keys")?;
303

            
304
    // This blocks until end of time or an error.
305
    relay.run().await
306
}
307

            
308
/// Initialize a runtime.
309
///
310
/// Any cli commands that need a runtime should call this so that we use a consistent runtime.
311
fn init_runtime() -> std::io::Result<impl ToplevelRuntime> {
312
    // Use the tokio runtime from tor_rtcompat unless we later find a reason to use tokio directly.
313
    // See https://gitlab.torproject.org/tpo/core/arti/-/work_items/1744.
314
    // Relays must use rustls as native-tls doesn't support
315
    // `CertifiedConn::export_keying_material()`.
316

            
317
    // Note: See comments in `tor_rtcompat::impls::rustls::RustlsProvider`
318
    // about choice of default crypto provider.
319
    let _idempotent_ignore = rustls::crypto::CryptoProvider::install_default(
320
        rustls::crypto::aws_lc_rs::default_provider(),
321
    );
322

            
323
    TokioRustlsRuntime::create()
324
}
325

            
326
/// The result of [`mainloop`].
327
enum MainloopStatus<T> {
328
    /// The result from the completed future.
329
    Finished(T),
330
    /// The future was cancelled due to a ctrl-c event.
331
    CtrlC,
332
}
333

            
334
/// Log the relay's identities and public ntor key.
335
fn log_public_keys(keymgr: &KeyMgr) -> anyhow::Result<()> {
336
    let rsa_id = keymgr
337
        .get::<RelayIdentityRsaKeypair>(&RelayIdentityRsaKeypairSpecifier::new())
338
        .context("Failed to get RSA identity from key manager")?
339
        .context("Missing RSA identity")?
340
        .to_rsa_identity();
341
    let ed_id = keymgr
342
        .get::<RelayIdentityKeypair>(&RelayIdentityKeypairSpecifier::new())
343
        .context("Failed to get Ed25519 identity from key manager")?
344
        .context("Missing Ed25519 identity")?
345
        .to_ed25519_id();
346

            
347
    let ntor_keys = keymgr
348
        .list_matching(&RelayNtorKeypairSpecifierPattern::new_any().arti_pattern()?)?
349
        .into_iter()
350
        .map(|entry| {
351
            let key_path = entry.key_path();
352
            let valid_until =
353
                SystemTime::from(RelayNtorKeypairSpecifier::try_from(key_path)?.valid_until);
354
            let key = keymgr
355
                .get_entry::<RelayNtorKeypair>(&entry)
356
                .context("Failed to get ntor key from key manager")?
357
                .context("Ntor key disappeared?!")?;
358
            Ok((valid_until, key))
359
        })
360
        .collect::<anyhow::Result<Vec<_>>>()?;
361

            
362
    // Find the longest valid ntor key (max as ordered by the "valid until" time).
363
    let ntor = ntor_keys
364
        .into_iter()
365
        .max_by_key(|x| x.0)
366
        .map(|x| x.1)
367
        .context("Missing ntor key")?;
368

            
369
    // Base64-encode the public ntor key.
370
    let ntor = Base64Unpadded::encode_string(ntor.public().inner().as_bytes());
371

            
372
    // Log the relay's identities.
373
    // TODO: We should also log this after a key rotation:
374
    // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/3773#note_3367789
375
    // TODO: This is useful at info level while we're developing,
376
    // but the level should probably be lowered in the future.
377
    tracing::info!("RSA identity: {rsa_id}");
378
    tracing::info!("Ed25519 identity: {ed_id}");
379
    tracing::info!("Ntor public key: {ntor}");
380

            
381
    Ok(())
382
}