1
//! Types and functions to configure a Tor client.
2
//!
3
//! Some of these are re-exported from lower-level crates.
4

            
5
use crate::err::ErrorDetail;
6
use derive_builder::Builder;
7
use derive_more::AsRef;
8
use fs_mistrust::{Mistrust, MistrustBuilder};
9
use serde::{Deserialize, Serialize};
10
use std::collections::HashMap;
11
use std::path::Path;
12
use std::path::PathBuf;
13
use std::result::Result as StdResult;
14
use std::time::Duration;
15

            
16
pub use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
17
pub use tor_config::convert_helper_via_multi_line_list_builder;
18
pub use tor_config::impl_standard_builder;
19
pub use tor_config::list_builder::{MultilineListBuilder, MultilineListBuilderError};
20
pub use tor_config::mistrust::BuilderExt as _;
21
pub use tor_config::{define_list_builder_accessors, define_list_builder_helper};
22
pub use tor_config::{BoolOrAuto, ConfigError};
23
pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
24
pub use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
25
pub use tor_linkspec::{ChannelMethod, HasChanMethod, PtTransportName, TransportId};
26

            
27
pub use tor_guardmgr::bridge::BridgeConfigBuilder;
28

            
29
#[cfg(feature = "bridge-client")]
30
#[cfg_attr(docsrs, doc(cfg(feature = "bridge-client")))]
31
pub use tor_guardmgr::bridge::BridgeParseError;
32

            
33
use tor_guardmgr::bridge::BridgeConfig;
34
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
35

            
36
/// Types for configuring how Tor circuits are built.
37
pub mod circ {
38
    pub use tor_circmgr::{
39
        CircMgrConfig, CircuitTiming, CircuitTimingBuilder, PathConfig, PathConfigBuilder,
40
        PreemptiveCircuitConfig, PreemptiveCircuitConfigBuilder,
41
    };
42
}
43

            
44
/// Types for configuring how Tor accesses its directory information.
45
pub mod dir {
46
    pub use tor_dirmgr::{
47
        Authority, AuthorityBuilder, DirMgrConfig, DirTolerance, DirToleranceBuilder,
48
        DownloadSchedule, DownloadScheduleConfig, DownloadScheduleConfigBuilder, FallbackDir,
49
        FallbackDirBuilder, NetworkConfig, NetworkConfigBuilder,
50
    };
51
}
52

            
53
/// Types for configuring pluggable transports.
54
#[cfg(feature = "pt-client")]
55
pub mod pt {
56
    pub use tor_ptmgr::config::{TransportConfig, TransportConfigBuilder};
57
}
58

            
59
/// Types for configuring onion services.
60
#[cfg(feature = "onion-service-service")]
61
pub mod onion_service {
62
    pub use tor_hsservice::config::{OnionServiceConfig, OnionServiceConfigBuilder};
63
}
64

            
65
/// Types for configuring vanguards.
66
pub mod vanguards {
67
    pub use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder};
