From 762994a5f8adf10a346274c4df44b2bb416a6706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Mangano?= Date: Fri, 11 Sep 2026 11:22:15 +0900 Subject: [PATCH 1/2] Configure sessions declaratively MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This: session = LibSSH::Session.new session.host = "…" Becomes: session = LibSSH::Session.new(host: "…") Right now only host, port and user are migrated. I will add the other options next. The proxy jump option will receive a LibSSH::Options, turning libssh_ruby_options into a linked list. The key function is libssh_ruby_apply_options, designed to be callable from a libssh proxy jump callback which cannot use the Ruby API. --- ext/libssh_ruby/libssh_ruby.c | 1 + ext/libssh_ruby/libssh_ruby.h | 14 +++- ext/libssh_ruby/options.c | 115 +++++++++++++++++++++++++++++++ ext/libssh_ruby/session.c | 66 +++++++----------- lib/libssh.rb | 2 + lib/libssh/options.rb | 21 ++++++ lib/libssh/session.rb | 11 +++ spec/integration/channel_spec.rb | 11 +-- spec/integration/session_spec.rb | 75 +++++++------------- 9 files changed, 216 insertions(+), 100 deletions(-) create mode 100644 ext/libssh_ruby/options.c create mode 100644 lib/libssh/options.rb create mode 100644 lib/libssh/session.rb diff --git a/ext/libssh_ruby/libssh_ruby.c b/ext/libssh_ruby/libssh_ruby.c index 9539fd2..0aebb44 100644 --- a/ext/libssh_ruby/libssh_ruby.c +++ b/ext/libssh_ruby/libssh_ruby.c @@ -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(); diff --git a/ext/libssh_ruby/libssh_ruby.h b/ext/libssh_ruby/libssh_ruby.h index 76c9c40..d1702d5 100644 --- a/ext/libssh_ruby/libssh_ruby.h +++ b/ext/libssh_ruby/libssh_ruby.h @@ -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; diff --git a/ext/libssh_ruby/options.c b/ext/libssh_ruby/options.c new file mode 100644 index 0000000..2a2b078 --- /dev/null +++ b/ext/libssh_ruby/options.c @@ -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; +} diff --git a/ext/libssh_ruby/session.c b/ext/libssh_ruby/session.c index c0a9a46..dd632a6 100644 --- a/ext/libssh_ruby/session.c +++ b/ext/libssh_ruby/session.c @@ -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); } @@ -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); @@ -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); @@ -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; @@ -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); @@ -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); } diff --git a/lib/libssh.rb b/lib/libssh.rb index 716bd0c..2c2c6ce 100644 --- a/lib/libssh.rb +++ b/lib/libssh.rb @@ -1,4 +1,6 @@ require 'libssh/version' require 'libssh/libssh_ruby' require 'libssh/key' +require 'libssh/options' +require 'libssh/session' require 'libssh/channel' diff --git a/lib/libssh/options.rb b/lib/libssh/options.rb new file mode 100644 index 0000000..1f71b51 --- /dev/null +++ b/lib/libssh/options.rb @@ -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 diff --git a/lib/libssh/session.rb b/lib/libssh/session.rb new file mode 100644 index 0000000..b0d145e --- /dev/null +++ b/lib/libssh/session.rb @@ -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 diff --git a/spec/integration/channel_spec.rb b/spec/integration/channel_spec.rb index c2f076e..1ebd573 100644 --- a/spec/integration/channel_spec.rb +++ b/spec/integration/channel_spec.rb @@ -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 @@ -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 diff --git a/spec/integration/session_spec.rb b/spec/integration/session_spec.rb index f25827e..7f44f3f 100644 --- a/spec/integration/session_spec.rb +++ b/spec/integration/session_spec.rb @@ -1,60 +1,46 @@ require 'spec_helper' RSpec.describe LibSSH::Session do - let(:session) { described_class.new } + def session + @session ||= build + end + + def build(options = {}) + @session = described_class.new( + host: SshHelper.host, + port: DockerHelper.port, + user: SshHelper.user, + **options, + ) + end after do - session.disconnect + @session&.disconnect + @session = nil end - describe '#user=' do - it 'is nullable' do - session.user = nil + describe "#initialize" do + specify "user is nullable" do + expect { build(user: nil) }.not_to raise_error end - end - describe '#host=' do - it 'raises error on bad host' do - expect { session.host = nil }.to raise_error ArgumentError, 'Invalid host: nil' - expect { session.host = "foo_bar" }.to raise_error ArgumentError, 'Invalid host: "foo_bar"' + it "raises an exception on bad host" do + expect { build(host: "foo_bar") }.to raise_error ArgumentError, 'Invalid host: foo_bar' end end describe '#connect' do - context 'without hostname' do - it 'raises an error' do - expect { session.connect }.to raise_error(LibSSH::Error) - end + specify "host is required" do + expect { build(host: nil).connect }.to raise_error LibSSH::Error end - context 'with wrong port number' do - before do - session.host = SshHelper.host - session.port = DockerHelper.port + 1 - end - - it 'raises an error' do - expect { session.connect }.to raise_error(LibSSH::Error) - end - end - - context 'with valid condition' do - before do - session.host = SshHelper.host - session.port = DockerHelper.port - end - - it 'succeeds' do - expect(session.connect).to be_nil - end + it "raises an exception on closed port" do + expect { build(port: 2).connect }.to raise_error LibSSH::Error end end describe '#userauth_list' do before do - session.host = SshHelper.host - session.port = DockerHelper.port - session.user = SshHelper.user session.connect end @@ -77,9 +63,6 @@ describe '#userauth_publickey' do before do - session.host = SshHelper.host - session.port = DockerHelper.port - session.user = SshHelper.user session.connect end @@ -91,12 +74,6 @@ end describe '#userauth_publickey_auto' do - before do - session.host = SshHelper.host - session.port = DockerHelper.port - session.user = SshHelper.user - end - context 'without valid private key' do it 'is denied' do session.connect @@ -107,9 +84,6 @@ describe '#userauth_password' do before do - session.host = SshHelper.host - session.port = DockerHelper.port - session.user = SshHelper.user session.connect end @@ -128,9 +102,6 @@ describe '#userauth_kbdint' do before do - session.host = SshHelper.host - session.port = DockerHelper.port - session.user = SshHelper.user session.connect end From b9002ff09c46bdadc3ac8850e5328a0ff646ec0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Mangano?= Date: Tue, 15 Sep 2026 13:50:23 +0900 Subject: [PATCH 2/2] Migrate the remaining session options to LibSSH::Options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Though the code is uninterestingly verbose, I am not fond of macros and variadic functions would lose printf’s type checking. --- ext/libssh_ruby/libssh_ruby.h | 13 ++- ext/libssh_ruby/options.c | 103 ++++++++++++++++++++++-- ext/libssh_ruby/session.c | 133 +------------------------------ lib/libssh/options.rb | 13 ++- spec/integration/session_spec.rb | 13 +++ 5 files changed, 132 insertions(+), 143 deletions(-) diff --git a/ext/libssh_ruby/libssh_ruby.h b/ext/libssh_ruby/libssh_ruby.h index d1702d5..ddc0cf5 100644 --- a/ext/libssh_ruby/libssh_ruby.h +++ b/ext/libssh_ruby/libssh_ruby.h @@ -20,9 +20,16 @@ void Init_libssh_pki(void); // 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 + char* host; // SSH_OPTIONS_HOST + unsigned int port; // SSH_OPTIONS_PORT + char* user; // SSH_OPTIONS_USER + long timeout; // SSH_OPTIONS_TIMEOUT + const char* key_exchange; // SSH_OPTIONS_KEY_EXCHANGE + const char* hmac_c_s; // SSH_OPTIONS_HMAC_C_S + const char* hmac_s_c; // SSH_OPTIONS_HMAC_S_C + const char* hostkeys; // SSH_OPTIONS_HOSTKEYS + const char* publickey_accepted_types; // SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES + int stricthostkeycheck; // SSH_OPTIONS_STRICTHOSTKEYCHECK }; struct libssh_ruby_options* libssh_ruby_clone_options(VALUE options); diff --git a/ext/libssh_ruby/options.c b/ext/libssh_ruby/options.c index 2a2b078..0c57db4 100644 --- a/ext/libssh_ruby/options.c +++ b/ext/libssh_ruby/options.c @@ -1,11 +1,20 @@ #include "libssh_ruby.h" -static ID id_host, id_port, id_user; +static ID id_host, id_port, id_user, id_timeout, id_key_exchange, id_hmac_c_s, + id_hmac_s_c, id_hostkeys, id_publickey_accepted_types, + id_stricthostkeycheck; void Init_libssh_options(void) { - id_host = rb_intern("host"); - id_port = rb_intern("port"); - id_user = rb_intern("user"); + id_host = rb_intern("host"); + id_port = rb_intern("port"); + id_user = rb_intern("user"); + id_timeout = rb_intern("timeout"); + id_key_exchange = rb_intern("key_exchange"); + id_hmac_c_s = rb_intern("hmac_c_s"); + id_hmac_s_c = rb_intern("hmac_s_c"); + id_hostkeys = rb_intern("hostkeys"); + id_publickey_accepted_types = rb_intern("publickey_accepted_types"); + id_stricthostkeycheck = rb_intern("stricthostkeycheck"); } void libssh_ruby_free_options(struct libssh_ruby_options *options) { @@ -55,6 +64,69 @@ int libssh_ruby_apply_options(struct libssh_ruby_options *options, } } + if (options->timeout) { + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &options->timeout); + if (rc < 0) { + if (asprintf(error, "Invalid timeout: %ld", options->timeout) == -1) + *error = NULL; + return rc; + } + } + + if (options->key_exchange) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, options->key_exchange); + if (rc < 0) { + if (asprintf(error, "Invalid key exchange methods: %s", options->key_exchange) == -1) + *error = NULL; + return rc; + } + } + + if (options->hmac_c_s) { + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, options->hmac_c_s); + if (rc < 0) { + if (asprintf(error, "Invalid client-to-server HMAC algorithms: %s", options->hmac_c_s) == -1) + *error = NULL; + return rc; + } + } + + if (options->hmac_s_c) { + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, options->hmac_s_c); + if (rc < 0) { + if (asprintf(error, "Invalid server-to-client HMAC algorithms: %s", options->hmac_s_c) == -1) + *error = NULL; + return rc; + } + } + + if (options->hostkeys) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, options->hostkeys); + if (rc < 0) { + if (asprintf(error, "Invalid server host key types: %s", options->hostkeys) == -1) + *error = NULL; + return rc; + } + } + + if (options->publickey_accepted_types) { + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, options->publickey_accepted_types); + if (rc < 0) { + if (asprintf(error, "Invalid public key algorithms: %s", options->publickey_accepted_types) == -1) + *error = NULL; + return rc; + } + } + + if (options->stricthostkeycheck != -1) { + rc = ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &options->stricthostkeycheck); + if (rc < 0) { + if (asprintf(error, "Invalid strict host key check flag: %d", options->stricthostkeycheck) == -1) + *error = NULL; + return rc; + } + } + return rc; } @@ -84,14 +156,31 @@ static unsigned int get_uint(VALUE options, ID name) { return NIL_P(value) ? 0 : NUM2UINT(value); } +static long get_long(VALUE options, ID name) { + VALUE value = rb_funcallv_public(options, name, 0, NULL); + return NIL_P(value) ? 0 : NUM2LONG(value); +} + +static int get_bool(VALUE options, ID name) { + VALUE value = rb_funcallv_public(options, name, 0, NULL); + return NIL_P(value) ? -1 : RTEST(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); + out->host = get_string(in, id_host); + out->port = get_uint (in, id_port); + out->user = get_string(in, id_user); + out->timeout = get_long (in, id_timeout); + out->key_exchange = get_string(in, id_key_exchange); + out->hmac_c_s = get_string(in, id_hmac_c_s); + out->hmac_s_c = get_string(in, id_hmac_s_c); + out->hostkeys = get_string(in, id_hostkeys); + out->publickey_accepted_types = get_string(in, id_publickey_accepted_types); + out->stricthostkeycheck = get_bool (in, id_stricthostkeycheck); return Qnil; } diff --git a/ext/libssh_ruby/session.c b/ext/libssh_ruby/session.c index dd632a6..d4d0865 100644 --- a/ext/libssh_ruby/session.c +++ b/ext/libssh_ruby/session.c @@ -85,130 +85,6 @@ static VALUE m_set_log_verbosity(VALUE self, VALUE verbosity) { return Qnil; } -static VALUE set_string_option(VALUE self, enum ssh_options_e type, const char* name, VALUE str) { - const void* value = NIL_P(str) ? NULL : StringValueCStr(str); - if (ssh_options_set(libssh_ruby_get_session(self), type, value) < 0) - rb_raise(rb_eArgError, "Invalid %s: %+" PRIsVALUE, name, str); - return Qnil; -} - -static VALUE set_int_option(VALUE self, enum ssh_options_e type, VALUE i) { - Check_Type(i, T_FIXNUM); - int j = FIX2INT(i); - - ssh_session session = libssh_ruby_get_session(self); - if (ssh_options_set(session, type, &j) == SSH_ERROR) - libssh_ruby_raise(session); - - return Qnil; -} - -static VALUE set_long_option(VALUE self, enum ssh_options_e type, VALUE i) { - Check_Type(i, T_FIXNUM); - long j = FIX2LONG(i); - - ssh_session session = libssh_ruby_get_session(self); - if (ssh_options_set(session, type, &j) == SSH_ERROR) - libssh_ruby_raise(session); - - return Qnil; -} - -/* - * @overload timeout=(sec) - * Set a timeout for the connection in seconds - * @since 0.2.0 - * @param [Fixnum] sec - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_TIMEOUT) - */ -static VALUE m_set_timeout(VALUE self, VALUE sec) { - return set_long_option(self, SSH_OPTIONS_TIMEOUT, sec); -} - -static VALUE set_comma_separated_option(VALUE self, enum ssh_options_e type, - const char* name, VALUE ary) { - VALUE str; - - Check_Type(ary, T_ARRAY); - str = rb_ary_join(ary, rb_str_new_cstr(",")); - - return set_string_option(self, type, name, str); -} - -/* - * @overload key_exchange=(methods) - * Set the key exchange method to be used - * @since 0.2.0 - * @param [Array] methods - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_KEY_EXCHANGE) - */ -static VALUE m_set_key_exchange(VALUE self, VALUE kex) { - return set_comma_separated_option(self, SSH_OPTIONS_KEY_EXCHANGE, "key exchange methods", kex); -} - -/* - * @overload hmac_c_s=(methods) - * Set the allowed HMAC algorithms from the client to the server. - * @param [Array] methods - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_HMAC_C_S) - */ -static VALUE m_set_hmac_c_s(VALUE self, VALUE algos) { - return set_comma_separated_option(self, SSH_OPTIONS_HMAC_C_S, "client-to-server HMAC algorithms", algos); -} - -/* - * @overload hmac_s_c=(methods) - * Set the allowed HMAC algorithms from the server to the client. - * @param [Array] methods - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_HMAC_S_C) - */ -static VALUE m_set_hmac_s_c(VALUE self, VALUE algos) { - return set_comma_separated_option(self, SSH_OPTIONS_HMAC_S_C, "server-to-client HMAC algorithms", algos); -} - -/* - * @overload hostkeys=(key_types) - * Set the preferred server host key types - * @since 0.2.0 - * @param [Array] key_types - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_HOSTKEYS) - */ -static VALUE m_set_hostkeys(VALUE self, VALUE hostkeys) { - return set_comma_separated_option(self, SSH_OPTIONS_HOSTKEYS, "host key types", hostkeys); -} - -/* - * @overload publickey_accepted_types=(publickey_types) - * Set the preferred public key algorithms to be used for authentication. - * @param [Array] publickey_types - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES) - */ -static VALUE m_set_publickey_accepted_types(VALUE self, VALUE publickey_types) { - return set_comma_separated_option(self, - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, - "public key types", - publickey_types); -} - -/* - * @overload stricthostkeycheck=(enable) - * Set the parameter StrictHostKeyChecking to avoid asking about a fingerprint - * @since 0.2.0 - * @param [TrueClass, FalseClass] enable - * @return [nil] - * @see http://api.libssh.org/stable/group__libssh__session.html ssh_options_set(SSH_OPTIONS_STRICTHOSTKEYCHECK) - */ -static VALUE m_set_stricthostkeycheck(VALUE self, VALUE enable) { - return set_int_option(self, SSH_OPTIONS_STRICTHOSTKEYCHECK, - 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; @@ -457,14 +333,7 @@ void Init_libssh_session(void) { I(gssapi_mic); #undef I - rb_define_method(rb_cLibSSHSession, "log_verbosity=", m_set_log_verbosity, 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); - rb_define_method(rb_cLibSSHSession, "hmac_s_c=", m_set_hmac_s_c, 1); - rb_define_method(rb_cLibSSHSession, "hostkeys=", m_set_hostkeys, 1); - rb_define_method(rb_cLibSSHSession, "publickey_accepted_types=", m_set_publickey_accepted_types, 1); - rb_define_method(rb_cLibSSHSession, "stricthostkeycheck=", m_set_stricthostkeycheck, 1); + rb_define_method(rb_cLibSSHSession, "log_verbosity=", m_set_log_verbosity, 1); rb_define_method(rb_cLibSSHSession, "connect", m_connect, 0); rb_define_method(rb_cLibSSHSession, "disconnect", m_disconnect, 0); diff --git a/lib/libssh/options.rb b/lib/libssh/options.rb index 1f71b51..50c1e8c 100644 --- a/lib/libssh/options.rb +++ b/lib/libssh/options.rb @@ -7,10 +7,21 @@ module LibSSH # user: "alice", # host: "localhost", # port: 22, + # timeout: 5, # seconds + # key_exchange: "ecdh-sha2-nistp256,…", + # hmac_c_s: "hmac-sha2-512,…", + # hmac_s_c: "hmac-sha2-512,…", + # hostkeys: "ssh-rsa,…", + # publickey_accepted_types: "ssh-rsa,…", + # stricthostkeycheck: false, # ) # + # See also libssh’s documentation for ssh_options_set. + # class Options - attr_accessor :user, :host, :port + attr_accessor :user, :host, :port, :timeout, :key_exchange, :hmac_c_s, + :hmac_s_c, :hostkeys, :publickey_accepted_types, + :stricthostkeycheck def initialize(attrs) attrs.each do |key, value| diff --git a/spec/integration/session_spec.rb b/spec/integration/session_spec.rb index 7f44f3f..46206b3 100644 --- a/spec/integration/session_spec.rb +++ b/spec/integration/session_spec.rb @@ -27,6 +27,19 @@ def build(options = {}) it "raises an exception on bad host" do expect { build(host: "foo_bar") }.to raise_error ArgumentError, 'Invalid host: foo_bar' end + + specify "full options" do + options = { + timeout: 5, # seconds + key_exchange: "ecdh-sha2-nistp256", + hmac_c_s: "hmac-sha2-512", + hmac_s_c: "hmac-sha2-512", + hostkeys: "ssh-rsa", + publickey_accepted_types: "ssh-rsa", + stricthostkeycheck: false, + } + expect { build(options) }.not_to raise_error + end end describe '#connect' do