Added basic ssh auth in rust without error handling

This commit is contained in:
Abdelilah El Aissaoui 2023-09-20 10:26:02 +02:00
parent bb49e9482c
commit 6dee4fbc93
No known key found for this signature in database
GPG key ID: 7587FC860F594869
15 changed files with 239 additions and 267 deletions

View file

@ -205,7 +205,7 @@ fun generateKotlinFromUdl() {
workingDir = File(project.projectDir, "rs")
commandLine = listOf(
"cargo", "run", "--features=uniffi/cli",
"--bin", "uniffi-bindgen", "generate", "src/repository_watcher.udl",
"--bin", "uniffi-bindgen", "generate", "src/gitnuro.udl",
"--language", "kotlin",
"--out-dir", rustGeneratedSource
)

View file

@ -13,6 +13,7 @@ name = "gitnuro_rs"
uniffi = { version = "0.24.1" }
notify = "6.0.1"
thiserror = "1.0.43"
libssh-rs = "0.2.1"
[build-dependencies]
uniffi = { version = "0.24.1", features = [ "build" ] }

View file

@ -1,3 +1,3 @@
fn main() {
uniffi::generate_scaffolding("src/repository_watcher.udl").unwrap();
uniffi::generate_scaffolding("src/gitnuro.udl").unwrap();
}

46
rs/src/gitnuro.udl Normal file
View file

@ -0,0 +1,46 @@
namespace gitnuro {
[Throws=WatcherInitError]
void watch_directory(string path, WatchDirectoryNotifier checker);
};
callback interface WatchDirectoryNotifier {
boolean should_keep_looping();
void detected_change(sequence<string> paths);
};
[Error]
interface WatcherInitError {
Generic(string error);
Io(string error);
PathNotFound();
WatchNotFound();
InvalidConfig();
MaxFilesWatch();
};
interface Session {
constructor();
void setup(string host, string? user, i32 port);
void public_key_auth();
void password_auth(string password);
void disconnect();
};
interface Channel {
constructor(Session session);
void open_session();
boolean is_open();
void close();
void request_exec(string command);
boolean poll_has_bytes(boolean is_stderr);
ReadResult read(boolean is_stderr, u64 len);
void write_byte(i32 byte);
void write_bytes(bytes data);
};
dictionary ReadResult {
u64 read_count;
bytes data;
};

View file

@ -1,13 +1,18 @@
extern crate notify;
use std::fmt::Debug;
use std::io::{Write};
use std::path::Path;
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::{Arc, RwLock};
use std::time::Duration;
use libssh_rs::{PollStatus, SshOption};
use notify::{Config, Error, ErrorKind, Event, RecommendedWatcher, RecursiveMode, Watcher};
uniffi::include_scaffolding!("repository_watcher");
uniffi::include_scaffolding!("gitnuro");
const ACCEPTED_SSH_TYPES: &str = "ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss";
fn watch_directory(
path: String,
@ -114,3 +119,125 @@ impl WatcherInitErrorConverter for ErrorKind {
}
}
}
struct Session {
pub session: RwLock<libssh_rs::Session>,
}
impl Session {
fn new() -> Self {
let session = libssh_rs::Session::new().unwrap();
Session {
session: RwLock::new(session)
}
}
fn setup(&self, host: String, user: Option<String>, port: i32) {
let session = self.session.write().unwrap();
session.set_option(SshOption::Hostname(host)).unwrap();
if let Some(user) = user {
session.set_option(SshOption::User(Some(user))).unwrap();
}
if let Ok(port) = port.try_into() {
session.set_option(SshOption::Port(port)).unwrap();
}
session.set_option(SshOption::PublicKeyAcceptedTypes(ACCEPTED_SSH_TYPES.to_string())).unwrap();
session.options_parse_config(None).unwrap();
session.connect().unwrap();
}
fn public_key_auth(&self) {
let session = self.session.write().unwrap();
session.userauth_public_key_auto(None, Some("")).unwrap();
}
fn password_auth(&self, password: String) {
let session = self.session.write().unwrap();
session.userauth_password(None, Some(&password)).unwrap();
}
fn disconnect(&self) {
let session = self.session.write().unwrap();
session.disconnect()
}
}
struct Channel {
channel: RwLock<libssh_rs::Channel>,
}
unsafe impl Send for Channel {}
unsafe impl Sync for Channel {}
impl Channel {
fn new(session: Arc<Session>) -> Self {
let session = session.session.write().unwrap();
let channel = session.new_channel().unwrap();
Channel {
channel: RwLock::new(channel)
}
}
fn open_session(&self) {
let channel = self.channel.write().unwrap();
channel.open_session().unwrap();
}
fn is_open(&self) -> bool {
let channel = self.channel.write().unwrap();
channel.is_open()
}
fn close(&self) {
let channel = self.channel.write().unwrap();
channel.close().unwrap();
}
fn request_exec(&self, command: String) {
let channel = self.channel.write().unwrap();
channel.request_exec(&command).unwrap();
}
fn poll_has_bytes(&self, is_stderr: bool) -> bool {
let channel = self.channel.write().unwrap();
let poll_timeout = channel.poll_timeout(is_stderr, None).unwrap();
match poll_timeout {
PollStatus::AvailableBytes(count) => count > 0,
PollStatus::EndOfFile => false
}
}
fn read(&self, is_stderr: bool, len: u64) -> ReadResult {
let ulen = len as usize;
let channel = self.channel.write().unwrap();
let mut buffer = vec![0; ulen];
let read = channel.read_timeout(&mut buffer, is_stderr, None).unwrap();
ReadResult {
read_count: read as u64,
data: buffer,
}
}
fn write_byte(&self, byte: i32) {
let channel = self.channel.write().unwrap();
channel.stdin().write_all(&byte.to_ne_bytes()).unwrap();
}
fn write_bytes(&self, data: Vec<u8>) {
let channel = self.channel.write().unwrap();
channel.stdin().write_all(&data).unwrap();
}
}
pub struct ReadResult {
read_count: u64,
data: Vec<u8>,
}

