1
//! Types and functions to configure a Tor Relay.
2

            
3
// TODO: It would be nice to remove the builder aspect of these config objects, as we don't need
4
// them for arti-relay. But I don't think we can do so while still using tor-config. See:
5
// https://gitlab.torproject.org/tpo/core/arti/-/issues/2253
6

            
7
mod listen;
8

            
9
use std::borrow::Cow;
10
use std::net::{IpAddr, SocketAddrV4, SocketAddrV6};
11
use std::path::PathBuf;
12

            
13
use derive_deftly::Deftly;
14
use derive_more::AsRef;
15
use directories::ProjectDirs;
16
use fs_mistrust::{Mistrust, MistrustBuilder};
17
use serde::{Deserialize, Serialize};
18
use std::sync::LazyLock;
19
use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
20
use tor_circmgr::{CircuitTiming, PathConfig, PreemptiveCircuitConfig};
21
use tor_config::derive::prelude::*;
22
use tor_config::{
23
    ConfigBuildError, ExplicitOrAuto, extend_builder::extend_with_replace, mistrust::BuilderExt,
24
};
25
use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
26
use tor_dircommon::config::{NetworkConfig, NetworkConfigBuilder};
27
use tor_dircommon::fallback::FallbackList;
28
use tor_guardmgr::bridge::BridgeConfig;
29
use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode};
30
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
31
use tracing::metadata::Level;
32
use tracing_subscriber::filter::EnvFilter;
33

            
34
use crate::util::NonEmptyList;
35

            
36
use self::listen::Listen;
37

            
38
/// Paths used for default configuration files.
39
2
pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
40
    // the base path resolver includes the 'ARTI_RELAY_CONFIG' variable
41
2
    let resolver = base_resolver();
42
2
    [
43
2
        "${ARTI_RELAY_CONFIG}/arti-relay.toml",
44
2
        "${ARTI_RELAY_CONFIG}/arti-relay.d/",
45
2
    ]
46
2
    .into_iter()
47
5
    .map(|f| CfgPath::new(f.into()).path(&resolver))
48
2
    .collect()
49
2
}
50

            
51
/// A [`CfgPathResolver`] with the base variables configured for a Tor relay.
52
///
53
/// A relay should have a single `CfgPathResolver` that is passed around where needed to ensure that
54
/// all parts of the relay are resolving paths consistently using the same variables.
55
/// If you need to resolve a path,
56
/// you likely want a reference to the existing resolver,
57
/// and not to create a new one here.
58
///
59
/// The supported variables are:
60
///   - `ARTI_RELAY_CACHE`:
61
///     An arti-specific cache directory.
62
///   - `ARTI_RELAY_CONFIG`:
63
///     An arti-specific configuration directory.
64
///   - `ARTI_RELAY_LOCAL_DATA`:
65
///     An arti-specific directory in the user's "local data" space.
66
///   - `PROGRAM_DIR`:
67
///     The directory of the currently executing binary.
68
///     See documentation for [`std::env::current_exe`] for security notes.
69
///   - `USER_HOME`:
70
///     The user's home directory.
71
///
72
/// These variables are implemented using the [`directories`] crate,
73
/// and so should use appropriate system-specific overrides under the hood.
74
/// (Some of those overrides are based on environment variables.)
75
/// For more information, see that crate's documentation.
76
//
77
// NOTE: We intentionally don't expose an `ARTI_RELAY_SHARED_DATA`
78
// (analogous to `ARTI_SHARED_DATA` in arti).
79
// This is almost certainly never intended over `ARTI_RELAY_LOCAL_DATA`,
80
// so by removing it we don't need to worry about bugs from mixing them up.
81
// We can introduce it later if really needed.
82
4
pub(crate) fn base_resolver() -> CfgPathResolver {
83
6
    let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
84
6
    let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
85
6
    let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
86
4
    let program_dir = get_program_dir().map(Cow::Owned);
87
4
    let user_home = tor_config_path::home().map(Cow::Borrowed);
88

            
89
4
    let mut resolver = CfgPathResolver::default();
90

            
91
4
    resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
92
4
    resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
93
4
    resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
94
4
    resolver.set_var("PROGRAM_DIR", program_dir);
95
4
    resolver.set_var("USER_HOME", user_home);
96

            
97
4
    resolver
98
4
}
99

            
100
/// The directory holding the currently executing program.
101
6
fn get_program_dir() -> Result<PathBuf, CfgPathError> {
102
6
    let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
103
6
    let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
104
6
    Ok(directory.to_owned())
105
6
}
106

            
107
/// A `ProjectDirs` object for Arti relays.
108
14
fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
109
    /// lazy lock holding the ProjectDirs object.