68
}
69

            
70
/// Configuration for client behavior relating to addresses.
71
///
72
/// This type is immutable once constructed. To create an object of this type,
73
/// use [`ClientAddrConfigBuilder`].
74
///
75
/// You can replace this configuration on a running Arti client.  Doing so will
76
/// affect new streams and requests, but will have no effect on existing streams
77
/// and requests.
78
2848
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
79
#[builder(build_fn(error = "ConfigBuildError"))]
80
#[builder(derive(Debug, Serialize, Deserialize))]
81
pub struct ClientAddrConfig {
82
    /// Should we allow attempts to make Tor connections to local addresses?
83
    ///
84
    /// This option is off by default, since (by default) Tor exits will
85
    /// always reject connections to such addresses.
86
    #[builder(default)]
87
    pub(crate) allow_local_addrs: bool,
88

            
89
    /// Should we allow attempts to connect to hidden services (`.onion` services)?
90
    ///
91
    /// This option is on by default.
92
    #[cfg(feature = "onion-service-client")]
93
    #[builder(default = "true")]
94
    pub(crate) allow_onion_addrs: bool,
95
}
96
impl_standard_builder! { ClientAddrConfig }
97

            
98
/// Configuration for client behavior relating to stream connection timeouts
99
///
100
/// This type is immutable once constructed. To create an object of this type,
101
/// use [`StreamTimeoutConfigBuilder`].
102
///
103
/// You can replace this configuration on a running Arti client.  Doing so will
104
/// affect new streams and requests, but will have no effect on existing streams
105
/// and requests—even those that are currently waiting.
106
2708
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
107
#[builder(build_fn(error = "ConfigBuildError"))]
108
#[builder(derive(Debug, Serialize, Deserialize))]
109
#[non_exhaustive]
110
pub struct StreamTimeoutConfig {
111
    /// How long should we wait before timing out a stream when connecting
112
    /// to a host?
113
    #[builder(default = "default_connect_timeout()")]
114
    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
115
    pub(crate) connect_timeout: Duration,
116

            
117
    /// How long should we wait before timing out when resolving a DNS record?
118
    #[builder(default = "default_dns_resolve_timeout()")]
119
    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
120
    pub(crate) resolve_timeout: Duration,
121

            
122
    /// How long should we wait before timing out when resolving a DNS
123
    /// PTR record?
124
    #[builder(default = "default_dns_resolve_ptr_timeout()")]
125
    #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
126
    pub(crate) resolve_ptr_timeout: Duration,
127
}
128
impl_standard_builder! { StreamTimeoutConfig }
129

            
130
/// Return the default stream timeout
131
1314
fn default_connect_timeout() -> Duration {
132
1314
    Duration::new(10, 0)
133
1314
}
134

            
135
/// Return the default resolve timeout
136
1314
fn default_dns_resolve_timeout() -> Duration {
137
1314
    Duration::new(10, 0)
138
1314
}
139

            
140
/// Return the default PTR resolve timeout
141
1314
fn default_dns_resolve_ptr_timeout() -> Duration {
142
1314
    Duration::new(10, 0)
143
1314
}
144

            
145
/// Configuration for overriding the status of our software.
146
///
147
/// # Issues
148
///
149
/// We only check these configuration values when we receive a new consensus,
150
/// or when we're starting up.  Therefore, if you change these values,
151
/// they won't have any effect until the next consensus is received.
152
2908
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
153
#[builder(build_fn(error = "ConfigBuildError"))]
154
#[builder(derive(Debug, Serialize, Deserialize))]
155
pub struct SoftwareStatusOverrideConfig {
156
    /// A list of protocols to pretend that we have,
157
    /// when checking whether our software is obsolete.
158
    //
159
    // We make this type a String in the builder, to avoid exposing Protocols in our API.
160
    #[builder(field(type = "String", build = "self.parse_protos()?"))]
161
    pub(crate) ignore_missing_required_protocols: tor_protover::Protocols,
162
}
163

            
164
impl SoftwareStatusOverrideConfigBuilder {
165
    /// Helper: Parse the ignore_missing_required_protocols field.
166
1378
    fn parse_protos(&self) -> Result<tor_protover::Protocols, ConfigBuildError> {
167
        use std::str::FromStr as _;
168
1378
        tor_protover::Protocols::from_str(&self.ignore_missing_required_protocols).map_err(|e| {
169
            ConfigBuildError::Invalid {
170
                field: "ignore_missing_required_protocols".to_string(),
171
                problem: e.to_string(),
172
            }
173
1378
        })
174
1378
    }
175
}
176

            
177
/// Configuration for where information should be stored on disk.
178
///
179
/// By default, cache information will be stored in `${ARTI_CACHE}`, and
180
/// persistent state will be stored in `${ARTI_LOCAL_DATA}`.  That means that
181
/// _all_ programs using these defaults will share their cache and state data.
182
/// If that isn't what you want,  you'll need to override these directories.
183
///
184
/// On unix, the default directories will typically expand to `~/.cache/arti`
185
/// and `~/.local/share/arti/` respectively, depending on the user's
186
/// environment. Other platforms will also use suitable defaults. For more
187
/// information, see the documentation for [`CfgPath`].
188
///
189
/// This section is for read/write storage.
190
///
191
/// You cannot change this section on a running Arti client.
192
2120
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
193
#[builder(build_fn(error = "ConfigBuildError"))]
194
#[builder(derive(Debug, Serialize, Deserialize))]
195
#[non_exhaustive]
196
pub struct StorageConfig {
197
    /// Location on disk for cached information.
198
    ///
199
    /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
200
    /// in it may be deleted outside of the control of Arti,
201
    /// and Arti will continue to function properly.
202
    /// It is also fine to delete the directory as a whole, while Arti is not running.
203
    //
204
    // Usage note, for implementations of Arti components:
205
    //
206
    // When files in this directory are to be used by a component, the cache_dir
207
    // value should be passed through to the component as-is, and the component is
208
    // then responsible for constructing an appropriate sub-path (for example,
209
    // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
210
    //
211
    // (This consistency rule is not current always followed by every component.)
212
    #[builder(setter(into), default = "default_cache_dir()")]
213
    cache_dir: CfgPath,
214

            
215
    /// Location on disk for less-sensitive persistent state information.
216
    // Usage note: see the note for `cache_dir`, above.
217
    #[builder(setter(into), default = "default_state_dir()")]
218
    state_dir: CfgPath,
219

            
220
    /// Location on disk for the Arti keystore.
221
    #[cfg(feature = "keymgr")]
222
    #[builder(sub_builder)]
223
    #[builder_field_attr(serde(default))]
224
    keystore: ArtiKeystoreConfig,
225

            
226
    /// Configuration about which permissions we want to enforce on our files.
227
    #[builder(sub_builder(fn_name = "build_for_arti"))]
228
    #[builder_field_attr(serde(default))]
229
    permissions: Mistrust,
230
}
231
impl_standard_builder! { StorageConfig }
232

            
233
/// Return the default cache directory.
234
1280
fn default_cache_dir() -> CfgPath {
235
1280
    CfgPath::new("${ARTI_CACHE}".to_owned())
236
1280
}
237

            
238
/// Return the default state directory.
239
974
fn default_state_dir() -> CfgPath {
240
974
    CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
241
974
}
242

            
243
/// Macro to avoid repeating code for `expand_*_dir` functions on StorageConfig
244
// TODO: generate the expand_*_dir functions using d-a instead
245
macro_rules! expand_dir {
246
    ($self:ident, $dirname:ident, $dircfg:ident) => {
247
        $self
248
            .$dirname
249
            .path($dircfg)
250
            .map_err(|e| ConfigBuildError::Invalid {
251
                field: stringify!($dirname).to_owned(),
252
                problem: e.to_string(),
253
            })
254
    };
255
}
256

            
257
impl StorageConfig {
258
    /// Try to expand `state_dir` to be a path buffer.
259
424
    pub(crate) fn expand_state_dir(
260
424
        &self,
261
424
        path_resolver: &CfgPathResolver,
262
424
    ) -> Result<PathBuf, ConfigBuildError> {
263
424
        expand_dir!(self, state_dir, path_resolver)
264
424
    }
265
    /// Try to expand `cache_dir` to be a path buffer.
266
44
    pub(crate) fn expand_cache_dir(
267
44
        &self,
268
44
        path_resolver: &CfgPathResolver,
269
44
    ) -> Result<PathBuf, ConfigBuildError> {
270
44
        expand_dir!(self, cache_dir, path_resolver)
271
44
    }
272
    /// Return the keystore config
273
    #[allow(clippy::unnecessary_wraps)]
274
936
    pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
275
936
        cfg_if::cfg_if! {
276
936
            if #[cfg(feature="keymgr")] {
277
936
                self.keystore.clone()
278
936
            } else {
279
936
                Default::default()
280
936
            }
281
936
        }
