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(())
diff --git a/rust/src/push/base_rules.rs b/rust/src/push/base_rules.rs
index 59fd27665a..cebc2c079b 100644
--- a/rust/src/push/base_rules.rs
+++ b/rust/src/push/base_rules.rs
@@ -64,6 +64,19 @@ pub const BASE_PREPEND_OVERRIDE_RULES: &[PushRule] = &[PushRule {
pub const BASE_APPEND_OVERRIDE_RULES: &[PushRule] = &[
PushRule {
+ rule_id: Cow::Borrowed("global/override/.org.matrix.msc4028.encrypted_event"),
+ priority_class: 5,
+ conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
+ EventMatchCondition {
+ key: Cow::Borrowed("type"),
+ pattern: Cow::Borrowed("m.room.encrypted"),
+ },
+ ))]),
+ actions: Cow::Borrowed(&[Action::Notify]),
+ default: true,
+ default_enabled: false,
+ },
+ PushRule {
rule_id: Cow::Borrowed("global/override/.m.rule.suppress_notices"),
priority_class: 5,
conditions: Cow::Borrowed(&[Condition::Known(KnownCondition::EventMatch(
diff --git a/rust/src/push/evaluator.rs b/rust/src/push/evaluator.rs
index 5b9bf9b26a..48e670478b 100644
--- a/rust/src/push/evaluator.rs
+++ b/rust/src/push/evaluator.rs
@@ -564,7 +564,7 @@ fn test_requires_room_version_supports_condition() {
};
let rules = PushRules::new(vec![custom_rule]);
result = evaluator.run(
- &FilteredPushRules::py_new(rules, BTreeMap::new(), true, false, true),
+ &FilteredPushRules::py_new(rules, BTreeMap::new(), true, false, true, false),
None,
None,
);
diff --git a/rust/src/push/mod.rs b/rust/src/push/mod.rs
index 8e91f506cc..5e1e8e1abb 100644
--- a/rust/src/push/mod.rs
+++ b/rust/src/push/mod.rs
@@ -527,6 +527,7 @@ pub struct FilteredPushRules {
msc1767_enabled: bool,
msc3381_polls_enabled: bool,
msc3664_enabled: bool,
+ msc4028_push_encrypted_events: bool,
}
#[pymethods]
@@ -538,6 +539,7 @@ impl FilteredPushRules {
msc1767_enabled: bool,
msc3381_polls_enabled: bool,
msc3664_enabled: bool,
+ msc4028_push_encrypted_events: bool,
) -> Self {
Self {
push_rules,
@@ -545,6 +547,7 @@ impl FilteredPushRules {
msc1767_enabled,
msc3381_polls_enabled,
msc3664_enabled,
+ msc4028_push_encrypted_events,
}
}
@@ -581,6 +584,12 @@ impl FilteredPushRules {
return false;
}
+ if !self.msc4028_push_encrypted_events
+ && rule.rule_id == "global/override/.org.matrix.msc4028.encrypted_event"
+ {
+ return false;
+ }
+
true
})
.map(|r| {
|