110
    static PROJECT_DIRS: LazyLock<Option<ProjectDirs>> =
111
2
        LazyLock::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
112

            
113
14
    PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
114
14
}
115

            
116
/// A configuration used by a TorRelay.
117
///
118
/// This is a builder so that it works with tor-config.
119
/// We don't expect to ever use it as a builder since we don't provide this as a public rust API.
120
#[derive(Clone, Deftly, Debug, Eq, PartialEq, AsRef)]
121
#[derive_deftly(TorConfig)]
122
#[deftly(tor_config(no_default_trait))]
123
#[non_exhaustive]
124
pub(crate) struct TorRelayConfig {
125
    /// Configuration for the "relay" part of the relay.
126
    // TODO: Add a better doc comment here once we figure out exactly how we want the config to be
127
    // structured.
128
    #[deftly(tor_config(sub_builder))]
129
    pub(crate) relay: RelayConfig,
130

            
131
    /// Information about the Tor network we want to connect to.
132
    #[deftly(tor_config(sub_builder))]
133
    pub(crate) tor_network: NetworkConfig,
134

            
135
    /// Logging configuration
136
    #[deftly(tor_config(sub_builder))]
137
    pub(crate) logging: LoggingConfig,
138

            
139
    /// Directories for storing information on disk
140
    #[deftly(tor_config(sub_builder))]
141
    pub(crate) storage: StorageConfig,
142

            
143
    /// Information about how to build paths through the network.
144
    #[deftly(tor_config(sub_builder))]
145
    pub(crate) channel: ChannelConfig,
146

            
147
    /// Configuration for system resources
148
    #[deftly(tor_config(sub_builder))]
149
    pub(crate) system: SystemConfig,
150

            
151
    /// Information about how to build paths through the network.
152
    // We don't expose this field in the config.
153
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
154
    // Needed to implement `CircMgrConfig`.
155
    #[as_ref]
156
    pub(crate) path_rules: PathConfig,
157

            
158
    /// Information about vanguards.
159
    // We don't expose this field in the config.
160
    #[deftly(tor_config(
161
        skip,
162
        build = r#"|_|
163
        VanguardConfigBuilder::default()
164
            .mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
165
            .build()
166
            .expect("Could not build a disabled `VanguardConfig`")"#
167
    ))]
168
    // Needed to implement `CircMgrConfig`.
169
    #[as_ref]
170
    pub(crate) vanguards: VanguardConfig,
171

            
172
    /// Information about how to retry and expire circuits and request for circuits.
173
    // We don't expose this field in the config.
174
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
175
    // Needed to implement `CircMgrConfig`.
176
    #[as_ref]
177
    pub(crate) circuit_timing: CircuitTiming,
178

            
179
    /// Information about preemptive circuits.
180
    // We don't expose this field in the config.
181
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
182
    // Needed to implement `CircMgrConfig`.
183
    #[as_ref]
184
    pub(crate) preemptive_circuits: PreemptiveCircuitConfig,
