From e741dfd3c1241737c301a3ae201587d354c2c105 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Tue, 28 Apr 2026 21:08:28 +0900 Subject: [PATCH] fingerd: don't nest utmp entry requests --- src/server/fingerd.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/server/fingerd.rs b/src/server/fingerd.rs index 5b0bc4a..4d3a688 100644 --- a/src/server/fingerd.rs +++ b/src/server/fingerd.rs @@ -9,7 +9,7 @@ use itertools::Itertools; use nix::sys::stat::stat; use serde::{Deserialize, Serialize}; use users::all_users; -use uucore::utmpx::Utmpx; +use uucore::utmpx::{Utmpx, UtmpxRecord}; use crate::proto::finger_protocol::{FingerResponseUserEntry, FingerResponseUserSession}; @@ -60,7 +60,7 @@ pub fn search_for_user( 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) { + match get_local_user(&username, None) { Ok(Some(user_entry)) => Some(Ok(user_entry)), Ok(None) => None, // User exists but has .nofinger, skip Err(err) => Some(Err(err)), @@ -78,9 +78,14 @@ pub fn finger_utmp_users( ) -> Vec> { Utmpx::iter_all_records() .filter(|entry| entry.is_user_process()) - .map(|entry| entry.user()) - .dedup() - .flat_map(|username| get_local_user(&username).transpose()) + .into_group_map_by(|entry| entry.user()) + .into_iter() + .map(|(username, records)| get_local_user(&username, Some(records))) + .filter_map(|result| match result { + Ok(Some(user_entry)) => Some(Ok(user_entry)), + Ok(None) => None, // User has .nofinger, skip + Err(err) => Some(Err(err)), + }) .collect() } @@ -111,7 +116,14 @@ fn read_file_content_if_exists(path: &Path) -> anyhow::Result> { /// Retrieve local user information for the given username. /// /// Returns None if the user does not exist. -fn get_local_user(username: &str) -> anyhow::Result> { +fn get_local_user( + username: &str, + utmp_records: Option>, +) -> anyhow::Result> { + tracing::trace!( + "Retrieving local user information for username: {}", + username + ); let username = username.to_string(); let user_entry = match nix::unistd::User::from_name(&username) { Ok(Some(user)) => user, @@ -142,16 +154,20 @@ fn get_local_user(username: &str) -> anyhow::Result records, + None => Utmpx::iter_all_records() + .filter(|entry| entry.user() == username) + .filter(|entry| entry.is_user_process()) + .collect::>(), + }; // TODO: Don't use utmp entries for this, read from lastlog instead - let user_never_logged_in = utmpx_records.peek().is_none(); + let user_never_logged_in = utmpx_records.is_empty(); let now = Utc::now().with_nanosecond(0).unwrap_or(Utc::now()); let sessions: Vec = utmpx_records + .into_iter() .filter_map(|entry| { let login_time = entry .login_time() @@ -257,7 +273,7 @@ mod tests { #[test] fn test_finger_root() { - let user_entry = get_local_user("root").unwrap().unwrap(); + let user_entry = get_local_user("root", None).unwrap().unwrap(); assert_eq!(user_entry.username, "root"); }