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