185
}
186

            
187
impl tor_config::load::TopLevel for TorRelayConfig {
188
    type Builder = TorRelayConfigBuilder;
189
}
190

            
191
impl tor_circmgr::CircMgrConfig for TorRelayConfig {}
192

            
193
// Needed to implement `GuardMgrConfig`.
194
impl AsRef<FallbackList> for TorRelayConfig {
195
    fn as_ref(&self) -> &FallbackList {
196
        self.tor_network.fallback_caches()
197
    }
198
}
199

            
200
// Needed to implement `GuardMgrConfig`.
201
impl AsRef<[BridgeConfig]> for TorRelayConfig {
202
    fn as_ref(&self) -> &[BridgeConfig] {
203
        // Relays don't use bridges.
204
        &[]
205
    }
206
}
207

            
208
impl tor_guardmgr::GuardMgrConfig for TorRelayConfig {
209
    fn bridges_enabled(&self) -> bool {
210
        // Relays don't use bridges.
211
        false
212
    }
213
}
214

            
215
/// Configuration for the "relay" part of the relay.
216
///
217
/// TODO: I'm not really sure what to call this yet. I'm expecting that we'll rename and reorganize
218
/// things as we add more options. But we should come back to this and update the name and/or doc
219
/// comment.
220
///
221
/// TODO: There's a high-level issue for discussing these options:
222
/// <https://gitlab.torproject.org/tpo/core/arti/-/issues/2252>
223
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
224
#[derive_deftly(TorConfig)]
225
#[deftly(tor_config(no_default_trait))]
226
pub(crate) struct RelayConfig {
227
    /// Addresses to listen on for incoming OR connections.
228
    #[deftly(tor_config(no_default))]
229
    pub(crate) listen: Listen,
230

            
231
    /// Addresses to advertise on the network for receiving OR connections.
232
    // For now, we've decided that we don't want to include any IP address auto-detection in
233
    // arti-relay, so we require users to provide the addresses to advertise. (So no `Option` and
234
    // `builder(default)` here).
235
    #[deftly(tor_config(no_default))]
236
    pub(crate) advertise: Advertise,
237
}
238

            
239
/// The address(es) to advertise on the network.
240
// TODO: We'll want to make sure we check that the addresses are valid before uploading them in a
241
// server descriptor (for example no `INADDR_ANY`, multicast, etc). We can't do that validation here
242
// during parsing, since we don't know exactly which addresses are valid or not. For example we
243
// don't know if local addresses are allowed as we don't know here whether the user plans to run a
244
// testing tor network. We also don't want to do the validation too late (for example when uploading
245
// the server descriptor) as it's better to validate at startup. A better place might be to perform
246
// the validation in the `RelayConfig` builder validate.
247
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
248
pub(crate) struct Advertise {
249
    /// All relays must advertise an IPv4 address.
250
    ipv4: NonEmptyList<SocketAddrV4>,
251
    /// Relays may optionally advertise an IPv6 address.
252
    ipv6: Vec<SocketAddrV6>,
253
}
254

            
255
impl Advertise {
256
    /// Return all IP addresses (both IPv4 and IPv6).
257
    pub(crate) fn all_ips(&self) -> Vec<IpAddr> {
258
        self.ipv4
259
            .iter()
260
            .map(|s| IpAddr::V4(*s.ip()))
261
            .chain(self.ipv6.iter().map(|s| IpAddr::V6(*s.ip())))
262
            .collect()
263
    }
264
}
265

            
266
/// Default log level.
267
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
268

            
269
/// Logging configuration options.
270
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
271
#[derive_deftly(TorConfig)]
272
#[deftly(tor_config(pre_build = "Self::validate"))]
273
#[non_exhaustive]
274
pub(crate) struct LoggingConfig {
275
    /// Filtering directives that determine tracing levels as described at
276
    /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr-for-Targets>
277
    ///
278
    /// You can override this setting with the `-l`, `--log-level` command line parameter.
279
    ///
280
    /// Example: "info,tor_proto::channel=trace"
281
    #[deftly(tor_config(default = "DEFAULT_LOG_LEVEL.to_string()"))]
282
    pub(crate) console: String,
283
}
284

            
285
impl LoggingConfigBuilder {
286
    /// Validate the options provided to the builder.
287
4
    fn validate(&self) -> Result<(), ConfigBuildError> {
288
4
        if let Some(console) = &self.console {
289
            EnvFilter::builder()
290
                .parse(console)
291
                .map_err(|e| ConfigBuildError::Invalid {
292
                    field: "console".to_string(),
293
                    problem: e.to_string(),
294
                })?;
295
4
        }
296
4
        Ok(())
297
4
    }
298
}
299

            
300
/// Configuration for where information should be stored on disk.
301
///
302
/// By default, cache information will be stored in `${ARTI_RELAY_CACHE}`, and
303
/// persistent state will be stored in `${ARTI_RELAY_LOCAL_DATA}`. That means that
304
/// _all_ programs using these defaults will share their cache and state data.
305
/// If that isn't what you want, you'll need to override these directories.
306
///
307
/// On unix, the default directories will typically expand to `~/.cache/arti`
308
/// and `~/.local/share/arti/` respectively, depending on the user's
309
/// environment. Other platforms will also use suitable defaults. For more
310
/// information, see the documentation for [`CfgPath`].
311
///
312
/// This section is for read/write storage.
313
///
314
/// You cannot change this section on a running relay.
315
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
316
#[derive_deftly(TorConfig)]
317
#[non_exhaustive]
318
pub(crate) struct StorageConfig {
319
    /// Location on disk for cached information.
320
    ///
321
    /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
322
    /// in it may be deleted outside of the control of Arti,
323
    /// and Arti will continue to function properly.
324
    /// It is also fine to delete the directory as a whole, while Arti is not running.
325
    ///
326
    /// Should be accessed through the `cache_dir()` getter to provide better error messages when
327
    /// resolving the path.
328
    //
329
    // Usage note, for implementations of Arti components:
330
    //
331
    // When files in this directory are to be used by a component, the cache_dir
332
    // value should be passed through to the component as-is, and the component is
333
    // then responsible for constructing an appropriate sub-path (for example,
334
    // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
335
    //
336
    // (This consistency rule is not current always followed by every component.)
337
    #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
338
    cache_dir: CfgPath,
339

            
340
    /// Location on disk for less-sensitive persistent state information.
341
    ///
342
    /// Should be accessed through the `state_dir()` getter to provide better error messages when
343
    /// resolving the path.
344
    // Usage note: see the note for `cache_dir`, above.
345
    #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
346
    state_dir: CfgPath,
347

            
348
    /// Location on disk for the Arti keystore.
349
    #[deftly(tor_config(sub_builder))]
350
    keystore: ArtiKeystoreConfig,
351

            
352
    /// Configuration about which permissions we want to enforce on our files.
353
    // NOTE: This 'build_for_arti()' hard-codes the config field name as `permissions` and the
354
    // environment variable as `ARTI_FS_DISABLE_PERMISSION_CHECKS`. These things should be
355
    // configured by the application, not lower-level libraries, but some other lower-level
356
    // libraries like `tor-hsservice` also use 'build_for_arti()'. So we're stuck with it for now.
357
    // It might be confusing in the future if relays use some environment variables prefixed with
358
    // "ARTI_" and others with "ARTI_RELAY_", so we should probably stick to just "ARTI_".
359
    #[deftly(tor_config(
360
        sub_builder(build_fn = "build_for_arti"),
361
        extend_with = "extend_with_replace"
362
    ))]
