Added first version of libssh implementation

This commit is contained in:
Abdelilah El Aissaoui 2022-12-16 22:29:33 +01:00
parent 1d5085b689
commit 944f4e9955
8 changed files with 328 additions and 44 deletions

View file

@ -40,7 +40,7 @@ dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
implementation("net.i2p.crypto:eddsa:0.3.0")
implementation("com.sun.jna:jna:3.0.9")
}
tasks.test {

View file

@ -0,0 +1,73 @@
package com.jetpackduba.gitnuro.credentials
import com.jetpackduba.gitnuro.credentials.streams.LibSshInputErrStream
import com.jetpackduba.gitnuro.credentials.streams.LibSshInputStream
import com.jetpackduba.gitnuro.credentials.streams.LibSshOutputStream
import com.jetpackduba.gitnuro.credentials.streams.checkValidResult
import java.io.InputStream
import java.io.OutputStream
class GProcessLibSsh : Process() {
private lateinit var channel: ssh_channel
private lateinit var session: ssh_session
private val outputStream by lazy {
LibSshOutputStream(channel)
}
private val inputStream by lazy {
LibSshInputStream(channel)
}
private val errorOutputStream by lazy {
LibSshInputErrStream(channel)
}
override fun getOutputStream(): OutputStream {
return outputStream
}
override fun getInputStream(): InputStream {
return inputStream
}
override fun getErrorStream(): InputStream {
return errorOutputStream
}
override fun waitFor(): Int {
if (isRunning())
Thread.sleep(100)
return exitValue()
}
override fun exitValue(): Int {
check(!isRunning())
println("exitValue called")
return sshLib.ssh_channel_close(channel)
}
override fun destroy() {
if (sshLib.ssh_channel_is_open(channel) == 1) {
checkValidResult(sshLib.ssh_channel_close(channel))
}
sshLib.ssh_disconnect(session)
println("Destroy called")
}
private fun isRunning(): Boolean {
return sshLib.ssh_channel_is_open(channel) == 1
}
fun setup(session: ssh_session, commandName: String) {
val channel = sshLib.ssh_channel_new(session)
checkValidResult(sshLib.ssh_channel_open_session(channel))
checkValidResult(sshLib.ssh_channel_request_exec(channel, commandName))
this.session = session
this.channel = channel
}
}

View file

@ -20,29 +20,30 @@ class GRemoteSession @Inject constructor(
private val client = SshClient.setUpDefaultClient()
private var connectFuture: ConnectFuture? = null
private var ssh_session: ssh_session? = null
override fun exec(commandName: String, timeout: Int): Process {
println(commandName)
// val session = connectFuture!!.clientSession
//
// val auth = session.auth()
// auth.addListener { arg0 ->
// println("Authentication completed with " + if (arg0.isSuccess) "success" else "failure")
// }
//
// session.waitFor(
// listOf(
// ClientSession.ClientSessionEvent.WAIT_AUTH,
// ClientSession.ClientSessionEvent.CLOSED,
// ClientSession.ClientSessionEvent.AUTHED
// ), Duration.ofHours(2)
// )
// auth.verify()
// val process = processProvider.get()
val connectFuture = checkNotNull(connectFuture)
val session = this.ssh_session ?: throw Exception("Session is null")
val process = GProcessLibSsh()
val session = connectFuture.clientSession
val auth = session.auth()
auth.addListener { arg0 ->
println("Authentication completed with " + if (arg0.isSuccess) "success" else "failure")
}
session.waitFor(
listOf(
ClientSession.ClientSessionEvent.WAIT_AUTH,
ClientSession.ClientSessionEvent.CLOSED,
ClientSession.ClientSessionEvent.AUTHED
), Duration.ofHours(2)
)
auth.verify()
val process = processProvider.get()
process.setup(session, commandName)
return process
}
@ -53,33 +54,46 @@ class GRemoteSession @Inject constructor(
}
fun setup(uri: URIish) {
client.open()
val port = if (uri.port == -1) {
DEFAULT_SSH_PORT
} else
uri.port
val session = sshLib.ssh_new()
sshLib.ssh_options_set(session, 0, uri.host)
sshLib.ssh_connect(session)
checkValidResult(sshLib.ssh_userauth_publickey_auto(session, uri.user, null))
val filePasswordProvider =
FilePasswordProvider { _, _, _ ->
credentialsStateManager.updateState(CredentialsState.SshCredentialsRequested)
this.ssh_session = session
var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsState.CredentialsRequested) {
credentials = credentialsStateManager.currentCredentialsState
}
if (credentials !is CredentialsState.SshCredentialsAccepted)
null
else
credentials.password
}
client.filePasswordProvider = filePasswordProvider
val connectFuture = client.connect(uri.user, uri.host, port)
connectFuture.await()
this.connectFuture = connectFuture
// client.open()
//
// val port = if (uri.port == -1) {
// DEFAULT_SSH_PORT
// } else
// uri.port
//
// val filePasswordProvider =
// FilePasswordProvider { _, _, _ ->
// credentialsStateManager.updateState(CredentialsState.SshCredentialsRequested)
//
// var credentials = credentialsStateManager.currentCredentialsState
// while (credentials is CredentialsState.CredentialsRequested) {
// credentials = credentialsStateManager.currentCredentialsState
// }
//
// if (credentials !is CredentialsState.SshCredentialsAccepted)
// null
// else
// credentials.password
// }
//
// client.filePasswordProvider = filePasswordProvider
//
// val connectFuture = client.connect(uri.user, uri.host, port)
// connectFuture.await()
//
// this.connectFuture = connectFuture
}
}
fun checkValidResult(result: Int) {
if (result != 0)
throw Exception("Result is $result")
}

View file

