Skip to main content

arti_client/
config.rs

1//! Types and functions to configure a Tor client.
2//!
3//! Some of these are re-exported from lower-level crates.
4
5use crate::err::ErrorDetail;
6use derive_deftly::Deftly;
7use derive_more::AsRef;
8use fs_mistrust::{Mistrust, MistrustBuilder};
9use std::collections::HashMap;
10use std::path::Path;
11use std::path::PathBuf;
12use std::result::Result as StdResult;
13use std::time::Duration;
14
15pub use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
16pub use tor_config::convert_helper_via_multi_line_list_builder;
17use tor_config::derive::prelude::*;
18use tor_config::extend_builder::extend_with_replace;
19pub use tor_config::impl_standard_builder;
20pub use tor_config::list_builder::{MultilineListBuilder, MultilineListBuilderError};
21pub use tor_config::mistrust::BuilderExt as _;
22pub use tor_config::{BoolOrAuto, ConfigError};
23pub use tor_config::{ConfigBuildError, ConfigurationSource, ConfigurationSources, Reconfigure};
24pub use tor_config::{define_list_builder_accessors, define_list_builder_helper};
25pub use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
26pub use tor_linkspec::{ChannelMethod, HasChanMethod, PtTransportName, TransportId};
27
28pub use tor_guardmgr::bridge::BridgeConfigBuilder;
29
30#[cfg(feature = "bridge-client")]
31pub use tor_guardmgr::bridge::BridgeParseError;
32
33use tor_guardmgr::bridge::BridgeConfig;
34use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
35
36/// Types for configuring how Tor circuits are built.
37pub 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.
45pub mod dir {
46    pub use tor_dircommon::authority::{AuthorityContacts, AuthorityContactsBuilder};
47    pub use tor_dircommon::config::{
48        DirTolerance, DirToleranceBuilder, DownloadScheduleConfig, DownloadScheduleConfigBuilder,
49        NetworkConfig, NetworkConfigBuilder,
50    };
51    pub use tor_dircommon::retry::{DownloadSchedule, DownloadScheduleBuilder};
52    pub use tor_dirmgr::{DirMgrConfig, FallbackDir, FallbackDirBuilder};
53}
54
55/// Types for configuring pluggable transports.
56#[cfg(feature = "pt-client")]
57pub mod pt {
58    pub use tor_ptmgr::config::{TransportConfig, TransportConfigBuilder};
59}
60
61/// Types for configuring onion services.
62#[cfg(feature = "onion-service-service")]
63pub mod onion_service {
64    pub use tor_hsservice::config::{OnionServiceConfig, OnionServiceConfigBuilder};
65}
66
67/// Types for configuring vanguards.
68pub mod vanguards {
69    pub use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder};
70}
71
72#[cfg(not(all(
73    feature = "vanguards",
74    any(feature = "onion-service-client", feature = "onion-service-service"),
75)))]
76use {
77    std::sync::LazyLock,
78    tor_config::ExplicitOrAuto,
79    tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode},
80};
81
82/// A [`VanguardConfig`] which is disabled.
83// It would be nice if the builder were const, but this is the best we can do.
84// Boxed so that this is guaranteed to use very little space if it's unused.
85#[cfg(not(all(
86    feature = "vanguards",
87    any(feature = "onion-service-client", feature = "onion-service-service"),
88)))]
89static DISABLED_VANGUARDS: LazyLock<Box<VanguardConfig>> = LazyLock::new(|| {
90    Box::new(
91        VanguardConfigBuilder::default()
92            .mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
93            .build()
94            .expect("Could not build a disabled `VanguardConfig`"),
95    )
96});
97
98/// Configuration for client behavior relating to addresses.
99///
100/// This type is immutable once constructed. To create an object of this type,
101/// use [`ClientAddrConfigBuilder`].
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.
106#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
107#[derive_deftly(TorConfig)]
108pub struct ClientAddrConfig {
109    /// Should we allow attempts to make Tor connections to local addresses?
110    ///
111    /// This option is off by default, since (by default) Tor exits will
112    /// always reject connections to such addresses.
113    #[deftly(tor_config(default))]
114    pub(crate) allow_local_addrs: bool,
115
116    /// Should we allow attempts to connect to hidden services (`.onion` services)?
117    ///
118    /// This option is on by default.
119    //
120    // NOTE: This could use tor_config(cfg) instead, but that would change the API.
121    #[cfg(feature = "onion-service-client")]
122    #[deftly(tor_config(default = "true"))]
123    pub(crate) allow_onion_addrs: bool,
124}
125
126/// Configuration for client behavior relating to stream connection timeouts
127///
128/// This type is immutable once constructed. To create an object of this type,
129/// use [`StreamTimeoutConfigBuilder`].
130///
131/// You can replace this configuration on a running Arti client.  Doing so will
132/// affect new streams and requests, but will have no effect on existing streams
133/// and requests—even those that are currently waiting.
134#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
135#[derive_deftly(TorConfig)]
136#[non_exhaustive]
137pub struct StreamTimeoutConfig {
138    /// How long should we wait before timing out a stream when connecting
139    /// to a host?
140    #[deftly(tor_config(default = "default_connect_timeout()"))]
141    pub(crate) connect_timeout: Duration,
142
143    /// How long should we wait before timing out when resolving a DNS record?
144    #[deftly(tor_config(default = "default_dns_resolve_timeout()"))]
145    pub(crate) resolve_timeout: Duration,
146
147    /// How long should we wait before timing out when resolving a DNS
148    /// PTR record?
149    #[deftly(tor_config(default = "default_dns_resolve_ptr_timeout()"))]
150    pub(crate) resolve_ptr_timeout: Duration,
151}
152
153/// Return the default stream timeout
154fn default_connect_timeout() -> Duration {
155    Duration::new(10, 0)
156}
157
158/// Return the default resolve timeout
159fn default_dns_resolve_timeout() -> Duration {
160    Duration::new(10, 0)
161}
162
163/// Return the default PTR resolve timeout
164fn default_dns_resolve_ptr_timeout() -> Duration {
165    Duration::new(10, 0)
166}
167
168/// Configuration for where information should be stored on disk.
169///
170/// By default, cache information will be stored in `${ARTI_CACHE}`, and
171/// persistent state will be stored in `${ARTI_LOCAL_DATA}`.  That means that
172/// _all_ programs using these defaults will share their cache and state data.
173/// If that isn't what you want,  you'll need to override these directories.
174///
175/// On unix, the default directories will typically expand to `~/.cache/arti`
176/// and `~/.local/share/arti/` respectively, depending on the user's
177/// environment. Other platforms will also use suitable defaults. For more
178/// information, see the documentation for [`CfgPath`].
179///
180/// This section is for read/write storage.
181///
182/// You cannot change this section on a running Arti client.
183#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
184#[derive_deftly(TorConfig)]
185pub struct StorageConfig {
186    /// Location on disk for cached information.
187    ///
188    /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
189    /// in it may be deleted outside of the control of Arti,
190    /// and Arti will continue to function properly.
191    /// It is also fine to delete the directory as a whole, while Arti is not running.
192    //
193    // Usage note, for implementations of Arti components:
194    //
195    // When files in this directory are to be used by a component, the cache_dir
196    // value should be passed through to the component as-is, and the component is
197    // then responsible for constructing an appropriate sub-path (for example,
198    // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
199    //
200    // (This consistency rule is not current always followed by every component.)
201    #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
202    pub(crate) cache_dir: CfgPath,
203
204    /// Location on disk for less-sensitive persistent state information.
205    // Usage note: see the note for `cache_dir`, above.
206    #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
207    state_dir: CfgPath,
208
209    /// Location on disk for the Arti keystore.
210    //
211    // NOTE: This could use tor_config(cfg) instead, but that would change the API.
212    #[cfg(feature = "keymgr")]
213    #[deftly(tor_config(sub_builder))]
214    keystore: ArtiKeystoreConfig,
215
216    /// Configuration about which permissions we want to enforce on our files.
217    #[deftly(tor_config(
218        sub_builder(build_fn = "build_for_arti"),
219        extend_with = "extend_with_replace"
220    ))]
221    permissions: Mistrust,
222}
223
224/// Return the default cache directory.
225fn default_cache_dir() -> CfgPath {
226    CfgPath::new("${ARTI_CACHE}".to_owned())
227}
228
229/// Return the default state directory.
230fn default_state_dir() -> CfgPath {
231    CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
232}
233
234/// Macro to avoid repeating code for `expand_*_dir` functions on StorageConfig
235// TODO: generate the expand_*_dir functions using d-a instead
236macro_rules! expand_dir {
237    ($self:ident, $dirname:ident, $dircfg:ident) => {
238        $self
239            .$dirname
240            .path($dircfg)
241            .map_err(|e| ConfigBuildError::Invalid {
242                field: stringify!($dirname).to_owned(),
243                problem: e.to_string(),
244            })
245    };
246}
247
248impl StorageConfig {
249    /// Try to expand `state_dir` to be a path buffer.
250    pub(crate) fn expand_state_dir(
251        &self,
252        path_resolver: &CfgPathResolver,
253    ) -> Result<PathBuf, ConfigBuildError> {
254        expand_dir!(self, state_dir, path_resolver)
255    }
256    /// Try to expand `cache_dir` to be a path buffer.
257    pub(crate) fn expand_cache_dir(
258        &self,
259        path_resolver: &CfgPathResolver,
260    ) -> Result<PathBuf, ConfigBuildError> {
261        expand_dir!(self, cache_dir, path_resolver)
262    }
263    /// Return the keystore config
264    #[allow(clippy::unnecessary_wraps)]
265    pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
266        cfg_if::cfg_if! {
267            if #[cfg(feature="keymgr")] {
268                self.keystore.clone()
269            } else {
270                Default::default()
271            }
272        }
273    }
274    /// Return the FS permissions to use for state and cache directories.
275    pub(crate) fn permissions(&self) -> &Mistrust {
276        &self.permissions
277    }
278}
279
280/// Configuration for anti-censorship features: bridges and pluggable transports.
281///
282/// A "bridge" is a relay that is not listed in the regular Tor network directory;
283/// clients use them to reach the network when a censor is blocking their
284/// connection to all the regular Tor relays.
285///
286/// A "pluggable transport" is a tool that transforms and conceals a user's connection
287/// to a bridge; clients use them to reach the network when a censor is blocking
288/// all traffic that "looks like Tor".
289///
290/// A [`BridgesConfig`] configuration has the following pieces:
291///    * A [`BridgeList`] of [`BridgeConfig`]s, which describes one or more bridges.
292///    * An `enabled` boolean to say whether or not to use the listed bridges.
293///    * A list of [`pt::TransportConfig`]s.
294///
295/// # Example
296///
297/// Here's an example of building a bridge configuration, and using it in a
298/// TorClientConfig.
299///
300/// The bridges here are fictitious; you'll need to use real bridges
301/// if you want a working configuration.
302///
303/// ```
304/// ##[cfg(feature = "pt-client")]
305/// # fn demo() -> anyhow::Result<()> {
306/// use arti_client::config::{TorClientConfig, BridgeConfigBuilder, CfgPath};
307/// // Requires that the pt-client feature is enabled.
308/// use arti_client::config::pt::TransportConfigBuilder;
309///
310/// let mut builder = TorClientConfig::builder();
311///
312/// // Add a single bridge to the list of bridges, from a bridge line.
313/// // This bridge line is made up for demonstration, and won't work.
314/// const BRIDGE1_LINE : &str = "Bridge obfs4 192.0.2.55:38114 316E643333645F6D79216558614D3931657A5F5F cert=YXJlIGZyZXF1ZW50bHkgZnVsbCBvZiBsaXR0bGUgbWVzc2FnZXMgeW91IGNhbiBmaW5kLg iat-mode=0";
315/// let bridge_1: BridgeConfigBuilder = BRIDGE1_LINE.parse()?;
316/// // This is where we pass `BRIDGE1_LINE` into the BridgeConfigBuilder.
317/// builder.bridges().bridges().push(bridge_1);
318///
319/// // Add a second bridge, built by hand.  This way is harder.
320/// // This bridge is made up for demonstration, and won't work.
321/// let mut bridge2_builder = BridgeConfigBuilder::default();
322/// bridge2_builder
323///     .transport("obfs4")
324///     .push_setting("iat-mode", "1")
325///     .push_setting(
326///         "cert",
327///         "YnV0IHNvbWV0aW1lcyB0aGV5IGFyZSByYW5kb20u8x9aQG/0cIIcx0ItBcTqiSXotQne+Q"
328///     );
329/// bridge2_builder.set_addrs(vec!["198.51.100.25:443".parse()?]);
330/// bridge2_builder.set_ids(vec!["7DD62766BF2052432051D7B7E08A22F7E34A4543".parse()?]);
331/// // Now insert the second bridge into our config builder.
332/// builder.bridges().bridges().push(bridge2_builder);
333///
334/// // Now configure an obfs4 transport. (Requires the "pt-client" feature)
335/// let mut transport = TransportConfigBuilder::default();
336/// transport
337///     .protocols(vec!["obfs4".parse()?])
338///     // Specify either the name or the absolute path of pluggable transport client binary, this
339///     // may differ from system to system.
340///     .path(CfgPath::new("/usr/bin/obfs4proxy".into()))
341///     .run_on_startup(true);
342/// builder.bridges().transports().push(transport);
343///
344/// let config = builder.build()?;
345/// // Now you can pass `config` to TorClient::create!
346/// # Ok(())}
347/// ```
348/// You can also find an example based on snowflake in arti-client example folder.
349//
350// We leave this as an empty struct even when bridge support is disabled,
351// as otherwise the default config file would generate an unknown section warning.
352#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
353#[derive_deftly(TorConfig)]
354#[deftly(tor_config(pre_build = "validate_bridges_config", attr = "non_exhaustive"))]
355#[non_exhaustive]
356pub struct BridgesConfig {
357    /// Should we use configured bridges?
358    ///
359    /// The default (`Auto`) is to use bridges if they are configured.
360    /// `false` means to not use even configured bridges.
361    /// `true` means to insist on the use of bridges;
362    /// if none are configured, that's then an error.
363    #[deftly(tor_config(default))]
364    pub(crate) enabled: BoolOrAuto,
365
366    /// Configured list of bridges (possibly via pluggable transports)
367    //
368    // NOTE: This isn't using the automatic list_builder code, because it doesn't yet
369    // support MultilineListBuilder.
370    #[deftly(tor_config(no_magic, sub_builder, setter(skip)))]
371    bridges: BridgeList,
372
373    /// Configured list of pluggable transports.
374    #[cfg(feature = "pt-client")] // NOTE: Could use tor_config(cfg)
375    #[deftly(tor_config(
376        list(element(build), listtype = "TransportConfigList"),
377        default = "vec![]"
378    ))]
379    pub(crate) transports: Vec<pt::TransportConfig>,
380}
381
382#[cfg(feature = "pt-client")]
383/// Determine if we need any pluggable transports.
384///
385/// If we do and their transports don't exist, we have a problem
386fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
387    use std::collections::HashSet;
388    use std::str::FromStr;
389
390    // These are all the protocols that the user has defined
391    let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
392    if let Some(transportlist) = bridges.opt_transports() {
393        for protocols in transportlist.iter() {
394            for protocol in protocols.get_protocols() {
395                protocols_defined.insert(protocol.clone());
396            }
397        }
398    }
399
400    // Iterate over all the transports that bridges are going to use
401    // If any one is valid, we validate the entire config
402    for maybe_protocol in bridges
403        .bridges
404        .bridges
405        .as_deref()
406        .unwrap_or_default()
407        .iter()
408    {
409        match maybe_protocol.get_transport() {
410            Some(raw_protocol) => {
411                // We convert the raw protocol string representation
412                // into a more proper one using PtTransportName
413                let protocol = TransportId::from_str(raw_protocol)
414                    // If id can't be parsed, simply skip it here.
415                    // The rest of the config validation/processing will generate an error for it.
416                    .unwrap_or_default()
417                    .into_pluggable();
418                // The None case represents when we aren't using a PT at all
419                match protocol {
420                    Some(protocol_required) => {
421                        if protocols_defined.contains(&protocol_required) {
422                            return Ok(());
423                        }
424                    }
425                    None => return Ok(()),
426                }
427            }
428            None => {
429                return Ok(());
430            }
431        }
432    }
433
434    Err(ConfigBuildError::Inconsistent {
435        fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
436        problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
437    })
438}
439
440/// Check that the bridge configuration is right
441#[allow(clippy::unnecessary_wraps)]
442fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
443    let _ = bridges; // suppresses unused variable for just that argument
444
445    use BoolOrAuto as BoA;
446
447    // Ideally we would run this post-build, rather than pre-build;
448    // doing it here means we have to recapitulate the defaulting.
449    // Happily the defaulting is obvious, cheap, and not going to change.
450    //
451    // Alternatively we could have derive_builder provide `build_unvalidated`,
452    // but that involves re-setting the build fn name for every field.
453    match (
454        bridges.enabled.unwrap_or_default(),
455        bridges.bridges.bridges.as_deref().unwrap_or_default(),
456    ) {
457        (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
458        (BoA::Explicit(true), []) => {
459            return Err(ConfigBuildError::Inconsistent {
460                fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
461                problem: "bridges.enabled=true, but no bridges defined".into(),
462            });
463        }
464    }
465    #[cfg(feature = "pt-client")]
466    {
467        if bridges_enabled(
468            bridges.enabled.unwrap_or_default(),
469            bridges.bridges.bridges.as_deref().unwrap_or_default(),
470        ) {
471            validate_pt_config(bridges)?;
472        }
473    }
474
475    Ok(())
476}
477
478/// Generic logic to check if bridges should be used or not
479fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
480    #[cfg(feature = "bridge-client")]
481    {
482        enabled.as_bool().unwrap_or(!bridges.is_empty())
483    }
484
485    #[cfg(not(feature = "bridge-client"))]
486    {
487        let _ = (enabled, bridges);
488        false
489    }
490}
491
492impl BridgesConfig {
493    /// Should the bridges be used?
494    fn bridges_enabled(&self) -> bool {
495        bridges_enabled(self.enabled, &self.bridges)
496    }
497}
498
499/// List of configured bridges, as found in the built configuration
500//
501// This type alias arranges that we can put `BridgeList` in `BridgesConfig`
502// and have derive_builder put a `BridgeListBuilder` in `BridgesConfigBuilder`.
503pub type BridgeList = Vec<BridgeConfig>;
504
505define_list_builder_helper! {
506    struct BridgeListBuilder {
507        bridges: [BridgeConfigBuilder],
508    }
509    built: BridgeList = bridges;
510    default = vec![];
511    #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
512    #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
513}
514
515convert_helper_via_multi_line_list_builder! {
516    struct BridgeListBuilder {
517        bridges: [BridgeConfigBuilder],
518    }
519}
520
521#[cfg(feature = "bridge-client")]
522define_list_builder_accessors! {
523    struct BridgesConfigBuilder {
524        pub bridges: [BridgeConfigBuilder],
525    }
526}
527
528/// A configuration used to bootstrap a [`TorClient`](crate::TorClient).
529///
530/// In order to connect to the Tor network, Arti needs to know a few
531/// well-known directory caches on the network, and the public keys of the
532/// network's directory authorities.  It also needs a place on disk to
533/// store persistent state and cached directory information. (See [`StorageConfig`]
534/// for default directories.)
535///
536/// Most users will create a TorClientConfig by running
537/// [`TorClientConfig::default`].
538///
539/// If you need to override the locations where Arti stores its
540/// information, you can make a TorClientConfig with
541/// [`TorClientConfigBuilder::from_directories`].
542///
543/// Finally, you can get fine-grained control over the members of a
544/// TorClientConfig using [`TorClientConfigBuilder`].
545#[derive(Clone, Deftly, Debug, AsRef, educe::Educe)]
546#[educe(PartialEq, Eq)]
547#[derive_deftly(TorConfig)]
548#[non_exhaustive]
549pub struct TorClientConfig {
550    /// Information about the Tor network we want to connect to.
551    #[deftly(tor_config(sub_builder))]
552    tor_network: dir::NetworkConfig,
553
554    /// Directories for storing information on disk
555    #[deftly(tor_config(sub_builder))]
556    pub(crate) storage: StorageConfig,
557
558    /// Information about when and how often to download directory information
559    #[deftly(tor_config(sub_builder))]
560    download_schedule: dir::DownloadScheduleConfig,
561
562    /// Information about how premature or expired our directories are allowed
563    /// to be.
564    ///
565    /// These options help us tolerate clock skew, and help survive the case
566    /// where the directory authorities are unable to reach consensus for a
567    /// while.
568    #[deftly(tor_config(sub_builder))]
569    directory_tolerance: dir::DirTolerance,
570
571    /// Facility to override network parameters from the values set in the
572    /// consensus.
573    #[deftly(tor_config(
574        setter(skip), // See note on accessor. This isn't the best way to do this.
575        field(ty = "HashMap<String, i32>"),
576        build = "|this: &Self| default_extend(this.override_net_params.clone())",
577        extend_with = "extend_with_replace"
578    ))]
579    pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
580
581    /// Information about bridges, pluggable transports, and so on
582    #[deftly(tor_config(sub_builder))]
583    pub(crate) bridges: BridgesConfig,
584
585    /// Information about how to build paths through the network.
586    #[deftly(tor_config(sub_builder))]
587    pub(crate) channel: ChannelConfig,
588
589    /// Configuration for system resources used by Arti
590    ///
591    /// Note that there are other settings in this section,
592    /// in `arti::cfg::SystemConfig` -
593    /// these two structs overlay here.
594    #[deftly(tor_config(sub_builder))]
595    pub(crate) system: SystemConfig,
596
597    /// Information about how to build paths through the network.
598    #[as_ref]
599    #[deftly(tor_config(sub_builder))]
600    path_rules: circ::PathConfig,
601
602    /// Information about preemptive circuits.
603    #[as_ref]
604    #[deftly(tor_config(sub_builder))]
605    preemptive_circuits: circ::PreemptiveCircuitConfig,
606
607    /// Information about how to retry and expire circuits and request for circuits.
608    #[as_ref]
609    #[deftly(tor_config(sub_builder))]
610    circuit_timing: circ::CircuitTiming,
611
612    /// Rules about which addresses the client is willing to connect to.
613    #[deftly(tor_config(sub_builder))]
614    pub(crate) address_filter: ClientAddrConfig,
615
616    /// Information about timing out client requests.
617    #[deftly(tor_config(sub_builder))]
618    pub(crate) stream_timeouts: StreamTimeoutConfig,
619
620    /// Information about vanguards.
621    // NOTE: Don't use `#[as_ref]` below, since we provide our own AsRef impl to handle when
622    // vanguards are disabled.
623    #[deftly(tor_config(sub_builder))]
624    pub(crate) vanguards: vanguards::VanguardConfig,
625
626    /// Resolves paths in this configuration.
627    ///
628    /// This is not [reconfigurable](crate::TorClient::reconfigure).
629    // We don't accept this from the builder/serde, and don't inspect it when comparing configs.
630    // This should be considered as ancillary data rather than a configuration option.
631    // TorClientConfig maybe isn't the best place for this, but this is where it needs to go to not
632    // require public API changes.
633    #[as_ref]
634    #[deftly(tor_config(skip, build = "|_| tor_config_path::arti_client_base_resolver()"))]
635    #[educe(PartialEq(ignore), Eq(ignore))]
636    pub(crate) path_resolver: CfgPathResolver,
637}
638
639impl tor_config::load::TopLevel for TorClientConfig {
640    type Builder = TorClientConfigBuilder;
641}
642
643/// Helper to add overrides to a default collection.
644fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
645    let mut collection = T::default();
646    collection.extend(to_add);
647    collection
648}
649
650/// Configuration for system resources used by Tor.
651///
652/// You cannot change this section on a running Arti client.
653///
654/// Note that there are other settings in this section,
655/// in `arti_client::config::SystemConfig`.
656#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
657#[derive_deftly(TorConfig)]
658#[non_exhaustive]
659pub struct SystemConfig {
660    /// Memory limits (approximate)
661    #[deftly(tor_config(sub_builder))]
662    pub(crate) memory: tor_memquota::Config,
663}
664
665impl AsRef<tor_guardmgr::VanguardConfig> for TorClientConfig {
666    fn as_ref(&self) -> &tor_guardmgr::VanguardConfig {
667        cfg_if::cfg_if! {
668            if #[cfg(all(
669                feature = "vanguards",
670                any(feature = "onion-service-client", feature = "onion-service-service"),
671            ))]
672            {
673                &self.vanguards
674            } else {
675                &DISABLED_VANGUARDS
676            }
677        }
678    }
679}
680
681impl tor_circmgr::CircMgrConfig for TorClientConfig {}
682
683#[cfg(feature = "onion-service-client")]
684impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
685
686#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
687impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
688    #[cfg(all(
689        feature = "vanguards",
690        any(feature = "onion-service-client", feature = "onion-service-service")
691    ))]
692    fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
693        &self.vanguards
694    }
695}
696
697impl AsRef<tor_dircommon::fallback::FallbackList> for TorClientConfig {
698    fn as_ref(&self) -> &tor_dircommon::fallback::FallbackList {
699        self.tor_network.fallback_caches()
700    }
701}
702impl AsRef<[BridgeConfig]> for TorClientConfig {
703    fn as_ref(&self) -> &[BridgeConfig] {
704        #[cfg(feature = "bridge-client")]
705        {
706            &self.bridges.bridges
707        }
708
709        #[cfg(not(feature = "bridge-client"))]
710        {
711            &[]
712        }
713    }
714}
715impl AsRef<BridgesConfig> for TorClientConfig {
716    fn as_ref(&self) -> &BridgesConfig {
717        &self.bridges
718    }
719}
720impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
721    fn bridges_enabled(&self) -> bool {
722        self.bridges.bridges_enabled()
723    }
724}
725
726impl TorClientConfig {
727    /// Try to create a DirMgrConfig corresponding to this object.
728    #[rustfmt::skip]
729    pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
730        Ok(dir::DirMgrConfig {
731            network:             self.tor_network        .clone(),
732            schedule:            self.download_schedule  .clone(),
733            tolerance:           self.directory_tolerance.clone(),
734            cache_dir:           self.storage.expand_cache_dir(&self.path_resolver)?,
735            cache_trust:         self.storage.permissions.clone(),
736            override_net_params: self.override_net_params.clone(),
737            extensions:          Default::default(),
738        })
739    }
740
741    /// Return a reference to the [`fs_mistrust::Mistrust`] object that we'll
742    /// use to check permissions on files and directories by default.
743    ///
744    /// # Usage notes
745    ///
746    /// In the future, specific files or directories may have stricter or looser
747    /// permissions checks applied to them than this default.  Callers shouldn't
748    /// use this [`Mistrust`] to predict what Arti will accept for a specific
749    /// file or directory.  Rather, you should use this if you have some file or
750    /// directory of your own on which you'd like to enforce the same rules as
751    /// Arti uses.
752    //
753    // NOTE: The presence of this accessor is _NOT_ in any form a commitment to
754    // expose every field from the configuration as an accessor.  We explicitly
755    // reject that slippery slope argument.
756    pub fn fs_mistrust(&self) -> &Mistrust {
757        self.storage.permissions()
758    }
759
760    /// Return the keystore config
761    pub fn keystore(&self) -> ArtiKeystoreConfig {
762        self.storage.keystore()
763    }
764
765    /// Get the state directory and its corresponding
766    /// [`Mistrust`] configuration.
767    pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
768        let state_dir = self
769            .storage
770            .expand_state_dir(&self.path_resolver)
771            .map_err(ErrorDetail::Configuration)?;
772        let mistrust = self.storage.permissions();
773
774        Ok((state_dir, mistrust))
775    }
776
777    /// Access the `tor_memquota` configuration
778    ///
779    /// Ad-hoc accessor for testing purposes.
780    /// (ideally we'd use `visibility` to make fields `pub`, but that doesn't work.)
781    #[cfg(feature = "testing")]
782    pub fn system_memory(&self) -> &tor_memquota::Config {
783        &self.system.memory
784    }
785}
786
787impl TorClientConfigBuilder {
788    /// Returns a `TorClientConfigBuilder` using the specified state and cache directories.
789    ///
790    /// All other configuration options are set to their defaults, except `storage.keystore.path`,
791    /// which is derived from the specified state directory.
792    pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
793    where
794        P: AsRef<Path>,
795        Q: AsRef<Path>,
796    {
797        let mut builder = Self::default();
798
799        builder
800            .storage()
801            .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
802            .state_dir(CfgPath::new_literal(state_dir.as_ref()));
803
804        builder
805    }
806
807    /// Return a mutable reference to a HashMap of `override_net_params`
808    ///
809    /// These parameters, if set, replace those that arrive in the network consensus document.
810    //
811    // NOTE: This is necessary for now because sub_builder isn't compatible with build().
812    pub fn override_net_params(&mut self) -> &mut HashMap<String, i32> {
813        &mut self.override_net_params
814    }
815}
816
817/// Return the filenames for the default user configuration files
818pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
819    // the base path resolver includes the 'ARTI_CONFIG' variable
820    let path_resolver = tor_config_path::arti_client_base_resolver();
821
822    ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
823        .into_iter()
824        .map(|f| {
825            let path = CfgPath::new(f.into()).path(&path_resolver)?;
826            Ok(ConfigurationSource::from_path(path))
827        })
828        .collect()
829}
830
831/// The environment variable we look at when deciding whether to disable FS permissions checking.
832#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
833pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
834
835/// Return true if the environment has been set up to disable FS permissions
836/// checking.
837///
838/// This function is exposed so that other tools can use the same checking rules
839/// as `arti-client`.  For more information, see
840/// [`TorClientBuilder`](crate::TorClientBuilder).
841#[deprecated(since = "0.5.0")]
842#[allow(deprecated)]
843pub fn fs_permissions_checks_disabled_via_env() -> bool {
844    std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
845}
846
847#[cfg(test)]
848mod test {
849    // @@ begin test lint list maintained by maint/add_warning @@
850    #![allow(clippy::bool_assert_comparison)]
851    #![allow(clippy::clone_on_copy)]
852    #![allow(clippy::dbg_macro)]
853    #![allow(clippy::mixed_attributes_style)]
854    #![allow(clippy::print_stderr)]
855    #![allow(clippy::print_stdout)]
856    #![allow(clippy::single_char_pattern)]
857    #![allow(clippy::unwrap_used)]
858    #![allow(clippy::unchecked_time_subtraction)]
859    #![allow(clippy::useless_vec)]
860    #![allow(clippy::needless_pass_by_value)]
861    #![allow(clippy::string_slice)] // See arti#2571
862    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
863    use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
864
865    use super::*;
866
867    #[test]
868    fn defaults() {
869        let dflt = TorClientConfig::default();
870        let b2 = TorClientConfigBuilder::default();
871        let dflt2 = b2.build().unwrap();
872        assert_eq!(&dflt, &dflt2);
873    }
874
875    #[test]
876    fn builder() {
877        let sec = std::time::Duration::from_secs(1);
878
879        let mut authorities = dir::AuthorityContacts::builder();
880        authorities.v3idents().push([22; 20].into());
881        authorities.v3idents().push([44; 20].into());
882        authorities.uploads().push(vec![
883            SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80)),
884            SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)),
885        ]);
886
887        let mut fallback = dir::FallbackDir::builder();
888        fallback
889            .rsa_identity([23; 20].into())
890            .ed_identity([99; 32].into())
891            .orports()
892            .push("127.0.0.7:7".parse().unwrap());
893
894        let mut bld = TorClientConfig::builder();
895        *bld.tor_network().authorities() = authorities;
896        bld.tor_network().set_fallback_caches(vec![fallback]);
897        bld.storage()
898            .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
899            .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
900        bld.download_schedule().retry_certs().attempts(10);
901        bld.download_schedule().retry_certs().initial_delay(sec);
902        bld.download_schedule().retry_certs().parallelism(3);
903        bld.download_schedule().retry_microdescs().attempts(30);
904        bld.download_schedule()
905            .retry_microdescs()
906            .initial_delay(10 * sec);
907        bld.download_schedule().retry_microdescs().parallelism(9);
908        bld.override_net_params()
909            .insert("wombats-per-quokka".to_owned(), 7);
910        bld.path_rules()
911            .ipv4_subnet_family_prefix(20)
912            .ipv6_subnet_family_prefix(48);
913        bld.circuit_timing()
914            .max_dirtiness(90 * sec)
915            .request_timeout(10 * sec)
916            .request_max_retries(22)
917            .request_loyalty(3600 * sec);
918        bld.address_filter().allow_local_addrs(true);
919
920        let val = bld.build().unwrap();
921
922        assert_ne!(val, TorClientConfig::default());
923    }
924
925    #[test]
926    fn bridges_supported() {
927        /// checks that when s is processed as TOML for a client config,
928        /// the resulting number of bridges is according to `exp`
929        fn chk(exp: Result<usize, ()>, s: &str) {
930            eprintln!("----------\n{s}\n----------\n");
931            let got = (|| {
932                let cfg: toml::Value = toml::from_str(s).unwrap();
933                let cfg: TorClientConfigBuilder = cfg.try_into()?;
934                let cfg = cfg.build()?;
935                let n_bridges = cfg.bridges.bridges.len();
936                Ok::<_, anyhow::Error>(n_bridges) // anyhow is just something we can use for ?
937            })()
938            .map_err(|_| ());
939            assert_eq!(got, exp);
940        }
941
942        let chk_enabled_or_auto = |exp, bridges_toml| {
943            for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
944                chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
945            }
946        };
947
948        let ok_1_if = |b: bool| b.then_some(1).ok_or(());
949
950        chk(
951            Err(()),
952            r#"
953                [bridges]
954                enabled = true
955            "#,
956        );
957
958        chk_enabled_or_auto(
959            ok_1_if(cfg!(feature = "bridge-client")),
960            r#"
961                bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
962            "#,
963        );
964
965        chk_enabled_or_auto(
966            ok_1_if(cfg!(feature = "pt-client")),
967            r#"
968                bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
969                [[bridges.transports]]
970                protocols = ["obfs4"]
971                path = "obfs4proxy"
972            "#,
973        );
974    }
975
976    #[test]
977    fn check_default() {
978        // We don't want to second-guess the directories crate too much
979        // here, so we'll just make sure it does _something_ plausible.
980
981        let dflt = default_config_files().unwrap();
982        assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
983        assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
984        assert_eq!(dflt.len(), 2);
985    }
986
987    #[test]
988    #[cfg(not(all(
989        feature = "vanguards",
990        any(feature = "onion-service-client", feature = "onion-service-service"),
991    )))]
992    fn check_disabled_vanguards_static() {
993        // Force us to evaluate the closure to ensure that it builds correctly.
994        #[allow(clippy::borrowed_box)]
995        let _: &Box<VanguardConfig> = LazyLock::force(&DISABLED_VANGUARDS);
996    }
997
998    #[test]
999    #[cfg(feature = "pt-client")]
1000    fn check_bridge_pt() {
1001        let from_toml = |s: &str| -> TorClientConfigBuilder {
1002            let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1003            let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1004            cfg
1005        };
1006
1007        let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1008            cfg.build(),
1009            expected,
1010        ) {
1011            (Ok(_), Ok(())) => {}
1012            (Err(e), Err(ex)) => {
1013                if !e.to_string().contains(ex) {
1014                    panic!("\"{e}\" did not contain {ex}");
1015                }
1016            }
1017            (Ok(_), Err(ex)) => {
1018                panic!("Expected {ex} but cfg succeeded");
1019            }
1020            (Err(e), Ok(())) => {
1021                panic!("Expected success but got error {e}")
1022            }
1023        };
1024
1025        let test_cases = [
1026            ("# No bridges", Ok(())),
1027            (
1028                r#"
1029                    # No bridges but we still enabled bridges
1030                    [bridges]
1031                    enabled = true
1032                    bridges = []
1033                "#,
1034                Err("bridges.enabled=true, but no bridges defined"),
1035            ),
1036            (
1037                r#"
1038                    # One non-PT bridge
1039                    [bridges]
1040                    enabled = true
1041                    bridges = [
1042                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1043                    ]
1044                "#,
1045                Ok(()),
1046            ),
1047            (
1048                r#"
1049                    # One obfs4 bridge
1050                    [bridges]
1051                    enabled = true
1052                    bridges = [
1053                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1054                    ]
1055                    [[bridges.transports]]
1056                    protocols = ["obfs4"]
1057                    path = "obfs4proxy"
1058                "#,
1059                Ok(()),
1060            ),
1061            (
1062                r#"
1063                    # One obfs4 bridge with unmanaged transport.
1064                    [bridges]
1065                    enabled = true
1066                    bridges = [
1067                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1068                    ]
1069                    [[bridges.transports]]
1070                    protocols = ["obfs4"]
1071                    proxy_addr = "127.0.0.1:31337"
1072                "#,
1073                Ok(()),
1074            ),
1075            (
1076                r#"
1077                    # Transport is both managed and unmanaged.
1078                    [[bridges.transports]]
1079                    protocols = ["obfs4"]
1080                    path = "obfsproxy"
1081                    proxy_addr = "127.0.0.1:9999"
1082                "#,
1083                Err("Cannot provide both path and proxy_addr"),
1084            ),
1085            (
1086                r#"
1087                    # One obfs4 bridge and non-PT bridge
1088                    [bridges]
1089                    enabled = false
1090                    bridges = [
1091                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1092                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1093                    ]
1094                    [[bridges.transports]]
1095                    protocols = ["obfs4"]
1096                    path = "obfs4proxy"
1097                "#,
1098                Ok(()),
1099            ),
1100            (
1101                r#"
1102                    # One obfs4 and non-PT bridge with no transport
1103                    [bridges]
1104                    enabled = true
1105                    bridges = [
1106                        "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1107                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1108                    ]
1109                "#,
1110                Ok(()),
1111            ),
1112            (
1113                r#"
1114                    # One obfs4 bridge with no transport
1115                    [bridges]
1116                    enabled = true
1117                    bridges = [
1118                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1119                    ]
1120                "#,
1121                Err("all bridges unusable due to lack of corresponding pluggable transport"),
1122            ),
1123            (
1124                r#"
1125                    # One obfs4 bridge with no transport but bridges are disabled
1126                    [bridges]
1127                    enabled = false
1128                    bridges = [
1129                        "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1130                    ]
1131                "#,
1132                Ok(()),
1133            ),
1134            (
1135                r#"
1136                        # One non-PT bridge with a redundant transports section
1137                        [bridges]
1138                        enabled = false
1139                        bridges = [
1140                            "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1141                        ]
1142                        [[bridges.transports]]
1143                        protocols = ["obfs4"]
1144                        path = "obfs4proxy"
1145                "#,
1146                Ok(()),
1147            ),
1148        ];
1149
1150        for (test_case, expected) in test_cases.iter() {
1151            chk(&from_toml(test_case), *expected);
1152        }
1153    }
1154}