Files
roowho2/src/server/varlink_api.rs
T
oysteikt be07298867
Build and test / check (push) Successful in 1m34s
Build and test / build (push) Successful in 1m41s
Build and test / test (push) Successful in 2m4s
Build and test / docs (push) Successful in 3m19s
fingerd: add raw user response variant
2026-04-29 06:22:20 +09:00

349 lines
11 KiB
Rust

use std::{os::fd::OwnedFd, time::Duration};
use anyhow::Context;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use tokio::time::timeout;
use zlink::{ReplyError, service::MethodReply};
use crate::{
proto::{WhodStatusUpdate, WhodUserEntry, finger_protocol::FingerResponseUserEntry},
server::{
fingerd::{self, FingerRequestInfo, FingerRequestNetworking, finger_utmp_users},
rwhod::RwhodStatusStore,
},
};
// Types for 'no.ntnu.pvv.roowho2.rwhod'
#[zlink::proxy("no.ntnu.pvv.roowho2.rwhod")]
pub trait VarlinkRwhodClientProxy {
async fn rwho(
&mut self,
all: bool,
) -> zlink::Result<Result<VarlinkRwhoResponse, VarlinkRwhodClientError>>;
async fn ruptime(
&mut self,
) -> zlink::Result<Result<VarlinkRuptimeResponse, VarlinkRwhodClientError>>;
}
#[derive(Debug, Deserialize)]
#[serde(tag = "method", content = "parameters")]
pub enum VarlinkRwhodClientRequest {
#[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Rwho")]
Rwho {
/// Retrieve all users, even those that have been idle for a long time.
all: bool,
},
#[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Ruptime")]
Ruptime,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
pub enum VarlinkRwhodClientResponse {
Rwho(VarlinkRwhoResponse),
Ruptime(VarlinkRuptimeResponse),
}
pub type VarlinkRwhoResponse = Vec<(String, WhodUserEntry)>;
pub type VarlinkRuptimeResponse = Vec<WhodStatusUpdate>;
#[derive(Debug, Clone, PartialEq, ReplyError)]
#[zlink(interface = "no.ntnu.pvv.roowho2.rwhod")]
pub enum VarlinkRwhodClientError {
InvalidRequest,
TimedOut,
}
// Types for 'no.ntnu.pvv.roowho2.finger'
#[zlink::proxy("no.ntnu.pvv.roowho2.finger")]
pub trait VarlinkFingerClientProxy {
async fn finger(
&mut self,
user_queries: Option<Vec<String>>,
match_fullnames: bool,
request_info: FingerRequestInfo,
request_networking: FingerRequestNetworking,
disable_user_account_db: bool,
raw_remote_output: bool,
) -> zlink::Result<Result<VarlinkFingerResponse, VarlinkFingerClientError>>;
}
#[derive(Debug, Deserialize)]
#[serde(tag = "method", content = "parameters")]
pub enum VarlinkFingerClientRequest {
#[serde(rename = "no.ntnu.pvv.roowho2.finger.Finger")]
Finger {
user_queries: Option<Vec<String>>,
match_fullnames: bool,
request_info: FingerRequestInfo,
request_networking: FingerRequestNetworking,
disable_user_account_db: bool,
raw_remote_output: bool,
},
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum VarlinkFingerClientResponse {
Finger(VarlinkFingerResponse),
}
pub type VarlinkFingerResponse = Vec<FingerResponseUserEntry>;
#[derive(Debug, Clone, PartialEq, ReplyError)]
#[zlink(interface = "no.ntnu.pvv.roowho2.finger")]
pub enum VarlinkFingerClientError {
InvalidRequest,
TimedOut,
}
// --------------------
#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[allow(unused)]
pub enum VarlinkMethod {
Rwhod(VarlinkRwhodClientRequest),
Finger(VarlinkFingerClientRequest),
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
#[allow(unused)]
pub enum VarlinkReply {
Rwhod(VarlinkRwhodClientResponse),
Finger(VarlinkFingerClientResponse),
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(untagged)]
#[allow(unused)]
pub enum VarlinkReplyError {
Rwhod(VarlinkRwhodClientError),
Finger(VarlinkFingerClientError),
}
#[derive(Debug, Clone)]
pub struct VarlinkRoowhoo2ClientServer {
whod_status_store: RwhodStatusStore,
}
impl VarlinkRoowhoo2ClientServer {
pub fn new(whod_status_store: RwhodStatusStore) -> Self {
Self { whod_status_store }
}
}
impl VarlinkRoowhoo2ClientServer {
// TODO: handle 'all' parameter
async fn handle_rwho_request(&self, _all: bool) -> VarlinkRwhoResponse {
tracing::debug!(all = _all, "Handling Rwho request");
let store = self.whod_status_store.read().await;
let mut all_user_entries = Vec::with_capacity(store.len());
for status_update in store.values() {
all_user_entries.extend_from_slice(
&status_update
.users
.iter()
.map(|user| (status_update.hostname.clone(), user.clone()))
.collect::<Vec<(String, WhodUserEntry)>>(),
);
}
all_user_entries
}
async fn handle_ruptime_request(&self) -> VarlinkRuptimeResponse {
tracing::debug!("Handling Ruptime request");
let store = self.whod_status_store.read().await;
store.values().cloned().collect()
}
async fn handle_finger_request(
&self,
user_queries: Option<Vec<String>>,
match_fullnames: bool,
request_info: FingerRequestInfo,
_request_networking: FingerRequestNetworking,
_disable_user_account_db: bool,
_raw_remote_output: bool,
) -> VarlinkFingerResponse {
tracing::debug!(
user_queries = ?user_queries,
match_fullnames = match_fullnames,
request_info = ?request_info,
"Handling Finger request",
);
match user_queries {
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()
})
.dedup_by(|a, b| match (&a.1, &b.1) {
(Ok(user_a), Ok(user_b)) => user_a.username == user_b.username,
_ => false,
})
.filter_map(|(username, user)| match user {
Ok(user_info) => Some(user_info),
Err(err) => {
tracing::error!(
"Error retrieving local user information for '{}': {}",
username,
err
);
None
}
})
.map(Box::new)
.map(FingerResponseUserEntry::Structured)
.collect(),
None => finger_utmp_users(&request_info)
.into_iter()
.filter_map(|res| match res {
Ok(user_info) => Some(user_info),
Err(err) => {
tracing::error!("Error retrieving local user information: {}", err);
None
}
})
.map(Box::new)
.map(FingerResponseUserEntry::Structured)
.collect(),
}
}
}
impl zlink::Service<zlink::unix::Stream> for VarlinkRoowhoo2ClientServer {
type MethodCall<'de> = VarlinkMethod;
type ReplyParams<'se> = VarlinkReply;
type ReplyStreamParams = ();
type ReplyStream = futures_util::stream::Empty<(zlink::Reply<()>, Vec<OwnedFd>)>;
type ReplyError<'se> = VarlinkReplyError;
async fn handle<'service>(
&'service mut self,
call: &'service zlink::Call<Self::MethodCall<'_>>,
_conn: &mut zlink::Connection<zlink::unix::Stream>,
_fds: Vec<std::os::fd::OwnedFd>,
) -> zlink::service::HandleResult<
Self::ReplyParams<'service>,
Self::ReplyStream,
Self::ReplyError<'service>,
> {
match call.method() {
VarlinkMethod::Rwhod(VarlinkRwhodClientRequest::Rwho { all }) => {
let result =
match timeout(Duration::from_secs(2), self.handle_rwho_request(*all)).await {
Ok(response) => response,
Err(_) => {
tracing::error!("Rwho request timed out after 2 seconds");
return (
MethodReply::Error(VarlinkReplyError::Rwhod(
VarlinkRwhodClientError::TimedOut,
)),
Default::default(),
);
}
};
(
MethodReply::Single(Some(VarlinkReply::Rwhod(
VarlinkRwhodClientResponse::Rwho(result),
))),
Default::default(),
)
}
VarlinkMethod::Rwhod(VarlinkRwhodClientRequest::Ruptime) => {
let result =
match timeout(Duration::from_secs(2), self.handle_ruptime_request()).await {
Ok(response) => response,
Err(_) => {
tracing::error!("Ruptime request timed out after 2 seconds");
return (
MethodReply::Error(VarlinkReplyError::Rwhod(
VarlinkRwhodClientError::TimedOut,
)),
Default::default(),
);
}
};
(
MethodReply::Single(Some(VarlinkReply::Rwhod(
VarlinkRwhodClientResponse::Ruptime(result),
))),
Default::default(),
)
}
VarlinkMethod::Finger(VarlinkFingerClientRequest::Finger {
user_queries,
match_fullnames,
request_info,
request_networking,
disable_user_account_db,
raw_remote_output,
}) => {
let result = match timeout(
Duration::from_secs(2),
self.handle_finger_request(
user_queries.clone(),
*match_fullnames,
request_info.clone(),
request_networking.clone(),
*disable_user_account_db,
*raw_remote_output,
),
)
.await
{
Ok(response) => response,
Err(_) => {
tracing::error!("Finger request timed out after 2 seconds");
return (
MethodReply::Error(VarlinkReplyError::Finger(
VarlinkFingerClientError::TimedOut,
)),
Default::default(),
);
}
};
(
MethodReply::Single(Some(VarlinkReply::Finger(
VarlinkFingerClientResponse::Finger(result),
))),
Default::default(),
)
}
}
}
}
pub async fn varlink_client_server_task(
socket: zlink::unix::Listener,
whod_status_store: RwhodStatusStore,
) -> anyhow::Result<()> {
let service = VarlinkRoowhoo2ClientServer::new(whod_status_store);
let server = zlink::Server::new(socket, service);
tracing::info!("Starting Rwhod client API server");
server
.run()
.await
.context("Rwhod client API server failed")?;
Ok(())
}