server/varlink_api: split into multiple files
This commit is contained in:
+13
-348
@@ -1,255 +1,24 @@
|
||||
use std::{collections::HashMap, os::fd::OwnedFd, time::Duration};
|
||||
mod fingerd;
|
||||
mod rwhod;
|
||||
mod walld;
|
||||
|
||||
use std::{os::fd::OwnedFd, time::Duration};
|
||||
|
||||
use anyhow::Context;
|
||||
use futures_util::stream;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::timeout;
|
||||
use zlink::{
|
||||
ReplyError,
|
||||
service::MethodReply,
|
||||
tokio::unix::{Listener as UnixListener, Stream as UnixStream},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
proto::{WhodStatusUpdate, WhodUserEntry, finger_protocol::FingerResponseUserEntry},
|
||||
server::{
|
||||
fingerd::{self, FingerRequestInfo, FingerRequestNetworking, finger_utmp_users},
|
||||
ignore_list::IgnoreList,
|
||||
rwhod::RwhodStatusStore,
|
||||
},
|
||||
};
|
||||
use crate::server::{ignore_list::IgnoreList, rwhod::RwhodStatusStore};
|
||||
|
||||
pub use crate::server::varlink_api::{fingerd::*, rwhod::*, walld::*};
|
||||
|
||||
pub const DEFAULT_CLIENT_SERVER_SOCKET_PATH: &str = "/run/roowho2/roowho2.varlink";
|
||||
|
||||
// Types for 'no.ntnu.pvv.roowho2.rwhod'
|
||||
|
||||
#[zlink::proxy("no.ntnu.pvv.roowho2.rwhod")]
|
||||
pub trait VarlinkRwhodClientProxy {
|
||||
/// `max_idle_seconds` is the maximum idle time (in seconds) for a user
|
||||
/// to be included in the response. `None` means no limit (i.e. the old
|
||||
/// `all: true`). The client is expected to always send an explicit
|
||||
/// value, defaulting to whatever it considers reasonable if the user
|
||||
/// didn't ask for a specific limit.
|
||||
async fn rwho(
|
||||
&mut self,
|
||||
max_idle_seconds: Option<i64>,
|
||||
) -> zlink::Result<Result<VarlinkRwhoResponse, VarlinkRwhodClientError>>;
|
||||
|
||||
/// See [`VarlinkRwhodClientProxy::rwho`] for the meaning of `max_idle_seconds`.
|
||||
async fn ruptime(
|
||||
&mut self,
|
||||
max_idle_seconds: Option<i64>,
|
||||
) -> 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 {
|
||||
/// Maximum idle time (in seconds) for a user to be included.
|
||||
/// `None` means no limit (i.e. return all users).
|
||||
max_idle_seconds: Option<i64>,
|
||||
},
|
||||
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Ruptime")]
|
||||
Ruptime {
|
||||
/// Maximum idle time (in seconds) for a user to be counted.
|
||||
/// `None` means no limit (i.e. return all users).
|
||||
max_idle_seconds: Option<i64>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VarlinkRwhodClientResponse {
|
||||
Rwho(VarlinkRwhoResponse),
|
||||
Ruptime(VarlinkRuptimeResponse),
|
||||
}
|
||||
|
||||
pub type VarlinkRwhoResponse = HashMap<String, Vec<WhodUserEntry>>;
|
||||
pub type VarlinkRuptimeResponse = Vec<WhodStatusUpdate>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, ReplyError)]
|
||||
#[zlink(interface = "no.ntnu.pvv.roowho2.rwhod")]
|
||||
pub enum VarlinkRwhodClientError {
|
||||
InvalidRequest,
|
||||
TimedOut,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
// 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,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
// Types for 'no.ntnu.pvv.roowho2.walld'
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "method", content = "parameters")]
|
||||
pub enum VarlinkWalldClientRequest {
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Wall")]
|
||||
Wall {
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
timeout_secs: u32,
|
||||
},
|
||||
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Write")]
|
||||
Write {
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[zlink::proxy("no.ntnu.pvv.roowho2.walld")]
|
||||
pub trait VarlinkWalldClientProxy {
|
||||
/// Broadcast `message` to the tty of every logged in user, optionally restricted to `group`.
|
||||
async fn wall(
|
||||
&mut self,
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
timeout_secs: u32,
|
||||
) -> zlink::Result<Result<VarlinkWalldClientResponse, VarlinkWalldClientError>>;
|
||||
|
||||
/// Send `message` to a the tty of a single `user`, optionally targeting a specific `tty`.
|
||||
async fn write(
|
||||
&mut self,
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
) -> zlink::Result<Result<VarlinkWalldClientResponse, VarlinkWalldClientError>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VarlinkWalldClientResponse {
|
||||
Wall(VarlinkWallResponse),
|
||||
Write(VarlinkWriteResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct VarlinkWallResponse {
|
||||
pub delivered: Vec<VarlinkWallDelivery>,
|
||||
pub failures: Vec<VarlinkWallDeliveryFailure>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct VarlinkWallDelivery {
|
||||
pub user: String,
|
||||
pub tty: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct VarlinkWallDeliveryFailure {
|
||||
pub user: String,
|
||||
pub tty: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct VarlinkWriteResponse {
|
||||
/// The tty the message was actually delivered to.
|
||||
/// This is relevant when the caller didn't specify one, and it was picked automatically.
|
||||
pub tty: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, ReplyError)]
|
||||
#[zlink(interface = "no.ntnu.pvv.roowho2.walld")]
|
||||
pub enum VarlinkWalldClientError {
|
||||
/// The walld service is not enabled on the server.
|
||||
Disabled,
|
||||
/// Caller was not authorized by polkit to perform the request.
|
||||
NotAuthorized,
|
||||
/// The target user is not logged in anywhere.
|
||||
UserNotLoggedIn { user: String },
|
||||
/// The target user has disabled incoming messages on the relevant tty/ttys.
|
||||
MessagesDisabled { user: String },
|
||||
/// The requested tty does not belong to the target user, or does not exist.
|
||||
TtyNotFound { user: String, tty: String },
|
||||
/// The request parameters were invalid (e.g. unknown group).
|
||||
InvalidRequest { message: String },
|
||||
/// The request timed out.
|
||||
TimedOut,
|
||||
/// Something went wrong while talking to the tty (open/write failure).
|
||||
Io { message: String },
|
||||
}
|
||||
|
||||
// TODO: use `thiserror` to derive `Display` the impl instead.
|
||||
|
||||
impl std::fmt::Display for VarlinkWalldClientError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VarlinkWalldClientError::Disabled => {
|
||||
write!(f, "The walld service is disabled on the server")
|
||||
}
|
||||
VarlinkWalldClientError::NotAuthorized => write!(f, "Caller is not authorized"),
|
||||
VarlinkWalldClientError::UserNotLoggedIn { user } => {
|
||||
write!(f, "User '{}' is not logged in", user)
|
||||
}
|
||||
VarlinkWalldClientError::MessagesDisabled { user } => {
|
||||
write!(f, "User '{}' has disabled incoming messages", user)
|
||||
}
|
||||
VarlinkWalldClientError::TtyNotFound { user, tty } => {
|
||||
write!(f, "Tty '{}' not found for user '{}'", tty, user)
|
||||
}
|
||||
VarlinkWalldClientError::InvalidRequest { message } => {
|
||||
write!(f, "Invalid request: {}", message)
|
||||
}
|
||||
VarlinkWalldClientError::TimedOut => write!(f, "Request timed out"),
|
||||
VarlinkWalldClientError::Io { message } => {
|
||||
write!(f, "I/O error while sending message: {}", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(unused)]
|
||||
@@ -299,112 +68,7 @@ impl VarlinkRoowhoo2ClientServer {
|
||||
}
|
||||
}
|
||||
|
||||
impl VarlinkRoowhoo2ClientServer {
|
||||
async fn handle_rwho_request(
|
||||
&self,
|
||||
max_idle_time: Option<chrono::TimeDelta>,
|
||||
) -> VarlinkRwhoResponse {
|
||||
tracing::debug!(?max_idle_time, "Handling Rwho request");
|
||||
let store = self.whod_status_store.read().await;
|
||||
|
||||
store
|
||||
.values()
|
||||
.filter_map(|status_update| {
|
||||
let users: Vec<WhodUserEntry> = status_update
|
||||
.users
|
||||
.iter()
|
||||
.filter(|user| max_idle_time.is_none_or(|max| user.idle_time < max))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
(!users.is_empty()).then(|| (status_update.hostname.clone(), users))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn handle_ruptime_request(
|
||||
&self,
|
||||
max_idle_time: Option<chrono::TimeDelta>,
|
||||
) -> VarlinkRuptimeResponse {
|
||||
tracing::debug!(?max_idle_time, "Handling Ruptime request");
|
||||
let store = self.whod_status_store.read().await;
|
||||
|
||||
store
|
||||
.values()
|
||||
.cloned()
|
||||
.map(|mut status_update| {
|
||||
if let Some(max_idle_time) = max_idle_time {
|
||||
status_update
|
||||
.users
|
||||
.retain(|user| user.idle_time < max_idle_time);
|
||||
}
|
||||
status_update
|
||||
})
|
||||
.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,
|
||||
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,
|
||||
_ => 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, self.finger_ignore_list.as_ref())
|
||||
.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 VarlinkRoowhoo2ClientServer {}
|
||||
|
||||
impl zlink::Service<UnixStream> for VarlinkRoowhoo2ClientServer {
|
||||
type MethodCall<'de> = VarlinkMethod;
|
||||
@@ -459,7 +123,7 @@ impl zlink::Service<UnixStream> for VarlinkRoowhoo2ClientServer {
|
||||
|
||||
let result = match timeout(
|
||||
Duration::from_secs(2),
|
||||
self.handle_rwho_request(max_idle_time),
|
||||
handle_rwho_request(&self.whod_status_store, max_idle_time),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -513,7 +177,7 @@ impl zlink::Service<UnixStream> for VarlinkRoowhoo2ClientServer {
|
||||
|
||||
let result = match timeout(
|
||||
Duration::from_secs(2),
|
||||
self.handle_ruptime_request(max_idle_time),
|
||||
handle_ruptime_request(&self.whod_status_store, max_idle_time),
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -555,7 +219,8 @@ impl zlink::Service<UnixStream> for VarlinkRoowhoo2ClientServer {
|
||||
|
||||
let result = match timeout(
|
||||
Duration::from_secs(2),
|
||||
self.handle_finger_request(
|
||||
handle_finger_request(
|
||||
&self.finger_ignore_list,
|
||||
user_queries.clone(),
|
||||
*match_fullnames,
|
||||
request_info.clone(),
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zlink::ReplyError;
|
||||
|
||||
use crate::{
|
||||
proto::finger_protocol::FingerResponseUserEntry,
|
||||
server::{
|
||||
fingerd::{self, FingerRequestInfo, FingerRequestNetworking, finger_utmp_users},
|
||||
ignore_list::IgnoreList,
|
||||
},
|
||||
};
|
||||
|
||||
#[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,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub async fn handle_finger_request(
|
||||
finger_ignore_list: &Option<IgnoreList>,
|
||||
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,
|
||||
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,
|
||||
_ => 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, finger_ignore_list.as_ref())
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zlink::ReplyError;
|
||||
|
||||
use crate::{
|
||||
proto::{WhodStatusUpdate, WhodUserEntry},
|
||||
server::rwhod::RwhodStatusStore,
|
||||
};
|
||||
|
||||
#[zlink::proxy("no.ntnu.pvv.roowho2.rwhod")]
|
||||
pub trait VarlinkRwhodClientProxy {
|
||||
/// `max_idle_seconds` is the maximum idle time (in seconds) for a user
|
||||
/// to be included in the response. `None` means no limit (i.e. the old
|
||||
/// `all: true`). The client is expected to always send an explicit
|
||||
/// value, defaulting to whatever it considers reasonable if the user
|
||||
/// didn't ask for a specific limit.
|
||||
async fn rwho(
|
||||
&mut self,
|
||||
max_idle_seconds: Option<i64>,
|
||||
) -> zlink::Result<Result<VarlinkRwhoResponse, VarlinkRwhodClientError>>;
|
||||
|
||||
/// See [`VarlinkRwhodClientProxy::rwho`] for the meaning of `max_idle_seconds`.
|
||||
async fn ruptime(
|
||||
&mut self,
|
||||
max_idle_seconds: Option<i64>,
|
||||
) -> 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 {
|
||||
/// Maximum idle time (in seconds) for a user to be included.
|
||||
/// `None` means no limit (i.e. return all users).
|
||||
max_idle_seconds: Option<i64>,
|
||||
},
|
||||
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Ruptime")]
|
||||
Ruptime {
|
||||
/// Maximum idle time (in seconds) for a user to be counted.
|
||||
/// `None` means no limit (i.e. return all users).
|
||||
max_idle_seconds: Option<i64>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VarlinkRwhodClientResponse {
|
||||
Rwho(VarlinkRwhoResponse),
|
||||
Ruptime(VarlinkRuptimeResponse),
|
||||
}
|
||||
|
||||
pub type VarlinkRwhoResponse = HashMap<String, Vec<WhodUserEntry>>;
|
||||
pub type VarlinkRuptimeResponse = Vec<WhodStatusUpdate>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, ReplyError)]
|
||||
#[zlink(interface = "no.ntnu.pvv.roowho2.rwhod")]
|
||||
pub enum VarlinkRwhodClientError {
|
||||
InvalidRequest,
|
||||
TimedOut,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub async fn handle_rwho_request(
|
||||
whod_status_store: &RwhodStatusStore,
|
||||
max_idle_time: Option<chrono::TimeDelta>,
|
||||
) -> VarlinkRwhoResponse {
|
||||
tracing::debug!(?max_idle_time, "Handling Rwho request");
|
||||
let store = whod_status_store.read().await;
|
||||
|
||||
store
|
||||
.values()
|
||||
.filter_map(|status_update| {
|
||||
let users: Vec<WhodUserEntry> = status_update
|
||||
.users
|
||||
.iter()
|
||||
.filter(|user| max_idle_time.is_none_or(|max| user.idle_time < max))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
(!users.is_empty()).then(|| (status_update.hostname.clone(), users))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn handle_ruptime_request(
|
||||
whod_status_store: &RwhodStatusStore,
|
||||
max_idle_time: Option<chrono::TimeDelta>,
|
||||
) -> VarlinkRuptimeResponse {
|
||||
tracing::debug!(?max_idle_time, "Handling Ruptime request");
|
||||
let store = whod_status_store.read().await;
|
||||
|
||||
store
|
||||
.values()
|
||||
.cloned()
|
||||
.map(|mut status_update| {
|
||||
if let Some(max_idle_time) = max_idle_time {
|
||||
status_update
|
||||
.users
|
||||
.retain(|user| user.idle_time < max_idle_time);
|
||||
}
|
||||
status_update
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zlink::ReplyError;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "method", content = "parameters")]
|
||||
pub enum VarlinkWalldClientRequest {
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Wall")]
|
||||
Wall {
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
timeout_secs: u32,
|
||||
},
|
||||
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Write")]
|
||||
Write {
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[zlink::proxy("no.ntnu.pvv.roowho2.walld")]
|
||||
pub trait VarlinkWalldClientProxy {
|
||||
/// Broadcast `message` to the tty of every logged in user, optionally restricted to `group`.
|
||||
async fn wall(
|
||||
&mut self,
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
timeout_secs: u32,
|
||||
) -> zlink::Result<Result<VarlinkWalldClientResponse, VarlinkWalldClientError>>;
|
||||
|
||||
/// Send `message` to a the tty of a single `user`, optionally targeting a specific `tty`.
|
||||
async fn write(
|
||||
&mut self,
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
) -> zlink::Result<Result<VarlinkWalldClientResponse, VarlinkWalldClientError>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VarlinkWalldClientResponse {
|
||||
Wall(VarlinkWallResponse),
|
||||
Write(VarlinkWriteResponse),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct VarlinkWallResponse {
|
||||
pub delivered: Vec<VarlinkWallDelivery>,
|
||||
pub failures: Vec<VarlinkWallDeliveryFailure>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct VarlinkWallDelivery {
|
||||
pub user: String,
|
||||
pub tty: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct VarlinkWallDeliveryFailure {
|
||||
pub user: String,
|
||||
pub tty: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct VarlinkWriteResponse {
|
||||
/// The tty the message was actually delivered to.
|
||||
/// This is relevant when the caller didn't specify one, and it was picked automatically.
|
||||
pub tty: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, ReplyError)]
|
||||
#[zlink(interface = "no.ntnu.pvv.roowho2.walld")]
|
||||
pub enum VarlinkWalldClientError {
|
||||
/// The walld service is not enabled on the server.
|
||||
Disabled,
|
||||
/// Caller was not authorized by polkit to perform the request.
|
||||
NotAuthorized,
|
||||
/// The target user is not logged in anywhere.
|
||||
UserNotLoggedIn { user: String },
|
||||
/// The target user has disabled incoming messages on the relevant tty/ttys.
|
||||
MessagesDisabled { user: String },
|
||||
/// The requested tty does not belong to the target user, or does not exist.
|
||||
TtyNotFound { user: String, tty: String },
|
||||
/// The request parameters were invalid (e.g. unknown group).
|
||||
InvalidRequest { message: String },
|
||||
/// The request timed out.
|
||||
TimedOut,
|
||||
/// Something went wrong while talking to the tty (open/write failure).
|
||||
Io { message: String },
|
||||
}
|
||||
|
||||
// TODO: use `thiserror` to derive `Display` the impl instead.
|
||||
|
||||
impl std::fmt::Display for VarlinkWalldClientError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VarlinkWalldClientError::Disabled => {
|
||||
write!(f, "The walld service is disabled on the server")
|
||||
}
|
||||
VarlinkWalldClientError::NotAuthorized => write!(f, "Caller is not authorized"),
|
||||
VarlinkWalldClientError::UserNotLoggedIn { user } => {
|
||||
write!(f, "User '{}' is not logged in", user)
|
||||
}
|
||||
VarlinkWalldClientError::MessagesDisabled { user } => {
|
||||
write!(f, "User '{}' has disabled incoming messages", user)
|
||||
}
|
||||
VarlinkWalldClientError::TtyNotFound { user, tty } => {
|
||||
write!(f, "Tty '{}' not found for user '{}'", tty, user)
|
||||
}
|
||||
VarlinkWalldClientError::InvalidRequest { message } => {
|
||||
write!(f, "Invalid request: {}", message)
|
||||
}
|
||||
VarlinkWalldClientError::TimedOut => write!(f, "Request timed out"),
|
||||
VarlinkWalldClientError::Io { message } => {
|
||||
write!(f, "I/O error while sending message: {}", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user