View file

@ -1,20 +0,0 @@
namespace gitnuro {
[Throws=WatcherInitError]
void watch_directory(string path, WatchDirectoryNotifier checker);
};
callback interface WatchDirectoryNotifier {
boolean should_keep_looping();
void detected_change(sequence<string> paths);
};
[Error]
interface WatcherInitError {
Generic(string error);
Io(string error);
PathNotFound();
WatchNotFound();
InvalidConfig();
MaxFilesWatch();
};

View file

@ -1,13 +1,14 @@
package com.jetpackduba.gitnuro.credentials
import com.jetpackduba.gitnuro.ssh.libssh.LibSshChannel
import com.jetpackduba.gitnuro.ssh.libssh.LibSshSession
import uniffi.gitnuro.Channel
import uniffi.gitnuro.Session
import java.io.InputStream
import java.io.OutputStream
class GProcessLibSsh : Process() {
private lateinit var channel: LibSshChannel
private lateinit var session: LibSshSession
private lateinit var session: Session
private val outputStream by lazy {
channel.outputStream
@ -42,7 +43,9 @@ class GProcessLibSsh : Process() {
check(!isRunning())
println("exitValue called")
return channel.close()
channel.close()
return 0
}
override fun destroy() {
@ -57,8 +60,8 @@ class GProcessLibSsh : Process() {
return channel.isOpen()
}
fun setup(session: LibSshSession, commandName: String) {
val channel = session.createChannel()
fun setup(session: Session, commandName: String) {
val channel = LibSshChannel(session)
channel.openSession()
channel.requestExec(commandName)

View file

@ -1,21 +1,17 @@
package com.jetpackduba.gitnuro.credentials
import com.jetpackduba.gitnuro.ssh.libssh.LibSshOptions
import com.jetpackduba.gitnuro.ssh.libssh.LibSshSession
import kotlinx.coroutines.CancellationException
import org.eclipse.jgit.transport.RemoteSession
import org.eclipse.jgit.transport.URIish
import uniffi.gitnuro.Session
import javax.inject.Inject
import javax.inject.Provider
private const val DEFAULT_SSH_PORT = 22
class GRemoteSession @Inject constructor(
private val processSession: Provider<LibSshSession>,
private val credentialsStateManager: CredentialsStateManager,
) : RemoteSession {
private var session: LibSshSession? = null
private var session: Session? = null
override fun exec(commandName: String, timeout: Int): Process {
println("Running command $commandName")
@ -32,44 +28,33 @@ class GRemoteSession @Inject constructor(
}
fun setup(uri: URIish) {
val session = processSession.get()
session.setOptions(LibSshOptions.SSH_OPTIONS_HOST, uri.host)
val session = Session()
session.setup(uri.host, uri.user, uri.port)
uri.user?.let {
session.setOptions(LibSshOptions.SSH_OPTIONS_USER, uri.user)
}
var result = session.publicKeyAuth()
session.setOptions(
LibSshOptions.SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
"ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss"
)
session.loadOptionsFromConfig()
session.connect()
var result = session.userAuthPublicKeyAuto(null, "")
if (result == 1) {
credentialsStateManager.updateState(CredentialsRequested.SshCredentialsRequested)
var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsRequested) {
credentials = credentialsStateManager.currentCredentialsState
}
val password = if (credentials !is CredentialsAccepted.SshCredentialsAccepted)
throw CancellationException("Credentials cancelled")
else
credentials.password
result = session.userAuthPublicKeyAuto(null, password)
if (result != 0) {
result = session.userAuthPassword(password)
}
}
if (result != 0)
throw Exception("Something went wrong with authentication. Code $result")
// if (result == 1) {
// credentialsStateManager.updateState(CredentialsRequested.SshCredentialsRequested)
//
// var credentials = credentialsStateManager.currentCredentialsState
// while (credentials is CredentialsRequested) {
// credentials = credentialsStateManager.currentCredentialsState
// }
//
// val password = if (credentials !is CredentialsAccepted.SshCredentialsAccepted)
// throw CancellationException("Credentials cancelled")
// else
// credentials.password
//
// result = session.userAuthPublicKeyAuto(null, password)
//
// if (result != 0) {
// result = session.userAuthPassword(password)
// }
// }
//
// if (result != 0)
// throw Exception("Something went wrong with authentication. Code $result")
this.session = session
}

View file

@ -3,30 +3,29 @@ package com.jetpackduba.gitnuro.ssh.libssh
import com.jetpackduba.gitnuro.ssh.libssh.streams.LibSshChannelInputErrStream
import com.jetpackduba.gitnuro.ssh.libssh.streams.LibSshChannelInputStream
import com.jetpackduba.gitnuro.ssh.libssh.streams.LibSshChannelOutputStream
import uniffi.gitnuro.Channel
import uniffi.gitnuro.Session
class LibSshChannel internal constructor(sshSession: ssh_session) {
private val sshLib = SSHLibrary.INSTANCE
private var channel: ssh_channel = sshLib.ssh_channel_new(sshSession)
class LibSshChannel internal constructor(sshSession: Session) {
private var channel = Channel(sshSession)
val outputStream = LibSshChannelOutputStream(channel)
val inputStream = LibSshChannelInputStream(channel)
val errorOutputStream = LibSshChannelInputErrStream(channel)
fun openSession() {
sshLib.ssh_channel_open_session(channel)
channel.openSession()
}
fun requestExec(commandName: String) {
sshLib.ssh_channel_request_exec(channel, commandName)
channel.requestExec(commandName)
}
fun isOpen(): Boolean {
return sshLib.ssh_channel_is_open(channel) == 1
return channel.isOpen()
}
fun close(): Int {
return sshLib.ssh_channel_close(channel)
fun close() {
channel.close()
}
}

View file

@ -1,50 +0,0 @@
package com.jetpackduba.gitnuro.ssh.libssh
/**
* Enum based on the enum "ssh_options_e" of libssh/libssh.h
*/
enum class LibSshOptions {
SSH_OPTIONS_HOST,
SSH_OPTIONS_PORT,
SSH_OPTIONS_PORT_STR,
SSH_OPTIONS_FD,
SSH_OPTIONS_USER,
SSH_OPTIONS_SSH_DIR,
SSH_OPTIONS_IDENTITY,
SSH_OPTIONS_ADD_IDENTITY,
SSH_OPTIONS_KNOWNHOSTS,
SSH_OPTIONS_TIMEOUT,
SSH_OPTIONS_TIMEOUT_USEC,
SSH_OPTIONS_SSH1,
SSH_OPTIONS_SSH2,
SSH_OPTIONS_LOG_VERBOSITY,
SSH_OPTIONS_LOG_VERBOSITY_STR,
SSH_OPTIONS_CIPHERS_C_S,
SSH_OPTIONS_CIPHERS_S_C,
SSH_OPTIONS_COMPRESSION_C_S,
SSH_OPTIONS_COMPRESSION_S_C,
SSH_OPTIONS_PROXYCOMMAND,
SSH_OPTIONS_BINDADDR,
SSH_OPTIONS_STRICTHOSTKEYCHECK,
SSH_OPTIONS_COMPRESSION,
SSH_OPTIONS_COMPRESSION_LEVEL,
SSH_OPTIONS_KEY_EXCHANGE,
SSH_OPTIONS_HOSTKEYS,
SSH_OPTIONS_GSSAPI_SERVER_IDENTITY,
SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY,
SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS,
SSH_OPTIONS_HMAC_C_S,
SSH_OPTIONS_HMAC_S_C,
SSH_OPTIONS_PASSWORD_AUTH,
SSH_OPTIONS_PUBKEY_AUTH,
SSH_OPTIONS_KBDINT_AUTH,
SSH_OPTIONS_GSSAPI_AUTH,
SSH_OPTIONS_GLOBAL_KNOWNHOSTS,
SSH_OPTIONS_NODELAY,
SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
SSH_OPTIONS_PROCESS_CONFIG,
SSH_OPTIONS_REKEY_DATA,
SSH_OPTIONS_REKEY_TIME,
SSH_OPTIONS_RSA_MIN_SIZE,
SSH_OPTIONS_IDENTITY_AGENT
}

View file

@ -1,62 +0,0 @@
package com.jetpackduba.gitnuro.ssh.libssh
import com.jetpackduba.gitnuro.logging.printError
import com.jetpackduba.gitnuro.ssh.libssh.streams.checkValidResult
import javax.inject.Inject
private const val TAG = "LibSshSession"
class LibSshSession @Inject constructor() {
private val sshLib = SSHLibrary.INSTANCE
private var session: ssh_session = sshLib.ssh_new()
private var channel: LibSshChannel? = null
fun setOptions(option: LibSshOptions, value: String) {
sshLib.ssh_options_set(session, option.ordinal, value)
}
fun loadOptionsFromConfig() {
checkValidResult("loadOptionsFromConfig", sshLib.ssh_options_parse_config(session, null))
}
fun connect() {
sshLib.ssh_connect(session)
}
fun userAuthPublicKeyAuto(username: String?, password: String?): Int {
val result = sshLib.ssh_userauth_publickey_auto(session, username, password)
if (result != 0)
printError(TAG, "Result is: $result.\nError is: ${getError()}")
return result
}
fun userAuthPassword(password: String): Int {
val result = sshLib.ssh_userauth_password(session, null, password)
if (result != 0)
printError(TAG, "Result is: $result.\nError is: ${getError()}")
return result
}
fun createChannel(): LibSshChannel {
val newChannel = LibSshChannel(session)
this.channel = newChannel
return newChannel
}
private fun getError(): String {
return sshLib.ssh_get_error(session)
}
fun disconnect() {
sshLib.ssh_disconnect(session)
}
}

View file

@ -1,44 +0,0 @@
package com.jetpackduba.gitnuro.ssh.libssh
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.PointerType
class ssh_session : PointerType()
class ssh_channel : PointerType()
@Suppress("FunctionName")
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_options_parse_config(session: ssh_session, fileName: String?): Int
fun ssh_connect(session: ssh_session): Int
fun ssh_userauth_publickey_auto(session: ssh_session, username: String?, password: String?): Int
fun ssh_userauth_password(session: ssh_session, username: String?, password: String?): Int
fun ssh_get_error(session: ssh_session): String
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_poll(sshChannel: ssh_channel, 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_is_open(sshChannel: ssh_channel): Int
companion object {
val INSTANCE = Native.load("ssh", SSHLibrary::class.java) as SSHLibrary
}
}

View file

@ -1,20 +1,17 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams
import com.jetpackduba.gitnuro.ssh.libssh.SSHLibrary
import com.jetpackduba.gitnuro.ssh.libssh.ssh_channel
import uniffi.gitnuro.Channel
import java.io.InputStream
class LibSshChannelInputErrStream(private val sshChannel: ssh_channel) : InputStream() {
class LibSshChannelInputErrStream(private val sshChannel: Channel) : InputStream() {
private var cancelled = false
private val sshLib = SSHLibrary.INSTANCE
override fun read(): Int {
val buffer = ByteArray(1)
return if (sshChannel.pollHasBytes(true)) {
val read = sshChannel.read(true, 1u)
val byteArray = read.data
return if (sshLib.ssh_channel_poll(sshChannel, 1) > 0) {
sshLib.ssh_channel_read(sshChannel, buffer, 1, 1)
val first = buffer.first()
val first = byteArray.first()
first.toInt()
} else

View file

@ -1,28 +1,26 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams
import com.jetpackduba.gitnuro.ssh.libssh.SSHLibrary
import com.jetpackduba.gitnuro.ssh.libssh.ssh_channel
import uniffi.gitnuro.Channel
import java.io.InputStream
class LibSshChannelInputStream(private val sshChannel: ssh_channel) : InputStream() {
private val sshLib = SSHLibrary.INSTANCE
class LibSshChannelInputStream(private val sshChannel: Channel) : InputStream() {
override fun read(b: ByteArray, off: Int, len: Int): Int {
val byteArray = ByteArray(len)
val result = sshLib.ssh_channel_read(sshChannel, byteArray, len, 0)
val result = sshChannel.read(false, len.toULong())
val byteArray = result.data
val read = result.readCount
for (i in 0 until len) {
b[off + i] = byteArray[i]
}
return result
return read.toInt()
}
override fun read(): Int {
val buffer = ByteArray(1)
sshLib.ssh_channel_read(sshChannel, buffer, 1, 0)
val result = sshChannel.read(false, 1u)
val first = buffer.first()
val first = result.data.first()
return first.toInt()
}

View file

@ -1,26 +1,18 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams
import com.jetpackduba.gitnuro.ssh.libssh.SSHLibrary
import com.jetpackduba.gitnuro.ssh.libssh.ssh_channel
import uniffi.gitnuro.Channel
import java.io.OutputStream
class LibSshChannelOutputStream(private val sshChannel: ssh_channel) : OutputStream() {
private val sshLib = SSHLibrary.INSTANCE
class LibSshChannelOutputStream(private val sshChannel: Channel) : OutputStream() {
override fun write(b: Int) {
val byteArrayData = byteArrayOf(b.toByte())
write(byteArrayData)
}
override fun write(b: ByteArray) {
sshLib.ssh_channel_write(sshChannel, b, b.size)
sshChannel.writeBytes(b)
}
override fun close() {
}
}
fun checkValidResult(tag: String, result: Int) {
if (result != 0)
throw Exception("$tag - Result is $result")
}