mod listen;
use std::borrow::Cow;
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
use std::path::PathBuf;
use derive_deftly::Deftly;
use derive_more::AsRef;
use directories::ProjectDirs;
use fs_mistrust::{Mistrust, MistrustBuilder};
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
use tor_circmgr::{CircuitTiming, PathConfig, PreemptiveCircuitConfig};
use tor_config::derive::prelude::*;
use tor_config::{
ConfigBuildError, ExplicitOrAuto, extend_builder::extend_with_replace, mistrust::BuilderExt,
};
use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
use tor_dircommon::config::{NetworkConfig, NetworkConfigBuilder};
use tor_dircommon::fallback::FallbackList;
use tor_guardmgr::bridge::BridgeConfig;
use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode};
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
use tracing::metadata::Level;
use tracing_subscriber::filter::EnvFilter;
use crate::util::NonEmptyList;
use self::listen::Listen;
pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
let resolver = base_resolver();
[
"${ARTI_RELAY_CONFIG}/arti-relay.toml",
"${ARTI_RELAY_CONFIG}/arti-relay.d/",
]
.into_iter()
.map(|f| CfgPath::new(f.into()).path(&resolver))
.collect()
}
pub(crate) fn base_resolver() -> CfgPathResolver {
let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
let program_dir = get_program_dir().map(Cow::Owned);
let user_home = tor_config_path::home().map(Cow::Borrowed);
let mut resolver = CfgPathResolver::default();
resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
resolver.set_var("PROGRAM_DIR", program_dir);
resolver.set_var("USER_HOME", user_home);
resolver
}
fn get_program_dir() -> Result<PathBuf, CfgPathError> {
let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
Ok(directory.to_owned())
}
fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
static PROJECT_DIRS: LazyLock<Option<ProjectDirs>> =
LazyLock::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
}
#[derive(Clone, Deftly, Debug, Eq, PartialEq, AsRef)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait))]
#[non_exhaustive]
pub(crate) struct TorRelayConfig {
#[deftly(tor_config(sub_builder))]
pub(crate) relay: RelayConfig,
#[deftly(tor_config(sub_builder))]
pub(crate) tor_network: NetworkConfig,
#[deftly(tor_config(sub_builder))]
pub(crate) logging: LoggingConfig,
#[deftly(tor_config(sub_builder))]
pub(crate) storage: StorageConfig,
#[deftly(tor_config(sub_builder))]
pub(crate) channel: ChannelConfig,
#[deftly(tor_config(sub_builder))]
pub(crate) system: SystemConfig,
#[deftly(tor_config(skip, build = "|_| Default::default()"))]
#[as_ref]
pub(crate) path_rules: PathConfig,
#[deftly(tor_config(
skip,
build = r#"|_|
VanguardConfigBuilder::default()
.mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
.build()
.expect("Could not build a disabled `VanguardConfig`")"#
))]
#[as_ref]
pub(crate) vanguards: VanguardConfig,
#[deftly(tor_config(skip, build = "|_| Default::default()"))]
#[as_ref]
pub(crate) circuit_timing: CircuitTiming,
#[deftly(tor_config(skip, build = "|_| Default::default()"))]
#[as_ref]
pub(crate) preemptive_circuits: PreemptiveCircuitConfig,
}
impl tor_config::load::TopLevel for TorRelayConfig {
type Builder = TorRelayConfigBuilder;
}
impl tor_circmgr::CircMgrConfig for TorRelayConfig {}
impl AsRef<FallbackList> for TorRelayConfig {
fn as_ref(&self) -> &FallbackList {
self.tor_network.fallback_caches()
}
}
impl AsRef<[BridgeConfig]> for TorRelayConfig {
fn as_ref(&self) -> &[BridgeConfig] {
&[]
}
}
impl tor_guardmgr::GuardMgrConfig for TorRelayConfig {
fn bridges_enabled(&self) -> bool {
false
}
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait))]
pub(crate) struct RelayConfig {
#[deftly(tor_config(no_default))]
pub(crate) listen: Listen,
#[deftly(tor_config(no_default))]
pub(crate) advertise: Advertise,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub(crate) struct Advertise {
ipv4: NonEmptyList<SocketAddrV4>,
ipv6: Vec<SocketAddrV6>,
}
impl Advertise {
pub(crate) fn all_addr(&self) -> Vec<SocketAddr> {
self.ipv4
.iter()
.map(|s| (*s).into())
.chain(self.ipv6.iter().map(|s| (*s).into()))
.collect()
}
}
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(pre_build = "Self::validate"))]
#[non_exhaustive]
pub(crate) struct LoggingConfig {
#[deftly(tor_config(default = "DEFAULT_LOG_LEVEL.to_string()"))]
pub(crate) console: String,
#[deftly(tor_config(default))]
pub(crate) log_sensitive_information: bool,
}
impl LoggingConfigBuilder {
fn validate(&self) -> Result<(), ConfigBuildError> {
if let Some(console) = &self.console {
EnvFilter::builder()
.parse(console)
.map_err(|e| ConfigBuildError::Invalid {
field: "console".to_string(),
problem: e.to_string(),
})?;
}
Ok(())
}
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[non_exhaustive]
pub(crate) struct StorageConfig {
#[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
cache_dir: CfgPath,
#[deftly(tor_config(default = "default_state_dir()", setter(into)))]
state_dir: CfgPath,
#[deftly(tor_config(sub_builder))]
keystore: ArtiKeystoreConfig,
#[deftly(tor_config(
sub_builder(build_fn = "build_for_arti"),
extend_with = "extend_with_replace"
))]
permissions: Mistrust,
}
impl StorageConfig {
pub(crate) fn permissions(&self) -> &Mistrust {
&self.permissions
}
pub(crate) fn state_dir(
&self,
resolver: &CfgPathResolver,
) -> Result<PathBuf, ConfigBuildError> {
resolve_cfg_path(&self.state_dir, "state_dir", resolver)
}
pub(crate) fn cache_dir(
&self,
resolver: &CfgPathResolver,
) -> Result<PathBuf, ConfigBuildError> {
resolve_cfg_path(&self.cache_dir, "cache_dir", resolver)
}
}
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[non_exhaustive]
pub(crate) struct SystemConfig {
#[deftly(tor_config(sub_builder))]
pub(crate) memory: tor_memquota::Config,
}
fn default_cache_dir() -> CfgPath {
CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
}
fn default_state_dir() -> CfgPath {
CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
}
fn resolve_cfg_path(
path: &CfgPath,
name: &str,
resolver: &CfgPathResolver,
) -> Result<PathBuf, ConfigBuildError> {
path.path(resolver).map_err(|e| ConfigBuildError::Invalid {
field: name.to_owned(),
problem: e.to_string(),
})
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
let project_dirs = project_dirs().unwrap();
let list = [
("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
("PROGRAM_DIR", &get_program_dir().unwrap()),
("USER_HOME", tor_config_path::home().unwrap()),
];
list.into_iter()
.map(|(a, b)| (a, b.to_owned()))
.collect::<Vec<_>>()
}
#[cfg(not(target_family = "windows"))]
#[test]
fn expand_variables() {
let path_resolver = base_resolver();
for (var, val) in cfg_variables() {
let p = CfgPath::new(format!("${{{var}}}/example"));
assert_eq!(p.to_string(), format!("${{{var}}}/example"));
let expected = val.join("example");
assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
}
let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
assert!(p.path(&path_resolver).is_err());
}
#[cfg(target_family = "windows")]
#[test]
fn expand_variables() {
let path_resolver = base_resolver();
for (var, val) in cfg_variables() {
let p = CfgPath::new(format!("${{{var}}}\\example"));
assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
let expected = val.join("example");
assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
}
let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
assert!(p.path(&path_resolver).is_err());
}
}