@@ -11,30 +11,9 @@ use std::time::Duration;
1111use remotefs:: { RemoteError , RemoteErrorType , RemoteResult } ;
1212use ssh2:: { MethodType as SshMethodType , Session } ;
1313
14- /**
15- * MIT License
16- *
17- * remotefs - Copyright (c) 2021 Christian Visintin
18- *
19- * Permission is hereby granted, free of charge, to any person obtaining a copy
20- * of this software and associated documentation files (the "Software"), to deal
21- * in the Software without restriction, including without limitation the rights
22- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23- * copies of the Software, and to permit persons to whom the Software is
24- * furnished to do so, subject to the following conditions:
25- *
26- * The above copyright notice and this permission notice shall be included in all
27- * copies or substantial portions of the Software.
28- *
29- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35- * SOFTWARE.
36- */
37- use super :: { config:: Config , SshOpts } ;
14+ use super :: config:: Config ;
15+ use super :: SshOpts ;
16+ use crate :: SshAgentIdentity ;
3817
3918// -- connect
4019
@@ -101,27 +80,45 @@ pub fn connect(opts: &SshOpts) -> RemoteResult<Session> {
10180 error ! ( "SSH handshake failed: {}" , err) ;
10281 return Err ( RemoteError :: new_ex ( RemoteErrorType :: ProtocolError , err) ) ;
10382 }
104- // Authenticate with password or key
105- match opts
106- . key_storage
107- . as_ref ( )
108- . and_then ( |x| x. resolve ( ssh_config. host . as_str ( ) , ssh_config. username . as_str ( ) ) )
109- {
110- Some ( rsa_key) => {
111- session_auth_with_rsakey (
112- & mut session,
113- & ssh_config. username ,
114- rsa_key. as_path ( ) ,
115- opts. password . as_deref ( ) ,
116- ssh_config. params . identity_file . as_deref ( ) ,
117- ) ?;
83+
84+ // if use_ssh_agent is enabled, try to authenticate with ssh agent
85+ if let Some ( ssh_agent_config) = & opts. ssh_agent_identity {
86+ match session_auth_with_agent ( & mut session, & ssh_config. username , ssh_agent_config) {
87+ Ok ( _) => {
88+ info ! ( "Authenticated with ssh agent" ) ;
89+ return Ok ( session) ;
90+ }
91+ Err ( err) => {
92+ error ! ( "Could not authenticate with ssh agent: {}" , err) ;
93+ }
11894 }
119- None => {
120- session_auth_with_password (
121- & mut session,
122- & ssh_config. username ,
123- opts. password . as_deref ( ) ,
124- ) ?;
95+ }
96+
97+ // Authenticate with password or key
98+ if !session. authenticated ( ) {
99+ match opts. key_storage . as_ref ( ) . and_then ( |x| {
100+ x. resolve ( ssh_config. host . as_str ( ) , ssh_config. username . as_str ( ) )
101+ . or ( x. resolve (
102+ ssh_config. resolved_host . as_str ( ) ,
103+ ssh_config. username . as_str ( ) ,
104+ ) )
105+ } ) {
106+ Some ( rsa_key) => {
107+ session_auth_with_rsakey (
108+ & mut session,
109+ & ssh_config. username ,
110+ rsa_key. as_path ( ) ,
111+ opts. password . as_deref ( ) ,
112+ ssh_config. params . identity_file . as_deref ( ) ,
113+ ) ?;
114+ }
115+ None => {
116+ session_auth_with_password (
117+ & mut session,
118+ & ssh_config. username ,
119+ opts. password . as_deref ( ) ,
120+ ) ?;
121+ }
125122 }
126123 }
127124 // Return session
@@ -199,6 +196,58 @@ fn set_algo_prefs(session: &mut Session, opts: &SshOpts, config: &Config) -> Rem
199196 Ok ( ( ) )
200197}
201198
199+ /// Authenticate on session with ssh agent
200+ fn session_auth_with_agent (
201+ session : & mut Session ,
202+ username : & str ,
203+ ssh_agent_config : & SshAgentIdentity ,
204+ ) -> RemoteResult < ( ) > {
205+ let mut agent = session
206+ . agent ( )
207+ . map_err ( |err| RemoteError :: new_ex ( RemoteErrorType :: ConnectionError , err) ) ?;
208+
209+ agent
210+ . connect ( )
211+ . map_err ( |err| RemoteError :: new_ex ( RemoteErrorType :: ConnectionError , err) ) ?;
212+
213+ agent
214+ . list_identities ( )
215+ . map_err ( |err| RemoteError :: new_ex ( RemoteErrorType :: ConnectionError , err) ) ?;
216+
217+ let mut connection_result = Err ( RemoteError :: new ( RemoteErrorType :: AuthenticationFailed ) ) ;
218+
219+ for identity in agent
220+ . identities ( )
221+ . map_err ( |err| RemoteError :: new_ex ( RemoteErrorType :: ConnectionError , err) ) ?
222+ {
223+ if ssh_agent_config. pubkey_matches ( identity. blob ( ) ) {
224+ debug ! ( "Trying to authenticate with ssh agent with key: {identity:?}" ) ;
225+ } else {
226+ continue ;
227+ }
228+ match agent. userauth ( username, & identity) {
229+ Ok ( ( ) ) => {
230+ connection_result = Ok ( ( ) ) ;
231+ debug ! ( "Authenticated with ssh agent with key: {identity:?}" ) ;
232+ break ;
233+ }
234+ Err ( err) => {
235+ debug ! ( "SSH agent auth failed: {err}" ) ;
236+ connection_result = Err ( RemoteError :: new_ex (
237+ RemoteErrorType :: AuthenticationFailed ,
238+ err,
239+ ) ) ;
240+ }
241+ }
242+ }
243+
244+ if let Err ( err) = agent. disconnect ( ) {
245+ warn ! ( "Could not disconnect from ssh agent: {err}" ) ;
246+ }
247+
248+ connection_result
249+ }
250+
202251/// Authenticate on session with private key
203252fn session_auth_with_rsakey (
204253 session : & mut Session ,
@@ -334,12 +383,12 @@ pub fn perform_shell_cmd_with_rc<S: AsRef<str>>(
334383#[ cfg( test) ]
335384mod test {
336385
337- use super :: * ;
338386 #[ cfg( feature = "with-containers" ) ]
339- use crate :: mock :: ssh as ssh_mock ;
387+ use ssh2_config :: ParseRule ;
340388
389+ use super :: * ;
341390 #[ cfg( feature = "with-containers" ) ]
342- use ssh2_config :: ParseRule ;
391+ use crate :: mock :: ssh as ssh_mock ;
343392
344393 #[ test]
345394 #[ cfg( feature = "with-containers" ) ]
@@ -349,7 +398,11 @@ mod test {
349398 let opts = SshOpts :: new ( "sftp" )
350399 . config_file ( config_file. path ( ) , ParseRule :: ALLOW_UNKNOWN_FIELDS )
351400 . password ( "password" ) ;
352- let session = connect ( & opts) . ok ( ) . unwrap ( ) ;
401+
402+ if let Err ( err) = connect ( & opts) {
403+ panic ! ( "Could not connect to server: {}" , err) ;
404+ }
405+ let session = connect ( & opts) . unwrap ( ) ;
353406 assert ! ( session. authenticated( ) ) ;
354407 }
355408
@@ -361,7 +414,7 @@ mod test {
361414 let opts = SshOpts :: new ( "sftp" )
362415 . config_file ( config_file. path ( ) , ParseRule :: ALLOW_UNKNOWN_FIELDS )
363416 . key_storage ( Box :: new ( ssh_mock:: MockSshKeyStorage :: default ( ) ) ) ;
364- let session = connect ( & opts) . ok ( ) . unwrap ( ) ;
417+ let session = connect ( & opts) . unwrap ( ) ;
365418 assert ! ( session. authenticated( ) ) ;
366419 }
367420
@@ -373,7 +426,7 @@ mod test {
373426 . port ( 10022 )
374427 . username ( "sftp" )
375428 . password ( "password" ) ;
376- let mut session = connect ( & opts) . ok ( ) . unwrap ( ) ;
429+ let mut session = connect ( & opts) . unwrap ( ) ;
377430 assert ! ( session. authenticated( ) ) ;
378431 // run commands
379432 assert ! ( perform_shell_cmd( & mut session, "pwd" ) . is_ok( ) ) ;
@@ -387,7 +440,7 @@ mod test {
387440 . port ( 10022 )
388441 . username ( "sftp" )
389442 . password ( "password" ) ;
390- let mut session = connect ( & opts) . ok ( ) . unwrap ( ) ;
443+ let mut session = connect ( & opts) . unwrap ( ) ;
391444 assert ! ( session. authenticated( ) ) ;
392445 // run commands
393446 assert_eq ! (
0 commit comments