282
936
    }
283
    /// Return the FS permissions to use for state and cache directories.
284
1078
    pub(crate) fn permissions(&self) -> &Mistrust {
285
1078
        &self.permissions
286
1078
    }
287
}
288

            
289
/// Configuration for anti-censorship features: bridges and pluggable transports.
290
///
291
/// A "bridge" is a relay that is not listed in the regular Tor network directory;
292
/// clients use them to reach the network when a censor is blocking their
293
/// connection to all the regular Tor relays.
294
///
295
/// A "pluggable transport" is a tool that transforms and conceals a user's connection
296
/// to a bridge; clients use them to reach the network when a censor is blocking
297
/// all traffic that "looks like Tor".
298
///
299
/// A [`BridgesConfig`] configuration has the following pieces:
300
///    * A [`BridgeList`] of [`BridgeConfig`]s, which describes one or more bridges.
301
///    * An `enabled` boolean to say whether or not to use the listed bridges.
302
///    * A list of [`pt::TransportConfig`]s.
303
///
304
/// # Example
305
///
306
/// Here's an example of building a bridge configuration, and using it in a
307
/// TorClientConfig.
308
///
309
/// The bridges here are fictitious; you'll need to use real bridges
310
/// if you want a working configuration.
311
///
312
/// ```
313
/// ##[cfg(feature = "pt-client")]
314
/// # fn demo() -> anyhow::Result<()> {
315
/// use arti_client::config::{TorClientConfig, BridgeConfigBuilder, CfgPath};
316
/// // Requires that the pt-client feature is enabled.
317
/// use arti_client::config::pt::TransportConfigBuilder;
318
///
319
/// let mut builder = TorClientConfig::builder();
320
///
321
/// // Add a single bridge to the list of bridges, from a bridge line.
322
/// // This bridge line is made up for demonstration, and won't work.
323
/// const BRIDGE1_LINE : &str = "Bridge obfs4 192.0.2.55:38114 316E643333645F6D79216558614D3931657A5F5F cert=YXJlIGZyZXF1ZW50bHkgZnVsbCBvZiBsaXR0bGUgbWVzc2FnZXMgeW91IGNhbiBmaW5kLg iat-mode=0";
324
/// let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse()?;
325
/// // This is where we pass `BRIDGE1_LINE` into the BridgeConfigBuilder.
326
/// builder.bridges().bridges().push(bridge_1);
327
///
328
/// // Add a second bridge, built by hand.  This way is harder.
329
/// // This bridge is made up for demonstration, and won't work.
330
/// let mut bridge2_builder = BridgeConfigBuilder::default();
331
/// bridge2_builder
332
///     .transport("obfs4")
333
///     .push_setting("iat-mode", "1")
334
///     .push_setting(
335
///         "cert",
336
///         "YnV0IHNvbWV0aW1lcyB0aGV5IGFyZSByYW5kb20u8x9aQG/0cIIcx0ItBcTqiSXotQne+Q"
337
///     );
338
/// bridge2_builder.set_addrs(vec!["198.51.100.25:443".parse()?]);
339
/// bridge2_builder.set_ids(vec!["7DD62766BF2052432051D7B7E08A22F7E34A4543".parse()?]);
340
/// // Now insert the second bridge into our config builder.
341
/// builder.bridges().bridges().push(bridge2_builder);
342
///
343
/// // Now configure an obfs4 transport. (Requires the "pt-client" feature)
344
/// let mut transport = TransportConfigBuilder::default();
345
/// transport
346
///     .protocols(vec!["obfs4".parse()?])
347
///     // Specify either the name or the absolute path of pluggable transport client binary, this
348
///     // may differ from system to system.
349
///     .path(CfgPath::new("/usr/bin/obfs4proxy".into()))
350
///     .run_on_startup(true);
351
/// builder.bridges().transports().push(transport);
352
///
353
/// let config = builder.build()?;
354
/// // Now you can pass `config` to TorClient::create!
355
/// # Ok(())}
356
/// ```
357
/// You can also find an example based on snowflake in arti-client example folder.
358
//
359
// We leave this as an empty struct even when bridge support is disabled,
360
// as otherwise the default config file would generate an unknown section warning.
361
2640
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
362
2
#[builder(build_fn(validate = "validate_bridges_config", error = "ConfigBuildError"))]
363
#[builder(derive(Debug, Serialize, Deserialize))]
364
#[non_exhaustive]
365
#[builder_struct_attr(non_exhaustive)] // This struct can be empty.
366
pub struct BridgesConfig {
367
    /// Should we use configured bridges?
368
    ///
369
    /// The default (`Auto`) is to use bridges if they are configured.
370
    /// `false` means to not use even configured bridges.
371
    /// `true` means to insist on the use of bridges;
372
    /// if none are configured, that's then an error.
373
    #[builder(default)]
374
    pub(crate) enabled: BoolOrAuto,
375

            
376
    /// Configured list of bridges (possibly via pluggable transports)
377
    #[builder(sub_builder, setter(custom))]
378
    #[builder_field_attr(serde(default))]
379
    bridges: BridgeList,
380

            
381
    /// Configured list of pluggable transports.
382
    #[builder(sub_builder, setter(custom))]
383
    #[builder_field_attr(serde(default))]
384
    #[cfg(feature = "pt-client")]
385
    pub(crate) transports: TransportConfigList,
386
}
387

            
388
/// A list of configured transport binaries (type alias for macrology).
389
#[cfg(feature = "pt-client")]
390
type TransportConfigList = Vec<pt::TransportConfig>;
391

            
392
#[cfg(feature = "pt-client")]
393
define_list_builder_helper! {
394
    pub struct TransportConfigListBuilder {
395
        transports: [pt::TransportConfigBuilder],
396
    }
397
    built: TransportConfigList = transports;
398
    default = vec![];
399
}
400

            
401
#[cfg(feature = "pt-client")]
402
define_list_builder_accessors! {
403
    struct BridgesConfigBuilder {
404
        pub transports: [pt::TransportConfigBuilder],
405
    }
406
}
407

            
408
impl_standard_builder! { BridgesConfig }
409

            
410
#[cfg(feature = "pt-client")]
411
/// Determine if we need any pluggable transports.
412
///
413
/// If we do and their transports don't exist, we have a problem
414
124
fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
415
    use std::collections::HashSet;
