{rwhod,fingerd}: add ignore-user lists
This commit is contained in:
+35
-2
@@ -22,6 +22,20 @@ in {
|
||||
default = true;
|
||||
};
|
||||
|
||||
ignore_list_path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = lib.literalExpression ''
|
||||
pkgs.writeText "rwhod-ignore-list" '''
|
||||
# Ignore the following users from rwhod
|
||||
user:user1
|
||||
user:user2
|
||||
uid:1001
|
||||
'''
|
||||
'';
|
||||
description = "Path to the ignore list for users that should be hidden from rwhod.";
|
||||
};
|
||||
|
||||
# TODO: allow configuring socket config
|
||||
};
|
||||
fingerd = {
|
||||
@@ -29,6 +43,20 @@ in {
|
||||
default = true;
|
||||
};
|
||||
|
||||
ignore_list_path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = lib.literalExpression ''
|
||||
pkgs.writeText "rwhod-ignore-list" '''
|
||||
# Ignore the following users from rwhod
|
||||
user:user1
|
||||
user:user2
|
||||
uid:1001
|
||||
'''
|
||||
'';
|
||||
description = "Path to the ignore list for users that should be hidden from fingerd.";
|
||||
};
|
||||
|
||||
# TODO: allow configuring socket config
|
||||
};
|
||||
};
|
||||
@@ -115,7 +143,7 @@ in {
|
||||
RuntimeDirectory = "roowho2/root-mnt";
|
||||
RuntimeDirectoryMode = "0700";
|
||||
RootDirectory = "/run/roowho2/root-mnt";
|
||||
BindReadOnlyPaths = [
|
||||
BindReadOnlyPaths = lib.filter (x: x != null) ([
|
||||
builtins.storeDir
|
||||
"/etc"
|
||||
|
||||
@@ -130,7 +158,12 @@ in {
|
||||
|
||||
# NOTE: finger needs access to stat tty devices
|
||||
"/dev"
|
||||
];
|
||||
|
||||
] ++ lib.optionals cfg.settings.rwhod.enable [
|
||||
cfg.settings.rwhod.ignore_list_path
|
||||
] ++ lib.optionals cfg.settings.fingerd.enable [
|
||||
cfg.settings.fingerd.ignore_list_path
|
||||
]);
|
||||
|
||||
UMask = "0077";
|
||||
};
|
||||
|
||||
+15
-4
@@ -14,6 +14,7 @@ use tracing_subscriber::layer::SubscriberExt;
|
||||
|
||||
use roowho2_lib::server::{
|
||||
config::{DEFAULT_CONFIG_PATH, LogLevel},
|
||||
ignore_list::IgnoreList,
|
||||
rwhod::{RwhodStatusStore, rwhod_packet_receiver_task, rwhod_packet_sender_task},
|
||||
varlink_api::varlink_client_server_task,
|
||||
};
|
||||
@@ -59,6 +60,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
tracing::subscriber::set_global_default(subscriber)
|
||||
.context("Failed to set global default tracing subscriber")?;
|
||||
|
||||
let rwhod_ignore_list = IgnoreList::load_optional(config.rwhod.ignore_list_path.as_deref())?;
|
||||
let finger_ignore_list = IgnoreList::load_optional(config.fingerd.ignore_list_path.as_deref())?;
|
||||
|
||||
let fd_map: HashMap<String, OwnedFd> =
|
||||
HashMap::from_iter(sd_notify::listen_fds_with_names()?.map(|(fd_num, name)| {
|
||||
(
|
||||
@@ -96,7 +100,11 @@ async fn main() -> anyhow::Result<()> {
|
||||
})
|
||||
.context("RWHOD server is enabled, but socket fd not provided by systemd")??;
|
||||
|
||||
join_set.spawn(rwhod_server(socket, whod_status_store.clone()));
|
||||
join_set.spawn(rwhod_server(
|
||||
socket,
|
||||
whod_status_store.clone(),
|
||||
rwhod_ignore_list.clone(),
|
||||
));
|
||||
} else {
|
||||
tracing::debug!("RWHOD server is disabled in configuration");
|
||||
}
|
||||
@@ -108,6 +116,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.try_clone()
|
||||
.context("Failed to clone RWHOD client-server socket fd")?,
|
||||
whod_status_store.clone(),
|
||||
finger_ignore_list,
|
||||
client_server_token,
|
||||
));
|
||||
|
||||
@@ -127,12 +136,12 @@ async fn ctrl_c_handler() -> anyhow::Result<()> {
|
||||
async fn rwhod_server(
|
||||
socket: UdpSocket,
|
||||
whod_status_store: RwhodStatusStore,
|
||||
ignore_list: Option<IgnoreList>,
|
||||
) -> anyhow::Result<()> {
|
||||
let socket = Arc::new(socket);
|
||||
|
||||
let interfaces = roowho2_lib::server::rwhod::determine_relevant_interfaces()?;
|
||||
let sender_task = rwhod_packet_sender_task(socket.clone(), interfaces);
|
||||
|
||||
let sender_task = rwhod_packet_sender_task(socket.clone(), interfaces, ignore_list);
|
||||
let receiver_task = rwhod_packet_receiver_task(socket.clone(), whod_status_store);
|
||||
|
||||
tokio::select! {
|
||||
@@ -146,6 +155,7 @@ async fn rwhod_server(
|
||||
async fn client_server(
|
||||
socket_fd: OwnedFd,
|
||||
whod_status_store: RwhodStatusStore,
|
||||
finger_ignore_list: Option<IgnoreList>,
|
||||
startup_token: CancellationToken,
|
||||
) -> anyhow::Result<()> {
|
||||
// SAFETY: see above
|
||||
@@ -153,7 +163,8 @@ async fn client_server(
|
||||
unsafe { std::os::unix::net::UnixListener::from_raw_fd(socket_fd.as_raw_fd()) };
|
||||
std_socket.set_nonblocking(true)?;
|
||||
let zlink_listener = zlink::unix::Listener::try_from(OwnedFd::from(std_socket))?;
|
||||
let client_server_task = varlink_client_server_task(zlink_listener, whod_status_store);
|
||||
let client_server_task =
|
||||
varlink_client_server_task(zlink_listener, whod_status_store, finger_ignore_list);
|
||||
|
||||
startup_token.cancel();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod config;
|
||||
pub mod fingerd;
|
||||
pub mod ignore_list;
|
||||
pub mod rwhod;
|
||||
pub mod varlink_api;
|
||||
|
||||
+16
-1
@@ -1,4 +1,4 @@
|
||||
use std::net::SocketAddrV4;
|
||||
use std::{net::SocketAddrV4, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -14,6 +14,9 @@ pub struct Config {
|
||||
/// Configuration for the rwhod server.
|
||||
pub rwhod: RwhodConfig,
|
||||
|
||||
/// Configuration for the fingerd server.
|
||||
pub fingerd: FingerdConfig,
|
||||
|
||||
/// Path to the Unix domain socket for client-server communication.
|
||||
///
|
||||
/// If left as `None`, the server expects to be served a file descriptor to the socket named 'client'.
|
||||
@@ -33,6 +36,9 @@ pub struct RwhodConfig {
|
||||
/// Enable or disable the rwhod server functionality.
|
||||
pub enable: bool,
|
||||
|
||||
/// Path to the ignore list for users that should be hidden from rwhod.
|
||||
pub ignore_list_path: Option<PathBuf>,
|
||||
|
||||
/// Network interfaces to listen on (e.g., ["eth0", "wlan0"]).
|
||||
///
|
||||
/// If left as `None`, the server will automatically determine relevant interfaces.
|
||||
@@ -45,3 +51,12 @@ pub struct RwhodConfig {
|
||||
/// If left as `None`, the server will automatically determine broadcast addresses for the selected interfaces.
|
||||
pub broadcast_addresses: Option<Vec<SocketAddrV4>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct FingerdConfig {
|
||||
/// Enable or disable the fingerd server functionality.
|
||||
pub enable: bool,
|
||||
|
||||
/// Path to the ignore list for users that should be hidden from fingerd.
|
||||
pub ignore_list_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@@ -12,14 +12,18 @@ use uucore::utmpx::{Utmpx, UtmpxRecord};
|
||||
|
||||
use crate::{
|
||||
proto::finger_protocol::{FingerResponseStructuredUserEntry, FingerResponseUserSession},
|
||||
server::fingerd::{FingerRequestInfo, local_email},
|
||||
server::{
|
||||
fingerd::{FingerRequestInfo, local_email},
|
||||
ignore_list::IgnoreList,
|
||||
},
|
||||
};
|
||||
|
||||
/// Search for users whose username or full name contains the search string.
|
||||
pub fn search_for_user(
|
||||
search_string: &str,
|
||||
match_fullnames: bool,
|
||||
_request_info: &FingerRequestInfo,
|
||||
request_info: &FingerRequestInfo,
|
||||
ignore_list: Option<&IgnoreList>,
|
||||
) -> Vec<anyhow::Result<FingerResponseStructuredUserEntry>> {
|
||||
(unsafe { all_users() })
|
||||
.filter_map(|user| {
|
||||
@@ -51,10 +55,14 @@ pub fn search_for_user(
|
||||
)
|
||||
.to_string();
|
||||
|
||||
if ignore_list.is_some_and(|ignore_list| ignore_list.ignores_uid(user.uid.as_raw())) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let matches_username = username.contains(search_string);
|
||||
let matches_fullname = match_fullnames && full_name.contains(search_string);
|
||||
if matches_username || matches_fullname {
|
||||
match get_local_user(&username, None) {
|
||||
match get_local_user(&username, None, request_info, ignore_list) {
|
||||
Ok(Some(user_entry)) => Some(Ok(user_entry)),
|
||||
Ok(None) => None, // User exists but has .nofinger, skip
|
||||
Err(err) => Some(Err(err)),
|
||||
@@ -68,13 +76,19 @@ pub fn search_for_user(
|
||||
|
||||
/// Retrieve information about all users currently logged in, based on utmpx records.
|
||||
pub fn finger_utmp_users(
|
||||
_request_info: &FingerRequestInfo,
|
||||
request_info: &FingerRequestInfo,
|
||||
ignore_list: Option<&IgnoreList>,
|
||||
) -> Vec<anyhow::Result<FingerResponseStructuredUserEntry>> {
|
||||
Utmpx::iter_all_records()
|
||||
.filter(|entry| entry.is_user_process())
|
||||
.into_group_map_by(|entry| entry.user())
|
||||
.into_iter()
|
||||
.map(|(username, records)| get_local_user(&username, Some(records)))
|
||||
.filter(|(username, _)| {
|
||||
!ignore_list.is_some_and(|ignore_list| ignore_list.ignores_username(username))
|
||||
})
|
||||
.map(|(username, records)| {
|
||||
get_local_user(&username, Some(records), request_info, ignore_list)
|
||||
})
|
||||
.filter_map(|result| match result {
|
||||
Ok(Some(user_entry)) => Some(Ok(user_entry)),
|
||||
Ok(None) => None, // User has .nofinger, skip
|
||||
@@ -113,6 +127,8 @@ fn read_file_content_if_exists(path: &Path) -> anyhow::Result<Option<String>> {
|
||||
fn get_local_user(
|
||||
username: &str,
|
||||
utmp_records: Option<Vec<UtmpxRecord>>,
|
||||
_request_info: &FingerRequestInfo,
|
||||
ignore_list: Option<&IgnoreList>,
|
||||
) -> anyhow::Result<Option<FingerResponseStructuredUserEntry>> {
|
||||
tracing::trace!(
|
||||
"Retrieving local user information for username: {}",
|
||||
@@ -131,6 +147,10 @@ fn get_local_user(
|
||||
}
|
||||
};
|
||||
|
||||
if ignore_list.is_some_and(|ignore_list| ignore_list.ignores_uid(user_entry.uid.as_raw())) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let nofinger_path = user_entry.dir.join(".nofinger");
|
||||
if nofinger_path.exists() {
|
||||
return Ok(None);
|
||||
@@ -249,7 +269,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_finger_root() {
|
||||
let user_entry = get_local_user("root", None).unwrap().unwrap();
|
||||
let user_entry = get_local_user(
|
||||
"root",
|
||||
None,
|
||||
&FingerRequestInfo::Long {
|
||||
prevent_files: false,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(user_entry.username, "root");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
use anyhow::Context;
|
||||
use nix::unistd::{Uid, User};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum IgnoreEntry {
|
||||
Uid(u32),
|
||||
User(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct IgnoreList {
|
||||
entries: HashSet<IgnoreEntry>,
|
||||
}
|
||||
|
||||
impl IgnoreList {
|
||||
pub fn load_optional(path: Option<&Path>) -> anyhow::Result<Option<Self>> {
|
||||
match path {
|
||||
Some(path) => Self::load(path).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("Failed to read ignore list {}", path.display()))?;
|
||||
Self::parse(&content)
|
||||
}
|
||||
|
||||
pub fn parse(content: &str) -> anyhow::Result<Self> {
|
||||
let mut entries = HashSet::new();
|
||||
|
||||
for (idx, raw_line) in content.lines().enumerate() {
|
||||
let line = raw_line.split('#').next().unwrap_or("").trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = if let Some(uid) = line.strip_prefix("uid:") {
|
||||
let uid = uid.trim().parse::<u32>().with_context(|| {
|
||||
format!("Invalid uid on ignore list line {}: {}", idx + 1, raw_line)
|
||||
})?;
|
||||
IgnoreEntry::Uid(uid)
|
||||
} else if let Some(user) = line.strip_prefix("user:") {
|
||||
let user = user.trim();
|
||||
if user.is_empty() {
|
||||
anyhow::bail!("Invalid user on ignore list line {}: {}", idx + 1, raw_line);
|
||||
}
|
||||
IgnoreEntry::User(user.to_string())
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Invalid ignore list entry on line {}: {}",
|
||||
idx + 1,
|
||||
raw_line
|
||||
);
|
||||
};
|
||||
|
||||
entries.insert(entry);
|
||||
}
|
||||
|
||||
Ok(Self { entries })
|
||||
}
|
||||
|
||||
pub fn ignores_username(&self, username: &str) -> bool {
|
||||
if self
|
||||
.entries
|
||||
.contains(&IgnoreEntry::User(username.to_string()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
match User::from_name(username) {
|
||||
Ok(Some(user)) => self.entries.contains(&IgnoreEntry::Uid(user.uid.as_raw())),
|
||||
Ok(None) => false,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to resolve username '{}' for ignore list lookup: {}",
|
||||
username,
|
||||
err
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ignores_uid(&self, uid: u32) -> bool {
|
||||
if self.entries.contains(&IgnoreEntry::Uid(uid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match User::from_uid(Uid::from_raw(uid)) {
|
||||
Ok(Some(user)) => self.entries.contains(&IgnoreEntry::User(user.name)),
|
||||
Ok(None) => false,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to resolve uid {} for ignore list lookup: {}",
|
||||
uid,
|
||||
err
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_ignore_list() {
|
||||
let list = IgnoreList::parse(
|
||||
&["uid:1000", "user:alice", " user:bob "].join("\n"), // "\n# comment\nuid:1000\nuser:alice # trailing comment\n\nuser:bob\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(list.ignores_uid(1000));
|
||||
assert!(list.ignores_username("alice"));
|
||||
assert!(list.ignores_username("bob"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ignore_list_with_comments() {
|
||||
let list = IgnoreList::parse(
|
||||
&[
|
||||
"# This is a comment",
|
||||
"uid:1000",
|
||||
"user:alice # trailing comment",
|
||||
"",
|
||||
"user:bob",
|
||||
]
|
||||
.join("\n"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(list.ignores_uid(1000));
|
||||
assert!(list.ignores_username("alice"));
|
||||
assert!(list.ignores_username("bob"));
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,10 @@ use tokio::{
|
||||
time::{Duration as TokioDuration, interval},
|
||||
};
|
||||
|
||||
use crate::{proto::Whod, server::rwhod::rwhod_status::generate_rwhod_status_update};
|
||||
use crate::{
|
||||
proto::Whod,
|
||||
server::{ignore_list::IgnoreList, rwhod::rwhod_status::generate_rwhod_status_update},
|
||||
};
|
||||
|
||||
/// Default port for rwhod communication.
|
||||
pub const RWHOD_BROADCAST_PORT: u16 = 513;
|
||||
@@ -100,13 +103,14 @@ pub async fn send_rwhod_packet_to_interface(
|
||||
pub async fn rwhod_packet_sender_task(
|
||||
socket: Arc<UdpSocket>,
|
||||
interfaces: Vec<RwhodSendTarget>,
|
||||
ignore_list: Option<IgnoreList>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut interval = interval(TokioDuration::from_secs(60));
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let status_update = generate_rwhod_status_update()?;
|
||||
let status_update = generate_rwhod_status_update(ignore_list.as_ref())?;
|
||||
|
||||
tracing::debug!("Generated rwhod packet: {:?}", status_update);
|
||||
|
||||
|
||||
@@ -7,12 +7,21 @@ use nix::{
|
||||
};
|
||||
use uucore::utmpx::Utmpx;
|
||||
|
||||
use crate::proto::{WhodStatusUpdate, WhodUserEntry};
|
||||
use crate::{
|
||||
proto::{WhodStatusUpdate, WhodUserEntry},
|
||||
server::ignore_list::IgnoreList,
|
||||
};
|
||||
|
||||
/// Reads utmp entries to determine currently logged-in users.
|
||||
pub fn generate_rwhod_user_entries(now: DateTime<Utc>) -> anyhow::Result<Vec<WhodUserEntry>> {
|
||||
pub fn generate_rwhod_user_entries(
|
||||
now: DateTime<Utc>,
|
||||
ignore_list: Option<&IgnoreList>,
|
||||
) -> anyhow::Result<Vec<WhodUserEntry>> {
|
||||
Utmpx::iter_all_records()
|
||||
.filter(|entry| entry.is_user_process())
|
||||
.filter(|entry| {
|
||||
!ignore_list.is_some_and(|ignore_list| ignore_list.ignores_username(&entry.user()))
|
||||
})
|
||||
.map(|entry| {
|
||||
let login_time = entry
|
||||
.login_time()
|
||||
@@ -44,7 +53,9 @@ pub fn generate_rwhod_user_entries(now: DateTime<Utc>) -> anyhow::Result<Vec<Who
|
||||
}
|
||||
|
||||
/// Generate a rwhod status update packet representing the current system state.
|
||||
pub fn generate_rwhod_status_update() -> anyhow::Result<WhodStatusUpdate> {
|
||||
pub fn generate_rwhod_status_update(
|
||||
ignore_list: Option<&IgnoreList>,
|
||||
) -> anyhow::Result<WhodStatusUpdate> {
|
||||
let sysinfo = sysinfo().unwrap();
|
||||
let load_average = sysinfo.load_average();
|
||||
let uptime = sysinfo.uptime();
|
||||
@@ -61,7 +72,7 @@ pub fn generate_rwhod_status_update() -> anyhow::Result<WhodStatusUpdate> {
|
||||
(load_average.2 * 100.0).abs() as i32,
|
||||
),
|
||||
now - uptime,
|
||||
generate_rwhod_user_entries(now)?,
|
||||
generate_rwhod_user_entries(now, ignore_list)?,
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
@@ -73,7 +84,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generate_rwhod_status_update() {
|
||||
let status_update = generate_rwhod_status_update().unwrap();
|
||||
let status_update = generate_rwhod_status_update(None).unwrap();
|
||||
println!("{:?}", status_update);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::{
|
||||
proto::{WhodStatusUpdate, WhodUserEntry, finger_protocol::FingerResponseUserEntry},
|
||||
server::{
|
||||
fingerd::{self, FingerRequestInfo, FingerRequestNetworking, finger_utmp_users},
|
||||
ignore_list::IgnoreList,
|
||||
rwhod::RwhodStatusStore,
|
||||
},
|
||||
};
|
||||
@@ -135,11 +136,18 @@ pub enum VarlinkReplyError {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VarlinkRoowhoo2ClientServer {
|
||||
whod_status_store: RwhodStatusStore,
|
||||
finger_ignore_list: Option<IgnoreList>,
|
||||
}
|
||||
|
||||
impl VarlinkRoowhoo2ClientServer {
|
||||
pub fn new(whod_status_store: RwhodStatusStore) -> Self {
|
||||
Self { whod_status_store }
|
||||
pub fn new(
|
||||
whod_status_store: RwhodStatusStore,
|
||||
finger_ignore_list: Option<IgnoreList>,
|
||||
) -> Self {
|
||||
Self {
|
||||
whod_status_store,
|
||||
finger_ignore_list,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +202,15 @@ impl VarlinkRoowhoo2ClientServer {
|
||||
Some(usernames) => usernames
|
||||
.into_iter()
|
||||
.flat_map::<Vec<_>, _>(|username| {
|
||||
fingerd::search_for_user(&username, match_fullnames, &request_info)
|
||||
.into_iter()
|
||||
.map(|res| (username.clone(), res))
|
||||
.collect()
|
||||
fingerd::search_for_user(
|
||||
&username,
|
||||
match_fullnames,
|
||||
&request_info,
|
||||
self.finger_ignore_list.as_ref(),
|
||||
)
|
||||
.into_iter()
|
||||
.map(|res| (username.clone(), res))
|
||||
.collect()
|
||||
})
|
||||
.dedup_by(|a, b| match (&a.1, &b.1) {
|
||||
(Ok(user_a), Ok(user_b)) => user_a.username == user_b.username,
|
||||
@@ -217,7 +230,7 @@ impl VarlinkRoowhoo2ClientServer {
|
||||
.map(Box::new)
|
||||
.map(FingerResponseUserEntry::Structured)
|
||||
.collect(),
|
||||
None => finger_utmp_users(&request_info)
|
||||
None => finger_utmp_users(&request_info, self.finger_ignore_list.as_ref())
|
||||
.into_iter()
|
||||
.filter_map(|res| match res {
|
||||
Ok(user_info) => Some(user_info),
|
||||
@@ -346,8 +359,9 @@ impl zlink::Service<zlink::unix::Stream> for VarlinkRoowhoo2ClientServer {
|
||||
pub async fn varlink_client_server_task(
|
||||
socket: zlink::unix::Listener,
|
||||
whod_status_store: RwhodStatusStore,
|
||||
finger_ignore_list: Option<IgnoreList>,
|
||||
) -> anyhow::Result<()> {
|
||||
let service = VarlinkRoowhoo2ClientServer::new(whod_status_store);
|
||||
let service = VarlinkRoowhoo2ClientServer::new(whod_status_store, finger_ignore_list);
|
||||
|
||||
let server = zlink::Server::new(socket, service);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user