Five conflicts, resolved: - crates/common/src/auth/authentication.rs: upstream's get_directory_for_token and JwtClaims replace extract_jwt_domain; the per-domain directory code (DIR-1, DIR-5 to DIR-7) is kept, and the token lookup routes through it. The release's one new Enterprise snippet was the body of get_directory_for_issuer, which stays returning None: a token naming no address gets the server default, as DIR-2 specifies and as v0.16.22 did. - crates/common/src/manager/application.rs: upstream's rewrite of the tests, with the temp directory names renamed again, and the 5(a) notice the name-purge change should have added. - crates/common/src/network/mta.rs: both sides' imports. - crates/main/Cargo.toml: the AGPL-only license kept, version 0.16.23. - Cargo.lock: upstream's, with the fork's crates added by Cargo.
299 lines
15 KiB
Rust
299 lines
15 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*
|
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
|
*/
|
|
|
|
use crate::broadcast::{BROADCAST_TOPIC, BroadcastBatch};
|
|
use common::{
|
|
BuildServer, Inner,
|
|
ipc::{BroadcastEvent, PushEvent, PushNotification, QueueEvent, RegistryChange},
|
|
};
|
|
use registry::types::EnumImpl;
|
|
use std::{sync::Arc, time::Duration};
|
|
use tokio::sync::watch;
|
|
use trc::{ClusterEvent, ServerEvent};
|
|
|
|
pub fn spawn_broadcast_subscriber(inner: Arc<Inner>, mut shutdown_rx: watch::Receiver<bool>) {
|
|
let this_node_id = {
|
|
let _core = inner.shared_core.load();
|
|
if _core.storage.coordinator.is_none() || _core.storage.registry.is_recovery_mode() {
|
|
return;
|
|
}
|
|
_core.network.node_id as u16
|
|
};
|
|
|
|
tokio::spawn(async move {
|
|
let mut retry_count = 0;
|
|
|
|
trc::event!(Cluster(ClusterEvent::SubscriberStart));
|
|
|
|
'subscribe: loop {
|
|
let coordinator = inner.shared_core.load().storage.coordinator.clone();
|
|
if coordinator.is_none() {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::SubscriberError),
|
|
Details = "Coordinator is no longer configured"
|
|
);
|
|
break;
|
|
}
|
|
|
|
let mut stream = match coordinator.subscribe(BROADCAST_TOPIC).await {
|
|
Ok(stream) => {
|
|
retry_count = 0;
|
|
stream
|
|
}
|
|
Err(err) => {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::SubscriberError),
|
|
CausedBy = err,
|
|
Details = "Failed to subscribe to channel"
|
|
);
|
|
|
|
match tokio::time::timeout(
|
|
Duration::from_secs(1 << retry_count.max(6)),
|
|
shutdown_rx.changed(),
|
|
)
|
|
.await
|
|
{
|
|
Ok(_) => {
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
retry_count += 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
loop {
|
|
tokio::select! {
|
|
message = stream.next() => {
|
|
match message {
|
|
Some(message) => {
|
|
let mut batch = BroadcastBatch::new(message.payload().iter());
|
|
let node_id = match batch.node_id() {
|
|
Some(node_id) => {
|
|
if node_id != this_node_id {
|
|
node_id
|
|
} else {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::MessageSkipped),
|
|
Details = message.payload()
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
None => {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::MessageInvalid),
|
|
Details = message.payload()
|
|
);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
inner
|
|
.shared_core
|
|
.load()
|
|
.storage
|
|
.data
|
|
.invalidate_read_snapshot();
|
|
|
|
loop {
|
|
match batch.next_event() {
|
|
Ok(Some(event)) => {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::MessageReceived),
|
|
From = node_id,
|
|
To = this_node_id,
|
|
Details = log_event(&event),
|
|
);
|
|
match event {
|
|
BroadcastEvent::PushNotification(notification) => {
|
|
// inbuxa: ST-7: another node's change raises this
|
|
// node's high-water mark for the account
|
|
match ¬ification {
|
|
common::ipc::PushNotification::StateChange(change) => inner
|
|
.build_server()
|
|
.core
|
|
.storage
|
|
.data
|
|
.note_change(change.account_id, change.change_id),
|
|
common::ipc::PushNotification::EmailPush(push) => inner
|
|
.build_server()
|
|
.core
|
|
.storage
|
|
.data
|
|
.note_change(push.account_id, push.change_id),
|
|
_ => {}
|
|
}
|
|
if inner
|
|
.ipc
|
|
.push_tx
|
|
.send(PushEvent::Publish {
|
|
notification,
|
|
broadcast: false,
|
|
})
|
|
.await
|
|
.is_err()
|
|
{
|
|
trc::event!(
|
|
Server(ServerEvent::ThreadError),
|
|
Details = "Error sending push notification.",
|
|
CausedBy = trc::location!()
|
|
);
|
|
}
|
|
}
|
|
BroadcastEvent::PushServerUpdate(account_id) => {
|
|
if inner
|
|
.ipc
|
|
.push_tx
|
|
.send(PushEvent::PushServerUpdate { account_id, broadcast: false })
|
|
.await
|
|
.is_err()
|
|
{
|
|
trc::event!(
|
|
Server(ServerEvent::ThreadError),
|
|
Details = "Error sending reload request.",
|
|
CausedBy = trc::location!()
|
|
);
|
|
}
|
|
}
|
|
BroadcastEvent::CacheInvalidate(changes) => {
|
|
inner.build_server().invalidate_local_caches(&changes).await;
|
|
|
|
}
|
|
BroadcastEvent::CacheInvalidateAll => {
|
|
inner.build_server().invalidate_all_local_caches();
|
|
}
|
|
BroadcastEvent::CacheInvalidateNegative => {
|
|
inner.build_server().invalidate_all_local_negative_caches();
|
|
}
|
|
BroadcastEvent::MtaQueueStatus { is_running } => {
|
|
let _ = inner
|
|
.ipc
|
|
.queue_tx
|
|
.send(QueueEvent::Paused(!is_running))
|
|
.await;
|
|
}
|
|
BroadcastEvent::QueueRefresh => {
|
|
if inner.shared_core.load().network.roles.outbound_mta {
|
|
let _ = inner
|
|
.ipc
|
|
.queue_tx
|
|
.send(QueueEvent::Refresh)
|
|
.await;
|
|
}
|
|
}
|
|
BroadcastEvent::RegistryChange(change) => {
|
|
match Box::pin(inner.build_server().reload_registry(change)).await {
|
|
Ok(result) => {
|
|
result.log();
|
|
}
|
|
Err(err) => {
|
|
trc::error!(
|
|
err.details("Failed to reload settings")
|
|
.caused_by(trc::location!())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(None) => break,
|
|
Err(_) => {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::MessageInvalid),
|
|
Details = message.payload()
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
trc::event!(
|
|
Cluster(ClusterEvent::SubscriberDisconnected),
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
_ = shutdown_rx.changed() => {
|
|
break 'subscribe;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
trc::event!(Cluster(ClusterEvent::SubscriberStop));
|
|
});
|
|
}
|
|
|
|
fn log_event(event: &BroadcastEvent) -> trc::Value {
|
|
match event {
|
|
BroadcastEvent::PushNotification(notification) => match notification {
|
|
PushNotification::StateChange(state_change) => trc::Value::Array(vec![
|
|
"StateChange".into(),
|
|
state_change.account_id.into(),
|
|
state_change.change_id.into(),
|
|
(*state_change.types.as_ref()).into(),
|
|
]),
|
|
PushNotification::CalendarAlert(calendar_alert) => trc::Value::Array(vec![
|
|
"CalendarAlert".into(),
|
|
calendar_alert.account_id.into(),
|
|
calendar_alert.event_id.into(),
|
|
calendar_alert.recurrence_id.into(),
|
|
calendar_alert.uid.clone().into(),
|
|
calendar_alert.alert_id.clone().into(),
|
|
]),
|
|
PushNotification::EmailPush(email_push) => trc::Value::Array(vec![
|
|
"EmailPush".into(),
|
|
email_push.account_id.into(),
|
|
email_push.email_id.into(),
|
|
email_push.change_id.into(),
|
|
]),
|
|
},
|
|
BroadcastEvent::PushServerUpdate(account_id) => {
|
|
trc::Value::Array(vec!["PushServerUpdate".into(), (*account_id).into()])
|
|
}
|
|
BroadcastEvent::RegistryChange(change) => match change {
|
|
RegistryChange::Insert(id) => trc::Value::Array(vec![
|
|
"RegistryInsert".into(),
|
|
id.object().as_str().into(),
|
|
id.id().id().into(),
|
|
]),
|
|
RegistryChange::Delete(id) => trc::Value::Array(vec![
|
|
"RegistryDelete".into(),
|
|
id.object().as_str().into(),
|
|
id.id().id().into(),
|
|
]),
|
|
RegistryChange::Reload(object) => {
|
|
trc::Value::Array(vec!["RegistryReload".into(), object.as_str().into()])
|
|
}
|
|
},
|
|
BroadcastEvent::CacheInvalidate(items) => {
|
|
let mut array = Vec::with_capacity(items.len() + 1);
|
|
array.push("CacheInvalidation".into());
|
|
for item in items {
|
|
array.push(format!("{:?}", item).into());
|
|
}
|
|
trc::Value::Array(array)
|
|
}
|
|
BroadcastEvent::CacheInvalidateAll => "CacheInvalidateAll".into(),
|
|
BroadcastEvent::CacheInvalidateNegative => "CacheInvalidateNegative".into(),
|
|
BroadcastEvent::MtaQueueStatus { is_running } => {
|
|
if *is_running {
|
|
"MtaQueueRunning".into()
|
|
} else {
|
|
"MtaQueuePaused".into()
|
|
}
|
|
}
|
|
BroadcastEvent::QueueRefresh => "QueueRefresh".into(),
|
|
}
|
|
}
|