summary refs log tree commit diff
path: root/rust/src
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src')
-rw-r--r--rust/src/acl/mod.rs102
-rw-r--r--rust/src/lib.rs2
2 files changed, 104 insertions, 0 deletions
diff --git a/rust/src/acl/mod.rs b/rust/src/acl/mod.rs
new file mode 100644
index 0000000000..071f2b7732
--- /dev/null
+++ b/rust/src/acl/mod.rs
@@ -0,0 +1,102 @@
+// Copyright 2023 The Matrix.org Foundation C.I.C.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! An implementation of Matrix server ACL rules.
+
+use std::net::Ipv4Addr;
+use std::str::FromStr;
+
+use anyhow::Error;
+use pyo3::prelude::*;
+use regex::Regex;
+
+use crate::push::utils::{glob_to_regex, GlobMatchType};
+
+/// Called when registering modules with python.
+pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
+    let child_module = PyModule::new(py, "acl")?;
+    child_module.add_class::<ServerAclEvaluator>()?;
+
+    m.add_submodule(child_module)?;
+
+    // We need to manually add the module to sys.modules to make `from
+    // synapse.synapse_rust import acl` work.
+    py.import("sys")?
+        .getattr("modules")?
+        .set_item("synapse.synapse_rust.acl", child_module)?;
+
+    Ok(())
+}
+
+#[derive(Debug, Clone)]
+#[pyclass(frozen)]
+pub struct ServerAclEvaluator {
+    allow_ip_literals: bool,
+    allow: Vec<Regex>,
+    deny: Vec<Regex>,
+}
+
+#[pymethods]
+impl ServerAclEvaluator {
+    #[new]
+    pub fn py_new(
+        allow_ip_literals: bool,
+        allow: Vec<&str>,
+        deny: Vec<&str>,
+    ) -> Result<Self, Error> {
+        let allow = allow
+            .iter()
+            .map(|s| glob_to_regex(s, GlobMatchType::Whole))
+            .collect::<Result<_, _>>()?;
+        let deny = deny
+            .iter()
+            .map(|s| glob_to_regex(s, GlobMatchType::Whole))
+            .collect::<Result<_, _>>()?;
+
+        Ok(ServerAclEvaluator {
+            allow_ip_literals,
+            allow,
+            deny,
+        })
+    }
+
+    pub fn server_matches_acl_event(&self, server_name: &str) -> bool {
+        // first of all, check if literal IPs are blocked, and if so, whether the
+        // server name is a literal IP
+        if !self.allow_ip_literals {
+            // check for ipv6 literals. These start with '['.
+            if server_name.starts_with('[') {
+                return false;
+            }
+
+            // check for ipv4 literals. We can just lift the routine from std::net.
+            if Ipv4Addr::from_str(server_name).is_ok() {
+                return false;
+            }
+        }
+
+        // next, check the deny list
+        if self.deny.iter().any(|e| e.is_match(server_name)) {
+            return false;
+        }
+
+        // then the allow list.
+        if self.allow.iter().any(|e| e.is_match(server_name)) {
+            return true;
+        }
+
+        // everything else should be rejected.
+        false
+    }
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index ce67f58611..c44c09bda7 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -2,6 +2,7 @@ use lazy_static::lazy_static;
 use pyo3::prelude::*;
 use pyo3_log::ResetHandle;
 
+pub mod acl;
 pub mod push;
 
 lazy_static! {
@@ -38,6 +39,7 @@ fn synapse_rust(py: Python<'_>, m: &PyModule) -> PyResult<()> {
     m.add_function(wrap_pyfunction!(get_rust_file_digest, m)?)?;
     m.add_function(wrap_pyfunction!(reset_logging_config, m)?)?;
 
+    acl::register_module(py, m)?;
     push::register_module(py, m)?;
 
     Ok(())