server/walld: init
This commit is contained in:
+174
-22
@@ -1,11 +1,39 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use nix::unistd::gethostname;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zlink::ReplyError;
|
||||
|
||||
use crate::server::walld::tty_utils::{self, TtyError};
|
||||
|
||||
#[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,
|
||||
source_tty: Option<String>,
|
||||
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,
|
||||
source_tty: Option<String>,
|
||||
target_user: String,
|
||||
target_tty: Option<String>,
|
||||
message: String,
|
||||
) -> zlink::Result<Result<VarlinkWalldClientResponse, VarlinkWalldClientError>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "method", content = "parameters")]
|
||||
pub enum VarlinkWalldClientRequest {
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Wall")]
|
||||
Wall {
|
||||
source_tty: Option<String>,
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
@@ -14,32 +42,13 @@ pub enum VarlinkWalldClientRequest {
|
||||
|
||||
#[serde(rename = "no.ntnu.pvv.roowho2.walld.Write")]
|
||||
Write {
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
source_tty: Option<String>,
|
||||
target_user: String,
|
||||
target_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 {
|
||||
@@ -122,3 +131,146 @@ impl std::fmt::Display for VarlinkWalldClientError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn username_for_uid(uid: u32) -> String {
|
||||
users::get_user_by_uid(uid)
|
||||
.map(|u| u.name().to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| uid.to_string())
|
||||
}
|
||||
|
||||
fn hostname() -> String {
|
||||
gethostname()
|
||||
.ok()
|
||||
.and_then(|h| h.into_string().ok())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
fn tty_error_to_walld_error(user: &str, tty: &str, err: TtyError) -> VarlinkWalldClientError {
|
||||
match err {
|
||||
TtyError::NotFound(_) | TtyError::InvalidName(_) | TtyError::NotCharacterDevice(_) => {
|
||||
VarlinkWalldClientError::TtyNotFound {
|
||||
user: user.to_string(),
|
||||
tty: tty.to_string(),
|
||||
}
|
||||
}
|
||||
TtyError::Unavailable(_) => VarlinkWalldClientError::MessagesDisabled {
|
||||
user: user.to_string(),
|
||||
},
|
||||
other => VarlinkWalldClientError::Io {
|
||||
message: other.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: have the client send the name of the TTY
|
||||
// verify that the TTY is owned by the user from peercred.
|
||||
|
||||
pub async fn handle_wall_request(
|
||||
_source_pid: u32,
|
||||
source_uid: u32,
|
||||
source_tty: Option<String>,
|
||||
message: String,
|
||||
group: Option<String>,
|
||||
nobanner: bool,
|
||||
timeout_secs: u32,
|
||||
) -> Result<VarlinkWallResponse, VarlinkWalldClientError> {
|
||||
if let Some(group) = &group
|
||||
&& users::get_group_by_name(group).is_none()
|
||||
{
|
||||
return Err(VarlinkWalldClientError::InvalidRequest {
|
||||
message: format!("unknown group {group:?}"),
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: authorize against polkit
|
||||
|
||||
let body = tty_utils::format_wall_message(
|
||||
&username_for_uid(source_uid),
|
||||
&hostname(),
|
||||
source_tty.as_deref(),
|
||||
&message,
|
||||
nobanner,
|
||||
);
|
||||
|
||||
let timeout = Duration::from_secs(timeout_secs.max(1) as u64);
|
||||
let sessions = tty_utils::all_sessions(group.as_deref());
|
||||
|
||||
let mut response = VarlinkWallResponse::default();
|
||||
for session in sessions {
|
||||
match tty_utils::deliver_message(&session.tty, &body, timeout).await {
|
||||
Ok(()) => response.delivered.push(VarlinkWallDelivery {
|
||||
user: session.user,
|
||||
tty: session.tty,
|
||||
}),
|
||||
Err(err) => response.failures.push(VarlinkWallDeliveryFailure {
|
||||
user: session.user,
|
||||
tty: session.tty,
|
||||
reason: err.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
const DEFAULT_WRITE_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub async fn handle_write_request(
|
||||
_source_pid: u32,
|
||||
source_uid: u32,
|
||||
source_tty: Option<String>,
|
||||
user: String,
|
||||
tty: Option<String>,
|
||||
message: String,
|
||||
) -> Result<VarlinkWriteResponse, VarlinkWalldClientError> {
|
||||
// TODO: authorize against polkit
|
||||
|
||||
let sessions = tty_utils::sessions_for_user(&user);
|
||||
if sessions.is_empty() {
|
||||
return Err(VarlinkWalldClientError::UserNotLoggedIn { user });
|
||||
}
|
||||
|
||||
let target_tty = match &tty {
|
||||
Some(tty) => {
|
||||
if !sessions.iter().any(|s| &s.tty == tty) {
|
||||
return Err(VarlinkWalldClientError::TtyNotFound {
|
||||
user,
|
||||
tty: tty.clone(),
|
||||
});
|
||||
}
|
||||
tty.clone()
|
||||
}
|
||||
// No tty requested, pick the one the user touched most recently
|
||||
None => sessions
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
tty_utils::stat_tty(&s.tty)
|
||||
.ok()
|
||||
.map(|info| (s.tty.clone(), info))
|
||||
})
|
||||
.max_by_key(|(_, info)| info.atime)
|
||||
.map(|(tty, _)| tty)
|
||||
.ok_or_else(|| VarlinkWalldClientError::MessagesDisabled { user: user.clone() })?,
|
||||
};
|
||||
|
||||
match tty_utils::stat_tty(&target_tty) {
|
||||
Ok(info) if info.writable => {}
|
||||
Ok(_) => return Err(VarlinkWalldClientError::MessagesDisabled { user }),
|
||||
Err(_) => {
|
||||
return Err(VarlinkWalldClientError::TtyNotFound {
|
||||
user,
|
||||
tty: target_tty,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let source_user = username_for_uid(source_uid);
|
||||
let body =
|
||||
tty_utils::format_write_message(&source_user, &hostname(), source_tty.as_deref(), &message);
|
||||
|
||||
tty_utils::deliver_message(&target_tty, &body, DEFAULT_WRITE_TIMEOUT)
|
||||
.await
|
||||
.map_err(|err| tty_error_to_walld_error(&user, &target_tty, err))?;
|
||||
|
||||
Ok(VarlinkWriteResponse { tty: target_tty })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user