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