diff --git a/repos/gems/recipes/src/ssh_terminal/content.mk b/repos/gems/recipes/src/ssh_terminal/content.mk
new file mode 100644
index 000000000..fc2841f2f
--- /dev/null
+++ b/repos/gems/recipes/src/ssh_terminal/content.mk
@@ -0,0 +1,12 @@
+SRC_DIR := src/server/ssh_terminal
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
+
+MIRROR_FROM_LIBPORTS := lib/mk/libc_pipe.mk \
+ src/lib/libc_pipe \
+ include/libc-plugin
+
+content: $(MIRROR_FROM_LIBPORTS)
+
+$(MIRROR_FROM_LIBPORTS):
+ mkdir -p $(dir $@)
+ cp -r $(GENODE_DIR)/repos/libports/$@ $(dir $@)
diff --git a/repos/gems/recipes/src/ssh_terminal/hash b/repos/gems/recipes/src/ssh_terminal/hash
new file mode 100644
index 000000000..0791c02f7
--- /dev/null
+++ b/repos/gems/recipes/src/ssh_terminal/hash
@@ -0,0 +1 @@
+2018-10-01 149f13d8d187bc35e8ce7875ac41b83a11682ba5
diff --git a/repos/gems/recipes/src/ssh_terminal/used_apis b/repos/gems/recipes/src/ssh_terminal/used_apis
new file mode 100644
index 000000000..5a72c98ae
--- /dev/null
+++ b/repos/gems/recipes/src/ssh_terminal/used_apis
@@ -0,0 +1,10 @@
+base
+gems
+libc
+libssh
+nic_session
+report_session
+os
+terminal_session
+timer_session
+vfs
diff --git a/repos/gems/run/ssh_terminal.run b/repos/gems/run/ssh_terminal.run
new file mode 100644
index 000000000..c1523767e
--- /dev/null
+++ b/repos/gems/run/ssh_terminal.run
@@ -0,0 +1,197 @@
+#
+# \brief Test for the SSH terminal
+#
+
+assert_spec x86
+
+if {[have_spec linux]} {
+ puts "Run script is not supported on this platform."
+ exit 0
+}
+
+# Build
+#
+
+set build_components {
+ core init
+ drivers/timer
+ drivers/nic
+ drivers/rtc
+ server/ssh_terminal
+ lib/libc_noux
+ lib/vfs/jitterentropy
+ lib/vfs/lxip
+ test/libports/ncurses
+ test/terminal_echo
+ noux
+ noux-pkg/bash
+}
+
+source ${genode_dir}/repos/base/run/platform_drv.inc
+append_platform_drv_build_components
+
+build $build_components
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+set config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+append_platform_drv_config
+
+append config {
+}
+
+install_config $config
+
+#
+# Generate a new host key
+#
+if {![file exists bin/ed25519_key]} {
+ exec ssh-keygen -t ed25519 -f bin/ed25519_key -q -N ""
+}
+
+#
+# Boot modules
+#
+
+# generic modules
+set boot_modules {
+ core ld.lib.so init timer nic_drv rtc_drv report_rom
+ noux test-terminal_echo
+
+ libc.lib.so pthread.lib.so libm.lib.so vfs.lib.so
+ vfs_lxip.lib.so lxip.lib.so libc_pipe.lib.so libc_noux.lib.so
+ posix.lib.so libcrypto.lib.so libssh.lib.so zlib.lib.so ncurses.lib.so
+ vfs_jitterentropy.lib.so ssh_terminal
+
+ bash.tar ed25519_key
+}
+
+# platform-specific modules
+append_platform_drv_boot_modules
+
+build_boot_image $boot_modules
+
+#
+# Execute test
+#
+
+append qemu_args " -m 512 -nographic "
+
+append_if [have_spec x86] qemu_args " -net nic,model=e1000 "
+
+append qemu_args " -net user -redir tcp:5555::22 "
+
+set lxip_match_string "ipaddr=(\[0-9\]+\.\[0-9\]+\.\[0-9\]+\.\[0-9\]+).*\n"
+
+if {[get_cmd_switch --autopilot]} {
+ run_genode_until $lxip_match_string 60
+ set serial_id [output_spawn_id]
+
+ if {[have_include "power_on/qemu"]} {
+ set host "localhost"
+ set port "5555"
+ } else {
+ regexp $lxip_match_string $output all host
+ puts ""
+ set port "22"
+ }
+ # wait for ssh_terminal to come up
+ sleep 5
+ spawn sshpass -p xuon ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l noux $host -p $port
+ set ssh_id $spawn_id
+ set spawn_id_list [list $ssh_id $serial_id]
+ run_genode_until "--- noux started ---" 15 $spawn_id_list
+ puts ""
+ puts ""
+} else {
+ run_genode_until forever
+}
+
+exec rm bin/ed25519_key bin/ed25519_key.pub
+
+# vi: set ft=tcl :
diff --git a/repos/gems/src/server/ssh_terminal/README b/repos/gems/src/server/ssh_terminal/README
new file mode 100644
index 000000000..704821c3c
--- /dev/null
+++ b/repos/gems/src/server/ssh_terminal/README
@@ -0,0 +1,114 @@
+This directory contains an minimal SSH Terminal server. It provides
+concurrent access to multiple Terminal sessions via interactive SSH sessions.
+Before using the component, please consult the _Notes_ section to learn
+about the current subtleties.
+
+For an example on how to use the server please look at the run script
+provided by _repos/gem/run/ssh_terminal_.
+
+
+Configuration
+~~~~~~~~~~~~~
+
+Examplary configuration:
+
+!
+!
+!
+!
+!
+!
+!
+! 012345678
+!
+!
+!
+!
+!
+!
+
+The above config snippet shows a configuration where two Noux clients may
+connect to the Terminal server and SSH connections with either a password or a
+public are processed on port 2022. All SSH logins for the user 'root' are
+linked to the 'noux-system' session, while all 'user' logins are forwarded
+to the 'noux-user' session.
+
+The '' node has several mandatory attributes:
+
+* 'port' specfies the TCP port the SSH server attempts to listen on.
+
+* 'ecdsa_key', 'ed25519_key' and 'rsa_key' set the respective
+ path to the host key. At least one of them must be specified.
+
+* 'allow_password' enables password logins while 'allow_publickey'
+ enables public-key logins. At least one of them must be specified.
+ Those Authentication methods are set for all logins even if they
+ are not useable by one or the other.
+
+Aside from the mandatory attributes there are optional ones:
+
+* 'log_logins' enables logging of login attempts. These attempts will
+ be printed to the LOG session. The default is 'yes'.
+
+The relation between a Terminal session and a SSH session is established
+by a '' node. It first specifes which Terminal client may access
+the server. Second and more importantly it specifies which SSH session has
+access to which Terminal session. The non policy-label specific attributes
+of the node form a login that is used to authenticate access via the SSH
+session:
+
+* 'user' sets the name of a login and is used throughout the component
+ to establish the relation between Terminal and SSH session and is therefor
+ mandatory.
+
+* 'password' sets the password of the login, which is used to authenticate
+ a login attempt.
+
+* 'pub_key' sets the path to the public-key file, which is used to authenticate
+ a login attempt.
+
+* 'multi_login' allows one Terminal session to be used by mutliple SSH
+ sessions concurrently, the default is false.
+
+Either one of the 'password' or 'pub_key' attributes must be set in order for
+the login to be valid.
+
+Normally, all Terminal sessions are expected to be established before an SSH
+connection is made. To accommdate the use-case where the Terminal client is
+made available in a dynamic fashion, the 'request_terminal' attribute can by
+set to 'yes'. In this case the SSH Terminal server will generate a report,
+which then can be inspected and reacted upon by a management component,
+whenever a SSH connection for a detached Terminal session is made. Data coming
+from the SSH session will be ignored as long as the Terminal client is not
+attached to the server. This mechanism is useful in cases where the attached
+Terminal might terminate itself, e.g. a shell when receiving EOF, but a
+subsequent login attempt expects the Terminal client to be still attached.
+
+The following snippet shows such a report:
+
+!
+
+The component will generate the report only once when the first login
+via SSH is made and the corresponding Terminal session is not available.
+All subsequent logins have to wait until the session is provided. If
+there are active SSH session after the Terminal in question has been detached,
+a new report will be generated.
+
+
+Notes
+~~~~~
+
+* The SSH connection MUST be forcefully cut at the client, e.g. when
+ using OpenSSH by entering '~.'.
+
+* Using tools that require an exec channel, e.g. scp(1) and rsync(1), is not
+ supported.
+
+* Host keys can be generated by using ssh-keygen(1) on your host system
+ (e.g., 'ssh-keygen -t ed25519_key -f ed25519_key' without a passphrase and
+ then use the 'ed25519_key' file).
+
+* Although concurrent access to one Terminal session via multiple SSH sessions
+ at the same time is possible, it should better be avoided as there are no
+ safety measures in place.
diff --git a/repos/gems/src/server/ssh_terminal/main.cc b/repos/gems/src/server/ssh_terminal/main.cc
new file mode 100644
index 000000000..fcc58417a
--- /dev/null
+++ b/repos/gems/src/server/ssh_terminal/main.cc
@@ -0,0 +1,1561 @@
+/*
+ * \brief Component providing a Terminal session via SSH
+ * \author Josef Soentgen
+ * \date 2018-09-25
+ *
+ * On the local side this component provides Terminal sessions to its
+ * configured clients while it also provides SSH sessions on the remote side.
+ * The relation between both sides is establish via the policy settings that
+ * determine which Terminal session may be accessed by a SSH login and the
+ * other way around.
+ *
+ * When the component gets started it will create a read-only login database.
+ * A login consists of a username and either a password or public-key or both.
+ * The username is the unique primary key and is used to identify the right
+ * Terminal session when a login is attempted. In return, it is also used to
+ * attach a Terminal session to an (existing) SSH session. The SSH protocol
+ * processing is done via libssh running in its own event thread while the
+ * EP handles the Terminal session. Locking is done at the appropriate places
+ * to synchronize both threads.
+ */
+
+/*
+ * 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* libc includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* libssh includes */
+#include
+#include
+#include
+
+
+namespace Util {
+
+ using Filename = Genode::String<256>;
+
+ template
+ struct Buffer
+ {
+ Genode::Lock _lock { };
+ char _data[C] { };
+ size_t _head { 0 };
+ size_t _tail { 0 };
+
+ size_t read_avail() const { return _head > _tail ? _head - _tail : 0; }
+ size_t write_avail() const { return _head <= C ? C - _head : 0; }
+ char const *content() const { return &_data[_tail]; }
+
+ Genode::Lock &lock() { return _lock; }
+ void append(char c) { _data[_head++] = c; }
+ void consume(size_t n) { _tail += n; }
+ void reset() { _head = _tail = 0; }
+ };
+
+ char const *get_time()
+ {
+ static char buffer[32];
+
+ char const *p = "";
+ Libc::with_libc([&] {
+ struct timespec ts;
+ if (clock_gettime(0, &ts)) { return; }
+
+ struct tm *tm = localtime((time_t*)&ts.tv_sec);
+ if (!tm) { return; }
+
+ size_t const n = strftime(buffer, sizeof(buffer), "%F %H:%M:%S", tm);
+ if (n > 0 && n < sizeof(buffer)) { p = buffer; }
+ }); /* Libc::with_libc */
+
+ return p;
+ }
+
+} /* namespace Util */
+
+
+/*
+ * Add the libssh callback forward declarations here so that we can use them
+ * from within the classes and thereby document the ones currently implemented.
+ */
+
+static int channel_data_cb(ssh_session, ssh_channel, void *, uint32_t, int, void *);
+static int channel_env_request_cb(ssh_session, ssh_channel, char const *, char const *, void *);
+static int channel_pty_request_cb(ssh_session, ssh_channel, char const *, int, int, int, int, void *);
+static int channel_pty_window_change_cb(ssh_session, ssh_channel, int, int, int, int, void *);
+static int channel_shell_request_cb(ssh_session, ssh_channel, void *);
+
+static void bind_incoming_connection(ssh_bind, void *);
+static int session_service_request_cb(ssh_session, char const *, void *);
+static int session_auth_password_cb(ssh_session, char const *, char const *, void *);
+static int session_auth_pubkey_cb(ssh_session, char const *, struct ssh_key_struct *, char, void *);
+static ssh_channel session_channel_open_request_cb(ssh_session, void *);
+
+
+namespace Ssh {
+
+ using namespace Genode;
+
+ using User = String<32>;
+ using Password = String<64>;
+ using Hash = String<65>;
+
+ struct Login;
+ struct Login_registry;
+
+ struct Terminal;
+ struct Server;
+
+}; /* namespace Ssh */
+
+
+struct Ssh::Login : Genode::Registry::Element
+{
+ Ssh::User user { };
+ Ssh::Password password { };
+ Ssh::Hash pub_key_hash { };
+ ssh_key pub_key { nullptr };
+
+ bool multi_login { false };
+ bool request_terminal { false };
+
+ /**
+ * Constructor
+ */
+ Login(Genode::Registry ®,
+ Ssh::User const &user,
+ Ssh::Password const &pw,
+ Util::Filename const &pk_file,
+ bool const multi_login,
+ bool const request_terminal)
+ :
+ Element(reg, *this),
+ user(user), password(pw), multi_login(multi_login),
+ request_terminal(request_terminal)
+ {
+ Libc::with_libc([&] {
+
+ if ( pk_file.valid()
+ && ssh_pki_import_pubkey_file(pk_file.string(), &pub_key)) {
+ Genode::error("could not import public key for user '",
+ user, "'");
+ }
+
+ if (pub_key) {
+ unsigned char *h = nullptr;
+ size_t hlen = 0;
+ /* pray and assume both calls never fail */
+ ssh_get_publickey_hash(pub_key, SSH_PUBLICKEY_HASH_SHA256,
+ &h, &hlen);
+ char const *p = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256,
+ h, hlen);
+ if (p) { pub_key_hash = Ssh::Hash(p); }
+
+ ssh_clean_pubkey_hash(&h);
+ /* abuse function to free fingerprint */
+ ssh_clean_pubkey_hash((unsigned char**)&p);
+ }
+ }); /* Libc::with_libc */
+ }
+
+ ~Login() { ssh_key_free(pub_key); }
+
+ bool auth_password() const { return password.valid(); }
+ bool auth_publickey() const { return pub_key != nullptr; }
+
+ void print(Genode::Output &out) const
+ {
+ Genode::print(out, "user ", user, ": ");
+ if (auth_password()) { Genode::print(out, "password "); }
+ if (auth_publickey()) { Genode::print(out, "public-key"); }
+ }
+};
+
+
+struct Ssh::Login_registry : Genode::Registry
+{
+ Genode::Allocator &_alloc;
+
+ Genode::Lock _lock { };
+
+ /**
+ * Import one login from node
+ */
+ bool _import_single(Genode::Xml_node const &node)
+ {
+ User user = node.attribute_value("user", User());
+ Password pw = node.attribute_value("password", Password());
+ Util::Filename pub = node.attribute_value("pub_key", Util::Filename());
+ bool multi_login = node.attribute_value("multi_login", false);
+ bool req_term = node.attribute_value("request_terminal", false);
+
+ if (!user.valid() || (!pw.valid() && !pub.valid())) {
+ Genode::warning("ignoring invalid policy");
+ return false;
+ }
+
+ if (lookup(user.string())) {
+ Genode::warning("ignoring already imported login ", user.string());
+ return false;
+ }
+
+ try {
+ new (&_alloc) Login(*this, user, pw, pub, multi_login, req_term);
+ return true;
+ } catch (...) { return false; }
+ }
+
+ void _remove_all()
+ {
+ for_each([&] (Login &login) {
+ Genode::destroy(&_alloc, &login);
+ });
+ }
+
+ /**
+ * Constructor
+ *
+ * \param alloc allocator for registry elements
+ */
+ Login_registry(Genode::Allocator &alloc) : _alloc(alloc) { }
+
+ /**
+ * Return registry lock
+ */
+ Genode::Lock &lock() { return _lock; }
+
+ /**
+ * Import all login information from config
+ */
+ void import(Genode::Xml_node const &node)
+ {
+ _remove_all();
+
+ try {
+ node.for_each_sub_node("policy",
+ [&] (Genode::Xml_node const &node) {
+ _import_single(node);
+ });
+ } catch (...) { }
+ }
+
+ /**
+ * Look up login information by user name
+ */
+ Ssh::Login const *lookup(char const *user) const
+ {
+ Login const *p = nullptr;
+ auto lookup_user = [&] (Login const &login) {
+ if (login.user == user) { p = &login; }
+ };
+ for_each(lookup_user);
+ return p;
+ }
+};
+
+
+class Ssh::Terminal
+{
+ public:
+
+ Util::Buffer<4096u> read_buf { };
+
+ int write_avail_fd { -1 };
+
+ private:
+
+ Util::Buffer<4096u> _write_buf { };
+
+ ::Terminal::Session::Size _size { 0, 0 };
+
+ Signal_context_capability _size_changed_sigh;
+ Signal_context_capability _connected_sigh;
+ Signal_context_capability _read_avail_sigh;
+
+ Ssh::User const _user { };
+
+ unsigned _attached_channels { 0u };
+ unsigned _pending_channels { 0u };
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Terminal(Ssh::User const &user) : _user(user) { }
+
+ ~Terminal() { }
+
+ Ssh::User const &user() const { return _user; }
+
+ unsigned attached_channels() const { return _attached_channels; }
+
+ void attach_channel() { ++_attached_channels; }
+
+ void detach_channel() { --_attached_channels; }
+
+ void reset_pending() { _pending_channels = 0; }
+
+ /*********************************
+ ** Terminal::Session interface **
+ *********************************/
+
+ /**
+ * Register signal handler to be notified once the size was changed
+ */
+ void size_changed_sigh(Signal_context_capability sigh) {
+ _size_changed_sigh = sigh; }
+
+ /**
+ * Register signal handler to be notified once we accepted the TCP
+ * connection
+ */
+ void connected_sigh(Signal_context_capability sigh) {
+ _connected_sigh = sigh; }
+
+ /**
+ * Register signal handler to be notified when data is available for
+ * reading
+ */
+ void read_avail_sigh(Signal_context_capability sigh)
+ {
+ _read_avail_sigh = sigh;
+
+ /* if read data is available right now, deliver signal immediately */
+ if (read_buffer_empty() && _read_avail_sigh.valid()) {
+ Signal_transmitter(_read_avail_sigh).submit();
+ }
+ }
+
+ /**
+ * Inform client about the finished initialization of the SSH
+ * session
+ */
+ void notify_connected()
+ {
+ if (!_connected_sigh.valid()) { return; }
+ Signal_transmitter(_connected_sigh).submit();
+ }
+
+ /**
+ * Inform client about avail data
+ */
+ void notify_read_avail()
+ {
+ if (!_read_avail_sigh.valid()) { return; }
+ Signal_transmitter(_read_avail_sigh).submit();
+ }
+
+ /**
+ * Inform client about the changed size of the remote terminal
+ */
+ void notify_size_changed()
+ {
+ if (!_size_changed_sigh.valid()) { return; }
+ Signal_transmitter(_size_changed_sigh).submit();
+ }
+
+ /**
+ * Set size of the Terminal session to match remote terminal
+ */
+ void size(::Terminal::Session::Size size) { _size = size; }
+
+ /**
+ * Return size of the Terminal session
+ */
+ ::Terminal::Session::Size size() const { return _size; }
+
+ /*****************
+ ** I/O methods **
+ *****************/
+
+ /**
+ * Send internal write buffer content to SSH channel
+ */
+ void send(ssh_channel channel)
+ {
+ Lock::Guard g(_write_buf.lock());
+
+ if (!_write_buf.read_avail()) { return; }
+
+ /* ignore send request */
+ if (!channel || !ssh_channel_is_open(channel)) { return; }
+
+ char const *src = _write_buf.content();
+ size_t const len = _write_buf.read_avail();
+ /* XXX we do not handle partial writes */
+ int const num_bytes = ssh_channel_write(channel, src, len);
+
+ if (num_bytes && (size_t)num_bytes < len) {
+ warning("send on channel was truncated");
+ }
+
+ if (++_pending_channels >= _attached_channels) {
+ _write_buf.reset();
+ }
+
+ /* at this point the client might have disconnected */
+ if (num_bytes < 0) { throw -1; }
+ }
+
+ /******************************************
+ ** Methods called by Terminal front end **
+ ******************************************/
+
+ /**
+ * Read out internal read buffer and copy into destination buffer.
+ */
+ size_t read(char *dst, size_t dst_len)
+ {
+ Genode::Lock::Guard g(read_buf.lock());
+
+ size_t const num_bytes = min(dst_len, read_buf.read_avail());
+ Genode::memcpy(dst, read_buf.content(), num_bytes);
+ read_buf.consume(num_bytes);
+
+ /* notify client if there are still bytes available for reading */
+ if (!read_buf.read_avail()) { read_buf.reset(); }
+ else {
+ if (_read_avail_sigh.valid()) {
+ Signal_transmitter(_read_avail_sigh).submit();
+ }
+ }
+
+ return num_bytes;
+ }
+
+ /**
+ * Write into internal buffer and copy to underlying socket
+ */
+ size_t write(char const *src, Genode::size_t src_len)
+ {
+ Lock::Guard g(_write_buf.lock());
+
+ size_t num_bytes = 0;
+ size_t i = 0;
+ while (_write_buf.write_avail() > 0 && i < src_len) {
+ char c = src[i];
+
+ if (c == '\n') { _write_buf.append('\r'); }
+
+ _write_buf.append(c);
+ num_bytes++;
+
+ i++;
+ }
+
+ /* wake the event loop up */
+ char c = 1;
+ ::write(write_avail_fd, &c, sizeof(c));
+
+ return num_bytes;
+ }
+
+ /**
+ * Return true if the internal read buffer is ready to receive data
+ */
+ bool read_buffer_empty()
+ {
+ Genode::Lock::Guard g(read_buf.lock());
+ return !read_buf.read_avail();
+ }
+};
+
+
+class Ssh::Server
+{
+ public:
+
+ struct Init_failed : Genode::Exception { };
+ struct Invalid_config : Genode::Exception { };
+
+ struct Session : Genode::Registry::Element
+ {
+ User _user { };
+ uint32_t _id { 0 };
+
+ int bad_auth_attempts { 0 };
+ bool auth_sucessful { false };
+
+ ssh_session session { nullptr };
+ ssh_channel channel { nullptr };
+ ssh_channel_callbacks channel_cb { nullptr };
+
+ Ssh::Terminal *terminal { nullptr };
+
+ Genode::Lock _access_lock { };
+ Genode::Lock &lock_terminal()
+ {
+ return _access_lock;
+ }
+
+ bool spawn_terminal { false };
+
+ Session(Genode::Registry ®,
+ ssh_session s,
+ ssh_channel_callbacks ccb,
+ uint32_t id)
+ : Element(reg, *this), _id(id), session(s), channel_cb(ccb) { }
+
+ void adopt(User const &user) { _user = user; }
+
+ User const &user() const { return _user; }
+ uint32_t id() const { return _id; }
+
+ void add_channel(ssh_channel c)
+ {
+ ssh_set_channel_callbacks(c, channel_cb);
+ channel = c;
+ }
+ };
+
+ private:
+
+ Genode::Env &_env;
+ Genode::Heap _heap;
+
+ bool _verbose { false };
+ bool _allow_password { false };
+ bool _allow_publickey { false };
+ bool _log_logins { false };
+
+ int _max_auth_attempts { 3 };
+ unsigned _port { 0u };
+ unsigned _log_level { 0u };
+
+ ssh_bind _ssh_bind;
+ ssh_event _event_loop;
+ ssh_key pub_key;
+
+ /*
+ * Since we always pass ourself as userdata pointer, we may
+ * safely use the same callback for all sessions and channels.
+ */
+ ssh_channel_callbacks_struct _channel_cb { };
+ ssh_server_callbacks_struct _session_cb { };
+ ssh_bind_callbacks_struct _bind_cb { };
+
+ void _initialize_channel_callbacks()
+ {
+ Genode::memset(&_channel_cb, 0, sizeof(_channel_cb));
+
+ _channel_cb.userdata = this;
+ _channel_cb.channel_data_function = channel_data_cb;
+ _channel_cb.channel_env_request_function = channel_env_request_cb;
+ _channel_cb.channel_pty_request_function = channel_pty_request_cb;
+ _channel_cb.channel_pty_window_change_function = channel_pty_window_change_cb;
+ _channel_cb.channel_shell_request_function = channel_shell_request_cb;
+
+ ssh_callbacks_init(&_channel_cb);
+ }
+
+ void _initialize_session_callbacks()
+ {
+ Genode::memset(&_session_cb, 0, sizeof(_session_cb));
+
+ _session_cb.userdata = this;
+ _session_cb.auth_password_function = session_auth_password_cb;
+ _session_cb.auth_pubkey_function = session_auth_pubkey_cb;
+ _session_cb.service_request_function = session_service_request_cb;
+ _session_cb.channel_open_request_session_function = session_channel_open_request_cb;
+
+ ssh_callbacks_init(&_session_cb);
+ }
+
+ void _initialize_bind_callbacks()
+ {
+ Genode::memset(&_bind_cb, 0, sizeof(_bind_cb));
+ _bind_cb.incoming_connection = bind_incoming_connection;
+ ssh_callbacks_init(&_bind_cb);
+ ssh_bind_set_callbacks(_ssh_bind, &_bind_cb, this);
+ }
+
+ using Session_registry = Genode::Registry;
+ Session_registry _sessions { };
+ uint32_t _session_id { 0 };
+
+ void _cleanup_session(Session &s)
+ {
+ if (s.auth_sucessful) { _log_logout(s); }
+
+ ssh_channel_free(s.channel);
+ s.channel = nullptr;
+
+ ssh_event_remove_session(_event_loop, s.session);
+ ssh_disconnect(s.session);
+ ssh_free(s.session);
+ s.session = nullptr;
+
+ if (s.terminal) {
+ s.terminal->detach_channel();
+ }
+
+ Genode::destroy(&_heap, &s);
+ }
+
+ void _cleanup_sessions()
+ {
+ auto cleanup = [&] (Session &s) {
+ if (!ssh_is_connected(s.session)) {
+ _cleanup_session(s);
+ }
+ };
+ _sessions.for_each(cleanup);
+ }
+
+ struct Terminal_session : Genode::Registry::Element
+ {
+ Ssh::Terminal &conn;
+
+ ssh_event _event_loop;
+
+ int _fds[2] { -1, -1 };
+
+ Terminal_session(Genode::Registry ®,
+ Ssh::Terminal &conn,
+ ssh_event event_loop)
+ : Element(reg, *this), conn(conn), _event_loop(event_loop)
+ {
+ if (pipe(_fds) ||
+ ssh_event_add_fd(_event_loop, _fds[0], POLLIN,
+ write_avail_cb, this) != SSH_OK) {
+ throw -1;
+ }
+
+ conn.write_avail_fd = _fds[1];
+ }
+
+ ~Terminal_session()
+ {
+ ssh_event_remove_fd(_event_loop, _fds[0]);
+ close(_fds[0]);
+ close(_fds[1]);
+ }
+ };
+
+ struct Terminal_registry : Genode::Registry
+ {
+ Genode::Lock _lock { };
+ Genode::Lock &lock() { return _lock; }
+ };
+ Terminal_registry _terminals { };
+
+ Login_registry &_logins;
+
+ Util::Filename rsa_key { };
+ Util::Filename ecdsa_key { };
+ Util::Filename ed25519_key { };
+
+ bool _config_once { false };
+
+ void _parse_config(Genode::Xml_node const &config)
+ {
+ using namespace Genode;
+
+ _verbose = config.attribute_value("verbose", false);
+
+ _log_level = config.attribute_value("debug", 0u);
+ _log_logins = config.attribute_value("log_logins", true);
+
+ Genode::Lock::Guard g(_logins.lock());
+ auto print = [&] (Login const &login) {
+ Genode::log("Login configured: ", login);
+ };
+ _logins.for_each(print);
+
+ if (_config_once) { return; }
+
+ _config_once = true;
+
+ _port = config.attribute_value("port", 0u);
+ if (!_port) {
+ error("port invalid");
+ throw Invalid_config();
+ }
+
+ _allow_password = config.attribute_value("allow_password", false);
+ _allow_publickey = config.attribute_value("allow_publickey", false);
+ if (!_allow_password && !_allow_publickey) {
+ error("authentication methods missing");
+ throw Invalid_config();
+ }
+
+ rsa_key = config.attribute_value("rsa_key", Util::Filename());
+ ecdsa_key = config.attribute_value("ecdsa_key", Util::Filename());
+ ed25519_key = config.attribute_value("ed25519_key", Util::Filename());
+
+ Genode::log("Allowed auth methods: ",
+ _allow_password ? "password " : "",
+ _allow_publickey ? "public-key" : "");
+ }
+
+ void _load_hostkey(Util::Filename const &file)
+ {
+ if (file.valid() &&
+ ssh_bind_options_set(_ssh_bind, SSH_BIND_OPTIONS_HOSTKEY,
+ file.string()) < 0) {
+ Genode::error("could not load hostkey '", file, "'");
+ throw -1;
+ }
+ }
+
+ /*
+ * Event execution
+ */
+
+ pthread_t _event_thread;
+
+ static void *server_loop(void *arg)
+ {
+ Ssh::Server *server = reinterpret_cast(arg);
+ Libc::with_libc([&] {
+ server->loop();
+ });
+ return nullptr;
+ }
+
+ static int write_avail_cb(socket_t fd, int revents, void *userdata)
+ {
+ int n = 0;
+ Libc::with_libc([&] {
+ char c;
+ n = ::read(fd, &c, sizeof(char));
+ });
+ return n;
+ }
+
+ bool _allow_multi_login(ssh_session s, Login const &login)
+ {
+ if (login.multi_login) { return true; }
+
+ bool found = false;
+ auto lookup = [&] (Session const &s) {
+ if (s.user() == login.user) { found = true; }
+ };
+ _sessions.for_each(lookup);
+ return !found;
+ }
+
+ /********************
+ ** Login messages **
+ ********************/
+
+ void _log_failed(char const *user, Session const &s, bool pubkey)
+ {
+ if (!_log_logins) { return; }
+
+ char const *date = Util::get_time();
+ Genode::log(date, " failed user ", user, " (", s.id(), ") ",
+ "with ", pubkey ? "public-key" : "password");
+ }
+
+ void _log_logout(Session const &s)
+ {
+ if (!_log_logins) { return; }
+
+ char const *date = Util::get_time();
+ Genode::log(date, " logout user ", s.user(), " (", s.id(), ")");
+ }
+
+ void _log_login(User const &user, Session const &s, bool pubkey)
+ {
+ if (!_log_logins) { return; }
+
+ char const *date = Util::get_time();
+ Genode::log(date, " login user ", user, " (", s.id(), ") ",
+ "with ", pubkey ? "public-key" : "password");
+ }
+
+ public:
+
+ Server(Genode::Env &env,
+ Genode::Xml_node const &config,
+ Ssh::Login_registry &logins)
+ : _env(env), _heap(env.ram(), env.rm()), _logins(logins)
+ {
+ Libc::with_libc([&] {
+ _parse_config(config);
+
+ if (ssh_init() < 0) { throw Init_failed(); }
+
+ _ssh_bind = ssh_bind_new();
+ if (!_ssh_bind) { throw Init_failed(); }
+
+ ssh_bind_options_set(_ssh_bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &_log_level);
+ ssh_bind_options_set(_ssh_bind, SSH_BIND_OPTIONS_BINDPORT, &_port);
+
+ _initialize_bind_callbacks();
+ _initialize_session_callbacks();
+ _initialize_channel_callbacks();
+
+ /*
+ * Always try to load all types of host key and error-out if
+ * the file is set but could not be loaded.
+ */
+ try {
+ _load_hostkey(rsa_key);
+ _load_hostkey(ecdsa_key);
+ _load_hostkey(ed25519_key);
+ } catch (...) { throw Init_failed(); }
+
+ _event_loop = ssh_event_new();
+
+ if (ssh_bind_listen(_ssh_bind) < 0) {
+ Genode::error("could not listen on port ", _port, ": ",
+ ssh_get_error(_ssh_bind));
+ throw Init_failed();
+ }
+
+ /* add AFTER(!) ssh_bind_listen call */
+ if (ssh_event_add_bind(_event_loop, _ssh_bind) < 0) {
+ Genode::error("unable to add server to event loop: ",
+ ssh_get_error(_ssh_bind));
+ throw Init_failed();
+ }
+
+ if (pthread_create(&_event_thread, nullptr, server_loop, this)) {
+ Genode::error("could not create event thread");
+ throw Init_failed();
+ }
+
+ Genode::log("Listen on port: ", _port);
+ }); /* Libc::with_libc */
+ }
+
+ void loop()
+ {
+ while (true) {
+
+
+ int const events = ssh_event_dopoll(_event_loop, -1);
+ if (events < 0) { _cleanup_sessions(); }
+
+ {
+ Genode::Lock::Guard g(_terminals.lock());
+
+ /* first remove all stale sessions */
+ auto cleanup = [&] (Session &s) {
+ if (ssh_is_connected(s.session)) { return ; }
+ _cleanup_session(s);
+ };
+ _sessions.for_each(cleanup);
+
+ /* second reset all active terminals */
+ auto reset_pending = [&] (Terminal_session &t) {
+ if (!t.conn.attached_channels()) { return; }
+ t.conn.reset_pending();
+ };
+ _terminals.for_each(reset_pending);
+
+ /*
+ * third send data on all sessions being attached
+ * to a terminal.
+ */
+ auto send = [&] (Session &s) {
+ if (!s.terminal) { return; }
+
+ try { s.terminal->send(s.channel); }
+ catch (...) { _cleanup_session(s); }
+ };
+ _sessions.for_each(send);
+ }
+ }
+ }
+
+ /***************************************************************
+ ** Methods below are only used by Terminal session front end **
+ ***************************************************************/
+
+ /**
+ * Attach Terminal session
+ */
+ void attach_terminal(Ssh::Terminal &conn)
+ {
+ Genode::Lock::Guard g(_terminals.lock());
+
+ try {
+ new (&_heap) Terminal_session(_terminals,
+ conn, _event_loop);
+ } catch (...) {
+ Genode::error("could not attach Terminal for user ", conn.user());
+ throw -1;
+ }
+
+ Genode::log("Attach Terminal for user ", conn.user());
+
+ /* there might be sessions already waiting on the terminal */
+ bool attached = false;
+ auto lookup = [&] (Session &s) {
+ if (s.user() == conn.user() && !s.terminal) {
+ s.terminal = &conn;
+ s.terminal->attach_channel();
+
+ attached = true;
+ }
+ };
+ _sessions.for_each(lookup);
+
+ if (attached) { conn.notify_connected(); }
+ }
+
+ /**
+ * Detach Terminal session
+ */
+ void detach_terminal(Ssh::Terminal &conn)
+ {
+ Genode::Lock::Guard g(_terminals.lock());
+
+ Terminal_session *p = nullptr;
+ auto lookup = [&] (Terminal_session &t) {
+ if (&t.conn == &conn) { p = &t; }
+ };
+ _terminals.for_each(lookup);
+
+ if (!p) {
+ Genode::error("could not detach Terminal for user ", conn.user());
+ return;
+ }
+ auto invalidate_terminal = [&] (Session &sess) {
+ if (sess.terminal != &conn) { return; }
+
+ sess.terminal = nullptr;
+ };
+ _sessions.for_each(invalidate_terminal);
+
+ Genode::log("Detach Terminal ", &conn, " for user ", conn.user());
+ Genode::destroy(&_heap, p);
+ }
+
+ /**
+ * Update config
+ */
+ void update_config(Genode::Xml_node const &config)
+ {
+ Genode::Lock::Guard g(_terminals.lock());
+
+ _parse_config(config);
+ ssh_bind_options_set(_ssh_bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &_log_level);
+ }
+
+ /*******************************************************
+ ** Methods below are only used by callback back ends **
+ *******************************************************/
+
+ /**
+ * Look up Terminal for session
+ */
+ Ssh::Terminal *lookup_terminal(Session &s)
+ {
+ Ssh::Terminal *p = nullptr;
+ auto lookup = [&] (Terminal_session &t) {
+ if (t.conn.user() == s.user()) { p = &t.conn; }
+ };
+ _terminals.for_each(lookup);
+ return p;
+ }
+
+ /**
+ * Look up Session for SSH session
+ */
+ Session *lookup_session(ssh_session s)
+ {
+ Session *p = nullptr;
+ auto lookup = [&] (Session &sess) {
+ if (sess.session == s) { p = &sess; }
+ };
+ _sessions.for_each(lookup);
+ return p;
+ }
+
+ /**
+ * Request Terminal
+ */
+ bool request_terminal(Session &session)
+ {
+ Genode::Lock::Guard g(_logins.lock());
+ Login const *l = _logins.lookup(session.user().string());
+ if (!l || !l->request_terminal) { return false; }
+
+ try {
+ Genode::String<64> report_label("request_terminal ", session.user());
+ Reporter reporter(_env, "request_terminal", report_label.string());
+ reporter.enabled(true);
+
+ Genode::Reporter::Xml_generator xml(reporter, [&] {
+ xml.attribute("user", session.user());
+ });
+ } catch (...) {
+ Genode::warning("could not enable login reporting");
+ return false;
+ }
+
+ if (_log_logins) {
+ char const *date = Util::get_time();
+ Genode::log(date, " request Terminal for user ", session.user(),
+ " (", session.session, ")");
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle new incoming connections
+ */
+ void incoming_connection(ssh_session s)
+ {
+ /*
+ * In case we get bombarded by incoming connections, deny
+ * all attempts when this arbritray level is reached.
+ */
+ enum { MEM_RESERVE = 128u * 1024, };
+ if (_env.pd().avail_ram().value < (size_t)MEM_RESERVE) { throw -1; }
+
+ new (&_heap) Session(_sessions, s, &_channel_cb, ++_session_id);
+
+ ssh_set_server_callbacks(s, &_session_cb);
+
+ int auth_methods = SSH_AUTH_METHOD_UNKNOWN;
+ auth_methods += _allow_password ? SSH_AUTH_METHOD_PASSWORD : 0;
+ auth_methods += _allow_publickey ? SSH_AUTH_METHOD_PUBLICKEY : 0;
+ ssh_set_auth_methods(s, auth_methods);
+
+ /*
+ * Normally we would check the result of the key exchange
+ * function but for better or worse using callbacks leads to
+ * a false negative. So ignore any result and move on in hope
+ * that the callsbacks will handle the situation.
+ *
+ * FIXME investigate why it somtimes fails in the first place.
+ */
+ ssh_handle_key_exchange(s);
+ ssh_event_add_session(_event_loop, s);
+ }
+
+ /**
+ * Handle password authentication
+ */
+ bool auth_password(ssh_session s, char const *u, char const *pass)
+ {
+ Session *p = lookup_session(s);
+ if (!p || p->session != s) {
+ Genode::warning("session not found");
+ return false;
+ }
+ Session &session = *p;
+
+ /*
+ * Even if there is no valid login for the user, let
+ * the client try anyway and check multi login afterwards.
+ */
+ Genode::Lock::Guard g(_logins.lock());
+ Login const *l = _logins.lookup(u);
+ if (l && l->user == u && l->password == pass) {
+ if (_allow_multi_login(s, *l)) {
+ session.bad_auth_attempts = 0;
+ session.auth_sucessful = true;
+ session.adopt(l->user);
+ _log_login(l->user, session, false);
+ return true;
+ } else {
+ ssh_disconnect(session.session);
+ _log_failed(u, session, false);
+ return false;
+ }
+ }
+
+ _log_failed(u, *p, false);
+
+ int &i = session.bad_auth_attempts;
+ if (++i >= _max_auth_attempts) {
+ if (_log_logins) {
+ char const *date = Util::get_time();
+ Genode::log(date, " disconnect user ", u, " (", session.id(),
+ ") after ", i, " failed authentication attempts"
+ " with password");
+ }
+ ssh_disconnect(session.session);
+ }
+ return false;
+ }
+
+ /**
+ * Handle public-key authentication
+ */
+ bool auth_pubkey(ssh_session s, char const *u,
+ struct ssh_key_struct *pubkey,
+ char signature_state)
+ {
+ Session *p = lookup_session(s);
+ if (!p || p->session != s) {
+ Genode::warning("session not found");
+ return false;
+ }
+ Session &session = *p;
+
+ if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
+ return SSH_AUTH_PARTIAL;
+ }
+
+ if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
+ Genode::Lock::Guard g(_logins.lock());
+ Login const *l = _logins.lookup(u);
+ if (l && !ssh_key_cmp(pubkey, l->pub_key,
+ SSH_KEY_CMP_PUBLIC)) {
+ if (_allow_multi_login(s, *l)) {
+ session.auth_sucessful = true;
+ session.adopt(l->user);
+ _log_login(l->user, session, true);
+ return SSH_AUTH_SUCCESS;
+ }
+ }
+ }
+
+ _log_failed(u, session, true);
+ return SSH_AUTH_DENIED;
+ }
+};
+
+
+/***********************
+ ** Channel callbacks **
+ ***********************/
+
+/**
+ * Handle SSH channel data request
+ *
+ * For now we ignore this request because there is no way to change the
+ * $ENV of the Terminal::Session client currently.
+ */
+static int channel_data_cb(ssh_session session, ssh_channel channel,
+ void *data, uint32_t len, int is_stderr,
+ void *userdata)
+{
+ if (len == 0) { return 0; }
+
+ using namespace Genode;
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ Ssh::Server::Session *p = server.lookup_session(session);
+ if (!p) {
+ error("session not found");
+ return SSH_ERROR;
+ }
+
+ if (p->channel != channel) {
+ error("wrong channel");
+ return SSH_ERROR;
+ }
+
+ if (!p->terminal) { return SSH_ERROR; }
+
+ Ssh::Terminal &conn = *p->terminal;
+ Lock::Guard g(conn.read_buf.lock());
+
+ char const *src = reinterpret_cast(data);
+ size_t num_bytes = 0;
+ size_t i = 0;
+
+ while (conn.read_buf.write_avail() > 0 && i < len) {
+ char c = src[i];
+
+ /* replace ^? with ^H and let's hope we do not break anything */
+ enum { DEL = 0x7f, BS = 0x08, };
+ if (c == DEL) { conn.read_buf.append(BS); }
+ else { conn.read_buf.append(c); }
+ num_bytes++;
+ i++;
+ }
+
+ conn.notify_read_avail();
+ return num_bytes;
+}
+
+
+/**
+ * Handle SSH channel shell request
+ *
+ * For now we ignore this request because there is no way to change the
+ * $ENV of the Terminal::Session client currently.
+ */
+static int channel_env_request_cb(ssh_session session, ssh_channel channel,
+ char const *env_name, char const *env_value,
+ void *userdata)
+{
+ return SSH_OK;
+}
+
+
+/**
+ * Handle SSH channel PTY resize request
+ */
+static int channel_pty_request_cb(ssh_session session, ssh_channel channel,
+ char const *term,
+ int cols, int rows, int py, int px,
+ void *userdata)
+{
+ using namespace Genode;
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ Ssh::Server::Session *p = server.lookup_session(session);
+ if (!p || p->channel != channel) { return SSH_ERROR; }
+
+ /*
+ * Look up terminal and in case there is none, check
+ * if we have to wait for another subsystem to come up.
+ * In this case we return successfully to the client
+ * and hope for the best.
+ */
+ if (!p->terminal) {
+ p->terminal = server.lookup_terminal(*p);
+ if (!p->terminal) {
+ return server.request_terminal(*p) ? SSH_OK
+ : SSH_ERROR;
+ }
+ }
+
+ p->terminal->attach_channel();
+
+ Ssh::Terminal &conn = *p->terminal;
+ conn.size(Terminal::Session::Size(cols, rows));
+ conn.notify_size_changed();
+
+ /* session handling already takes care of having a terminal attached */
+ conn.notify_connected();
+ return SSH_OK;
+}
+
+
+/**
+ * Handle SSH channel PTY resize request
+ */
+static int channel_pty_window_change_cb(ssh_session session, ssh_channel channel,
+ int width, int height, int pxwidth, int pwheight,
+ void *userdata)
+{
+ (void)pxwidth;
+ (void)pwheight;
+
+ using namespace Genode;
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ Ssh::Server::Session *p = server.lookup_session(session);
+ if (!p || p->channel != channel || !p->terminal) { return SSH_ERROR; }
+
+ Ssh::Terminal &conn = *p->terminal;
+ conn.size(Terminal::Session::Size(width, height));
+ conn.notify_size_changed();
+ return SSH_OK;
+}
+
+
+/**
+ * Handle SSH channel shell request
+ *
+ * For now we ignore this request as the shell is implicitly provided when
+ * the PTY request is handled.
+ */
+static int channel_shell_request_cb(ssh_session session, ssh_channel channel,
+ void *userdata)
+{
+ return SSH_OK;
+}
+
+
+/***********************
+ ** Session callbacks **
+ ***********************/
+
+/**
+ * Handle SSH session service requests
+ */
+static int session_service_request_cb(ssh_session session,
+ char const *service, void *userdata)
+{
+ (void)service;
+ (void)userdata;
+
+ return Genode::strcmp(service, "ssh-userauth") == 0 ? 0 : -1;
+}
+
+
+/**
+ * Handle SSH session password authentication requests
+ */
+static int session_auth_password_cb(ssh_session session,
+ char const *user, char const *password,
+ void *userdata)
+{
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ return server.auth_password(session, user, password) ? SSH_AUTH_SUCCESS
+ : SSH_AUTH_DENIED;
+}
+
+
+/**
+ * Handle SSH session public-key authentication requests
+ */
+static int session_auth_pubkey_cb(ssh_session session, char const *user,
+ struct ssh_key_struct *pubkey,
+ char state, void *userdata)
+{
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ return server.auth_pubkey(session, user, pubkey, state) ? SSH_AUTH_SUCCESS
+ : SSH_AUTH_DENIED;
+
+}
+
+
+/**
+ * Handle SSH session open channel requests
+ */
+static ssh_channel session_channel_open_request_cb(ssh_session session,
+ void *userdata)
+{
+ using namespace Genode;
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ Ssh::Server::Session *p = server.lookup_session(session);
+ if (!p) {
+ error("could not look up session");
+ return nullptr;
+ }
+
+ /* for now only one channel */
+ if (p->channel) {
+ log("Only one channel per session supported");
+ return nullptr;
+ }
+
+ ssh_channel channel = ssh_channel_new(p->session);
+
+ if (!channel) {
+ error("could not create new channel: '", ssh_get_error(p->session));
+ return nullptr;
+ }
+
+ p->add_channel(channel);
+ return channel;
+}
+
+
+/**
+ * Handle new incoming SSH session requests
+ */
+static void bind_incoming_connection(ssh_bind sshbind, void *userdata)
+{
+ using namespace Genode;
+
+ ssh_session session = ssh_new();
+ if (!session || ssh_bind_accept(sshbind, session)) {
+ error("could not accept session: '", ssh_get_error(session), "'");
+ ssh_free(session);
+ return;
+ }
+
+ Ssh::Server &server = *reinterpret_cast(userdata);
+ try { server.incoming_connection(session); }
+ catch (...) {
+ ssh_disconnect(session);
+ ssh_free(session);
+ }
+}
+
+
+/*
+ * Terminal session front end
+ */
+
+namespace Terminal {
+ class Session_component;
+ class Root_component;
+};
+
+class Terminal::Session_component : public Genode::Rpc_object,
+ public Ssh::Terminal
+{
+ private:
+
+ Genode::Attached_ram_dataspace _io_buffer;
+
+ public:
+
+ Session_component(Genode::Env &env,
+ Genode::size_t io_buffer_size,
+ Ssh::User const &user)
+ :
+ Ssh::Terminal(user),
+ _io_buffer(env.ram(), env.rm(), io_buffer_size)
+ { }
+
+ /********************************
+ ** Terminal session interface **
+ ********************************/
+
+ Genode::size_t read(void *buf, Genode::size_t) override { return 0; }
+ Genode::size_t write(void const *buf, Genode::size_t) override { return 0; }
+
+ Size size() override { return Ssh::Terminal::size(); }
+ bool avail() override { return !Ssh::Terminal::read_buffer_empty(); }
+
+ void read_avail_sigh(Genode::Signal_context_capability sigh) override {
+ Ssh::Terminal::read_avail_sigh(sigh); }
+
+ void connected_sigh(Genode::Signal_context_capability sigh) override {
+ Ssh::Terminal::connected_sigh(sigh); }
+
+ void size_changed_sigh(Genode::Signal_context_capability sigh) override {
+ Ssh::Terminal::size_changed_sigh(sigh); }
+
+ Genode::Dataspace_capability _dataspace() { return _io_buffer.cap(); }
+
+ Genode::size_t _read(Genode::size_t num)
+ {
+ Genode::size_t num_bytes = 0;
+ Libc::with_libc([&] () {
+ char *buf = _io_buffer.local_addr();
+ num = Genode::min(_io_buffer.size(), num);
+ num_bytes = Ssh::Terminal::read(buf, num);
+ });
+ return num_bytes;
+ }
+
+ Genode::size_t _write(Genode::size_t num)
+ {
+ ssize_t written = 0;
+ Libc::with_libc([&] () {
+ char *buf = _io_buffer.local_addr();
+ num = Genode::min(num, _io_buffer.size());
+ written = Ssh::Terminal::write(buf, num);
+
+ if (written < 0) {
+ Genode::error("write error, dropping data");
+ written = 0;
+ }
+ });
+ return written;
+ }
+};
+
+
+class Terminal::Root_component : public Genode::Root_component
+{
+ private:
+
+ Genode::Env &_env;
+
+ Genode::Attached_rom_dataspace _config_rom { _env, "config" };
+ Genode::Xml_node _config { _config_rom.xml() };
+
+ Genode::Heap _logins_alloc { _env.ram(), _env.rm() };
+ Ssh::Login_registry _logins { _logins_alloc };
+
+ Ssh::Server _server { _env, _config, _logins };
+
+ Genode::Signal_handler _config_sigh {
+ _env.ep(), *this, &Terminal::Root_component::_handle_config_update };
+
+ void _handle_config_update()
+ {
+ _config_rom.update();
+ if (!_config_rom.valid()) { return; }
+
+ {
+ Genode::Lock::Guard g(_logins.lock());
+ _logins.import(_config_rom.xml());
+ }
+
+ _server.update_config(_config_rom.xml());
+ }
+
+ protected:
+
+ Session_component *_create_session(const char *args)
+ {
+ using namespace Genode;
+
+ try {
+ Session_label const label = label_from_args(args);
+ Session_policy policy(label, _config);
+
+ Ssh::User const user = policy.attribute_value("user", Ssh::User());
+ if (!user.valid()) { throw -1; }
+
+ Ssh::Login const *login = _logins.lookup(user.string());
+ if (!login) { throw -1; }
+
+ Session_component *s = nullptr;
+ Libc::with_libc([&] () {
+ s = new (md_alloc()) Session_component(_env, 4096, login->user);
+ });
+
+ try {
+ _server.attach_terminal(*s);
+ return s;
+ } catch (...) {
+ Genode::destroy(md_alloc(), s);
+ throw;
+ }
+ } catch (...) { throw Service_denied(); }
+ }
+
+ void _destroy_session(Session_component *s)
+ {
+ _server.detach_terminal(*s);
+ Genode::destroy(md_alloc(), s);
+ }
+
+ public:
+
+ Root_component(Genode::Env &env,
+ Genode::Allocator &md_alloc)
+ :
+ Genode::Root_component(&env.ep().rpc_ep(),
+ &md_alloc),
+ _env(env)
+ {
+ _config_rom.sigh(_config_sigh);
+ _handle_config_update();
+ }
+};
+
+
+struct Main
+{
+ Genode::Env &_env;
+
+ Genode::Sliced_heap _sliced_heap { _env.ram(), _env.rm() };
+ Terminal::Root_component _root { _env, _sliced_heap };
+
+ Main(Genode::Env &env) : _env(env)
+ {
+ Genode::log("--- SSH terminal started ---");
+ _env.parent().announce(env.ep().manage(_root));
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env) { static Main main(env); }
diff --git a/repos/gems/src/server/ssh_terminal/target.mk b/repos/gems/src/server/ssh_terminal/target.mk
new file mode 100644
index 000000000..1137fe155
--- /dev/null
+++ b/repos/gems/src/server/ssh_terminal/target.mk
@@ -0,0 +1,5 @@
+TARGET = ssh_terminal
+SRC_CC = main.cc
+LIBS = base libc pthread libssh libc_pipe
+
+CC_CXX_WARN_STRICT =