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