diff --git a/Cargo.lock b/Cargo.lock index cb4d9be74..fcd5f56f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,7 +116,9 @@ name = "arti-tor-client" version = "0.0.0" dependencies = [ "anyhow", + "derive_builder", "futures", + "serde", "thiserror", "tor-chanmgr", "tor-circmgr", diff --git a/crates/arti/src/arti_defaults.toml b/crates/arti/src/arti_defaults.toml index f2e69a265..9fc0479bd 100644 --- a/crates/arti/src/arti_defaults.toml +++ b/crates/arti/src/arti_defaults.toml @@ -104,3 +104,9 @@ request_max_retries = 32 # request is still waiting for its own circuits to complete, the request # will wait this long before using the unexpectedly available circuit. request_loyalty = "50 msec" + +# Rules for client configuration +[client_config] + +# Are we running as localhost (e.g. on chutney)? +is_localhost = false diff --git a/crates/arti/src/main.rs b/crates/arti/src/main.rs index c5767d9a7..f46a743a6 100644 --- a/crates/arti/src/main.rs +++ b/crates/arti/src/main.rs @@ -91,6 +91,7 @@ mod proxy; use std::sync::Arc; use tor_circmgr::CircMgrConfig; +use tor_client::ClientConfig; use tor_client::TorClient; use tor_config::CfgPath; use tor_dirmgr::{DirMgrConfig, DownloadScheduleConfig, NetworkConfig}; @@ -175,6 +176,9 @@ pub struct ArtiConfig { /// Information about how to expire circuits. circuit_timing: tor_circmgr::CircuitTiming, + + /// Information about client configuration parameters. + client_config: tor_client::ClientConfig, } /// Configuration for where information should be stored on disk. @@ -222,13 +226,14 @@ async fn run( statecfg: PathBuf, dircfg: DirMgrConfig, circcfg: CircMgrConfig, + clientcfg: ClientConfig, ) -> Result<()> { use futures::FutureExt; futures::select!( r = exit::wait_for_ctrl_c().fuse() => r, r = async { let client = - Arc::new(TorClient::bootstrap(runtime.clone(), statecfg, dircfg, circcfg).await?); + Arc::new(TorClient::bootstrap(runtime.clone(), statecfg, dircfg, circcfg, clientcfg).await?); proxy::run_socks_proxy(runtime, client, socks_port).await }.fuse() => r, ) @@ -272,6 +277,7 @@ fn main() -> Result<()> { let statecfg = config.storage.state_dir.path()?; let dircfg = config.get_dir_config()?; let circcfg = config.get_circ_config()?; + let clientcfg = config.client_config; let socks_port = match config.socks_port { Some(s) => s, @@ -287,7 +293,7 @@ fn main() -> Result<()> { let runtime = tor_rtcompat::async_std::create_runtime()?; let rt_copy = runtime.clone(); - rt_copy.block_on(run(runtime, socks_port, statecfg, dircfg, circcfg))?; + rt_copy.block_on(run(runtime, socks_port, statecfg, dircfg, circcfg, clientcfg))?; Ok(()) } diff --git a/crates/tor-client/Cargo.toml b/crates/tor-client/Cargo.toml index 071cc090a..d48824cf3 100644 --- a/crates/tor-client/Cargo.toml +++ b/crates/tor-client/Cargo.toml @@ -25,8 +25,10 @@ tor-proto = { path="../tor-proto", version="0.0.0" } tor-rtcompat = { path="../tor-rtcompat", version="0.0.0" } anyhow = "1.0.38" +derive_builder = "0.10.2" futures = "0.3.13" tracing = "0.1.26" +serde = { version = "1.0.124", features = ["derive"] } thiserror = "1.0.24" [dev-dependencies] diff --git a/crates/tor-client/src/client.rs b/crates/tor-client/src/client.rs index 4647f5385..452fbac0d 100644 --- a/crates/tor-client/src/client.rs +++ b/crates/tor-client/src/client.rs @@ -4,6 +4,7 @@ //! Once the client is bootstrapped, you can make anonymous //! connections ("streams") over the Tor network using //! `TorClient::connect()`. +use crate::config::ClientConfig; use tor_circmgr::{CircMgrConfig, IsolationToken, TargetPort}; use tor_dirmgr::{DirEvent, DirMgrConfig}; use tor_proto::circuit::{ClientCirc, IpVersionPreference}; @@ -38,6 +39,8 @@ pub struct TorClient { circmgr: Arc>, /// Directory manager for keeping our directory material up to date. dirmgr: Arc>, + /// Client configuration + clientcfg: ClientConfig } /// Preferences for how to route a stream over the Tor network. @@ -136,13 +139,14 @@ impl TorClient { /// /// Return a client once there is enough directory material to /// connect safely over the Tor network. - // TODO: Make a ClientConfig to combine DirMgrConfig and circ_cfg + // TODO: Expand ClientConfig to combine DirMgrConfig and circ_cfg // and state_cfg. pub async fn bootstrap( runtime: R, state_cfg: PathBuf, dir_cfg: DirMgrConfig, circ_cfg: CircMgrConfig, + client_cfg: ClientConfig, ) -> Result> { let statemgr = tor_persist::FsStateMgr::from_path(state_cfg)?; let chanmgr = Arc::new(tor_chanmgr::ChanMgr::new(runtime.clone())); @@ -154,6 +158,7 @@ impl TorClient { Arc::clone(&circmgr), ) .await?; + let clientcfg = client_cfg; circmgr.update_network_parameters(dirmgr.netdir().params()); @@ -180,11 +185,12 @@ impl TorClient { runtime, circmgr, dirmgr, + clientcfg, }) } /// Validate if we are an valid hostname or not - fn is_valid_hostname(hostname: &str) -> bool { + fn is_valid_hostname(&self, hostname: &str) -> bool { /// Check if we have the valid characters for a hostname fn is_valid_char(byte: u8) -> bool { ((b'a'..=b'z').contains(&byte)) @@ -208,7 +214,7 @@ impl TorClient { || hostname.ends_with('.') || hostname.starts_with('.') || hostname.is_empty() - || hostname.to_lowercase().eq("localhost")) + || (hostname.to_lowercase().eq("localhost") && !self.clientcfg.is_localhost)) || is_ipv6_str(hostname) } @@ -235,7 +241,7 @@ impl TorClient { if addr.to_lowercase().ends_with(".onion") { return Err(anyhow!("Rejecting .onion address as unsupported.")); } - if !Self::is_valid_hostname(addr) { + if !Self::is_valid_hostname(self, addr) { return Err(anyhow!("Rejecting hostname as invalid.")); } if let Ok(ip) = IpAddr::from_str(addr) { @@ -272,7 +278,7 @@ impl TorClient { if hostname.to_lowercase().ends_with(".onion") { return Err(anyhow!("Rejecting .onion address as unsupported.")); } - if !Self::is_valid_hostname(hostname) { + if !Self::is_valid_hostname(self, hostname) { return Err(anyhow!("Rejecting hostname as invalid.")); } diff --git a/crates/tor-client/src/config.rs b/crates/tor-client/src/config.rs new file mode 100644 index 000000000..9319aa996 --- /dev/null +++ b/crates/tor-client/src/config.rs @@ -0,0 +1,26 @@ +//! Configuration logic for launching a circuit manager. + +use derive_builder::Builder; +use serde::Deserialize; + +/// Configuration for clients +/// +/// This type is immutable once constructed. To create an object of this type, +/// use [`ClientConfigBuilder`]. +#[derive(Debug, Clone, Builder, Deserialize)] +#[builder] +pub struct ClientConfig { + /// Are we running as localhost? + #[builder(default)] + pub(crate) is_localhost: bool, +} + +// NOTE: it seems that `unwrap` may be safe because of builder defaults +// check `derive_builder` documentation for details +// https://docs.rs/derive_builder/0.10.2/derive_builder/#default-values +#[allow(clippy::unwrap_used)] +impl Default for ClientConfig { + fn default() -> Self { + ClientConfigBuilder::default().build().unwrap() + } +} diff --git a/crates/tor-client/src/lib.rs b/crates/tor-client/src/lib.rs index 1400a8ff3..d202c2109 100644 --- a/crates/tor-client/src/lib.rs +++ b/crates/tor-client/src/lib.rs @@ -94,10 +94,14 @@ #![warn(clippy::unseparated_literal_suffix)] #![deny(clippy::unwrap_used)] +mod config; + mod client; pub use client::{ConnectPrefs, TorClient}; +pub use config::{ClientConfig, ClientConfigBuilder}; + pub use tor_circmgr::IsolationToken; /// An anonymized stream over the Tor network. ///