arti_client/
config.rs

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