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