rwhod: react to audit pam events to broadcast updates
Listen to read-only audit packets from the kernel to react on user logins/logouts. This way, we can immediately notify other machines on the network whenever there is a change to the user session list.
This commit is contained in:
Generated
+115
-18
@@ -56,7 +56,7 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -67,7 +67,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -205,9 +205,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.2"
|
||||
version = "4.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd059f9da4f5c36b3787f65d38ccaab1cc315f07b01f89abc8359ee6a8205011"
|
||||
checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -237,9 +237,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
version = "4.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -359,7 +359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -440,12 +440,48 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.33"
|
||||
@@ -494,9 +530,13 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@@ -742,6 +782,54 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-audit"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca9e80389b423ef073a74f393a57c37ffef7bcf058212c233412f19a026109b8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
"netlink-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-packet-core"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
|
||||
dependencies = [
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-proto"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"log",
|
||||
"netlink-packet-core",
|
||||
"netlink-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "netlink-sys"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
@@ -761,7 +849,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -815,6 +903,12 @@ version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.12.1"
|
||||
@@ -929,6 +1023,9 @@ dependencies = [
|
||||
"futures-util",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"netlink-packet-audit",
|
||||
"netlink-proto",
|
||||
"netlink-sys",
|
||||
"nix",
|
||||
"sd-notify",
|
||||
"serde",
|
||||
@@ -961,7 +1058,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1018,14 +1115,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.1",
|
||||
"syn 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -1093,7 +1190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1115,9 +1212,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f"
|
||||
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1134,7 +1231,7 @@ dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1144,7 +1241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1164,7 +1261,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.1",
|
||||
"syn 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+5
-2
@@ -19,7 +19,7 @@ autolib = false
|
||||
anyhow = "1.0.104"
|
||||
bytes = "1.12.1"
|
||||
chrono = { version = "0.4.45", features = ["serde"] }
|
||||
clap = { version = "4.6.2", features = ["derive"] }
|
||||
clap = { version = "4.6.3", features = ["derive"] }
|
||||
futures-util = "0.3.33"
|
||||
nix = { version = "0.31.3", features = ["hostname", "net", "fs", "user"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
@@ -29,7 +29,7 @@ tracing = "0.1.44"
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
# onc-rpc = "0.3.2"
|
||||
sd-notify = { version = "0.5.0", optional = true }
|
||||
serde_json = "1.0.150"
|
||||
serde_json = "1.0.151"
|
||||
uucore = { version = "0.9.0", features = ["utmpx"] }
|
||||
zlink = { version = "0.7.0", features = ["introspection"] }
|
||||
clap_complete = "4.6.7"
|
||||
@@ -39,6 +39,9 @@ caps = "0.5.6"
|
||||
users = { version = "0.11.0", default-features = false }
|
||||
tracing-journald = "0.3.2"
|
||||
chrono-tz = "0.10.4"
|
||||
netlink-proto = "0.12.0"
|
||||
netlink-sys = { version = "0.8.8", features = ["tokio_socket"] }
|
||||
netlink-packet-audit = "0.6.1"
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "2.0.7"
|
||||
|
||||
@@ -79,6 +79,16 @@ in {
|
||||
'';
|
||||
};
|
||||
|
||||
realtime_updates = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
example = false;
|
||||
description = ''
|
||||
Whether to react to Linux audit log activity (e.g. logins/logouts)
|
||||
and push status updates immediately.
|
||||
'';
|
||||
};
|
||||
|
||||
ignoreUsers = lib.mkOption {
|
||||
type = with lib.types; listOf (either str ints.unsigned);
|
||||
default = [ ];
|
||||
@@ -276,6 +286,22 @@ in {
|
||||
} // cfg.settings.rwhod.socketConfig;
|
||||
};
|
||||
|
||||
systemd.sockets.roowho2-audit = lib.mkIf (cfg.settings.rwhod.enable && cfg.settings.rwhod.realtime_updates) {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
description = "Roowho2 Audit Netlink Socket";
|
||||
unitConfig = {
|
||||
ConditionKernelCommandLine = [
|
||||
"!audit=0"
|
||||
"!audit=off"
|
||||
];
|
||||
};
|
||||
socketConfig = {
|
||||
Service = "roowho2.service";
|
||||
FileDescriptorName = "audit_socket";
|
||||
ListenNetlink = "audit 1"; # AUDIT_NLGRP_READLOG
|
||||
};
|
||||
};
|
||||
|
||||
# systemd.sockets.roowho2-fingerd = lib.mkIf cfg.settings.fingerd.enable && cfg.settings.fingerd.enableRemoteFinger {
|
||||
# wantedBy = [ "sockets.target" ];
|
||||
# description = "Roowho2 Fingerd Socket";
|
||||
|
||||
+5
-1
@@ -19,9 +19,13 @@ nixpkgs.lib.nixosSystem {
|
||||
({ config, lib, ... }: {
|
||||
system.stateVersion = config.system.nixos.release;
|
||||
virtualisation.graphics = false;
|
||||
|
||||
virtualisation.memorySize = 256;
|
||||
|
||||
boot.kernelParams = [
|
||||
"audit=1"
|
||||
"audit_backlog_limit=1024"
|
||||
];
|
||||
|
||||
users.users.alice.extraGroups = [ "wheel" ];
|
||||
users.users.bob.uid = 1001;
|
||||
|
||||
|
||||
+27
-1
@@ -105,12 +105,20 @@ async fn main() -> anyhow::Result<()> {
|
||||
})
|
||||
.context("RWHOD server is enabled, but socket fd not provided by systemd")??;
|
||||
|
||||
let audit_socket: Option<OwnedFd> = fd_map
|
||||
.get("audit_socket")
|
||||
.map(|fd| fd.try_clone())
|
||||
.transpose()
|
||||
.context("Failed to clone audit socket fd")?;
|
||||
|
||||
join_set.spawn(rwhod_server(
|
||||
socket,
|
||||
whod_status_store.clone(),
|
||||
rwhod_ignore_list.clone(),
|
||||
config.rwhod.interfaces.clone(),
|
||||
config.rwhod.send_interval(),
|
||||
config.rwhod.realtime_updates_enabled(),
|
||||
audit_socket,
|
||||
));
|
||||
} else {
|
||||
tracing::debug!("RWHOD server is disabled in configuration");
|
||||
@@ -148,13 +156,31 @@ async fn rwhod_server(
|
||||
ignore_list: Option<IgnoreList>,
|
||||
allowed_interfaces: Option<HashSet<String>>,
|
||||
send_interval: Duration,
|
||||
realtime_updates_enabled: bool,
|
||||
audit_socket: Option<OwnedFd>,
|
||||
) -> anyhow::Result<()> {
|
||||
let socket = Arc::new(socket);
|
||||
|
||||
let interfaces =
|
||||
roowho2_lib::server::rwhod::determine_relevant_interfaces(allowed_interfaces.as_ref())?;
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
match (realtime_updates_enabled, audit_socket) {
|
||||
(true, Some(fd)) => {
|
||||
tokio::spawn(roowho2_lib::server::rwhod::audit_change_notifier(tx, fd));
|
||||
}
|
||||
(true, None) => {
|
||||
tracing::warn!(
|
||||
"rwhod.realtime_updates is enabled, but no audit socket fd was provided by \
|
||||
systemd (e.g. the kernel might not have been booted with `audit=1`); \
|
||||
falling back to interval-only updates"
|
||||
);
|
||||
}
|
||||
(false, _) => {}
|
||||
}
|
||||
|
||||
let sender_task =
|
||||
rwhod_packet_sender_task(socket.clone(), interfaces, ignore_list, send_interval);
|
||||
rwhod_packet_sender_task(socket.clone(), interfaces, ignore_list, send_interval, rx);
|
||||
let receiver_task = rwhod_packet_receiver_task(socket.clone(), whod_status_store);
|
||||
|
||||
tokio::select! {
|
||||
|
||||
@@ -61,6 +61,13 @@ pub struct RwhodConfig {
|
||||
///
|
||||
/// If left as `None`, defaults to 4096.
|
||||
pub max_status_entries: Option<usize>,
|
||||
|
||||
/// Whether to react to Linux audit log activity (e.g. logins/logouts)
|
||||
/// and push a status update immediately, instead of only on the
|
||||
/// regular `send_interval_seconds` interval.
|
||||
///
|
||||
/// If left as `None`, defaults to `false`.
|
||||
pub realtime_updates: Option<bool>,
|
||||
}
|
||||
|
||||
impl RwhodConfig {
|
||||
@@ -95,6 +102,11 @@ impl RwhodConfig {
|
||||
None => DEFAULT_MAX_STATUS_ENTRIES,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves [`Self::realtime_updates`] into a concrete flag.
|
||||
pub fn realtime_updates_enabled(&self) -> bool {
|
||||
self.realtime_updates.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
mod audit_watcher;
|
||||
mod packet_receiver;
|
||||
mod packet_sender;
|
||||
mod rwhod_status;
|
||||
mod status_registry;
|
||||
|
||||
pub use audit_watcher::audit_change_notifier;
|
||||
pub use packet_receiver::rwhod_packet_receiver_task;
|
||||
pub use packet_sender::{determine_relevant_interfaces, rwhod_packet_sender_task};
|
||||
pub use rwhod_status::{generate_rwhod_status_update, generate_rwhod_user_entries};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd};
|
||||
|
||||
use futures_util::stream::StreamExt;
|
||||
use netlink_packet_audit::AuditMessage;
|
||||
use netlink_proto::sys::TokioSocket;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// `AUDIT_USER_START` is emitted when a PAM session is opened
|
||||
const AUDIT_USER_START: u16 = 1105;
|
||||
/// `AUDIT_USER_END` is emitted when a PAM session is closed
|
||||
const AUDIT_USER_END: u16 = 1106;
|
||||
|
||||
/// Listens for audit messages on the systemd-provided netlink socket and sends a
|
||||
/// notification on the provided channel whenever a user session is opened or closed,
|
||||
/// indicating that there rwhod status may have changed.
|
||||
pub async fn audit_change_notifier(sender: mpsc::Sender<()>, socket_fd: OwnedFd) {
|
||||
// SAFETY: `socket_fd` is a systemd-provided netlink socket, already
|
||||
// bound and subscribed to the audit multicast group.
|
||||
let socket = unsafe { TokioSocket::from_raw_fd(socket_fd.into_raw_fd()) };
|
||||
|
||||
let (connection, _handle, mut messages) = netlink_proto::from_socket_with_codec::<
|
||||
AuditMessage,
|
||||
TokioSocket,
|
||||
netlink_packet_audit::NetlinkAuditCodec,
|
||||
>(socket);
|
||||
|
||||
tokio::spawn(connection);
|
||||
|
||||
tracing::info!("Listening for realtime session updates via the Linux audit log");
|
||||
|
||||
loop {
|
||||
match messages.next().await {
|
||||
Some((msg, _addr))
|
||||
if matches!(msg.header.message_type, AUDIT_USER_START | AUDIT_USER_END) =>
|
||||
{
|
||||
tracing::debug!("Received session-related audit message: {:?}", msg);
|
||||
if sender.send(()).await.is_err() {
|
||||
tracing::debug!("Realtime update receiver dropped, stopping audit watcher");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some((msg, _addr)) => {
|
||||
tracing::trace!(
|
||||
"Ignoring audit message unrelated to sessions (type {})",
|
||||
msg.header.message_type
|
||||
);
|
||||
}
|
||||
None => {
|
||||
tracing::warn!(
|
||||
"Audit netlink connection closed unexpectedly; realtime updates disabled \
|
||||
for the rest of this run"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
};
|
||||
use tokio::{
|
||||
net::UdpSocket,
|
||||
sync::mpsc,
|
||||
time::{Duration as TokioDuration, interval},
|
||||
};
|
||||
|
||||
@@ -115,11 +116,31 @@ pub async fn rwhod_packet_sender_task(
|
||||
interfaces: Vec<RwhodSendTarget>,
|
||||
ignore_list: Option<IgnoreList>,
|
||||
send_interval: TokioDuration,
|
||||
mut realtime_update_trigger: mpsc::Receiver<()>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut interval = interval(send_interval);
|
||||
let mut trigger_closed = false;
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if trigger_closed {
|
||||
interval.tick().await;
|
||||
} else {
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {}
|
||||
triggered = realtime_update_trigger.recv() => {
|
||||
if triggered.is_some() {
|
||||
tracing::debug!("Sending an early rwhod update due to realtime trigger");
|
||||
interval.reset();
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Realtime update channel closed unexpectedly; falling back to interval-only updates"
|
||||
);
|
||||
trigger_closed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let status_update = generate_rwhod_status_update(ignore_list.as_ref())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user