416
    use std::str::FromStr;
417

            
418
    // These are all the protocols that the user has defined
419
124
    let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
420
124
    if let Some(transportlist) = bridges.opt_transports() {
421
10
        for protocols in transportlist.iter() {
422
10
            for protocol in protocols.get_protocols() {
423
10
                protocols_defined.insert(protocol.clone());
424
10
            }
425
        }
426
114
    }
427

            
428
    // Iterate over all the transports that bridges are going to use
429
    // If any one is valid, we validate the entire config
430
124
    for maybe_protocol in bridges
431
124
        .bridges
432
124
        .bridges
433
124
        .as_deref()
434
124
        .unwrap_or_default()
435
124
        .iter()
436
    {
437
124
        match maybe_protocol.get_transport() {
438
124
            Some(raw_protocol) => {
439
124
                // We convert the raw protocol string representation
440
124
                // into a more proper one using PtTransportName
441
124
                let protocol = TransportId::from_str(raw_protocol)
442
124
                    // If id can't be parsed, simply skip it here.
443
124
                    // The rest of the config validation/processing will generate an error for it.
444
124
                    .unwrap_or_default()
445
124
                    .into_pluggable();
446
124
                // The None case represents when we aren't using a PT at all
447
124
                match protocol {
448
12
                    Some(protocol_required) => {
449
12
                        if protocols_defined.contains(&protocol_required) {
450
10
                            return Ok(());
451
2
                        }
452
                    }
453
112
                    None => return Ok(()),
454
                }
455
            }
456
            None => {
457
                return Ok(());
458
            }
459
        }
460
    }
461

            
462
2
    Err(ConfigBuildError::Inconsistent {
463
2
        fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
464
2
        problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
465
2
    })
466
124
}
467

            
468
/// Check that the bridge configuration is right
469
#[allow(clippy::unnecessary_wraps)]
470
1424
fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
471
1424
    let _ = bridges; // suppresses unused variable for just that argument
472

            
473
    use BoolOrAuto as BoA;
474

            
475
    // Ideally we would run this post-build, rather than pre-build;
476
    // doing it here means we have to recapitulate the defaulting.
477
    // Happily the defaulting is obvious, cheap, and not going to change.
478
    //
479
    // Alternatively we could have derive_builder provide `build_unvalidated`,
480
    // but that involves re-setting the build fn name for every field.
481
1424
    match (
482
1424
        bridges.enabled.unwrap_or_default(),
483
1424
        bridges.bridges.bridges.as_deref().unwrap_or_default(),
484
1424
    ) {
485
1420
        (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
486
4
        (BoA::Explicit(true), []) => {
487
4
            return Err(ConfigBuildError::Inconsistent {
488
4
                fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
489
4
                problem: "bridges.enabled=true, but no bridges defined".into(),
490
4
            })
491
        }
492
    }
493
    #[cfg(feature = "pt-client")]
494
    {
495
1420
        if bridges_enabled(
496
1420
            bridges.enabled.unwrap_or_default(),
497
1420
            bridges.bridges.bridges.as_deref().unwrap_or_default(),
498
1420
        ) {
499
124
            validate_pt_config(bridges)?;
500
1296
        }
501
    }
502

            
503
1418
    Ok(())
504
1424
}
505

            
506
/// Generic logic to check if bridges should be used or not
507
1462
fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
508
1462
    #[cfg(feature = "bridge-client")]
509
1462
    {
510
1462
        enabled.as_bool().unwrap_or(!bridges.is_empty())
511
1462
    }
512
1462

            
513
1462
    #[cfg(not(feature = "bridge-client"))]
514
1462
    {
515
1462
        let _ = (enabled, bridges);
516
1462
        false
517
1462
    }