@ -0,0 +1,47 @@
package com.jetpackduba.gitnuro.credentials
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.PointerType
class ssh_session : PointerType()
class ssh_channel : PointerType()
interface SSHLibrary : Library {
fun ssh_new(): ssh_session
fun ssh_disconnect(session: ssh_session): ssh_session
fun ssh_options_set(session: ssh_session, enumValue: Int, value: String)
fun ssh_connect(session: ssh_session) : Int
fun ssh_userauth_password(session: ssh_session, username: String, password: String): Int
fun ssh_userauth_publickey_auto(session: ssh_session, username: String?, password: String?): Int
fun ssh_channel_new(sshSession: ssh_session): ssh_channel
fun ssh_channel_open_session(sshChannel: ssh_channel): Int
fun ssh_channel_request_exec(sshChannel: ssh_channel, command: String): Int
fun ssh_channel_read(sshChannel: ssh_channel, buffer: ByteArray, count: Int, isStderr: Int): Int
fun ssh_channel_read_timeout(sshChannel: ssh_channel, buffer: ByteArray, count: Int, isStderr: Int, timeoutMs: Int): Int
fun ssh_channel_poll(sshChannel: ssh_channel, isStderr: Int): Int
fun ssh_channel_read_nonblocking(sshChannel: ssh_channel, buffer: ByteArray, count: Int, isStderr: Int): Int
fun ssh_channel_write(sshChannel: ssh_channel, data: ByteArray, len: Int): Int
fun ssh_channel_close(sshChannel: ssh_channel): Int
fun ssh_channel_send_eof(sshChannel: ssh_channel): Int
fun ssh_channel_free(sshChannel: ssh_channel)
fun ssh_channel_is_open(sshChannel: ssh_channel): Int
companion object {
val INSTANCE = Native.loadLibrary(
"libssh",
SSHLibrary::class.java
) as SSHLibrary
}
}
val sshLib = SSHLibrary.INSTANCE

View file

@ -0,0 +1,10 @@
package com.jetpackduba.gitnuro.credentials.streams
class Convertest {
companion object {
fun printIt(byteArray: ByteArray) {
val intList = byteArray.map { it.toInt() }
println(intList)
}
}
}

View file

@ -0,0 +1,65 @@
package com.jetpackduba.gitnuro.credentials.streams
import com.jetpackduba.gitnuro.credentials.sshLib
import com.jetpackduba.gitnuro.credentials.ssh_channel
import java.io.InputStream
class LibSshInputErrStream(private val sshChannel: ssh_channel) : InputStream() {
private var cancelled = false
private var calls = 0
// override fun read(b: ByteArray, off: Int, len: Int): Int {
// return sshLib.ssh_channel_read(sshChannel, b, len, 1)
// }
override fun read(): Int {
println("Read error")
val buffer = ByteArray(1)
return if (sshLib.ssh_channel_poll(sshChannel, 1) > 0) {
sshLib.ssh_channel_read(sshChannel, buffer, 1, 1)
val first = buffer.first()
println("Read error finished ${first.toInt()}")
print(String(buffer))
first.toInt()
} else
-1
}
// override fun read(b: ByteArray, off: Int, len: Int): Int {
// calls++
//
// println("Read error started, call $calls for len of $len with offset $off")
//
// val byteArray = ByteArray(len)
// val result = sshLib.ssh_channel_read(sshChannel, byteArray, len, 1)
// for(i in 0 until len) {
// b[off + i] = byteArray[i]
// }
//
// println("Read ended ${byteArray.map { it.toInt() }}")
//
// return result
// }
//
// override fun read(): Int {
// val buffer = ByteArray(1)
//
// sshLib.ssh_channel_read(sshChannel, buffer, 1, 1)
//
// val first = buffer.first()
//
// println("Error message is ${String(buffer)}")
//
// return first.toInt()
// }
override fun close() {
println("Closing error")
cancelled = true
}
}

View file

@ -0,0 +1,42 @@
package com.jetpackduba.gitnuro.credentials.streams
import com.jetpackduba.gitnuro.credentials.sshLib
import com.jetpackduba.gitnuro.credentials.ssh_channel
import java.io.InputStream
class LibSshInputStream(private val sshChannel: ssh_channel) : InputStream() {
private var calls = 0
override fun read(b: ByteArray, off: Int, len: Int): Int {
calls++
println("Read started, call $calls for len of $len with offset $off")
val byteArray = ByteArray(len)
val result = sshLib.ssh_channel_read(sshChannel, byteArray, len, 0)
for(i in 0 until len) {
b[off + i] = byteArray[i]
}
println("Read ended ${byteArray.map { it.toInt() }}")
return result
}
override fun read(): Int {
val buffer = ByteArray(1)
sshLib.ssh_channel_read(sshChannel, buffer, 1, 0)
val first = buffer.first()
print(String(buffer))
return first.toInt()
}
override fun close() {
println("Closing input")
}
}

View file

@ -0,0 +1,33 @@
package com.jetpackduba.gitnuro.credentials.streams
import com.jetpackduba.gitnuro.credentials.sshLib
import com.jetpackduba.gitnuro.credentials.ssh_channel
import java.io.OutputStream
import java.nio.ByteBuffer
class LibSshOutputStream(private val sshChannel: ssh_channel) : OutputStream() {
override fun write(b: Int) {
println("write int")
val byteArrayData = byteArrayOf(b.toByte())
write(byteArrayData)
println("write int finished")
}
override fun write(b: ByteArray) {
println("write byte")
sshLib.ssh_channel_write(sshChannel, b, b.size)
println("write byte finished")
}
override fun close() {
println("Closing output")
}
}
fun checkValidResult(result: Int) {
if (result != 0)
throw Exception("Result is $result")
}