From 9361dcf941fabb14e94f472754b0e0a26cc56e13 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Fri, 13 Feb 2026 01:14:19 +0900 Subject: [PATCH] fingerd: read homedir files, read gecos fields --- Cargo.lock | 10 +++++ Cargo.toml | 1 + nix/module.nix | 1 + src/proto/finger_protocol.rs | 8 +--- src/server/fingerd.rs | 79 ++++++++++++++++++++++++++++-------- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0537202..46d208c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +[[package]] +name = "caps" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1ddba47aba30b6a889298ad0109c3b8dcb0e8fc993b459daa7067d46f865e0" +dependencies = [ + "libc", +] + [[package]] name = "cc" version = "1.2.52" @@ -858,6 +867,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", + "caps", "chrono", "clap", "clap_complete", diff --git a/Cargo.toml b/Cargo.toml index 762f05f..bf6c46e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ zlink = { version = "0.3.0", features = ["introspection"] } clap_complete = "4.5.65" itertools = "0.14.0" tokio-util = "0.7.18" +caps = "0.5.6" [features] default = ["systemd"] diff --git a/nix/module.nix b/nix/module.nix index 74143b1..14f5d59 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -112,6 +112,7 @@ in { "/etc" # NOTE: need logind socket for utmp entries "/run/systemd" + "/home" ]; UMask = "0077"; diff --git a/src/proto/finger_protocol.rs b/src/proto/finger_protocol.rs index 7fcb13d..a0a8358 100644 --- a/src/proto/finger_protocol.rs +++ b/src/proto/finger_protocol.rs @@ -587,13 +587,7 @@ impl FingerResponseUserSession { let messages_on = !line.ends_with("(messages off)"); - Ok(Self { - tty, - login_time, - idle_time, - host, - messages_on, - }) + Ok(Self::new(tty, login_time, idle_time, host, messages_on)) } } diff --git a/src/server/fingerd.rs b/src/server/fingerd.rs index dbab847..2131e0e 100644 --- a/src/server/fingerd.rs +++ b/src/server/fingerd.rs @@ -1,4 +1,8 @@ -use std::{net::hostname, path::Path}; +use std::{ + net::hostname, + os::unix::fs::{MetadataExt, PermissionsExt}, + path::Path, +}; use chrono::{DateTime, Duration, Timelike, Utc}; use nix::sys::stat::stat; @@ -6,6 +10,28 @@ use uucore::utmpx::Utmpx; use crate::proto::finger_protocol::{FingerResponseUserEntry, FingerResponseUserSession}; +fn read_file_content_if_exists(path: &Path) -> anyhow::Result> { + let file_is_readable = path.exists() + && path.is_file() + && (((path.metadata()?.permissions().mode() & 0o400 != 0 + && nix::unistd::geteuid().as_raw() == path.metadata()?.uid()) + || (path.metadata()?.permissions().mode() & 0o040 != 0 + && nix::unistd::getegid().as_raw() == path.metadata()?.gid()) + || (path.metadata()?.permissions().mode() & 0o004 != 0)) + || caps::has_cap( + None, + caps::CapSet::Effective, + caps::Capability::CAP_DAC_READ_SEARCH, + )?) + && path.metadata()?.len() > 0; + + if file_is_readable { + Ok(Some(std::fs::read_to_string(path)?.trim().to_string())) + } else { + Ok(None) + } +} + /// Retrieve local user information for the given username. /// /// Returns None if the user does not exist. @@ -32,12 +58,24 @@ pub fn get_local_user(username: &str) -> anyhow::Result = full_name.split(',').collect(); + + let office = gecos_fields.get(1).map(|s| s.to_string()); + let office_phone = gecos_fields.get(2).map(|s| s.to_string()); + let home_phone = gecos_fields.get(3).map(|s| s.to_string()); + let hostname = hostname()?.to_str().unwrap_or("localhost").to_string(); - let now = Utc::now().with_nanosecond(0).unwrap_or(Utc::now()); - let sessions: Vec = Utmpx::iter_all_records() + let mut utmpx_records = Utmpx::iter_all_records() .filter(|entry| entry.user() == username) .filter(|entry| entry.is_user_process()) + .peekable(); + + // TODO: Don't use utmp entries for this, read from lastlog instead + let user_never_logged_in = utmpx_records.peek().is_none(); + + let now = Utc::now().with_nanosecond(0).unwrap_or(Utc::now()); + let sessions: Vec = utmpx_records .filter_map(|entry| { let login_time = entry .login_time() @@ -74,23 +112,32 @@ pub fn get_local_user(username: &str) -> anyhow::Result 0 && forward_path.is_file() { - Some(std::fs::read_to_string(&forward_path)?.trim().to_string()) - } else { - None - }; + let forward = read_file_content_if_exists(&forward_path)?; + + let pgpkey_path = user_entry.dir.join(".pgpkey"); + let pgpkey = read_file_content_if_exists(&pgpkey_path)?; + + let project_path = user_entry.dir.join(".project"); + let project = read_file_content_if_exists(&project_path)?; let plan_path = user_entry.dir.join(".plan"); - let plan = if plan_path.exists() && plan_path.metadata()?.len() > 0 && plan_path.is_file() { - Some(std::fs::read_to_string(&plan_path)?.trim().to_string()) - } else { - None - }; + let plan = read_file_content_if_exists(&plan_path)?; Ok(Some(FingerResponseUserEntry::new( - username, full_name, home_dir, shell, None, None, None, false, sessions, forward, None, - None, None, plan, + username, + full_name, + home_dir, + shell, + office, + office_phone, + home_phone, + user_never_logged_in, + sessions, + forward, + None, + pgpkey, + project, + plan, ))) }