518
1462
}
519

            
520
impl BridgesConfig {
521
    /// Should the bridges be used?
522
42
    fn bridges_enabled(&self) -> bool {
523
42
        bridges_enabled(self.enabled, &self.bridges)
524
42
    }
525
}
526

            
527
/// List of configured bridges, as found in the built configuration
528
//
529
// This type alias arranges that we can put `BridgeList` in `BridgesConfig`
530
// and have derive_builder put a `BridgeListBuilder` in `BridgesConfigBuilder`.
531
pub type BridgeList = Vec<BridgeConfig>;
532

            
533
define_list_builder_helper! {
534
    struct BridgeListBuilder {
535
        bridges: [BridgeConfigBuilder],
536
    }
537
    built: BridgeList = bridges;
538
    default = vec![];
539
    #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
540
    #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
541
}
542

            
543
convert_helper_via_multi_line_list_builder! {
544
    struct BridgeListBuilder {
545
        bridges: [BridgeConfigBuilder],
546
    }
547
}
548

            
549
#[cfg(feature = "bridge-client")]
550
define_list_builder_accessors! {
551
    struct BridgesConfigBuilder {
552
        pub bridges: [BridgeConfigBuilder],
553
    }
554
}
555

            
556
/// A configuration used to bootstrap a [`TorClient`](crate::TorClient).
557
///
558
/// In order to connect to the Tor network, Arti needs to know a few
559
/// well-known directory caches on the network, and the public keys of the
560
/// network's directory authorities.  It also needs a place on disk to
561
/// store persistent state and cached directory information. (See [`StorageConfig`]
562
/// for default directories.)
563
///
564
/// Most users will create a TorClientConfig by running
565
/// [`TorClientConfig::default`].
566
///
567
/// If you need to override the locations where Arti stores its
568
/// information, you can make a TorClientConfig with
569
/// [`TorClientConfigBuilder::from_directories`].
570
///
571
/// Finally, you can get fine-grained control over the members of a
572
/// TorClientConfig using [`TorClientConfigBuilder`].
573
1776
#[derive(Clone, Builder, Debug, AsRef, educe::Educe)]
574
#[educe(PartialEq, Eq)]
575
8
#[builder(build_fn(error = "ConfigBuildError"))]
576
#[builder(derive(Serialize, Deserialize, Debug))]
577
#[non_exhaustive]
578
pub struct TorClientConfig {
579
    /// Information about the Tor network we want to connect to.
580
    #[builder(sub_builder)]
581
    #[builder_field_attr(serde(default))]
582
    tor_network: dir::NetworkConfig,
583

            
584
    /// Directories for storing information on disk
585
    #[builder(sub_builder)]
586
    #[builder_field_attr(serde(default))]
587
    pub(crate) storage: StorageConfig,
588

            
589
    /// Information about when and how often to download directory information
590
    #[builder(sub_builder)]
591
    #[builder_field_attr(serde(default))]
592
    download_schedule: dir::DownloadScheduleConfig,
593

            
594
    /// Information about how premature or expired our directories are allowed
595
    /// to be.
596
    ///
597
    /// These options help us tolerate clock skew, and help survive the case
598
    /// where the directory authorities are unable to reach consensus for a
599
    /// while.
600
    #[builder(sub_builder)]
601
    #[builder_field_attr(serde(default))]
602
    directory_tolerance: dir::DirTolerance,
603

            
604
    /// Facility to override network parameters from the values set in the
605
    /// consensus.
606
    #[builder(
607
        sub_builder,
608
        field(
609
            type = "HashMap<String, i32>",
610
            build = "default_extend(self.override_net_params.clone())"
611
        )
612
    )]
613
    #[builder_field_attr(serde(default))]
614
    pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
615

            
616
    /// Information about bridges, pluggable transports, and so on
617
    #[builder(sub_builder)]
618
    #[builder_field_attr(serde(default))]
619
    pub(crate) bridges: BridgesConfig,
620

            
621
    /// Information about how to build paths through the network.
622
    #[builder(sub_builder)]
623
    #[builder_field_attr(serde(default))]
624
    pub(crate) channel: ChannelConfig,
625

            
626
    /// Configuration for system resources used by Arti
627
    ///
628
    /// Note that there are other settings in this section,
629
    /// in `arti::cfg::SystemConfig` -
630
    /// these two structs overlay here.
631
    #[builder(sub_builder)]
632
    #[builder_field_attr(serde(default))]
633
    pub(crate) system: SystemConfig,
634

            
635
    /// Information about how to build paths through the network.
636
    #[as_ref]
637
    #[builder(sub_builder)]
638
    #[builder_field_attr(serde(default))]
639
    path_rules: circ::PathConfig,
640

            
641
    /// Information about preemptive circuits.
642
    #[as_ref]
643
    #[builder(sub_builder)]
644
    #[builder_field_attr(serde(default))]
645
    preemptive_circuits: circ::PreemptiveCircuitConfig,
646

            
647
    /// Information about how to retry and expire circuits and request for circuits.
648
    #[as_ref]
649
    #[builder(sub_builder)]
650
    #[builder_field_attr(serde(default))]
651
    circuit_timing: circ::CircuitTiming,
652

            
653
    /// Rules about which addresses the client is willing to connect to.
654
    #[builder(sub_builder)]
655
    #[builder_field_attr(serde(default))]
656
    pub(crate) address_filter: ClientAddrConfig,
657

            
658
    /// Information about timing out client requests.
659
    #[builder(sub_builder)]
660
    #[builder_field_attr(serde(default))]
661
    pub(crate) stream_timeouts: StreamTimeoutConfig,
