Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/libssh_ruby/libssh_ruby.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ void Init_libssh_ruby(void) {
rb_define_singleton_method(rb_mLibSSH, "version", m_version,
-1);

Init_libssh_options();
Init_libssh_session();
Init_libssh_channel();
Init_libssh_error();
Expand Down
14 changes: 13 additions & 1 deletion ext/libssh_ruby/libssh_ruby.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,32 @@ extern VALUE rb_mLibSSH;
extern VALUE rb_cLibSSHKey;

void Init_libssh_ruby(void);
void Init_libssh_options(void);
void Init_libssh_session(void);
void Init_libssh_channel(void);
void Init_libssh_error(void);
void Init_libssh_key(void);
void Init_libssh_pki(void);

[[noreturn]] void libssh_ruby_raise(ssh_session session);
// C equivalent of LibSSH::Options.
struct libssh_ruby_options {
char* host; // SSH_OPTIONS_HOST
unsigned int port; // SSH_OPTIONS_PORT
char* user; // SSH_OPTIONS_USER
};

struct libssh_ruby_options* libssh_ruby_clone_options(VALUE options);
int libssh_ruby_apply_options(struct libssh_ruby_options *options, ssh_session session, char **error);
void libssh_ruby_free_options(struct libssh_ruby_options *options);

// Underlying structure behind LibSSH::Session.
struct libssh_ruby_session {
ssh_session session;
struct libssh_ruby_options *options;
};

ssh_session libssh_ruby_get_session(VALUE session);
[[noreturn]] void libssh_ruby_raise(ssh_session session);

struct KeyHolderStruct {
ssh_key key;
Expand Down
115 changes: 115 additions & 0 deletions ext/libssh_ruby/options.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "libssh_ruby.h"

static ID id_host, id_port, id_user;

void Init_libssh_options(void) {
id_host = rb_intern("host");
id_port = rb_intern("port");
id_user = rb_intern("user");
}

void libssh_ruby_free_options(struct libssh_ruby_options *options) {
if (!options) return;
ruby_xfree(options->host);
ruby_xfree(options->user);
ruby_xfree(options);
}

/*
* Configure the session with the given options.
* Forward the return code of ssh_options_set.
* The caller must free() *error.
* Does not require the GVL.
*/
int libssh_ruby_apply_options(struct libssh_ruby_options *options,
ssh_session session,
char **error) {
int rc = SSH_OK;
*error = NULL;

if (options->host) {
// Host is first because it may set the user and port too.
rc = ssh_options_set(session, SSH_OPTIONS_HOST, options->host);
if (rc < 0) {
if (asprintf(error, "Invalid host: %s", options->host) == -1)
*error = NULL;
return rc;
}
}

if (options->port) {
rc = ssh_options_set(session, SSH_OPTIONS_PORT, &options->port);
if (rc < 0) {
if (asprintf(error, "Invalid port: %u", options->port) == -1)
*error = NULL;
return rc;
}
}

if (options->user) {
rc = ssh_options_set(session, SSH_OPTIONS_USER, options->user);
if (rc < 0) {
if (asprintf(error, "Invalid user: %s", options->user) == -1)
*error = NULL;
return rc;
}
}

return rc;
}

// libssh_ruby_clone_options ///////////////////////////////////////////////////

struct copy_options_args {
VALUE in;
struct libssh_ruby_options *out;
};

static char* clone_string(VALUE string) {
char* source = StringValuePtr(string);
size_t length = RSTRING_LEN(string);
char* copy = ruby_xmalloc(length + 1);
memcpy(copy, source, length);
copy[length] = '\0';
return copy;
}

static char* get_string(VALUE options, ID name) {
VALUE value = rb_funcallv_public(options, name, 0, NULL);
return NIL_P(value) ? NULL : clone_string(value);
}

static unsigned int get_uint(VALUE options, ID name) {
VALUE value = rb_funcallv_public(options, name, 0, NULL);
return NIL_P(value) ? 0 : NUM2UINT(value);
}

static VALUE copy_options(VALUE data) {
struct copy_options_args *args = (void*) data;
VALUE in = args->in;
struct libssh_ruby_options* out = args->out;

out->host = get_string(in, id_host);
out->port = get_uint(in, id_port);
out->user = get_string(in, id_user);

return Qnil;
}

/*
* Convert Ruby’s LibSSH::Options into C’s libssh_ruby_options.
* The caller must free the returned value with libssh_ruby_free_options.
*/
struct libssh_ruby_options* libssh_ruby_clone_options(VALUE options) {
int state;
struct copy_options_args args = {
.in = options,
.out = RB_ZALLOC(struct libssh_ruby_options),
};
rb_protect(copy_options, (VALUE) &args, &state);
if (state) {
libssh_ruby_free_options(args.out);
rb_jump_tag(state);
}
return args.out;
}
66 changes: 24 additions & 42 deletions ext/libssh_ruby/session.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ static void session_mark(RB_UNUSED_VAR(void *arg)) {}

static void session_free(void *arg) {
struct libssh_ruby_session *holder = arg;
if (holder->session != NULL) {
ssh_free(holder->session);
holder->session = NULL;
}
ssh_free(holder->session);
libssh_ruby_free_options(holder->options);
ruby_xfree(holder);
}

Expand Down Expand Up @@ -94,29 +92,6 @@ static VALUE set_string_option(VALUE self, enum ssh_options_e type, const char*
return Qnil;
}

/*
* @overload host=(host)
* Set the hostname or IP address to connect to.
* @param [String] host
* @return [nil]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_HOST)
*/
static VALUE m_set_host(VALUE self, VALUE host) {
return set_string_option(self, SSH_OPTIONS_HOST, "host", host);
}

/*
* @overload user=(user)
* Set the username for authentication.
* @since 0.2.0
* @param [String] user
* @return [nil]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_USER)
*/
static VALUE m_set_user(VALUE self, VALUE user) {
return set_string_option(self, SSH_OPTIONS_USER, "user", user);
}

static VALUE set_int_option(VALUE self, enum ssh_options_e type, VALUE i) {
Check_Type(i, T_FIXNUM);
int j = FIX2INT(i);
Expand All @@ -128,18 +103,6 @@ static VALUE set_int_option(VALUE self, enum ssh_options_e type, VALUE i) {
return Qnil;
}

/*
* @overload port=(port)
* Set the port to connect to.
* @since 0.2.0
* @param [Fixnum] port
* @return [nil]
* @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_PORT)
*/
static VALUE m_set_port(VALUE self, VALUE port) {
return set_int_option(self, SSH_OPTIONS_PORT, port);
}

static VALUE set_long_option(VALUE self, enum ssh_options_e type, VALUE i) {
Check_Type(i, T_FIXNUM);
long j = FIX2LONG(i);
Expand Down Expand Up @@ -246,6 +209,26 @@ static VALUE m_set_stricthostkeycheck(VALUE self, VALUE enable) {
INT2FIX(RTEST(enable) ? 1 : 0));
}

// LibSSH::Session#set_options(LibSSH::Options)
static VALUE m_set_options(VALUE self, VALUE value) {
struct libssh_ruby_options **options = &unwrap_session(self)->options;
if (*options) rb_raise(rb_eArgError, "Cannot set options twice.");
*options = libssh_ruby_clone_options(value);

ssh_session session = libssh_ruby_get_session(self);
char *error;
int rc = libssh_ruby_apply_options(*options, session, &error);
if (error) {
VALUE exception_argv[1] = { rb_str_new_cstr(error) };
free(error);
rb_exc_raise(rb_class_new_instance(1, exception_argv, rb_eArgError));
} else if (rc < 0) {
libssh_ruby_raise(session);
}

return Qnil;
}

struct nogvl_session_args {
ssh_session session;
int rc;
Expand Down Expand Up @@ -475,9 +458,6 @@ void Init_libssh_session(void) {
#undef I

rb_define_method(rb_cLibSSHSession, "log_verbosity=", m_set_log_verbosity, 1);
rb_define_method(rb_cLibSSHSession, "host=", m_set_host, 1);
rb_define_method(rb_cLibSSHSession, "user=", m_set_user, 1);
rb_define_method(rb_cLibSSHSession, "port=", m_set_port, 1);
rb_define_method(rb_cLibSSHSession, "timeout=", m_set_timeout, 1);
rb_define_method(rb_cLibSSHSession, "key_exchange=", m_set_key_exchange, 1);
rb_define_method(rb_cLibSSHSession, "hmac_c_s=", m_set_hmac_c_s, 1);
Expand All @@ -497,4 +477,6 @@ void Init_libssh_session(void) {
rb_define_method(rb_cLibSSHSession, "userauth_kbdint", m_userauth_kbdint, 0);
rb_define_method(rb_cLibSSHSession, "userauth_kbdint_getnprompts", m_userauth_kbdint_getnpromts, 0);
rb_define_method(rb_cLibSSHSession, "userauth_kbdint_setanswer", m_userauth_kbdint_setanswer, 2);

rb_define_private_method(rb_cLibSSHSession, "set_options", m_set_options, 1);
}
2 changes: 2 additions & 0 deletions lib/libssh.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require 'libssh/version'
require 'libssh/libssh_ruby'
require 'libssh/key'
require 'libssh/options'
require 'libssh/session'
require 'libssh/channel'
21 changes: 21 additions & 0 deletions lib/libssh/options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "libssh/libssh_ruby"

module LibSSH
# Declarative options for LibSSH::Session.
#
# LibSSH::Options.new(
# user: "alice",
# host: "localhost",
# port: 22,
# )
#
class Options
attr_accessor :user, :host, :port

def initialize(attrs)
attrs.each do |key, value|
send("#{key}=", value)
end
end
end
end
11 changes: 11 additions & 0 deletions lib/libssh/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "libssh/libssh_ruby"

module LibSSH
class Session
# Configure the session with a LibSSH::Options or its Hash equivalent.
def initialize(options)
options = Options.new(options) unless options.is_a? Options
set_options(options)
end
end
end
11 changes: 6 additions & 5 deletions spec/integration/channel_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

RSpec.describe LibSSH::Channel do
let(:session) do
@session = LibSSH::Session.new
@session.host = SshHelper.host
@session.port = DockerHelper.port
@session.user = SshHelper.user
@session = LibSSH::Session.new(
host: SshHelper.host,
port: DockerHelper.port,
user: SshHelper.user,
)
@session.connect
@session.userauth_password(SshHelper.password)
@session
Expand All @@ -21,7 +22,7 @@
describe '#open_session' do
context 'without connected session' do
it 'raises an error' do
channel = described_class.new(LibSSH::Session.new)
channel = described_class.new(LibSSH::Session.new(host: SshHelper.host))
expect { channel.open_session { :ng } }.to raise_error(ArgumentError)
end
end
Expand Down
Loading