Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for glob pattern matching in Host directives #306

Merged
merged 2 commits into from
Jul 4, 2024
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
1 change: 1 addition & 0 deletions russh-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version = "1.65"
[dependencies]
dirs-next = "2.0"
futures = { workspace = true }
globset = "0.4.14"
log = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["io-util", "net", "macros", "process"] }
Expand Down
50 changes: 35 additions & 15 deletions russh-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::io::Read;
use std::net::ToSocketAddrs;
use std::path::Path;

use globset::Glob;
use log::debug;
use thiserror::*;

Expand All @@ -34,6 +35,7 @@ pub struct Config {
pub port: u16,
pub identity_file: Option<String>,
pub proxy_command: Option<String>,
pub proxy_jump: Option<String>,
pub add_keys_to_agent: AddKeysToAgent,
}

Expand All @@ -45,22 +47,30 @@ impl Config {
port: 22,
identity_file: None,
proxy_command: None,
proxy_jump: None,
add_keys_to_agent: AddKeysToAgent::default(),
}
}
}

impl Config {
fn update_proxy_command(&mut self) {
if let Some(ref mut prox) = self.proxy_command {
*prox = prox.replace("%h", &self.host_name);
*prox = prox.replace("%p", &format!("{}", self.port));
}
// Look for any of the ssh_config(5) percent-style tokens and expand them
// based on current data in the struct, returning a new String. This function
// can be employed late/lazy eg just before establishing a stream using ProxyCommand
// but also can be used to modify Hostname as config parse time
fn expand_tokens(&self, original: &str) -> String {
let mut string = original.to_string();
string = string.replace("%u", &self.user);
string = string.replace("%h", &self.host_name); // remote hostname (from context "host")
string = string.replace("%H", &self.host_name); // remote hostname (from context "host")
string = string.replace("%p", &format!("{}", self.port)); // original typed hostname (from context "host")
string = string.replace("%%", "%");
string
}

pub async fn stream(&mut self) -> Result<Stream, Error> {
self.update_proxy_command();
pub async fn stream(&self) -> Result<Stream, Error> {
if let Some(ref proxy_command) = self.proxy_command {
let proxy_command = self.expand_tokens(proxy_command);
let cmd: Vec<&str> = proxy_command.split(' ').collect();
Stream::proxy_command(cmd.first().unwrap_or(&""), cmd.get(1..).unwrap_or(&[]))
.await
Expand Down Expand Up @@ -105,9 +115,9 @@ pub enum AddKeysToAgent {
pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
let mut config: Option<Config> = None;
for line in file.lines() {
let line = line.trim();
if let Some(n) = line.find(' ') {
let (key, value) = line.split_at(n);
let tokens = line.trim().splitn(2, ' ').collect::<Vec<&str>>();
if tokens.len() == 2 {
let (key, value) = (tokens.first().unwrap_or(&""), tokens.get(1).unwrap_or(&""));
let lower = key.to_lowercase();
if let Some(ref mut config) = config {
match lower.as_str() {
Expand All @@ -116,10 +126,7 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
config.user.clear();
config.user.push_str(value.trim_start());
}
"hostname" => {
config.host_name.clear();
config.host_name.push_str(value.trim_start())
}
"hostname" => config.host_name = config.expand_tokens(value.trim_start()),
"port" => {
if let Ok(port) = value.trim_start().parse() {
config.port = port
Expand Down Expand Up @@ -148,6 +155,7 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
}
}
"proxycommand" => config.proxy_command = Some(value.trim_start().to_string()),
"proxyjump" => config.proxy_jump = Some(value.trim_start().to_string()),
"addkeystoagent" => match value.to_lowercase().as_str() {
"yes" => config.add_keys_to_agent = AddKeysToAgent::Yes,
"confirm" => config.add_keys_to_agent = AddKeysToAgent::Confirm,
Expand All @@ -158,7 +166,11 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
debug!("{:?}", key);
}
}
} else if lower.as_str() == "host" && value.trim_start() == host {
} else if lower.as_str() == "host"
&& value
.split_whitespace()
.any(|x| check_host_against_glob_pattern(host, x))
{
let mut c = Config::default(host);
c.port = 22;
config = Some(c)
Expand All @@ -171,3 +183,11 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
Err(Error::HostNotFound)
}
}

fn check_host_against_glob_pattern(candidate: &str, glob_pattern: &str) -> bool {
dbg!(candidate, glob_pattern);
match Glob::new(glob_pattern) {
Ok(glob) => glob.compile_matcher().is_match(candidate),
_ => false,
}
}
Loading