@@ -8,6 +8,7 @@ use std::io::Read;
88use std:: net:: ToSocketAddrs ;
99use std:: path:: Path ;
1010
11+ use globset:: Glob ;
1112use log:: debug;
1213use thiserror:: * ;
1314
@@ -34,6 +35,7 @@ pub struct Config {
3435 pub port : u16 ,
3536 pub identity_file : Option < String > ,
3637 pub proxy_command : Option < String > ,
38+ pub proxy_jump : Option < String > ,
3739 pub add_keys_to_agent : AddKeysToAgent ,
3840}
3941
@@ -45,22 +47,30 @@ impl Config {
4547 port : 22 ,
4648 identity_file : None ,
4749 proxy_command : None ,
50+ proxy_jump : None ,
4851 add_keys_to_agent : AddKeysToAgent :: default ( ) ,
4952 }
5053 }
5154}
5255
5356impl Config {
54- fn update_proxy_command ( & mut self ) {
55- if let Some ( ref mut prox) = self . proxy_command {
56- * prox = prox. replace ( "%h" , & self . host_name ) ;
57- * prox = prox. replace ( "%p" , & format ! ( "{}" , self . port) ) ;
58- }
57+ // Look for any of the ssh_config(5) percent-style tokens and expand them
58+ // based on current data in the struct, returning a new String. This function
59+ // can be employed late/lazy eg just before establishing a stream using ProxyCommand
60+ // but also can be used to modify Hostname as config parse time
61+ fn expand_tokens ( & self , original : & str ) -> String {
62+ let mut string = original. to_string ( ) ;
63+ string = string. replace ( "%u" , & self . user ) ;
64+ string = string. replace ( "%h" , & self . host_name ) ; // remote hostname (from context "host")
65+ string = string. replace ( "%H" , & self . host_name ) ; // remote hostname (from context "host")
66+ string = string. replace ( "%p" , & format ! ( "{}" , self . port) ) ; // original typed hostname (from context "host")
67+ string = string. replace ( "%%" , "%" ) ;
68+ string
5969 }
6070
61- pub async fn stream ( & mut self ) -> Result < Stream , Error > {
62- self . update_proxy_command ( ) ;
71+ pub async fn stream ( & self ) -> Result < Stream , Error > {
6372 if let Some ( ref proxy_command) = self . proxy_command {
73+ let proxy_command = self . expand_tokens ( proxy_command) ;
6474 let cmd: Vec < & str > = proxy_command. split ( ' ' ) . collect ( ) ;
6575 Stream :: proxy_command ( cmd. first ( ) . unwrap_or ( & "" ) , cmd. get ( 1 ..) . unwrap_or ( & [ ] ) )
6676 . await
@@ -105,9 +115,9 @@ pub enum AddKeysToAgent {
105115pub fn parse ( file : & str , host : & str ) -> Result < Config , Error > {
106116 let mut config: Option < Config > = None ;
107117 for line in file. lines ( ) {
108- let line = line. trim ( ) ;
109- if let Some ( n ) = line . find ( ' ' ) {
110- let ( key, value) = line . split_at ( n ) ;
118+ let tokens = line. trim ( ) . splitn ( 2 , ' ' ) . collect :: < Vec < & str > > ( ) ;
119+ if tokens . len ( ) == 2 {
120+ let ( key, value) = ( tokens . first ( ) . unwrap_or ( & "" ) , tokens . get ( 1 ) . unwrap_or ( & "" ) ) ;
111121 let lower = key. to_lowercase ( ) ;
112122 if let Some ( ref mut config) = config {
113123 match lower. as_str ( ) {
@@ -116,10 +126,7 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
116126 config. user . clear ( ) ;
117127 config. user . push_str ( value. trim_start ( ) ) ;
118128 }
119- "hostname" => {
120- config. host_name . clear ( ) ;
121- config. host_name . push_str ( value. trim_start ( ) )
122- }
129+ "hostname" => config. host_name = config. expand_tokens ( value. trim_start ( ) ) ,
123130 "port" => {
124131 if let Ok ( port) = value. trim_start ( ) . parse ( ) {
125132 config. port = port
@@ -148,6 +155,7 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
148155 }
149156 }
150157 "proxycommand" => config. proxy_command = Some ( value. trim_start ( ) . to_string ( ) ) ,
158+ "proxyjump" => config. proxy_jump = Some ( value. trim_start ( ) . to_string ( ) ) ,
151159 "addkeystoagent" => match value. to_lowercase ( ) . as_str ( ) {
152160 "yes" => config. add_keys_to_agent = AddKeysToAgent :: Yes ,
153161 "confirm" => config. add_keys_to_agent = AddKeysToAgent :: Confirm ,
@@ -158,7 +166,11 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
158166 debug ! ( "{:?}" , key) ;
159167 }
160168 }
161- } else if lower. as_str ( ) == "host" && value. trim_start ( ) == host {
169+ } else if lower. as_str ( ) == "host"
170+ && value
171+ . split_whitespace ( )
172+ . any ( |x| check_host_against_glob_pattern ( host, x) )
173+ {
162174 let mut c = Config :: default ( host) ;
163175 c. port = 22 ;
164176 config = Some ( c)
@@ -171,3 +183,11 @@ pub fn parse(file: &str, host: &str) -> Result<Config, Error> {
171183 Err ( Error :: HostNotFound )
172184 }
173185}
186+
187+ fn check_host_against_glob_pattern ( candidate : & str , glob_pattern : & str ) -> bool {
188+ dbg ! ( candidate, glob_pattern) ;
189+ match Glob :: new ( glob_pattern) {
190+ Ok ( glob) => glob. compile_matcher ( ) . is_match ( candidate) ,
191+ _ => false ,
192+ }
193+ }
0 commit comments