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