1616
1717package com .decodified .scalassh
1818
19- import net .schmizz .sshj .{Config , DefaultConfig }
20-
21- import io .Source
2219import java .io .{File , IOException }
23-
24- import HostKeyVerifiers ._
2520import java .security .PublicKey
2621
2722import net .schmizz .sshj .common .SecurityUtils
2823import net .schmizz .sshj .connection .channel .direct .PTYMode
2924import net .schmizz .sshj .transport .verification .{HostKeyVerifier , OpenSSHKnownHosts }
25+ import net .schmizz .sshj .{Config , DefaultConfig }
3026
31- import annotation .tailrec
27+ import scala .annotation .tailrec
28+ import scala .util .{Failure , Success , Try }
3229import scala .util .control .NonFatal
30+ import scala .io .Source
31+ import HostKeyVerifiers ._
3332
34- trait HostConfigProvider extends (String ⇒ Validated [HostConfig ])
33+ trait HostConfigProvider extends (String ⇒ Try [HostConfig ])
3534
3635object HostConfigProvider {
3736 implicit def fromLogin (login : SshLogin ): HostConfigProvider =
3837 new HostConfigProvider {
39- def apply (host : String ) = Right (HostConfig (login = login, hostName = host))
38+ def apply (host : String ) = Success (HostConfig (login = login, hostName = host))
4039 }
4140 implicit def fromHostConfig (config : HostConfig ): HostConfigProvider =
4241 new HostConfigProvider {
43- def apply (host : String ) = Right (if (config.hostName.isEmpty) config.copy(hostName = host) else config)
42+ def apply (host : String ) = Success (if (config.hostName.isEmpty) config.copy(hostName = host) else config)
4443 }
4544}
4645
@@ -51,7 +50,7 @@ final case class HostConfig(login: SshLogin,
5150 connectionTimeout : Option [Int ] = None ,
5251 commandTimeout : Option [Int ] = None ,
5352 enableCompression : Boolean = false ,
54- hostKeyVerifier : HostKeyVerifier = KnownHosts .right. toOption getOrElse DontVerify ,
53+ hostKeyVerifier : HostKeyVerifier = KnownHosts .toOption getOrElse DontVerify ,
5554 ptyConfig : Option [PTYConfig ] = None ,
5655 sshjConfig : Config = HostConfig .DefaultSshjConfig )
5756
@@ -67,28 +66,24 @@ object HostConfig {
6766}
6867
6968sealed abstract class FromStringsHostConfigProvider extends HostConfigProvider {
70- protected def rawLines (host : String ): Validated [(String , TraversableOnce [String ])]
69+ protected def rawLines (host : String ): Try [(String , TraversableOnce [String ])]
7170
72- def apply (host : String ): Validated [HostConfig ] =
73- rawLines(host).right. flatMap {
71+ def apply (host : String ): Try [HostConfig ] =
72+ rawLines(host).flatMap {
7473 case (source, lines) ⇒
7574 for {
76- settings ← splitToMap(lines, source).right
77- login ← login(settings, source).right
78- port ← optIntSetting(" port" , settings, source).right
79- connectTimeout ← optIntSetting(" connect-timeout" , settings, source).right
80- connectionTimeout ← optIntSetting(" connection-timeout" , settings, source).right
81- commandTimeout ← optIntSetting(" command-timeout" , settings, source).right
82- enableCompression ← optBoolSetting(" enable-compression" , settings, source).right
83- verifier ← setting(" fingerprint" , settings, source).right
84- .map(forFingerprint)
85- .left
86- .flatMap(_ ⇒ KnownHosts )
87- .right
75+ settings ← splitToMap(lines, source)
76+ login ← login(settings, source)
77+ port ← optIntSetting(" port" , settings, source)
78+ connectTimeout ← optIntSetting(" connect-timeout" , settings, source)
79+ connectionTimeout ← optIntSetting(" connection-timeout" , settings, source)
80+ commandTimeout ← optIntSetting(" command-timeout" , settings, source)
81+ enableCompression ← optBoolSetting(" enable-compression" , settings, source)
82+ verifier ← setting(" fingerprint" , settings, source).transform(x ⇒ Success (forFingerprint(x)), _ ⇒ KnownHosts )
8883 } yield {
8984 HostConfig (
9085 login,
91- hostName = setting(" host-name" , settings, source).right. toOption getOrElse host,
86+ hostName = setting(" host-name" , settings, source).toOption getOrElse host,
9287 port = port getOrElse 22 ,
9388 connectTimeout = connectTimeout,
9489 connectionTimeout = connectionTimeout,
@@ -99,28 +94,28 @@ sealed abstract class FromStringsHostConfigProvider extends HostConfigProvider {
9994 }
10095 }
10196
102- private def login (settings : Map [String , String ], source : String ): Validated [SshLogin ] =
103- setting(" login-type" , settings, source).right. flatMap {
97+ private def login (settings : Map [String , String ], source : String ): Try [SshLogin ] =
98+ setting(" login-type" , settings, source).flatMap {
10499 case " password" ⇒ passwordLogin(settings, source)
105100 case " keyfile" ⇒ keyfileLogin(settings, source)
106101 case " agent" ⇒ agentLogin(settings, source)
107102 case x ⇒
108- Left (
109- " Illegal login-type setting '%s ' in host config '%s ': expecting either 'password' or 'keyfile' "
110- .format(x, source ))
103+ Failure (
104+ SSH . Error ( s " Illegal login-type setting ' $x ' in host config ' $source ': " +
105+ " expecting either 'password', 'keyfile' or 'agent' " ))
111106 }
112107
113- private def passwordLogin (settings : Map [String , String ], source : String ): Validated [PasswordLogin ] =
108+ private def passwordLogin (settings : Map [String , String ], source : String ): Try [PasswordLogin ] =
114109 for {
115- user ← setting(" username" , settings, source).right
116- pass ← setting(" password" , settings, source).right
110+ user ← setting(" username" , settings, source)
111+ pass ← setting(" password" , settings, source)
117112 } yield PasswordLogin (user, pass)
118113
119- private def keyfileLogin (settings : Map [String , String ], source : String ): Validated [PublicKeyLogin ] =
120- setting(" username" , settings, source).right.map { user ⇒
114+ private def keyfileLogin (settings : Map [String , String ], source : String ): Try [PublicKeyLogin ] =
115+ for (user ← setting(" username" , settings, source)) yield {
121116 import PublicKeyLogin ._
122- val keyfile = setting(" keyfile" , settings, source).right. toOption
123- val passphrase = setting(" passphrase" , settings, source).right. toOption
117+ val keyfile = setting(" keyfile" , settings, source).toOption
118+ val passphrase = setting(" passphrase" , settings, source).toOption
124119 PublicKeyLogin (
125120 user,
126121 passphrase.map(SimplePasswordProducer ),
@@ -134,43 +129,35 @@ sealed abstract class FromStringsHostConfigProvider extends HostConfigProvider {
134129 )
135130 }
136131
137- private def agentLogin (settings : Map [String , String ], source : String ): Validated [AgentLogin ] = {
138- val user = setting(" username" , settings, source).right. toOption
139- val host = setting(" host" , settings, source).right. toOption
140- Right (AgentLogin (user getOrElse System .getProperty(" user.home" ), host getOrElse " localhost" ))
132+ private def agentLogin (settings : Map [String , String ], source : String ): Try [AgentLogin ] = {
133+ val user = setting(" username" , settings, source).toOption
134+ val host = setting(" host" , settings, source).toOption
135+ Success (AgentLogin (user getOrElse System .getProperty(" user.home" ), host getOrElse " localhost" ))
141136 }
142137
143- private def setting (key : String , settings : Map [String , String ], source : String ): Validated [String ] =
138+ private def setting (key : String , settings : Map [String , String ], source : String ): Try [String ] =
144139 settings.get(key) match {
145- case Some (user) ⇒ Right (user)
146- case None ⇒ Left ( s " Host config ' $source' is missing required setting ' $key' " )
140+ case Some (user) ⇒ Success (user)
141+ case None ⇒ Failure ( SSH . Error ( s " Host config ' $source' is missing required setting ' $key' " ) )
147142 }
148143
149- private def optIntSetting (key : String , settings : Map [String , String ], source : String ): Validated [Option [Int ]] = {
150- setting(key, settings, source) match {
151- case Right (value) ⇒
152- try Right (Some (value.toInt))
153- catch {
154- case _ : NumberFormatException ⇒
155- Left (s " Value ' $value' for setting ' $key' in host config ' $source' is not a legal integer " )
156- }
157- case Left (_) ⇒ Right (None )
158- }
159- }
144+ private def optIntSetting (key : String , settings : Map [String , String ], source : String ): Try [Option [Int ]] =
145+ setting(key, settings, source).transform(x ⇒ Success (Some (x.toInt)), _ ⇒ Success (None ))
160146
161- private def optBoolSetting (key : String , settings : Map [String , String ], source : String ): Validated [Option [Boolean ]] =
147+ private def optBoolSetting (key : String , settings : Map [String , String ], source : String ): Try [Option [Boolean ]] =
162148 setting(key, settings, source) match {
163- case Right (" yes" | " YES" | " true" | " TRUE" ) ⇒ Right (Some (true ))
164- case Right (value) ⇒ Left (s " Value ' $value' for setting ' $key' in host config ' $source' is not a legal integer " )
165- case Left (_) ⇒ Right (None )
149+ case Success (" yes" | " YES" | " true" | " TRUE" ) ⇒ Success (Some (true ))
150+ case Failure (_) ⇒ Success (None )
151+ case Success (value) ⇒
152+ Failure (SSH .Error (s " Value ' $value' for setting ' $key' in host config ' $source' is not a legal boolean " ))
166153 }
167154
168155 private def splitToMap (lines : TraversableOnce [String ], source : String ) =
169- lines.foldLeft(Right (Map .empty): Validated [Map [String , String ]]) {
170- case (Right (map), line) if line.nonEmpty && line.charAt(0 ) != '#' ⇒
156+ lines.foldLeft(Success (Map .empty): Try [Map [String , String ]]) {
157+ case (Success (map), line) if line.nonEmpty && line.charAt(0 ) != '#' ⇒
171158 line.indexOf('=' ) match {
172- case - 1 ⇒ Left ( s " Host config ' $source' contains illegal line: \n $line" )
173- case ix ⇒ Right (map.updated(line.substring(0 , ix).trim, line.substring(ix + 1 ).trim))
159+ case - 1 ⇒ Failure ( SSH . Error ( s " Host config ' $source' contains illegal line: \n $line" ) )
160+ case ix ⇒ Success (map.updated(line.substring(0 , ix).trim, line.substring(ix + 1 ).trim))
174161 }
175162 case (result, _) ⇒ result
176163 }
@@ -181,16 +168,16 @@ object HostFileConfig {
181168 def apply (): HostConfigProvider = apply(DefaultHostFileDir )
182169 def apply (hostFilesDir : String ): HostConfigProvider =
183170 new FromStringsHostConfigProvider {
184- protected def rawLines (host : String ) = {
171+ protected def rawLines (host : String ): Try [( String , TraversableOnce [ String ])] = {
185172 val locations = searchLocations(host).map(name ⇒ new File (hostFilesDir + File .separator + name))
186173 locations.find(_.exists) match {
187174 case Some (file) ⇒
188- try Right (file.getAbsolutePath → Source .fromFile(file, " utf8" ).getLines())
189- catch { case e : IOException ⇒ Left ( s " Could not read host file ' $file' due to $e" ) }
175+ try Success (file.getAbsolutePath → Source .fromFile(file, " utf8" ).getLines())
176+ catch { case e : IOException ⇒ Failure ( SSH . Error ( s " Could not read host file ' $file' due to $e" ) ) }
190177 case None ⇒
191- Left (
192- s " Host files ' ${locations.mkString(" ', '" )}' not found, " +
193- " either provide one or use a concrete HostConfig, PasswordLogin, PublicKeyLogin or AgentLogin" )
178+ Failure (
179+ SSH . Error ( s " Host files ' ${locations.mkString(" ', '" )}' not found, " +
180+ " either provide one or use a concrete HostConfig, PasswordLogin, PublicKeyLogin or AgentLogin" ))
194181 }
195182 }
196183 }
@@ -212,7 +199,7 @@ object HostResourceConfig {
212199 def apply (): HostConfigProvider = apply(" " )
213200 def apply (resourceBase : String ): HostConfigProvider =
214201 new FromStringsHostConfigProvider {
215- protected def rawLines (host : String ): Validated [(String , TraversableOnce [String ])] = {
202+ protected def rawLines (host : String ): Try [(String , TraversableOnce [String ])] = {
216203 val locations = HostFileConfig .searchLocations(host).map(resourceBase + _)
217204 locations
218205 .map { location ⇒
@@ -225,11 +212,11 @@ object HostResourceConfig {
225212 }
226213 }
227214 .find(_._2.nonEmpty) match {
228- case Some (result) ⇒ Right (result)
215+ case Some (result) ⇒ Success (result)
229216 case None ⇒
230- Left (
231- s " Host resources ' ${locations.mkString(" ', '" )}' not found, " +
232- s " either provide one or use a concrete HostConfig, PasswordLogin, PublicKeyLogin or AgentLogin " )
217+ Failure (
218+ SSH . Error ( s " Host resources ' ${locations.mkString(" ', '" )}' not found, " +
219+ s " either provide one or use a concrete HostConfig, PasswordLogin, PublicKeyLogin or AgentLogin " ))
233220 }
234221 }
235222 }
@@ -241,19 +228,21 @@ object HostKeyVerifiers {
241228 def verify (hostname : String , port : Int , key : PublicKey ) = true
242229 }
243230
244- lazy val KnownHosts : Validated [HostKeyVerifier ] = {
231+ lazy val KnownHosts : Try [HostKeyVerifier ] = {
245232 val sshDir = System .getProperty(" user.home" ) + File .separator + " .ssh" + File .separator
246- for {
247- error1 ← fromKnownHostsFile(new File (sshDir + " known_hosts" )).left
248- error2 ← fromKnownHostsFile(new File (sshDir + " known_hosts2" )).left
249- } yield error1 + " and " + error2
233+ fromKnownHostsFile(new File (sshDir + " known_hosts" )).recoverWith {
234+ case NonFatal (e1) ⇒
235+ fromKnownHostsFile(new File (sshDir + " known_hosts2" )).recoverWith {
236+ case NonFatal (e2) ⇒ Failure (SSH .Error (e1 + " and " + e2))
237+ }
238+ }
250239 }
251240
252- def fromKnownHostsFile (knownHostsFile : File ): Validated [HostKeyVerifier ] =
241+ def fromKnownHostsFile (knownHostsFile : File ): Try [HostKeyVerifier ] =
253242 if (knownHostsFile.exists()) {
254- try Right (new OpenSSHKnownHosts (knownHostsFile))
255- catch { case NonFatal (e) ⇒ Left ( s " Could not read $knownHostsFile due to $e " ) }
256- } else Left ( knownHostsFile.toString + " not found" )
243+ try Success (new OpenSSHKnownHosts (knownHostsFile))
244+ catch { case NonFatal (e) ⇒ Failure ( SSH . Error ( s " Could not read $knownHostsFile" , e) ) }
245+ } else Failure ( SSH . Error ( knownHostsFile.toString + " not found" ) )
257246
258247 def forFingerprint (fingerprint : String ): HostKeyVerifier =
259248 fingerprint match {
0 commit comments