diff --git a/recipes/pkg/ssh_terminal/README b/recipes/pkg/ssh_terminal/README new file mode 100644 index 0000000..735739a --- /dev/null +++ b/recipes/pkg/ssh_terminal/README @@ -0,0 +1,14 @@ + + SSH terminal client + +A Nitpicker terminal that connects to a shell on a remote SSH server. + +It is configured with a "host" file found in its root directory with the +following format: + + +The port, pass, and known attributes are optional. The client will first +try to authenticate with a keypair found in the root directory with a +fallback to password authentication. The client will automatically +disconnect from hosts that are not found in "/known_hosts", unless the +"known" attribute is set to a false in the host file. diff --git a/recipes/pkg/ssh_terminal/archives b/recipes/pkg/ssh_terminal/archives new file mode 100644 index 0000000..28c298f --- /dev/null +++ b/recipes/pkg/ssh_terminal/archives @@ -0,0 +1,10 @@ +_/pkg/terminal +_/src/libc +_/src/libcrypto +_/src/nit_fb +_/src/vfs +_/src/zlib +_/src/vfs_jitterentropy +_/src/libssh +_/src/ssh_client +_/src/vfs_lwip diff --git a/recipes/pkg/ssh_terminal/hash b/recipes/pkg/ssh_terminal/hash new file mode 100644 index 0000000..1d6b0ea --- /dev/null +++ b/recipes/pkg/ssh_terminal/hash @@ -0,0 +1 @@ +2018-07-17-k fb159d9fe48bb3081f79f0d78e4b41dba0a164a4 diff --git a/recipes/pkg/ssh_terminal/runtime b/recipes/pkg/ssh_terminal/runtime new file mode 100644 index 0000000..1dd2afb --- /dev/null +++ b/recipes/pkg/ssh_terminal/runtime @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/recipes/src/ssh_client/content.mk b/recipes/src/ssh_client/content.mk new file mode 100644 index 0000000..865d866 --- /dev/null +++ b/recipes/src/ssh_client/content.mk @@ -0,0 +1,2 @@ +SRC_DIR = src/app/ssh_client +include $(GENODE_DIR)/repos/base/recipes/src/content.inc diff --git a/recipes/src/ssh_client/hash b/recipes/src/ssh_client/hash new file mode 100644 index 0000000..80391ce --- /dev/null +++ b/recipes/src/ssh_client/hash @@ -0,0 +1 @@ +2018-07-17 54242fdfe92139b894d789ecb438f632d1214ff9 diff --git a/recipes/src/ssh_client/used_apis b/recipes/src/ssh_client/used_apis new file mode 100644 index 0000000..75c44c8 --- /dev/null +++ b/recipes/src/ssh_client/used_apis @@ -0,0 +1,6 @@ +base +libc +libssh +os +terminal_session +vfs diff --git a/run/ssh_client.run b/run/ssh_client.run new file mode 100644 index 0000000..2b12d35 --- /dev/null +++ b/run/ssh_client.run @@ -0,0 +1,177 @@ +# +# \brief Test of ssh_client +# \author Emery Hemingway +# + +if {[have_spec odroid_xu] || [have_spec linux] || + [expr [have_spec imx53] && [have_spec trustzone]]} { + puts "Run script does not support this platform." + exit 0 +} + +set build_components { + app/ssh_client + drivers/nic + lib/vfs/import + lib/vfs/lwip +} + +proc gpio_drv { } { if {[have_spec rpi] && [have_spec hw]} { return hw_gpio_drv } + if {[have_spec rpi] && [have_spec foc]} { return foc_gpio_drv } + return gpio_drv } + +lappend_if [have_spec gpio] build_components drivers/gpio + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +lappend_if [expr {[nic_drv_binary] == "nic_drv"}] build_components drivers/nic +lappend_if [expr {[nic_drv_binary] == "usb_drv"}] build_components drivers/usb + +build $build_components + +create_boot_directory + +import_from_depot \ + genodelabs/src/[base_src] \ + genodelabs/pkg/[drivers_interactive_pkg] \ + genodelabs/src/init \ + genodelabs/pkg/terminal \ + genodelabs/src/input_filter \ + +append config { + + + + + + + + + + + + + + + + } + +append_platform_drv_config + +append_if [have_spec gpio] config " + + + + + " + +append config { + + + + + + + + + } [nic_drv_config] { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2000-01-01 00:00 + 01234567890123456789 + + + + + + + + + + + + +} + +install_config $config + +# generic modules +set boot_modules { + libc.lib.so + libcrypto.lib.so + libm.lib.so + libssh.lib.so + ssh_client + vfs_import.lib.so + vfs.lib.so + vfs_lwip.lib.so + zlib.lib.so +} + +# platform-specific modules +append_platform_drv_boot_modules + +lappend boot_modules [nic_drv_binary] + +lappend_if [have_spec ps2] boot_modules ps2_drv +lappend_if [have_spec framebuffer] boot_modules fb_drv + +lappend_if [have_spec gpio] boot_modules [gpio_drv] + +build_boot_image $boot_modules + +append_if [have_spec x86] qemu_args " -net nic,model=e1000 " +append_if [have_spec lan9118] qemu_args " -net nic,model=lan9118 " +append qemu_args " -net user -net dump,file=[run_dir].pcap" + +run_genode_until forever diff --git a/src/app/ssh_client/component.cc b/src/app/ssh_client/component.cc new file mode 100644 index 0000000..076d02c --- /dev/null +++ b/src/app/ssh_client/component.cc @@ -0,0 +1,330 @@ +/* + * \brief SSH client as a Terminal client + * \author Prashanth Mundkur + * \author Emery Hemingway + * \date 2018-06-18 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include + +/* Libssh includes */ +#include +#include + +/* Libc includes */ +#include +#include + + +namespace Ssh_client { + using namespace Genode; + struct Main; + + typedef Genode::String<64> String; + + String string_attr(Xml_node const &node, + char const *key, char const *def = "") { + return node.attribute_value(key, String(def)); } +} + + +struct Ssh_client::Main +{ + Libc::Env &_env; + + Terminal::Connection _terminal { _env }; + + Genode::Signal_handler
_terminal_handler { + _env.ep(), *this, &Main::_handle_terminal }; + + Genode::Signal_handler
_size_handler { + _env.ep(), *this, &Main::_handle_size }; + + Libc::Select_handler
_select_handler { + *this, &Main::_select_ready }; + + ssh_session _session = ssh_new(); + ssh_channel _channel = NULL; + + typedef Genode::String<128> String; + String _hostname { }; + String _password { }; + + /* must the host be known */ + bool _host_known = true; + + void _exit(int code) + { + if (_session) { + if (_channel) { + ssh_channel_free(_channel); + } + ssh_free(_session); + } + + ssh_finalize(); + _env.parent().exit(code); + Genode::sleep_forever(); + } + + void _die() + { + if (_session) + Genode::error(ssh_get_error(_session)); + _exit(~0); + } + + void _handle_terminal() + { + Libc::with_libc([&] () { + while (_terminal.avail()) { + char buf[256]; + size_t n = _terminal.read(buf, sizeof(buf)); + ssh_channel_write(_channel, buf, n); + } + }); + } + + void _handle_size() + { + Libc::with_libc([&] () { + auto size = _terminal.size(); + ssh_channel_change_pty_size(_channel, size.columns(), size.lines()); + }); + } + + void _handle_channel(int nready) + { + Libc::with_libc([&] () { + char buffer[256]; + + fd_set readfds; + fd_set noop; + + if (ssh_channel_is_eof(_channel)) _exit(0); + + while (nready) { + while (true) { + int n = ssh_channel_read_nonblocking(_channel, buffer, sizeof(buffer), 0); + if (!n) break; + if (n < 0) _die(); + _terminal.write(buffer, n); + } + + FD_ZERO(&noop); + FD_ZERO(&readfds); + FD_SET(ssh_get_fd(_session), &readfds); + + nready = _select_handler.select(ssh_get_fd(_session)+1, readfds, noop, noop); + } + }); + } + + void _select_ready(int nready, fd_set const &readfds, fd_set const &writefds, fd_set const &exceptfds) + { + _handle_channel(nready); + } + + static void _log_host_usage() + { + char buf[1024]; + Xml_generator gen(buf, sizeof(buf), "host", [&gen] () { + gen.attribute("name", "..."); + gen.attribute("port", 22); + gen.attribute("user", "..."); + gen.attribute("pass", "..."); + gen.attribute("known", "yes"); + }); + + log("host file format: ", Xml_node(buf, gen.used())); + } + + void _configure() + { + using namespace Genode; + + _env.config([&] (Xml_node const &config) { + int verbosity = config.attribute_value("verbose", false) + ? SSH_LOG_FUNCTIONS : SSH_LOG_NOLOG; + ssh_options_set(_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + }); + + /* read all files from the root directory */ + ssh_options_set(_session, SSH_OPTIONS_SSH_DIR, "/"); + ssh_options_set(_session, SSH_OPTIONS_KNOWNHOSTS, "/known_hosts"); + + /* read the XML formatted host file */ + char buf[4096]; + FILE *f = fopen("host", "r"); + if (f == NULL) { + error("failed to open \"/host\" configuration file"); + _log_host_usage(); + _exit(~0); + } + size_t n = fread(buf, 1, sizeof(buf), f); + fclose(f); + + Xml_node host_cfg(buf, n); + try { + host_cfg.attribute("name").value(&_hostname); + ssh_options_set(_session, SSH_OPTIONS_HOST, + _hostname.string()); + ssh_options_set(_session, SSH_OPTIONS_PORT_STR, + string_attr(host_cfg, "port").string()); + ssh_options_set(_session, SSH_OPTIONS_USER, + string_attr(host_cfg, "user").string()); + _password = host_cfg.attribute_value("pass", String()); + _host_known = host_cfg.attribute_value("known", true); + } + catch (...) { + error("failed to parse host configuration"); + error(host_cfg); + _log_host_usage(); + throw; + _exit(~0); + } + } + + void _authenticate_public_key() + { + int rc = SSH_AUTH_AGAIN; + while (rc == SSH_AUTH_AGAIN) { + rc = ssh_userauth_publickey_auto(_session, NULL, NULL); + switch (rc) { + case SSH_AUTH_SUCCESS: + Genode::log("public key authentication successful"); return; + case SSH_AUTH_ERROR: + Genode::error("public key authentication failed"); break; + case SSH_AUTH_DENIED: + Genode::error("public key authentication denied"); break; + case SSH_AUTH_PARTIAL: + Genode::error("additional authentication is required"); break; + case SSH_AUTH_AGAIN: + default: + break; + } + } + + if (ssh_userauth_password(_session, NULL, _password.string()) == SSH_OK) { + Genode::log("password authentication successful"); + return; + } + + Genode::error("password authentication denied"); + if (ssh_userauth_none(_session, NULL) == SSH_OK) { + Genode::log("anonymous authentication successful"); + } + + _die(); + } + + void _connect() + { + if (ssh_connect(_session) != SSH_OK) _die(); + + { + ssh_key hostkey = NULL; + ssh_get_publickey(_session, &hostkey); + { + unsigned char *hash = NULL; + size_t hashlen = 0; + ssh_get_publickey_hash(hostkey, SSH_PUBLICKEY_HASH_SHA1, + &hash, &hashlen); + { + char *hexhost = ssh_get_hexa(hash, hashlen); + log(_hostname, " ", (char const *)hexhost); + ssh_string_free_char(hexhost); + } + ssh_clean_pubkey_hash(&hash); + } + ssh_key_free(hostkey); + } + + if (!ssh_is_server_known(_session)) { + if (_host_known) { + error("unknown host"); + _exit(~0); + } else { + ssh_write_knownhost(_session); + } + } + + _authenticate_public_key(); + + if (char *banner = ssh_get_issue_banner(_session)) { + Genode::log((const char *)banner); + free(banner); + } + + _channel = ssh_channel_new(_session); + if (_channel == NULL) _die(); + + if (ssh_channel_open_session(_channel) != SSH_OK) _die(); + + auto size = _terminal.size(); + if (ssh_channel_request_pty_size(_channel, "screen", + size.columns(), + size.lines()) != SSH_OK) _die(); + + if (ssh_channel_request_shell(_channel) != SSH_OK) _die(); + } + + Main(Libc::Env &env) : _env(env) + { + if (!_session) { + Genode::error("failed to initialize libssh session"); + _die(); + } + + _terminal.read_avail_sigh(_terminal_handler); + _terminal.size_changed_sigh(_size_handler); + + _configure(); + _connect(); + + _handle_channel(1); + _handle_terminal(); + } + + ~Main() + { + ssh_free(_session); + } +}; + + +static void log_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + (void)userdata; + (void)function; + Genode::log(buffer); +} + + +void Libc::Component::construct(Libc::Env &env) +{ + with_libc([&] () { + Genode::log("libssh ", ssh_version(0)); + + ssh_set_log_callback(log_callback); + ssh_init(); + + static Ssh_client::Main main(env); + }); +} diff --git a/src/app/ssh_client/target.mk b/src/app/ssh_client/target.mk new file mode 100644 index 0000000..b1c2ff9 --- /dev/null +++ b/src/app/ssh_client/target.mk @@ -0,0 +1,5 @@ +TARGET := ssh_client +LIBS += base libc libssh +SRC_CC += component.cc + +CC_CXX_WARN_STRICT =