Skip to content
Merged
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ thiserror = "2.0.17"
uuid = { version = "1.19.0", features = ["v4", "v5"] }
futures = "0.3.31"
futures-timer = "3.0.3"
base64 = "0.22.1"

tokio = { version = "1.48.0", features = ["rt-multi-thread", "macros", "sync", "time"] }
2 changes: 2 additions & 0 deletions nmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
All notable changes to the `nmrs` crate will be documented in this file.

## [Unreleased]
### Added
- Input validation before any D-Bus operations ([#173](https://github.com/cachebag/nmrs/pull/173))

## [1.3.5] - 2026-01-13
### Changed
Expand Down
1 change: 1 addition & 0 deletions nmrs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ thiserror.workspace = true
uuid.workspace = true
futures.workspace = true
futures-timer.workspace = true
base64.workspace = true

[dev-dependencies]
tokio.workspace = true
Expand Down
8 changes: 8 additions & 0 deletions nmrs/src/core/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::dbus::{NMAccessPointProxy, NMDeviceProxy, NMProxy, NMWirelessProxy};
use crate::monitoring::info::current_ssid;
use crate::types::constants::{device_state, device_type, timeouts};
use crate::util::utils::{decode_ssid_or_empty, nm_proxy};
use crate::util::validation::{validate_ssid, validate_wifi_security};
use crate::Result;

/// Decision on whether to reuse a saved connection or create a fresh one.
Expand All @@ -34,6 +35,10 @@ enum SavedDecision {
/// If a saved connection exists but fails, it will be deleted and a fresh
/// connection will be attempted with the provided credentials.
pub(crate) async fn connect(conn: &Connection, ssid: &str, creds: WifiSecurity) -> Result<()> {
// Validate inputs before attempting connection
validate_ssid(ssid)?;
validate_wifi_security(&creds)?;

debug!(
"Connecting to '{}' | secured={} is_psk={} is_eap={}",
ssid,
Expand Down Expand Up @@ -159,6 +164,9 @@ pub(crate) async fn forget(conn: &Connection, ssid: &str) -> Result<()> {
use std::collections::HashMap;
use zvariant::{OwnedObjectPath, Value};

// Validate SSID
validate_ssid(ssid)?;

debug!("Starting forget operation for: {ssid}");

let nm = NMProxy::new(conn).await?;
Expand Down
20 changes: 17 additions & 3 deletions nmrs/src/core/connection_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,33 @@ use zbus::Connection;
use zvariant::{OwnedObjectPath, Value};

use crate::util::utils::{connection_settings_proxy, settings_proxy};
use crate::util::validation::validate_ssid;
use crate::Result;

/// Finds the D-Bus path of a saved connection by SSID.
/// Finds the D-Bus path of a saved connection by SSID or connection name.
///
/// Iterates through all saved connections in NetworkManager's settings
/// and returns the path of the first one whose connection ID matches
/// the given SSID.
/// the given SSID or name.
///
/// Returns `None` if no saved connection exists for this SSID.
/// Note: This function is used for both WiFi SSIDs and VPN connection names.
/// The validation enforces WiFi SSID rules (max 32 bytes), which is also
/// reasonable for VPN connection names.
///
/// Returns `None` if no saved connection exists for this SSID/name.
pub(crate) async fn get_saved_connection_path(
conn: &Connection,
ssid: &str,
) -> Result<Option<OwnedObjectPath>> {
// Validate the connection name/SSID
if ssid.trim().is_empty() {
return Ok(None);
}

// Validate using SSID rules (max 32 bytes, no special chars)
// This applies to both WiFi SSIDs and connection names
validate_ssid(ssid)?;

let settings = settings_proxy(conn).await?;

let reply = settings.call_method("ListConnections", &()).await?;
Expand Down
13 changes: 13 additions & 0 deletions nmrs/src/core/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::builders::build_wireguard_connection;
use crate::core::state_wait::wait_for_connection_activation;
use crate::dbus::{NMActiveConnectionProxy, NMProxy};
use crate::util::utils::{extract_connection_state_reason, nm_proxy, settings_proxy};
use crate::util::validation::{validate_connection_name, validate_vpn_credentials};
use crate::Result;

/// Connects to a WireGuard connection.
Expand All @@ -35,6 +36,9 @@ use crate::Result;
/// WireGuard activations do not require binding to an underlying device.
/// Use "/" so NetworkManager auto-selects.
pub(crate) async fn connect_vpn(conn: &Connection, creds: VpnCredentials) -> Result<()> {
// Validate VPN credentials before attempting connection
validate_vpn_credentials(&creds)?;

debug!("Connecting to VPN: {}", creds.name);

let nm = NMProxy::new(conn).await?;
Expand Down Expand Up @@ -132,6 +136,9 @@ pub(crate) async fn connect_vpn(conn: &Connection, creds: VpnCredentials) -> Res
/// If found, deactivates the connection. If not found, assumes already
/// disconnected and returns success.
pub(crate) async fn disconnect_vpn(conn: &Connection, name: &str) -> Result<()> {
// Validate connection name
validate_connection_name(name)?;

debug!("Disconnecting VPN: {name}");

let nm = NMProxy::new(conn).await?;
Expand Down Expand Up @@ -482,6 +489,9 @@ pub(crate) async fn list_vpn_connections(conn: &Connection) -> Result<Vec<VpnCon
///
/// If currently connected, the connection will be disconnected first before deletion.
pub(crate) async fn forget_vpn(conn: &Connection, name: &str) -> Result<()> {
// Validate connection name
validate_connection_name(name)?;

debug!("Starting forget operation for VPN: {name}");

match disconnect_vpn(conn, name).await {
Expand Down Expand Up @@ -562,6 +572,9 @@ pub(crate) async fn forget_vpn(conn: &Connection, name: &str) -> Result<()> {
///
/// The connection must be in the active connections list to retrieve full details.
pub(crate) async fn get_vpn_info(conn: &Connection, name: &str) -> Result<VpnConnectionInfo> {
// Validate connection name
validate_connection_name(name)?;

let nm = NMProxy::new(conn).await?;
let active_conns = nm.active_connections().await?;

Expand Down
1 change: 1 addition & 0 deletions nmrs/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
//! This module contains helper functions used throughout the crate.

pub(crate) mod utils;
pub(crate) mod validation;
Loading
Loading