363
    permissions: Mistrust,
364
}
365

            
366
impl StorageConfig {
367
    /// Return the FS permissions to use for state and cache directories.
368
    pub(crate) fn permissions(&self) -> &Mistrust {
369
        &self.permissions
370
    }
371

            
372
    /// Return the fully expanded path of the state directory.
373
    pub(crate) fn state_dir(
374
        &self,
375
        resolver: &CfgPathResolver,
376
    ) -> Result<PathBuf, ConfigBuildError> {
377
        resolve_cfg_path(&self.state_dir, "state_dir", resolver)
378
    }
379

            
380
    /// Return the fully expanded path of the cache directory.
381
    pub(crate) fn cache_dir(
382
        &self,
383
        resolver: &CfgPathResolver,
384
    ) -> Result<PathBuf, ConfigBuildError> {
385
        resolve_cfg_path(&self.cache_dir, "cache_dir", resolver)
386
    }
387
}
388

            
389
/// Configuration for system resources used by the relay.
390
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
391
#[derive_deftly(TorConfig)]
392
#[non_exhaustive]
393
pub(crate) struct SystemConfig {
394
    /// Memory limits (approximate)
395
    #[deftly(tor_config(sub_builder))]
396
    pub(crate) memory: tor_memquota::Config,
397
}
398

            
399
/// Return the default cache directory.
400
4
fn default_cache_dir() -> CfgPath {
401
4
    CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
402
4
}
403

            
404
/// Return the default state directory.
405
4
fn default_state_dir() -> CfgPath {
406
4
    CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
407
4
}
408

            
409
/// Helper to return a `ConfigBuildError` if the path could not be resolved.
410
fn resolve_cfg_path(
411
    path: &CfgPath,
412
    name: &str,
413
    resolver: &CfgPathResolver,
414
) -> Result<PathBuf, ConfigBuildError> {
415
    path.path(resolver).map_err(|e| ConfigBuildError::Invalid {
416
        field: name.to_owned(),
417
        problem: e.to_string(),
418
    })
419
}
420

            
421
#[cfg(test)]
422
mod test {
423
    // @@ begin test lint list maintained by maint/add_warning @@
424
    #![allow(clippy::bool_assert_comparison)]
425
    #![allow(clippy::clone_on_copy)]
426
    #![allow(clippy::dbg_macro)]
427
    #![allow(clippy::mixed_attributes_style)]
428
    #![allow(clippy::print_stderr)]
429
    #![allow(clippy::print_stdout)]
430
    #![allow(clippy::single_char_pattern)]
431
    #![allow(clippy::unwrap_used)]
432
    #![allow(clippy::unchecked_time_subtraction)]
433
    #![allow(clippy::useless_vec)]
434
    #![allow(clippy::needless_pass_by_value)]
435
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
436

            
437
    use super::*;
438

            
439
    fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
440
        let project_dirs = project_dirs().unwrap();
441
        let list = [
442
            ("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
443
            ("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
444
            ("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
445
            ("PROGRAM_DIR", &get_program_dir().unwrap()),
446
            ("USER_HOME", tor_config_path::home().unwrap()),
447
        ];
448

            
449
        list.into_iter()
450
            .map(|(a, b)| (a, b.to_owned()))
451
            .collect::<Vec<_>>()
452
    }
453

            
454
    #[cfg(not(target_family = "windows"))]
455
    #[test]
456
    fn expand_variables() {
457
        let path_resolver = base_resolver();
458

            
459
        for (var, val) in cfg_variables() {
460
            let p = CfgPath::new(format!("${{{var}}}/example"));
461
            assert_eq!(p.to_string(), format!("${{{var}}}/example"));
462

            
463
            let expected = val.join("example");
464
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
465
        }
466

            
467
        let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
468
        assert!(p.path(&path_resolver).is_err());
469
    }
470

            
471
    #[cfg(target_family = "windows")]
472
    #[test]
473
    fn expand_variables() {
474
        let path_resolver = base_resolver();
475

            
476
        for (var, val) in cfg_variables() {
477
            let p = CfgPath::new(format!("${{{var}}}\\example"));
478
            assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
479

            
480
            let expected = val.join("example");
481
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
482
        }
483

            
484
        let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
485
        assert!(p.path(&path_resolver).is_err());
486
    }
487
}