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