662

            
663
    /// Information about vanguards.
664
    #[builder(sub_builder)]
665
    #[builder_field_attr(serde(default))]
666
    pub(crate) vanguards: vanguards::VanguardConfig,
667

            
668
    /// Support for running with known-obsolete versions.
669
    #[builder(sub_builder)]
670
    #[builder_field_attr(serde(default))]
671
    pub(crate) use_obsolete_software: SoftwareStatusOverrideConfig,
672

            
673
    /// Resolves paths in this configuration.
674
    ///
675
    /// This is not [reconfigurable](crate::TorClient::reconfigure).
676
    // We don't accept this from the builder/serde, and don't inspect it when comparing configs.
677
    // This should be considered as ancillary data rather than a configuration option.
678
    // TorClientConfig maybe isn't the best place for this, but this is where it needs to go to not
679
    // require public API changes.
680
    #[as_ref]
681
    #[builder(setter(skip))]
682
    #[builder_field_attr(serde(skip))]
683
    #[educe(PartialEq(ignore), Eq(ignore))]
684
    #[builder(default = "tor_config_path::arti_client_base_resolver()")]
685
    pub(crate) path_resolver: CfgPathResolver,
686
}
687
impl_standard_builder! { TorClientConfig }
688

            
689
impl tor_config::load::TopLevel for TorClientConfig {
690
    type Builder = TorClientConfigBuilder;
691
}
692

            
693
/// Helper to add overrides to a default collection.
694
1386
fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
695
1386
    let mut collection = T::default();
696
1386
    collection.extend(to_add);
697
1386
    collection
698
1386
}
699

            
700
/// Configuration for system resources used by Tor.
701
///
702
/// You cannot change this section on a running Arti client.
703
///
704
/// Note that there are other settings in this section,
705
/// in `arti_client::config::SystemConfig`.
706
2300
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
707
#[builder(build_fn(error = "ConfigBuildError"))]
708
#[builder(derive(Debug, Serialize, Deserialize))]
709
#[non_exhaustive]
710
pub struct SystemConfig {
711
    /// Memory limits (approximate)
712
    #[builder(sub_builder(fn_name = "build"))]
713
    #[builder_field_attr(serde(default))]
714
    pub(crate) memory: tor_memquota::Config,
715
}
716
impl_standard_builder! { SystemConfig }
717

            
718
impl tor_circmgr::CircMgrConfig for TorClientConfig {
719
    #[cfg(all(
720
        feature = "vanguards",
721
        any(feature = "onion-service-client", feature = "onion-service-service")
722
    ))]
723
42
    fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
724
42
        &self.vanguards
725
42
    }
726
}
727
#[cfg(feature = "onion-service-client")]
728
impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
729

            
730
#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
731
impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
732
    #[cfg(all(
733
        feature = "vanguards",
734
        any(feature = "onion-service-client", feature = "onion-service-service")
735
    ))]
736
    fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
737
        &self.vanguards
738
    }
739
}
740

            
741
impl AsRef<tor_guardmgr::fallback::FallbackList> for TorClientConfig {
742
42
    fn as_ref(&self) -> &tor_guardmgr::fallback::FallbackList {
743
42
        self.tor_network.fallback_caches()
744
42
    }
745
}
746
impl AsRef<[BridgeConfig]> for TorClientConfig {
747
    fn as_ref(&self) -> &[BridgeConfig] {
748
        #[cfg(feature = "bridge-client")]
749
        {
750
            &self.bridges.bridges
751
        }
752

            
753
        #[cfg(not(feature = "bridge-client"))]
754
        {
755
            &[]
756
        }
757
    }
758
}
759
impl AsRef<BridgesConfig> for TorClientConfig {
760
34
    fn as_ref(&self) -> &BridgesConfig {
761
34
        &self.bridges
762
34
    }
763
}
764
impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
765
42
    fn bridges_enabled(&self) -> bool {
766
42
        self.bridges.bridges_enabled()
767
42
    }
768
}
769

            
770
impl TorClientConfig {
771
    /// Try to create a DirMgrConfig corresponding to this object.
772
    #[rustfmt::skip]
773
44
    pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
774
44
        Ok(dir::DirMgrConfig {
775
44
            network:             self.tor_network        .clone(),
776
44
            schedule:            self.download_schedule  .clone(),
777
44
            tolerance:           self.directory_tolerance.clone(),
778
44
            cache_dir:           self.storage.expand_cache_dir(&self.path_resolver)?,
779
44
            cache_trust:         self.storage.permissions.clone(),
780
44
            override_net_params: self.override_net_params.clone(),
781
44
            extensions:          Default::default(),
782
        })
783
44
    }
784

            
785
    /// Return a reference to the [`fs_mistrust::Mistrust`] object that we'll
786
    /// use to check permissions on files and directories by default.
787
    ///
788
    /// # Usage notes
789
    ///
790
    /// In the future, specific files or directories may have stricter or looser
791
    /// permissions checks applied to them than this default.  Callers shouldn't
792
    /// use this [`Mistrust`] to predict what Arti will accept for a specific
793
    /// file or directory.  Rather, you should use this if you have some file or
794
    /// directory of your own on which you'd like to enforce the same rules as
795
    /// Arti uses.
796
    //
797
    // NOTE: The presence of this accessor is _NOT_ in any form a commitment to
798
    // expose every field from the configuration as an accessor.  We explicitly
799
    // reject that slippery slope argument.
