Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions example/server-with-allowlist.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions rkvm-input/src/device.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
20 changes: 20 additions & 0 deletions rkvm-input/src/evdev.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::glue::{self, libevdev};

use std::ffi::CStr;
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::mem::MaybeUninit;
Expand Down Expand Up @@ -51,6 +52,25 @@ impl Evdev {
})
}

pub fn name(&self) -> &CStr {
let name = unsafe { glue::libevdev_get_name(self.as_ptr()) };
let name = unsafe { CStr::from_ptr(name) };

name
}

pub fn vendor(&self) -> u16 {
unsafe { glue::libevdev_get_id_vendor(self.as_ptr()) as _ }
}

pub fn product(&self) -> u16 {
unsafe { glue::libevdev_get_id_product(self.as_ptr()) as _ }
}

pub fn version(&self) -> u16 {
unsafe { glue::libevdev_get_id_version(self.as_ptr()) as _ }
}

pub fn file(&self) -> Option<&AsyncFd<File>> {
self.file.as_ref()
}
Expand Down
37 changes: 28 additions & 9 deletions rkvm-input/src/interceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -107,22 +108,19 @@ impl Interceptor {
}

pub fn name(&self) -> &CStr {
let name = unsafe { glue::libevdev_get_name(self.evdev.as_ptr()) };
let name = unsafe { CStr::from_ptr(name) };

name
self.evdev.name()
}

pub fn vendor(&self) -> u16 {
unsafe { glue::libevdev_get_id_vendor(self.evdev.as_ptr()) as _ }
self.evdev.vendor()
}

pub fn product(&self) -> u16 {
unsafe { glue::libevdev_get_id_product(self.evdev.as_ptr()) as _ }
self.evdev.product()
}

pub fn version(&self) -> u16 {
unsafe { glue::libevdev_get_id_version(self.evdev.as_ptr()) as _ }
self.evdev.version()
}

pub fn rel(&self) -> RelCaps {
Expand Down Expand Up @@ -180,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
Expand Down Expand Up @@ -279,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 change: 1 addition & 0 deletions rkvm-input/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod abs;
pub mod device;
pub mod event;
pub mod interceptor;
pub mod key;
Expand Down
10 changes: 6 additions & 4 deletions rkvm-input/src/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::device::DeviceSpec;
use crate::interceptor::{Interceptor, OpenError};
use crate::registry::Registry;

Expand All @@ -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 }
}
Expand All @@ -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();

Expand Down Expand Up @@ -70,10 +71,11 @@ async fn monitor(sender: Sender<Result<Interceptor, Error>>) {
continue;
}

let interceptor = match Interceptor::open(&path, &registry).await {
let interceptor = match Interceptor::open(&path, &registry, &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() {
Expand Down
1 change: 1 addition & 0 deletions rkvm-input/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub struct Handle {

impl Drop for Handle {
fn drop(&mut self) {
tracing::trace!("Dropping {:?}", self.entry);
assert!(self.entries.lock().unwrap().remove(&self.entry));
}
}
9 changes: 9 additions & 0 deletions rkvm-server/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rkvm_input::device::DeviceSpec;
use rkvm_input::key::{Button, Key, Keyboard};
use serde::Deserialize;
use std::collections::HashSet;
Expand All @@ -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)]
Expand Down Expand Up @@ -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();
}
}
2 changes: 1 addition & 1 deletion rkvm-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion rkvm-server/src/server.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down