diff --git a/CHANGELOG.md b/CHANGELOG.md index f122ffc..4cef9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Support audio attachments. - Added CLI command for viewing unreachable actors. - Implemented NodeInfo 2.1. +- Added `federation.onion_proxy_url` configuration parameter (enables proxy for requests to `.onion` domains). ### Changed diff --git a/contrib/mitra_config.yaml b/contrib/mitra_config.yaml index b3819f1..2d1d409 100644 --- a/contrib/mitra_config.yaml +++ b/contrib/mitra_config.yaml @@ -47,6 +47,8 @@ retention: #federation: # # Proxy for outgoing requests # #proxy_url: 'socks5h://127.0.0.1:9050' +# # Proxy for outgoing requests to .onion targets +# #onion_proxy_url: 'socks5h://127.0.0.1:9050' # List of blocked domains #blocked_instances: [] diff --git a/mitra-config/src/config.rs b/mitra-config/src/config.rs index 95de835..830a0b8 100644 --- a/mitra-config/src/config.rs +++ b/mitra-config/src/config.rs @@ -99,6 +99,7 @@ impl Config { _url: self.try_instance_url().unwrap(), actor_key: self.instance_rsa_key.clone().unwrap(), proxy_url: self.federation.proxy_url.clone(), + onion_proxy_url: self.federation.onion_proxy_url.clone(), is_private: matches!(self.environment, Environment::Development), } } @@ -131,6 +132,7 @@ pub struct Instance { pub actor_key: RsaPrivateKey, // Proxy for outgoing requests pub proxy_url: Option, + pub onion_proxy_url: Option, // Private instance won't send signed HTTP requests pub is_private: bool, } @@ -161,6 +163,7 @@ impl Instance { _url: Url::parse(url).unwrap(), actor_key: generate_weak_rsa_key().unwrap(), proxy_url: None, + onion_proxy_url: None, is_private: true, } } @@ -179,6 +182,7 @@ mod tests { _url: instance_url, actor_key: instance_rsa_key, proxy_url: None, + onion_proxy_url: None, is_private: true, }; @@ -198,6 +202,7 @@ mod tests { _url: instance_url, actor_key: instance_rsa_key, proxy_url: None, + onion_proxy_url: None, is_private: true, }; diff --git a/mitra-config/src/federation.rs b/mitra-config/src/federation.rs index da4316b..a42dc73 100644 --- a/mitra-config/src/federation.rs +++ b/mitra-config/src/federation.rs @@ -3,4 +3,5 @@ use serde::Deserialize; #[derive(Clone, Default, Deserialize)] pub struct FederationConfig { pub proxy_url: Option, + pub onion_proxy_url: Option, } diff --git a/src/activitypub/deliverer.rs b/src/activitypub/deliverer.rs index 5935e2c..462fa46 100644 --- a/src/activitypub/deliverer.rs +++ b/src/activitypub/deliverer.rs @@ -7,7 +7,10 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use mitra_config::Instance; -use mitra_utils::crypto_rsa::deserialize_private_key; +use mitra_utils::{ + crypto_rsa::deserialize_private_key, + urls::get_hostname, +}; use crate::database::{ DatabaseClient, @@ -47,18 +50,28 @@ pub enum DelivererError { #[error("activity serialization error")] SerializationError(#[from] serde_json::Error), + #[error("inavlid URL")] + UrlError(#[from] url::ParseError), + #[error(transparent)] RequestError(#[from] reqwest::Error), #[error("http error {0:?}")] HttpError(reqwest::StatusCode), - - #[error(transparent)] - DatabaseError(#[from] DatabaseError), } -fn build_client(instance: &Instance) -> reqwest::Result { - build_federation_client(instance, DELIVERER_TIMEOUT) +fn build_client( + instance: &Instance, + request_uri: &str, +) -> Result { + let hostname = get_hostname(request_uri)?; + let is_onion = hostname.ends_with(".onion"); + let client = build_federation_client( + instance, + is_onion, + DELIVERER_TIMEOUT, + )?; + Ok(client) } async fn send_activity( @@ -76,7 +89,7 @@ async fn send_activity( actor_key_id, )?; - let client = build_client(instance)?; + let client = build_client(instance, inbox_url)?; let request = client.post(inbox_url) .header("Host", headers.host) .header("Date", headers.date) diff --git a/src/activitypub/fetcher/fetchers.rs b/src/activitypub/fetcher/fetchers.rs index 336c92e..e68fce2 100644 --- a/src/activitypub/fetcher/fetchers.rs +++ b/src/activitypub/fetcher/fetchers.rs @@ -6,7 +6,7 @@ use serde_json::Value; use mitra_config::Instance; use mitra_utils::{ files::sniff_media_type, - urls::guess_protocol, + urls::{get_hostname, guess_protocol}, }; use crate::activitypub::{ @@ -31,6 +31,9 @@ pub enum FetchError { #[error(transparent)] SignatureError(#[from] HttpSignatureError), + #[error("inavlid URL")] + UrlError(#[from] url::ParseError), + #[error(transparent)] RequestError(#[from] reqwest::Error), @@ -50,8 +53,18 @@ pub enum FetchError { OtherError(&'static str), } -fn build_client(instance: &Instance) -> reqwest::Result { - build_federation_client(instance, FETCHER_TIMEOUT) +fn build_client( + instance: &Instance, + request_uri: &str, +) -> Result { + let hostname = get_hostname(request_uri)?; + let is_onion = hostname.ends_with(".onion"); + let client = build_federation_client( + instance, + is_onion, + FETCHER_TIMEOUT, + )?; + Ok(client) } fn build_request( @@ -75,7 +88,7 @@ async fn send_request( url: &str, query_params: &[(&str, &str)], ) -> Result { - let client = build_client(instance)?; + let client = build_client(instance, url)?; let mut request_builder = build_request(instance, client, Method::GET, url) .header(reqwest::header::ACCEPT, AP_MEDIA_TYPE); @@ -113,7 +126,7 @@ pub async fn fetch_file( file_max_size: usize, output_dir: &Path, ) -> Result<(String, usize, Option), FetchError> { - let client = build_client(instance)?; + let client = build_client(instance, url)?; let request_builder = build_request(instance, client, Method::GET, url); let response = request_builder.send().await?.error_for_status()?; @@ -164,7 +177,7 @@ pub async fn perform_webfinger_query( guess_protocol(&actor_address.hostname), actor_address.hostname, ); - let client = build_client(instance)?; + let client = build_client(instance, &webfinger_url)?; let request_builder = build_request(instance, client, Method::GET, &webfinger_url); let webfinger_data = request_builder diff --git a/src/activitypub/http_client.rs b/src/activitypub/http_client.rs index 69ca881..05318ed 100644 --- a/src/activitypub/http_client.rs +++ b/src/activitypub/http_client.rs @@ -9,10 +9,16 @@ const CONNECTION_TIMEOUT: u64 = 30; pub fn build_federation_client( instance: &Instance, + is_onion: bool, timeout: u64, ) -> reqwest::Result { let mut client_builder = Client::builder(); - if let Some(ref proxy_url) = instance.proxy_url { + let mut maybe_proxy_url = instance.proxy_url.as_ref(); + if is_onion { + maybe_proxy_url = maybe_proxy_url + .or(instance.onion_proxy_url.as_ref()); + }; + if let Some(proxy_url) = maybe_proxy_url { let proxy = Proxy::all(proxy_url)?; client_builder = client_builder.proxy(proxy); };