800
306
    pub fn fs_mistrust(&self) -> &Mistrust {
801
306
        self.storage.permissions()
802
306
    }
803

            
804
    /// Return the keystore config
805
    pub fn keystore(&self) -> ArtiKeystoreConfig {
806
        self.storage.keystore()
807
    }
808

            
809
    /// Get the state directory and its corresponding
810
    /// [`Mistrust`] configuration.
811
420
    pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
812
420
        let state_dir = self
813
420
            .storage
814
420
            .expand_state_dir(&self.path_resolver)
815
420
            .map_err(ErrorDetail::Configuration)?;
816
420
        let mistrust = self.storage.permissions();
817
420

            
818
420
        Ok((state_dir, mistrust))
819
420
    }
820

            
821
    /// Access the `tor_memquota` configuration
822
    ///
823
    /// Ad-hoc accessor for testing purposes.
824
    /// (ideally we'd use `visibility` to make fields `pub`, but that doesn't work.)
825
    #[cfg(feature = "testing")]
826
34
    pub fn system_memory(&self) -> &tor_memquota::Config {
827
34
        &self.system.memory
828
34
    }
829
}
830

            
831
impl TorClientConfigBuilder {
832
    /// Returns a `TorClientConfigBuilder` using the specified state and cache directories.
833
    ///
834
    /// All other configuration options are set to their defaults, except `storage.keystore.path`,
835
    /// which is derived from the specified state directory.
836
8
    pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
837
8
    where
838
8
        P: AsRef<Path>,
839
8
        Q: AsRef<Path>,
840
8
    {
841
8
        let mut builder = Self::default();
842
8

            
843
8
        builder
844
8
            .storage()
845
8
            .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
846
8
            .state_dir(CfgPath::new_literal(state_dir.as_ref()));
847
8

            
848
8
        builder
849
8
    }
850
}
851

            
852
/// Return the filenames for the default user configuration files
853
410
pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
854
410
    // the base path resolver includes the 'ARTI_CONFIG' variable
855
410
    let path_resolver = tor_config_path::arti_client_base_resolver();
856
410

            
857
410
    ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
858
410
        .into_iter()
859
833
        .map(|f| {
860
820
            let path = CfgPath::new(f.into()).path(&path_resolver)?;
861
820
            Ok(ConfigurationSource::from_path(path))
862
833
        })
863
410
        .collect()
