diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 969ed223..44fc4e85 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -156,9 +156,12 @@ where debug!("Checking acl allowance for {}", destination); - if !services().acl.is_federation_with_allowed_fedi_dest(&actual_destination) { + if !services() + .acl + .is_federation_with_allowed_fedi_dest(&actual_destination) + { debug!("blocked sending federation to {:?}", actual_destination); - + return Err(Error::ACLBlock(destination.to_owned())); } diff --git a/src/config/acl.rs b/src/config/acl.rs index d580d5a5..4b5aa24e 100644 --- a/src/config/acl.rs +++ b/src/config/acl.rs @@ -1,11 +1,11 @@ -use std::collections::HashSet; use serde::Deserialize; +use std::collections::HashSet; use url::Host; -#[derive(Deserialize,Debug, Default, Clone)] +#[derive(Deserialize, Debug, Default, Clone)] pub struct AccessControlListConfig { /// setting this explicitly enables allowlists - pub(crate)allow_list: Option>>, + pub(crate) allow_list: Option>>, #[serde(default)] - pub(crate)block_list: HashSet> -} \ No newline at end of file + pub(crate) block_list: HashSet>, +} diff --git a/src/config/mod.rs b/src/config/mod.rs index af0638d4..8c074968 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,7 +2,8 @@ use std::{ collections::BTreeMap, fmt, net::{IpAddr, Ipv4Addr}, - path::PathBuf, sync::Arc, + path::PathBuf, + sync::Arc, }; use figment::Figment; @@ -13,7 +14,7 @@ use tracing::{error, warn}; pub(crate) mod acl; mod proxy; -use self::{proxy::ProxyConfig, acl::AccessControlListConfig}; +use self::{acl::AccessControlListConfig, proxy::ProxyConfig}; #[derive(Clone, Debug, Deserialize)] pub struct Config { diff --git a/src/database/key_value/acl.rs b/src/database/key_value/acl.rs index 17c560f3..ef29b20b 100644 --- a/src/database/key_value/acl.rs +++ b/src/database/key_value/acl.rs @@ -3,40 +3,49 @@ use std::collections::HashSet; use tracing::warn; use url::Host; -use crate::{service::acl::{Data, AclDatabaseEntry, AclMode}, KeyValueDatabase}; +use crate::{ + service::acl::{AclDatabaseEntry, AclMode, Data}, + KeyValueDatabase, +}; impl Data for KeyValueDatabase { - fn check_acl(&self,host: &Host ) -> crate::Result> { + fn check_acl(&self, host: &Host) -> crate::Result> { let thing = self.acl_list.get(host.to_string().as_bytes())?; - if let Some(thing) = thing { - match thing.first() { + if let Some(thing) = thing { + match thing.first() { Some(0x1) => Ok(Some(AclMode::Allow)), Some(0x0) => Ok(Some(AclMode::Block)), Some(invalid) => { - warn!("found invalid value for mode byte in value {}, probably db corruption", invalid); + warn!( + "found invalid value for mode byte in value {}, probably db corruption", + invalid + ); Ok(None) } None => Ok(None), } - }else { + } else { Ok(None) - } + } } fn add_acl(&self, acl: AclDatabaseEntry) -> crate::Result<()> { - self.acl_list.insert(acl.hostname.to_string().as_bytes(), match acl.mode { - AclMode::Block => &[0x0], - AclMode::Allow => &[0x1], - }) + self.acl_list.insert( + acl.hostname.to_string().as_bytes(), + match acl.mode { + AclMode::Block => &[0x0], + AclMode::Allow => &[0x1], + }, + ) } - fn remove_acl(&self,host: Host) -> crate::Result<()> { + fn remove_acl(&self, host: Host) -> crate::Result<()> { self.acl_list.remove(host.to_string().as_bytes()) } fn get_all_acls(&self) -> HashSet { let mut set = HashSet::new(); - + self.acl_list.iter().for_each(|it| { let Ok(key) = String::from_utf8(it.0) else { return; @@ -49,13 +58,19 @@ impl Data for KeyValueDatabase { Some(0x1) => AclMode::Allow, Some(0x0) => AclMode::Block, Some(invalid) => { - warn!("found invalid value for mode byte in value {}, probably db corruption", invalid); + warn!( + "found invalid value for mode byte in value {}, probably db corruption", + invalid + ); return; } None => return, }; - set.insert(AclDatabaseEntry { mode: mode, hostname: parsed_host }); + set.insert(AclDatabaseEntry { + mode: mode, + hostname: parsed_host, + }); }); set } -} \ No newline at end of file +} diff --git a/src/database/key_value/mod.rs b/src/database/key_value/mod.rs index d8581e4a..daf591d9 100644 --- a/src/database/key_value/mod.rs +++ b/src/database/key_value/mod.rs @@ -12,4 +12,4 @@ mod transaction_ids; mod uiaa; mod users; -mod acl; \ No newline at end of file +mod acl; diff --git a/src/database/mod.rs b/src/database/mod.rs index 8ce53f8d..17ea9b65 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -173,7 +173,7 @@ pub struct KeyValueDatabase { pub(super) lasttimelinecount_cache: Mutex>, pub(super) presence_timer_sender: Arc>, - pub(super) acl_list: Arc + pub(super) acl_list: Arc, } impl KeyValueDatabase { diff --git a/src/service/acl/data.rs b/src/service/acl/data.rs index 984eddd4..bd5d918b 100644 --- a/src/service/acl/data.rs +++ b/src/service/acl/data.rs @@ -1,40 +1,39 @@ use std::collections::HashSet; use clap::ValueEnum; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use url::Host; - pub trait Data: Send + Sync { /// check if given host exists in Acls, if so return it - fn check_acl(&self,host: &Host ) -> crate::Result>; + fn check_acl(&self, host: &Host) -> crate::Result>; /// add a given Acl entry to the database fn add_acl(&self, acl: AclDatabaseEntry) -> crate::Result<()>; /// remove a given Acl entry from the database - fn remove_acl(&self,host: Host) -> crate::Result<()>; + fn remove_acl(&self, host: Host) -> crate::Result<()>; /// list all acls fn get_all_acls(&self) -> HashSet; } -#[derive(Serialize,Deserialize, Debug, Clone, Copy, Hash, Eq, PartialEq, ValueEnum)] -pub enum AclMode{ +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, Eq, PartialEq, ValueEnum)] +pub enum AclMode { Block, - Allow + Allow, } impl AclMode { - pub fn to_emoji(&self)-> char { + pub fn to_emoji(&self) -> char { match self { AclMode::Block => '❎', AclMode::Allow => '✅', } } } -#[derive(Serialize,Deserialize, Debug, Clone, Hash, Eq,PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)] -pub struct AclDatabaseEntry { +pub struct AclDatabaseEntry { pub(crate) mode: AclMode, - pub(crate) hostname: Host -} \ No newline at end of file + pub(crate) hostname: Host, +} diff --git a/src/service/acl/mod.rs b/src/service/acl/mod.rs index db32d93e..586aa8d3 100644 --- a/src/service/acl/mod.rs +++ b/src/service/acl/mod.rs @@ -1,93 +1,107 @@ - - use std::sync::Arc; use ruma::ServerName; -use tracing::{warn, debug, error}; +use tracing::{debug, error, warn}; use url::Host; -use crate::{config::acl::AccessControlListConfig, api::server_server::FedDest}; +use crate::{api::server_server::FedDest, config::acl::AccessControlListConfig}; pub use self::data::*; mod data; pub struct Service { pub db: &'static dyn Data, - pub acl_config: Arc + pub acl_config: Arc, } impl Service { - pub fn list_acls(&self, filter: Option) -> Vec { - let mut set = self.db.get_all_acls(); - self.acl_config.allow_list.clone().unwrap_or_default().iter().for_each(|it| { - set.insert(AclDatabaseEntry { mode: AclMode::Allow, hostname: it.to_owned() }); + pub fn list_acls(&self, filter: Option) -> Vec { + let mut set = self.db.get_all_acls(); + self.acl_config + .allow_list + .clone() + .unwrap_or_default() + .iter() + .for_each(|it| { + set.insert(AclDatabaseEntry { + mode: AclMode::Allow, + hostname: it.to_owned(), + }); }); - self.acl_config.block_list.clone().iter().for_each(|it| { - set.insert(AclDatabaseEntry { mode: AclMode::Block, hostname: it.to_owned() }); + self.acl_config.block_list.clone().iter().for_each(|it| { + set.insert(AclDatabaseEntry { + mode: AclMode::Block, + hostname: it.to_owned(), }); - match filter { - Some(filter) => set.into_iter().filter(|it| it.mode == filter).collect(), - None => set.into_iter().collect(), - } + }); + match filter { + Some(filter) => set.into_iter().filter(|it| it.mode == filter).collect(), + None => set.into_iter().collect(), } - pub fn remove_acl(&self, host: Host) -> crate::Result<()> { - self.db.remove_acl(host) + } + pub fn remove_acl(&self, host: Host) -> crate::Result<()> { + self.db.remove_acl(host) + } + + pub fn add_acl(&self, host: Host, mode: AclMode) -> crate::Result<()> { + self.db.add_acl(AclDatabaseEntry { + mode: mode, + hostname: host, + }) + } + /// same as federation_with_allowed however it can work with the fedi_dest type + pub fn is_federation_with_allowed_fedi_dest(&self, fedi_dest: &FedDest) -> bool { + let hostname = if let Ok(name) = Host::parse(&fedi_dest.hostname()) { + name + } else { + warn!( + "cannot deserialise hostname for server with name {:?}", + fedi_dest + ); + return false; + }; + return self.is_federation_with_allowed(hostname); + } + + /// same as federation_with_allowed however it can work with the fedi_dest type + pub fn is_federation_with_allowed_server_name(&self, srv: &ServerName) -> bool { + let hostname = if let Ok(name) = Host::parse(srv.host()) { + name + } else { + warn!("cannot deserialise hostname for server with name {:?}", srv); + return false; + }; + return self.is_federation_with_allowed(hostname); + } + /// is federation allowed with this particular server? + pub fn is_federation_with_allowed(&self, server_host_name: Host) -> bool { + debug!("checking federation allowance for {}", server_host_name); + // check blocklist first + if self.acl_config.block_list.contains(&server_host_name) { + return false; + } + let mut allow_list_enabled = false; + // check allowlist + if let Some(list) = &self.acl_config.allow_list { + if list.contains(&server_host_name) { + return true; + } + allow_list_enabled = true; } - pub fn add_acl(&self, host: Host, mode: AclMode) -> crate::Result<()> { - self.db.add_acl(AclDatabaseEntry { mode: mode, hostname: host }) - } - /// same as federation_with_allowed however it can work with the fedi_dest type - pub fn is_federation_with_allowed_fedi_dest(&self,fedi_dest: &FedDest) -> bool { - let hostname = if let Ok(name) = Host::parse(&fedi_dest.hostname()) { - name - } else { - warn!("cannot deserialise hostname for server with name {:?}",fedi_dest); - return false; - }; - return self.is_federation_with_allowed(hostname); - } - - /// same as federation_with_allowed however it can work with the fedi_dest type - pub fn is_federation_with_allowed_server_name(&self,srv: &ServerName) -> bool { - let hostname = if let Ok(name) = Host::parse(srv.host()) { - name - } else { - warn!("cannot deserialise hostname for server with name {:?}",srv); - return false; - }; - return self.is_federation_with_allowed(hostname); - } - /// is federation allowed with this particular server? - pub fn is_federation_with_allowed(&self,server_host_name: Host) -> bool { - debug!("checking federation allowance for {}", server_host_name); - // check blocklist first - if self.acl_config.block_list.contains(&server_host_name) { - return false; + //check database + match self.db.check_acl(&server_host_name) { + Err(error) => { + error!("database failed with {}", error); + false } - let mut allow_list_enabled = false; - // check allowlist - if let Some(list) = &self.acl_config.allow_list { - if list.contains(&server_host_name) { - return true; - } - allow_list_enabled = true; - } - - //check database - match self.db.check_acl(&server_host_name) { - Err(error) => { - error!("database failed with {}",error); - false - } - Ok(None) if allow_list_enabled => false, - Ok(None) => true, - Ok(Some(data::AclMode::Block)) => false, - Ok(Some(data::AclMode::Allow)) if allow_list_enabled => true, - Ok(Some(data::AclMode::Allow)) => { - warn!("allowlist value found in database for {} but allow list is not enabled, denied request", server_host_name); - false - } - + Ok(None) if allow_list_enabled => false, + Ok(None) => true, + Ok(Some(data::AclMode::Block)) => false, + Ok(Some(data::AclMode::Allow)) if allow_list_enabled => true, + Ok(Some(data::AclMode::Allow)) => { + warn!("allowlist value found in database for {} but allow list is not enabled, denied request", server_host_name); + false } } -} \ No newline at end of file + } +} diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 9a458ec9..0a49664c 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -40,7 +40,7 @@ use crate::{ Error, PduEvent, Result, }; -use super::{pdu::PduBuilder, acl::AclMode}; +use super::{acl::AclMode, pdu::PduBuilder}; const PAGE_SIZE: usize = 100; @@ -81,16 +81,9 @@ enum AdminCommand { #[cfg_attr(test, derive(Debug))] #[derive(Subcommand)] enum AclCommand { - Add { - mode: AclMode, - hostname: String - }, - Remove { - hostname: String - }, - List{ - filter: Option - } + Add { mode: AclMode, hostname: String }, + Remove { hostname: String }, + List { filter: Option }, } #[cfg_attr(test, derive(Debug))] @@ -1279,42 +1272,60 @@ impl Service { AdminCommand::Acl(AclCommand::Add { mode, hostname }) => { let host = match Host::parse(&hostname) { Ok(host) => host, - Err(error) => return Ok(RoomMessageEventContent::text_plain(format!("failed to parse hostname with error {}",error))), + Err(error) => { + return Ok(RoomMessageEventContent::text_plain(format!( + "failed to parse hostname with error {}", + error + ))) + } }; if let Err(error) = services().acl.add_acl(host.clone(), mode) { - error!("encountered {} while trying to add acl with host {} and mode {:?}",error,host,mode); + error!( + "encountered {} while trying to add acl with host {} and mode {:?}", + error, host, mode + ); RoomMessageEventContent::text_plain("error, couldn't add acl") } else { RoomMessageEventContent::text_plain("successfully added ACL") } - - }, - AdminCommand::Acl(AclCommand::Remove { hostname }) => { + } + AdminCommand::Acl(AclCommand::Remove { hostname }) => { let host = match Host::parse(&hostname) { Ok(host) => host, - Err(error) => return Ok(RoomMessageEventContent::text_plain(format!("failed to parse hostname with error {}",error))), + Err(error) => { + return Ok(RoomMessageEventContent::text_plain(format!( + "failed to parse hostname with error {}", + error + ))) + } }; if let Err(error) = services().acl.remove_acl(host.clone()) { - error!("encountered {} while trying to remove acl with host {}",error,host); + error!( + "encountered {} while trying to remove acl with host {}", + error, host + ); RoomMessageEventContent::text_plain("error, couldn't remove acl") } else { RoomMessageEventContent::text_plain("successfully removed ACL") } - }, - AdminCommand::Acl(AclCommand::List { filter}) => { + } + AdminCommand::Acl(AclCommand::List { filter }) => { let results = services().acl.list_acls(filter); let mut results_html = String::new(); results.iter().for_each(|it| { - results_html.push_str(&format!("* {} | {}\n",it.hostname,it.mode.to_emoji())); + results_html.push_str(&format!("* {} | {}\n", it.hostname, it.mode.to_emoji())); }); - RoomMessageEventContent::text_plain(format!(" + RoomMessageEventContent::text_plain(format!( + " List of services: \n ❎ = blocked\n ✅ = allowed\n {} - ",results_html)) - }, + ", + results_html + )) + } }; Ok(reply_message_content) diff --git a/src/service/mod.rs b/src/service/mod.rs index a2d71e95..d3c64097 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -6,8 +6,8 @@ use std::{ use lru_cache::LruCache; use crate::{Config, Result}; -pub mod acl; pub mod account_data; +pub mod acl; pub mod admin; pub mod appservice; pub mod globals; @@ -34,7 +34,7 @@ pub struct Services { pub key_backups: key_backups::Service, pub media: media::Service, pub sending: Arc, - pub acl: acl::Service + pub acl: acl::Service, } impl Services { @@ -121,7 +121,10 @@ impl Services { sending: sending::Service::build(db, &config), globals: globals::Service::load(db, config)?, - acl: acl::Service { db: db, acl_config: acl_conf }, + acl: acl::Service { + db: db, + acl_config: acl_conf, + }, }) } fn memory_usage(&self) -> String { diff --git a/src/service/rooms/event_handler/mod.rs b/src/service/rooms/event_handler/mod.rs index 90605aa1..d9688a80 100644 --- a/src/service/rooms/event_handler/mod.rs +++ b/src/service/rooms/event_handler/mod.rs @@ -1645,7 +1645,10 @@ impl Service { /// Returns Ok if the acl allows the server pub fn acl_check(&self, server_name: &ServerName, room_id: &RoomId) -> Result<()> { - if !services().acl.is_federation_with_allowed_server_name(server_name) { + if !services() + .acl + .is_federation_with_allowed_server_name(server_name) + { info!( "Server {} was denied by server ACL in {}", server_name, room_id diff --git a/src/utils/error.rs b/src/utils/error.rs index 6beb1b32..45f6a547 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -85,7 +85,7 @@ pub enum Error { #[error("{0} in {1}")] InconsistentRoomState(&'static str, ruma::OwnedRoomId), #[error("blocked {0}")] - ACLBlock(OwnedServerName) + ACLBlock(OwnedServerName), } impl Error {