mirror of
https://github.com/htrefil/rkvm.git
synced 2025-01-18 10:26:12 +01:00
feat(server): device allowlist
This commit is contained in:
parent
754217adf4
commit
5bd5ab20e4
8 changed files with 113 additions and 8 deletions
27
example/server-with-allowlist.toml
Normal file
27
example/server-with-allowlist.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
listen = "0.0.0.0:5258"
|
||||
# See `switch-keys.md` in the repository root for the list of all possible keys.
|
||||
switch-keys = ["left-alt", "left-ctrl"]
|
||||
# Whether switch key presses should be propagated on the server and its clients.
|
||||
# Optional, defaults to true.
|
||||
# propagate-switch-keys = true
|
||||
certificate = "/etc/rkvm/certificate.pem"
|
||||
key = "/etc/rkvm/key.pem"
|
||||
|
||||
# This is to prevent malicious clients from connecting to the server.
|
||||
# Make sure this matches your client's config.
|
||||
#
|
||||
# Change this to your own value before deploying rkvm.
|
||||
password = "123456789"
|
||||
|
||||
# A device must match one of the listed devices to be forwarded
|
||||
#
|
||||
# Filling out all fields means all fields must match exactly for it to be
|
||||
# considered a match
|
||||
[[device-allowlist]]
|
||||
name = "device name"
|
||||
vendor-id = 123
|
||||
product-id = 456
|
||||
|
||||
# Filling out only one field means it must match, and the others are not checked
|
||||
[[device-allowlist]]
|
||||
vendor-id = 7
|
42
rkvm-input/src/device.rs
Normal file
42
rkvm-input/src/device.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
/// Describes parts of a device
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct DeviceSpec {
|
||||
pub name: Option<std::ffi::CString>,
|
||||
pub vendor_id: Option<u16>,
|
||||
pub product_id: Option<u16>,
|
||||
}
|
||||
|
||||
impl DeviceSpec {
|
||||
/// Compares the given values to this DeviceSpec
|
||||
///
|
||||
/// A None value means we skip that comparison
|
||||
pub fn matches(
|
||||
&self,
|
||||
other_name: &std::ffi::CStr,
|
||||
other_vendor_id: &u16,
|
||||
other_product_id: &u16,
|
||||
) -> bool {
|
||||
if let Some(name) = &self.name {
|
||||
if name.as_c_str() != other_name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vendor_id) = &self.vendor_id {
|
||||
if vendor_id != other_vendor_id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(product_id) = &self.product_id {
|
||||
if product_id != other_product_id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ pub use caps::{AbsCaps, KeyCaps, RelCaps, Repeat};
|
|||
|
||||
use crate::abs::{AbsAxis, AbsEvent, ToolType};
|
||||
use crate::convert::Convert;
|
||||
use crate::device::DeviceSpec;
|
||||
use crate::evdev::Evdev;
|
||||
use crate::event::Event;
|
||||
use crate::glue;
|
||||
|
@ -177,9 +178,28 @@ impl Interceptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(registry))]
|
||||
pub(crate) async fn open(path: &Path, registry: &Registry) -> Result<Self, OpenError> {
|
||||
#[tracing::instrument(skip(registry, device_allowlist))]
|
||||
pub(crate) async fn open(
|
||||
path: &Path,
|
||||
registry: &Registry,
|
||||
device_allowlist: &[DeviceSpec],
|
||||
) -> Result<Self, OpenError> {
|
||||
let evdev = Evdev::open(path).await?;
|
||||
|
||||
// An empty allowlist means we allow all devices
|
||||
if !device_allowlist.is_empty() {
|
||||
let name = evdev.name();
|
||||
let vendor_id = evdev.vendor();
|
||||
let product_id = evdev.product();
|
||||
|
||||
if !device_allowlist
|
||||
.iter()
|
||||
.any(|check| check.matches(&name, &vendor_id, &product_id))
|
||||
{
|
||||
return Err(OpenError::NotMatchingAllowlist);
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = evdev.file().unwrap().get_ref().metadata()?;
|
||||
|
||||
let reader_handle = registry
|
||||
|
@ -276,6 +296,8 @@ unsafe impl Send for Interceptor {}
|
|||
pub(crate) enum OpenError {
|
||||
#[error("Not appliable")]
|
||||
NotAppliable,
|
||||
#[error("Device doesn't match allowlist")]
|
||||
NotMatchingAllowlist,
|
||||
#[error(transparent)]
|
||||
Io(#[from] Error),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod abs;
|
||||
pub mod device;
|
||||
pub mod event;
|
||||
pub mod interceptor;
|
||||
pub mod key;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::device::DeviceSpec;
|
||||
use crate::interceptor::{Interceptor, OpenError};
|
||||
use crate::registry::Registry;
|
||||
|
||||
|
@ -16,9 +17,9 @@ pub struct Monitor {
|
|||
}
|
||||
|
||||
impl Monitor {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(device_allowlist: Vec<DeviceSpec>) -> Self {
|
||||
let (sender, receiver) = mpsc::channel(1);
|
||||
tokio::spawn(monitor(sender));
|
||||
tokio::spawn(monitor(sender, device_allowlist));
|
||||
|
||||
Self { receiver }
|
||||
}
|
||||
|
@ -31,7 +32,7 @@ impl Monitor {
|
|||
}
|
||||
}
|
||||
|
||||
async fn monitor(sender: Sender<Result<Interceptor, Error>>) {
|
||||
async fn monitor(sender: Sender<Result<Interceptor, Error>>, device_allowlist: Vec<DeviceSpec>) {
|
||||
let run = async {
|
||||
let registry = Registry::new();
|
||||
|
||||
|
@ -70,10 +71,11 @@ async fn monitor(sender: Sender<Result<Interceptor, Error>>) {
|
|||
continue;
|
||||
}
|
||||
|
||||
let interceptor = match Interceptor::open(&path, ®istry).await {
|
||||
let interceptor = match Interceptor::open(&path, ®istry, &device_allowlist).await {
|
||||
Ok(interceptor) => interceptor,
|
||||
Err(OpenError::Io(err)) => return Err(err),
|
||||
Err(OpenError::NotAppliable) => continue,
|
||||
Err(OpenError::NotMatchingAllowlist) => continue,
|
||||
};
|
||||
|
||||
if sender.send(Ok(interceptor)).await.is_err() {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use rkvm_input::device::DeviceSpec;
|
||||
use rkvm_input::key::{Button, Key, Keyboard};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
|
@ -13,6 +14,8 @@ pub struct Config {
|
|||
pub password: String,
|
||||
pub switch_keys: HashSet<SwitchKey>,
|
||||
pub propagate_switch_keys: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub device_allowlist: Vec<DeviceSpec>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -1236,4 +1239,10 @@ mod test {
|
|||
let config = include_str!("../../example/server.toml");
|
||||
toml::from_str::<Config>(config).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_with_allowlist_parses() {
|
||||
let config = include_str!("../../example/server-with-allowlist.toml");
|
||||
toml::from_str::<Config>(config).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ async fn main() -> ExitCode {
|
|||
let propagate_switch_keys = config.propagate_switch_keys.unwrap_or(true);
|
||||
|
||||
tokio::select! {
|
||||
result = server::run(config.listen, acceptor, &config.password, &switch_keys, propagate_switch_keys) => {
|
||||
result = server::run(config.listen, acceptor, &config.password, &switch_keys, propagate_switch_keys, config.device_allowlist) => {
|
||||
if let Err(err) = result {
|
||||
tracing::error!("Error: {}", err);
|
||||
return ExitCode::FAILURE;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use rkvm_input::abs::{AbsAxis, AbsInfo};
|
||||
use rkvm_input::device::DeviceSpec;
|
||||
use rkvm_input::event::Event;
|
||||
use rkvm_input::key::{Key, KeyEvent};
|
||||
use rkvm_input::monitor::Monitor;
|
||||
|
@ -39,11 +40,12 @@ pub async fn run(
|
|||
password: &str,
|
||||
switch_keys: &HashSet<Key>,
|
||||
propagate_switch_keys: bool,
|
||||
device_allowlist: Vec<DeviceSpec>,
|
||||
) -> Result<(), Error> {
|
||||
let listener = TcpListener::bind(&listen).await.map_err(Error::Network)?;
|
||||
tracing::info!("Listening on {}", listen);
|
||||
|
||||
let mut monitor = Monitor::new();
|
||||
let mut monitor = Monitor::new(device_allowlist);
|
||||
let mut devices = Slab::<Device>::new();
|
||||
let mut clients = Slab::<(Sender<_>, SocketAddr)>::new();
|
||||
let mut current = 0;
|
||||
|
|
Loading…
Reference in a new issue