864
410
}
865

            
866
/// The environment variable we look at when deciding whether to disable FS permissions checking.
867
#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
868
pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
869

            
870
/// Return true if the environment has been set up to disable FS permissions
871
/// checking.
872
///
873
/// This function is exposed so that other tools can use the same checking rules
874
/// as `arti-client`.  For more information, see
875
/// [`TorClientBuilder`](crate::TorClientBuilder).
876
#[deprecated(since = "0.5.0")]
877
#[allow(deprecated)]
878
pub fn fs_permissions_checks_disabled_via_env() -> bool {
879
    std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
880
}
881

            
882
#[cfg(test)]
883
mod test {
884
    // @@ begin test lint list maintained by maint/add_warning @@
885
    #![allow(clippy::bool_assert_comparison)]
886
    #![allow(clippy::clone_on_copy)]
887
    #![allow(clippy::dbg_macro)]
888
    #![allow(clippy::mixed_attributes_style)]
889
    #![allow(clippy::print_stderr)]
890
    #![allow(clippy::print_stdout)]
891
    #![allow(clippy::single_char_pattern)]
892
    #![allow(clippy::unwrap_used)]
893
    #![allow(clippy::unchecked_duration_subtraction)]
894
    #![allow(clippy::useless_vec)]
895
    #![allow(clippy::needless_pass_by_value)]
896
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
897
    use super::*;
898

            
899
    #[test]
900
    fn defaults() {
901
        let dflt = TorClientConfig::default();
902
        let b2 = TorClientConfigBuilder::default();
903
        let dflt2 = b2.build().unwrap();
904
        assert_eq!(&dflt, &dflt2);
905
    }
906

            
907
    #[test]
908
    fn builder() {
909
        let sec = std::time::Duration::from_secs(1);
910

            
911
        let auth = dir::Authority::builder()
912
            .name("Fred")
913
            .v3ident([22; 20].into())
914
            .clone();
915
        let mut fallback = dir::FallbackDir::builder();
916
        fallback
917
            .rsa_identity([23; 20].into())
918
            .ed_identity([99; 32].into())
919
            .orports()
920
            .push("127.0.0.7:7".parse().unwrap());
921

            
922
        let mut bld = TorClientConfig::builder();
923
        bld.tor_network().set_authorities(vec![auth]);
924
        bld.tor_network().set_fallback_caches(vec![fallback]);
925
        bld.storage()
926
            .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
927
            .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
928
        bld.download_schedule().retry_certs().attempts(10);
929
        bld.download_schedule().retry_certs().initial_delay(sec);
930
        bld.download_schedule().retry_certs().parallelism(3);
931
        bld.download_schedule().retry_microdescs().attempts(30);
932
        bld.download_schedule()
933
            .retry_microdescs()
934
            .initial_delay(10 * sec);
935
        bld.download_schedule().retry_microdescs().parallelism(9);
936
        bld.override_net_params()
937
            .insert("wombats-per-quokka".to_owned(), 7);
938
        bld.path_rules()
939
            .ipv4_subnet_family_prefix(20)
940
            .ipv6_subnet_family_prefix(48);
941
        bld.circuit_timing()
942
            .max_dirtiness(90 * sec)
943
            .request_timeout(10 * sec)
944
            .request_max_retries(22)
945
            .request_loyalty(3600 * sec);
946
        bld.address_filter().allow_local_addrs(true);
947

            
948
        let val = bld.build().unwrap();
949

            
950
        assert_ne!(val, TorClientConfig::default());
951
    }
952

            
953
    #[test]
954
    fn bridges_supported() {
955
        /// checks that when s is processed as TOML for a client config,
956
        /// the resulting number of bridges is according to `exp`
957
        fn chk(exp: Result<usize, ()>, s: &str) {
958
            eprintln!("----------\n{s}\n----------\n");
959
            let got = (|| {
960
                let cfg: toml::Value = toml::from_str(s).unwrap();
961
                let cfg: TorClientConfigBuilder = cfg.try_into()?;
962
                let cfg = cfg.build()?;
963
                let n_bridges = cfg.bridges.bridges.len();
964
                Ok::<_, anyhow::Error>(n_bridges) // anyhow is just something we can use for ?
965
            })()
966
            .map_err(|_| ());
967
            assert_eq!(got, exp);
968
        }
969

            
970
        let chk_enabled_or_auto = |exp, bridges_toml| {
971
            for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
972
                chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
973
            }
974
        };
975

            
976
        let ok_1_if = |b: bool| b.then_some(1).ok_or(());
977

            
978
        chk(
979
            Err(()),
980
            r#"
981
                [bridges]
982
                enabled = true
983
            "#,
984
        );
985

            
986
        chk_enabled_or_auto(
987
            ok_1_if(cfg!(feature = "bridge-client")),
988
            r#"
989
                bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
990
            "#,
991
        );
992

            
993
        chk_enabled_or_auto(
994
            ok_1_if(cfg!(feature = "pt-client")),
995
            r#"
996
                bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
997
                [[bridges.transports]]
998
                protocols = ["obfs4"]
999
                path = "obfs4proxy"
            "#,
        );
    }
    #[test]
    fn check_default() {
        // We don't want to second-guess the directories crate too much
        // here, so we'll just make sure it does _something_ plausible.
        let dflt = default_config_files().unwrap();
        assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
        assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
        assert_eq!(dflt.len(), 2);
    }
    #[test]
    #[cfg(feature = "pt-client")]
    fn check_bridge_pt() {
        let from_toml = |s: &str| -> TorClientConfigBuilder {
            let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
            let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
            cfg
        };
        let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
            cfg.build(),
            expected,
        ) {
            (Ok(_), Ok(())) => {}
            (Err(e), Err(ex)) => {
                if !e.to_string().contains(ex) {
                    panic!("\"{e}\" did not contain {ex}");
                }
            }
            (Ok(_), Err(ex)) => {
                panic!("Expected {ex} but cfg succeeded");
            }
            (Err(e), Ok(())) => {
                panic!("Expected success but got error {e}")
            }
        };
        let test_cases = [
            ("# No bridges", Ok(())),
            (
                r#"
                    # No bridges but we still enabled bridges
                    [bridges]
                    enabled = true
                    bridges = []
                "#,
                Err("bridges.enabled=true, but no bridges defined"),
            ),
            (
                r#"
                    # One non-PT bridge
                    [bridges]
                    enabled = true
                    bridges = [
                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
                    ]
                "#,
                Ok(()),
            ),
            (
                r#"
                    # One obfs4 bridge
                    [bridges]
                    enabled = true
                    bridges = [
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                    [[bridges.transports]]
                    protocols = ["obfs4"]
                    path = "obfs4proxy"
                "#,
                Ok(()),
            ),
            (
                r#"
                    # One obfs4 bridge with unmanaged transport.
                    [bridges]
                    enabled = true
                    bridges = [
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                    [[bridges.transports]]
                    protocols = ["obfs4"]
                    proxy_addr = "127.0.0.1:31337"
                "#,
                Ok(()),
            ),
            (
                r#"
                    # Transport is both managed and unmanaged.
                    [[bridges.transports]]
                    protocols = ["obfs4"]
                    path = "obfsproxy"
                    proxy_addr = "127.0.0.1:9999"
                "#,
                Err("Cannot provide both path and proxy_addr"),
            ),
            (
                r#"
                    # One obfs4 bridge and non-PT bridge
                    [bridges]
                    enabled = false
                    bridges = [
                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                    [[bridges.transports]]
                    protocols = ["obfs4"]
                    path = "obfs4proxy"
                "#,
                Ok(()),
            ),
            (
                r#"
                    # One obfs4 and non-PT bridge with no transport
                    [bridges]
                    enabled = true
                    bridges = [
                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                "#,
                Ok(()),
            ),
            (
                r#"
                    # One obfs4 bridge with no transport
                    [bridges]
                    enabled = true
                    bridges = [
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                "#,
                Err("all bridges unusable due to lack of corresponding pluggable transport"),
            ),
            (
                r#"
                    # One obfs4 bridge with no transport but bridges are disabled
                    [bridges]
                    enabled = false
                    bridges = [
                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
                    ]
                "#,
                Ok(()),
            ),
            (
                r#"
                        # One non-PT bridge with a redundant transports section
                        [bridges]
                        enabled = false
                        bridges = [
                            "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
                        ]
                        [[bridges.transports]]
                        protocols = ["obfs4"]
                        path = "obfs4proxy"
                "#,
                Ok(()),
            ),
        ];
        for (test_case, expected) in test_cases.iter() {
            chk(&from_toml(test_case), *expected);
        }
    }
}