ssh_terminal: moved to genode world

Fixes #4258
This commit is contained in:
Tomasz Gajewski 2021-09-27 21:30:44 +02:00 committed by Christian Helmuth
parent d4a6342295
commit ecb1a6187c
17 changed files with 0 additions and 2904 deletions

View File

@ -1,8 +0,0 @@
SRC_DIR := src/server/ssh_terminal
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
content: $(MIRROR_FROM_LIBPORTS)
$(MIRROR_FROM_LIBPORTS):
mkdir -p $(dir $@)
cp -r $(GENODE_DIR)/repos/libports/$@ $(dir $@)

View File

@ -1 +0,0 @@
2021-10-13 c49961ec747733def5b1d6c7f0a0fca302ecda8f

View File

@ -1,10 +0,0 @@
base
gems
libc
libssh
nic_session
report_session
os
terminal_session
timer_session
vfs

View File

@ -1,328 +0,0 @@
#
# \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
#
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
lappend build_components test/exec_terminal
build $build_components
create_boot_directory
import_from_depot [depot_user]/src/[base_src] \
[depot_user]/src/bash \
[depot_user]/src/coreutils-minimal \
[depot_user]/src/exec_terminal \
[depot_user]/src/init \
[depot_user]/src/ipxe_nic_drv \
[depot_user]/src/libc \
[depot_user]/src/openssl \
[depot_user]/src/libssh \
[depot_user]/src/platform_drv \
[depot_user]/src/posix \
[depot_user]/src/fs_rom \
[depot_user]/src/nic_router \
[depot_user]/src/rtc_drv \
[depot_user]/src/ssh_terminal \
[depot_user]/src/vfs \
[depot_user]/src/vfs_jitterentropy \
[depot_user]/src/vfs_lxip \
[depot_user]/src/vfs_pipe \
[depot_user]/src/vim-minimal \
[depot_user]/src/zlib
#
# Generate config
#
set config {
<config verbose="no">
<parent-provides>
<service name="CPU"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="IRQ"/>
<service name="LOG"/>
<service name="PD"/>
<service name="RAM"/>
<service name="RM"/>
<service name="ROM"/>
</parent-provides>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
<route>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="nic_drv" caps="120">
<binary name="ipxe_nic_drv"/>
<resource name="RAM" quantum="8M"/>
<route>
<service name="Uplink"> <child name="nic_router"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="nic_router" caps="200">
<resource name="RAM" quantum="10M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config verbose_domain_state="yes">
<policy label_prefix="ssh_terminal" domain="downlink"/>
<policy label_prefix="nic_drv" domain="uplink"/>
<domain name="uplink">
<nat domain="downlink"
tcp-ports="16384"
udp-ports="16384"
icmp-ids="16384"/>
<tcp-forward port="22" domain="downlink" to="10.0.3.2"/>
</domain>
<domain name="downlink" interface="10.0.3.1/24">
<dhcp-server ip_first="10.0.3.2" ip_last="10.0.3.2" dns_config_from="uplink"/>
<tcp dst="0.0.0.0/0"><permit-any domain="uplink" /></tcp>
<udp dst="0.0.0.0/0"><permit-any domain="uplink" /></udp>
<icmp dst="0.0.0.0/0" domain="uplink"/>
</domain>
</config>
<route>
<service name="Timer"> <child name="timer"/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</route>
</start>
<start name="rtc_drv">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Rtc"/> </provides>
<route>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="ram_fs">
<resource name="RAM" quantum="8M"/>
<binary name="vfs"/>
<provides> <service name="File_system"/> </provides>
<config>
<vfs> <ram/> </vfs>
<default-policy root="/" writeable="yes"/>
</config>
<route>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="ssh_terminal" caps="250">
<resource name="RAM" quantum="32M"/>
<provides> <service name="Terminal"/> </provides>
<config port="22" allow_password="yes" show_password="yes" ed25519_key="/etc/ssh/ed25519_key">
<policy label_prefix="dynamic" user="noux" password="xuon" multi_login="yes" request_terminal="yes"/>
<policy label_prefix="always-running-noux" user="charlie" password="xuon"/>
<libc stdout="/dev/log" stderr="/dev/log" socket="/socket" pipe="/pipe" rtc="/dev/rtc"/>
<vfs>
<dir name="dev">
<log/>
<jitterentropy name="random"/>
<jitterentropy name="urandom"/>
<inline name="rtc">2000-01-01 00:00</inline>
</dir>
<dir name="etc">
<dir name="ssh">
<rom name="ed25519_key"/>
</dir>
</dir>
<dir name="socket"> <lxip dhcp="yes"/> </dir>
<dir name="pipe"> <pipe/> </dir>
</vfs>
</config>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</route>
</start>
<start name="report_rom" caps="100">
<resource name="RAM" quantum="4M"/>
<provides>
<service name="Report"/>
<service name="ROM"/>
</provides>
<config verbose="no">
<policy label="exec_terminal -> exec_terminal.config" report="ssh_terminal -> request_terminal"/>
<policy label="dynamic -> config" report="exec_terminal -> config"/>
</config>
<route>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</route>
</start>
<start name="exec_terminal" caps="100">
<resource name="RAM" quantum="4M"/>
<route>
<service name="ROM" label="exec_terminal.config"> <child name="report_rom"/> </service>
<service name="Report" label="config"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</route>
</start>
<start name="dynamic" caps="1000">
<binary name="init"/>
<resource name="RAM" quantum="80M"/>
<route>
<service name="File_system"> <child name="ram_fs"/> </service>
<service name="ROM" label="config"> <child name="report_rom"/> </service>
<service name="ROM" label_last="coreutils-minimal.tar"> <parent label="coreutils-minimal.tar"/> </service>
<service name="ROM" label_last="vim-minimal.tar"> <parent label="vim-minimal.tar"/> </service>
<service name="Terminal"> <child name="ssh_terminal"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
</route>
</start>
}
append_platform_drv_config
append config {
</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 {
ed25519_key
exec_terminal
}
# platform-specific modules
append_platform_drv_boot_modules
build_boot_image $boot_modules
#
# Execute test
#
append qemu_args " -nographic "
append_qemu_nic_args "hostfwd=tcp::5555-:22"
set nic_router_match_string ".uplink. dynamic IP config. interface (\[0-9\]+\.\[0-9\]+\.\[0-9\]+\.\[0-9\]+).*\n"
if {[get_cmd_switch --autopilot]} {
run_genode_until $nic_router_match_string 60
set serial_id [output_spawn_id]
if {[have_include "power_on/qemu"]} {
set host "localhost"
set port "5555"
} else {
regexp $nic_router_match_string $output all host
set port "22"
}
# wait for ssh_terminal to come up
run_genode_until "--- SSH terminal started ---.*\n" 15 $serial_id
for {set index 0} {$index < 3} {incr index} {
puts "test interactive channel"
spawn sshpass -p xuon ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l noux $host -p $port
set ssh_id $spawn_id
run_genode_until {/bin/bash] Hello from Genode!.*\n} 15 $serial_id
send -i $ssh_id "ls\r"
run_genode_until "bin" 15 $ssh_id
send -i $ssh_id "exit\r"
run_genode_until "child \"init\" exited with exit value 0.*\n" 15 $serial_id
puts "test exec channel echo"
set echo_text "The quick brown fox jumps over the lazy dog"
spawn sshpass -p xuon ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l noux $host -p $port echo "$echo_text"
set ssh_id $spawn_id
run_genode_until ".*$echo_text.*\n" 15 $ssh_id
run_genode_until "child \"init\" exited with exit value 0.*\n" 15 $serial_id
puts "test exec channel ls"
spawn sshpass -p xuon ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l noux $host -p $port "ls"
set ssh_id $spawn_id
run_genode_until "bin" 15 $ssh_id
run_genode_until "child \"init\" exited with exit value 0.*\n" 15 $serial_id
puts "test exec channel with empty command will not hang"
spawn sshpass -p xuon ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l noux $host -p $port " "
set ssh_id $spawn_id
run_genode_until "child \"init\" exited with exit value.*\n" 15 $serial_id
}
puts ""
puts ""
} else {
run_genode_until forever
}
exec rm bin/ed25519_key bin/ed25519_key.pub
# vi: set ft=tcl :

View File

@ -1,273 +0,0 @@
#
# \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 timer
drivers/nic
drivers/rtc
server/ssh_terminal
server/fs_rom
server/vfs
lib/vfs/jitterentropy
lib/vfs/lxip
lib/vfs/pipe
test/libports/ncurses
test/terminal_echo
noux-pkg/bash
server/nic_router
}
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
create_boot_directory
#
# Generate config
#
set config {
<config verbose="no">
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="100"/>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes"/>
</start>
<start name="nic_drv">
<binary name="ipxe_nic_drv"/>
<resource name="RAM" quantum="8M"/>
<route>
<service name="Uplink"> <child name="nic_router"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="nic_router" caps="200">
<resource name="RAM" quantum="10M"/>
<provides>
<service name="Nic"/>
<service name="Uplink"/>
</provides>
<config verbose_domain_state="yes">
<policy label_prefix="ssh_terminal" domain="downlink"/>
<policy label_prefix="nic_drv" domain="uplink"/>
<domain name="uplink">
<nat domain="downlink"
tcp-ports="16384"
udp-ports="16384"
icmp-ids="16384"/>
<tcp-forward port="22" domain="downlink" to="10.0.3.2"/>
</domain>
<domain name="downlink" interface="10.0.3.1/24">
<dhcp-server ip_first="10.0.3.2" ip_last="10.0.3.2"/>
<tcp dst="0.0.0.0/0"><permit-any domain="uplink" /></tcp>
<udp dst="0.0.0.0/0"><permit-any domain="uplink" /></udp>
<icmp dst="0.0.0.0/0" domain="uplink"/>
</domain>
</config>
</start>
<start name="rtc_drv">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Rtc"/> </provides>
</start>
<start name="ssh_terminal" caps="250">
<resource name="RAM" quantum="32M"/>
<provides> <service name="Terminal"/> </provides>
<config ld_verbose="yes" port="22" allow_password="yes"
show_password="yes" ed25519_key="/etc/ssh/ed25519_key">
<policy label_prefix="/bin/bash" user="genode" password="xuon" multi_login="yes" request_terminal="yes"/>
<policy label_prefix="test-terminal_echo" user="charlie" password="echo"/>
<vfs>
<dir name="dev">
<log/>
<jitterentropy name="random"/>
<jitterentropy name="urandom"/>
<rtc/>
</dir>
<dir name="etc">
<dir name="ssh">
<rom name="ed25519_key"/>
</dir>
</dir>
<dir name="socket"> <lxip dhcp="yes"/> </dir>
<dir name="pipe"> <pipe/> </dir>
</vfs>
<libc stdout="/dev/log" stderr="/dev/log" socket="/socket" pipe="/pipe" rtc="/dev/rtc">
</libc>
</config>
<route>
<service name="Nic"> <child name="nic_router"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="test-terminal_echo">
<resource name="RAM" quantum="1M"/>
</start>
<start name="vfs">
<resource name="RAM" quantum="10M"/>
<provides><service name="File_system"/></provides>
<config>
<vfs> <tar name="bash.tar"/> </vfs>
<default-policy root="/" writeable="yes"/>
</config>
</start>
<start name="vfs_rom">
<resource name="RAM" quantum="10M"/>
<binary name="fs_rom"/>
<provides> <service name="ROM"/> </provides>
<config/>
<route>
<service name="File_system"> <child name="vfs"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="/bin/bash" caps="500">
<resource name="RAM" quantum="64M"/>
<config verbose="yes">
<libc stdin="/dev/terminal" stdout="/dev/terminal" stderr="/dev/terminal"
rtc="/dev/null"/>
<vfs>
<dir name="dev"> <terminal/> <log/> <null/> </dir>
<inline name=".bash_profile">
echo Welcome to Genode! > /dev/log
</inline>
</vfs>
<env key="TERM" value="screen"/>
<env key="HOME" value="/"/>
<env key="IGNOREEOF" value="3"/>
<arg value="/bin/bash"/>
<arg value="--login"/>
</config>
<route>
<service name="ROM" label_suffix=".lib.so"> <parent/> </service>
<service name="ROM" label_last="/bin/bash"> <child name="vfs_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
}
append_platform_drv_config
append config {
</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 ipxe_nic_drv rtc_drv report_rom vfs fs_rom
test-terminal_echo nic_router
libc.lib.so libm.lib.so vfs.lib.so
vfs_lxip.lib.so lxip.lib.so
posix.lib.so libcrypto.lib.so libssh.lib.so zlib.lib.so ncurses.lib.so
vfs_jitterentropy.lib.so vfs_pipe.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 " -nographic "
append_qemu_nic_args "hostfwd=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 genode $host -p $port
set ssh_id $spawn_id
set spawn_id_list [list $ssh_id $serial_id]
run_genode_until {.*\[init -> /bin/bash.*\] Welcome to Genode!.*\n} 15 $spawn_id_list
puts ""
puts ""
} else {
run_genode_until forever
}
exec rm bin/ed25519_key bin/ed25519_key.pub
# vi: set ft=tcl :

View File

@ -1,121 +0,0 @@
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/gems/run/ssh_terminal_ or _repos/gems/run/ssh_exec_channel_.
Configuration
~~~~~~~~~~~~~
Examplary configuration:
! <config port="2022" ed25519_key="/etc/ed25519_host_key
! allow_password="yes" allow_publickey="yes"/>
! <vfs>
! <policy label="noux-system" user="root" password="toor"/>
! <policy label="noux-user" user="user" pub_key="/etc/user.pub"/>
!
! <dir name="dev">
! <log/> <rtc/>
! <inline name="random">012345678</inline>
! </dir>
! <dir name="socket"> <lxip dhcp="yes"/> </dir>
! <dir name="etc"> <fs/> </dir>
! </vfs>
! <libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" socket="/socket"/>
! </config>
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 '<config>' 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 '<policy>' node. It first specifies 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 therefore
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 accommodate 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:
! <request_terminal user="baron"/>
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. Once the
Terminal session has been detached an 'exit' report is generated. Detaching a
terminal client will lead to a disconnect for the corresponding SSH session If
there are active SSH session after the Terminal in question has been detached, a
new report will be generated.
Notes
~~~~~
* A helper component is needed, in case an exec channel is required or when
Terminal sessions need to be started *after* the SSH connection is
established. An example can be found here: _repos/gems/src/test/exec_terminal/
* The SSH connection MUST be forcefully cut at the client in case the Terminal
session is established *before* the ssh channel is open. This can be done
when using OpenSSH by entering '~.'.
* 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).
* Reports from concurrent logins will override each other and potentially lead
to lost reports.
* 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.

View File

@ -1,190 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_LOGIN_H_
#define _SSH_TERMINAL_LOGIN_H_
/* Genode includes */
#include <util/string.h>
#include <base/heap.h>
#include <base/registry.h>
/* libssh includes */
#include <libssh/libssh.h>
/* local includes */
#include "util.h"
namespace Ssh {
using namespace Genode;
using namespace Util;
using User = String<32>;
using Password = String<64>;
using Hash = String<65>;
struct Login;
struct Login_registry;
}
struct Ssh::Login : Genode::Registry<Ssh::Login>::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<Login> &reg,
Ssh::User const &user,
Ssh::Password const &pw,
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 */
}
virtual ~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<Ssh::Login>
{
Genode::Allocator &_alloc;
Util::Pthread_mutex _mutex { };
/**
* 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());
Filename pub = node.attribute_value("pub_key", 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 mutex
*/
Util::Pthread_mutex &mutex() { return _mutex; }
/**
* 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;
}
};
#endif /* _SSH_TERMINAL_LOGIN_H_ */

View File

@ -1,52 +0,0 @@
/*
* \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 <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
/* local includes */
#include "root_component.h"
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); }

View File

@ -1,115 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_ROOT_COMPONENT_H_
#define _SSH_TERMINAL_ROOT_COMPONENT_H_
/* Genode includes */
#include <root/component.h>
#include <os/session_policy.h>
/* local includes */
#include "session_component.h"
#include "server.h"
namespace Terminal
{
using namespace Genode;
class Root_component;
}
class Terminal::Root_component : public Genode::Root_component<Session_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<Terminal::Root_component> _config_sigh {
_env.ep(), *this, &Terminal::Root_component::_handle_config_update };
void _handle_config_update()
{
_config_rom.update();
if (!_config_rom.valid()) { return; }
Libc::with_libc([&] () {
{
Util::Pthread_mutex::Guard guard(_logins.mutex());
_logins.import(_config_rom.xml());
}
_server.update_config(_config_rom.xml());
});
}
protected:
Session_component *_create_session(const char *args)
{
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;
s = new (md_alloc()) Session_component(_env, 4096, login->user);
try {
Libc::with_libc([&] () { _server.attach_terminal(*s); });
return s;
} catch (...) {
Genode::destroy(md_alloc(), s);
throw;
}
} catch (...) { throw Service_denied(); }
}
void _destroy_session(Session_component *s)
{
Libc::with_libc([&] () { _server.detach_terminal(*s); });
Genode::destroy(md_alloc(), s);
}
public:
Root_component(Genode::Env &env,
Genode::Allocator &md_alloc)
:
Genode::Root_component<Session_component>(&env.ep().rpc_ep(),
&md_alloc),
_env(env)
{
_config_rom.sigh(_config_sigh);
_handle_config_update();
}
};
#endif /* _SSH_TERMINAL_ROOT_COMPONENT_H_ */

View File

@ -1,707 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \author Sid Hussmann
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include "server.h"
/*
* Add the libssh callback forward declarations here so that we can use them
* from within the classes and thereby document the ones currently implemented.
*/
extern int channel_data_cb(ssh_session, ssh_channel, void *, uint32_t, int, void *);
extern int channel_env_request_cb(ssh_session, ssh_channel, char const *, char const *, void *);
extern int channel_pty_request_cb(ssh_session, ssh_channel, char const *, int, int, int, int, void *);
extern int channel_pty_window_change_cb(ssh_session, ssh_channel, int, int, int, int, void *);
extern int channel_shell_request_cb(ssh_session, ssh_channel, void *);
extern int channel_exec_request_cb(ssh_session, ssh_channel, char const *, void *);
extern void bind_incoming_connection(ssh_bind, void *);
extern int session_service_request_cb(ssh_session, char const *, void *);
extern int session_auth_password_cb(ssh_session, char const *, char const *, void *);
extern int session_auth_pubkey_cb(ssh_session, char const *, struct ssh_key_struct *, char, void *);
extern ssh_channel session_channel_open_request_cb(ssh_session, void *);
/**
* forward declaration of the write available callback.
*/
static int write_avail_cb(socket_t fd, int revents, void *userdata);
Ssh::Terminal_session::Terminal_session(Genode::Registry<Terminal_session> &reg,
Ssh::Terminal &conn,
ssh_event event_loop)
:
Element(reg, *this), conn(conn), _event_loop(event_loop)
{
if (pipe(_fds)) {
Genode::error("Failed to create wakeup pipe");
throw -1;
}
conn.write_avail_fd = _fds[1];
_state = PIPE_INITIALIZED;
}
void Ssh::Terminal_session::initialize_ssh_event_fds()
{
if (_state != PIPE_INITIALIZED ||
ssh_event_add_fd(_event_loop,
_fds[0],
POLLIN,
write_avail_cb,
this) != SSH_OK) {
Genode::error("Failed to initialize ssh event file descriptors");
throw -1;
}
_state = SSH_INITIALIZED;
}
Ssh::Server::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) {
Genode::error("ssh_init failed.");
throw Init_failed();
}
_ssh_bind = ssh_bind_new();
if (!_ssh_bind) {
Genode::error("ssh_bind failed.");
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 (...) {
Genode::error("loading keys failed.");
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();
}
/* add pipe to wake up loop on late connecting terminal */
if (pipe(_server_fds) ||
ssh_event_add_fd(_event_loop,
_server_fds[0],
POLLIN,
write_avail_cb,
this) != SSH_OK ) {
Genode::error("Failed to create wakeup pipe");
throw -1;
}
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 */
}
Ssh::Server::~Server()
{
close(_server_fds[0]);
close(_server_fds[1]);
}
void Ssh::Server::_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;
_channel_cb.channel_exec_request_function = channel_exec_request_cb;
ssh_callbacks_init(&_channel_cb);
}
void Ssh::Server::_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 Ssh::Server::_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);
}
void Ssh::Server::_cleanup_session(Session &s)
{
if (s.auth_sucessful) {
_log_logout(s);
}
ssh_channel_free(s.channel);
s.channel = nullptr;
ssh_blocking_flush(s.session, 5*1000);
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();
}
try {
_request_terminal_reporter.generate([&] (Xml_generator& xml) {
xml.attribute("user", s.user());
xml.attribute("exit", "now");
});
} catch (...) {
Genode::warning("could not enable exit reporting");
}
Genode::destroy(&_heap, &s);
}
void Ssh::Server::_cleanup_sessions()
{
auto cleanup = [&] (Session &s) {
if (!ssh_is_connected(s.session)) {
_cleanup_session(s);
}
};
_sessions.for_each(cleanup);
}
void Ssh::Server::_parse_config(Genode::Xml_node const &config)
{
using Util::Filename;
_verbose = config.attribute_value("verbose", false);
_log_level = config.attribute_value("debug", 0u);
_log_logins = config.attribute_value("log_logins", true);
{
Util::Pthread_mutex::Guard guard(_logins.mutex());
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", Filename());
_ecdsa_key = config.attribute_value("ecdsa_key", Filename());
_ed25519_key = config.attribute_value("ed25519_key", Filename());
Genode::log("Allowed auth methods: ",
_allow_password ? "password " : "",
_allow_publickey ? "public-key" : "");
}
void Ssh::Server::_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;
}
}
void *Ssh::Server::_server_loop(void *arg)
{
Ssh::Server *server = reinterpret_cast<Ssh::Server *>(arg);
server->loop();
return nullptr;
}
bool Ssh::Server::_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;
}
void Ssh::Server::_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 Ssh::Server::_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 Ssh::Server::_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");
}
void Ssh::Server::attach_terminal(Ssh::Terminal &conn)
{
Util::Pthread_mutex::Guard guard(_terminals.mutex());
try {
new (&_heap) Terminal_session(_terminals,
conn, _event_loop);
} catch (...) {
Genode::error("could not attach Terminal for user ",
conn.user());
throw -1;
}
/* 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);
_wake_loop();
}
void Ssh::Server::detach_terminal(Ssh::Terminal &conn)
{
Util::Pthread_mutex::Guard guard(_terminals.mutex());
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_detached = true;
/* flush before destroying the terminal */
try { sess.terminal->send(sess.channel); }
catch (...) { }
};
_sessions.for_each(invalidate_terminal);
_wake_loop();
}
void Ssh::Server::update_config(Genode::Xml_node const &config)
{
Util::Pthread_mutex::Guard guard(_terminals.mutex());
_parse_config(config);
ssh_bind_options_set(_ssh_bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &_log_level);
}
Ssh::Terminal *Ssh::Server::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;
}
Ssh::Session *Ssh::Server::lookup_session(ssh_session s)
{
Session *p = nullptr;
auto lookup = [&] (Session &sess) {
if (sess.session == s) { p = &sess; }
};
_sessions.for_each(lookup);
return p;
}
bool Ssh::Server::request_terminal(Session &session,
const char* command)
{
Util::Pthread_mutex::Guard guard(_logins.mutex());
Login const *l = _logins.lookup(session.user().string());
if (!l || !l->request_terminal) {
return false;
}
try {
_request_terminal_reporter.generate([&] (Xml_generator& xml) {
xml.attribute("user", session.user());
if (command) {
xml.attribute("command", command);
}
});
} 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;
}
void Ssh::Server::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) {
error("Too many connections");
throw -1;
}
/*
* Queue up new ssh_session to be enabled later in pthread ssh loop.
* We can't directly add the new Session object to the _session registry,
* because this ssh callback may be invoked from within a
* _session.for_each(...) invocation. The internal _session Genode::Mutex
* is taken during _session.for_each(...) and during a 'new' here,
* which would lead to a deadlock.
*/
new (&_heap) Session(_new_sessions, s, &_channel_cb, ++_session_id);
}
bool Ssh::Server::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.
*/
Util::Pthread_mutex::Guard guard(_logins.mutex());
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;
}
bool Ssh::Server::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;
/*
* In this first state the given pubkey is solely probed.
* Ideally we would check here if the given pubkey is in fact to the
* configured one, i.e., reading a 'authorized_keys' like file and
* check its entries.
*
* For now we simple accept all keys and reject them in the later
* state.
*/
if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
return true;
}
/*
* In this second state we check the provided pubkey and if it
* matches allow authentication to proceed.
*/
if (signature_state == SSH_PUBLICKEY_STATE_VALID) {
Util::Pthread_mutex::Guard guard(_logins.mutex());
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 true;
}
}
}
_log_failed(u, session, true);
return false;
}
void Ssh::Server::loop()
{
while (true) {
int const events = ssh_event_dopoll(_event_loop, -1);
if (events == SSH_ERROR) {
_cleanup_sessions();
}
{
Util::Pthread_mutex::Guard guard(_terminals.mutex());
/* finish pending initialization of terminal sessions */
auto initialize = [&] (Terminal_session &t) {
try {
if (t._state == Terminal_session::PIPE_INITIALIZED) {
t.initialize_ssh_event_fds();
}
} catch (...) {
/* Not sure what to do here - terminal is "almost" attached.
Previously service was denied in that case but as
descriptor handling must be performed in ssh loop thread
it is too late for that. */
}
};
_terminals.for_each(initialize);
/* remove all stale sessions */
auto cleanup = [&] (Session &s) {
if (s.terminal_detached) {
Terminal_session *p = nullptr;
auto lookup = [&] (Terminal_session &t) {
if (&t.conn == s.terminal) {
p = &t;
s.terminal = nullptr;
}
};
_terminals.for_each(lookup);
if (p)
Genode::destroy(&_heap, p);
}
if (!s.terminal_detached
&& 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);
}
/* enable all new sessions that got added by ssh callbacks */
auto activate = [&] (Session &inactive_session) {
/* re-queue session object */
new (&_heap) Session(_sessions,
inactive_session.session,
inactive_session.channel_cb,
inactive_session.id());
ssh_session s = inactive_session.session;
/* remove temporary object */
Genode::destroy(&_heap, &inactive_session);
/* activate session */
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.
*/
int key_exchange_result = ssh_handle_key_exchange(s);
if (SSH_OK != key_exchange_result) {
Genode::warning("key exchange returned ", key_exchange_result);
}
ssh_event_add_session(_event_loop, s);
};
_new_sessions.for_each(activate);
}
}
void Ssh::Server::_wake_loop()
{
/* wake the event loop up */
char c = 1;
::write(_server_fds[1], &c, sizeof(c));
}
static int write_avail_cb(socket_t fd, int revents, void *userdata)
{
char c;
return ::read(fd, &c, sizeof(char));
}

View File

@ -1,270 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_SERVER_H_
#define _SSH_TERMINAL_SERVER_H_
/* Genode includes */
#include <base/log.h>
#include <base/registry.h>
#include <os/reporter.h>
/* libc includes */
#include <poll.h>
#include <pthread.h>
/* libssh includes */
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
/* local includes */
#include "login.h"
#include "terminal.h"
namespace Ssh {
using namespace Genode;
struct Server;
struct Session;
struct Terminal_session;
struct Terminal_registry;
}
struct Ssh::Session : Genode::Registry<Session>::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 };
bool terminal_detached { false };
Session(Genode::Registry<Session> &reg,
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;
}
};
struct Ssh::Terminal_session : Genode::Registry<Terminal_session>::Element
{
Ssh::Terminal &conn;
ssh_event _event_loop;
int _fds[2] { -1, -1 };
enum State { UNINITIALIZED,
PIPE_INITIALIZED,
SSH_INITIALIZED } _state = UNINITIALIZED;
Terminal_session(Genode::Registry<Terminal_session> &reg,
Ssh::Terminal &conn,
ssh_event event_loop);
~Terminal_session()
{
switch (_state) {
case SSH_INITIALIZED:
ssh_event_remove_fd(_event_loop, _fds[0]);
[[fallthrough]];
case PIPE_INITIALIZED:
close(_fds[0]);
close(_fds[1]);
[[fallthrough]];
case UNINITIALIZED:
break;
}
}
void initialize_ssh_event_fds();
};
struct Ssh::Terminal_registry : Genode::Registry<Terminal_session>
{
Util::Pthread_mutex _mutex { };
Util::Pthread_mutex &mutex() { return _mutex; }
};
class Ssh::Server
{
public:
struct Init_failed : Genode::Exception { };
struct Invalid_config : Genode::Exception { };
private:
using Session_registry = Genode::Registry<Session>;
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 };
int _server_fds[2] { -1, -1 };
bool _config_once { false };
ssh_bind _ssh_bind;
ssh_event _event_loop;
Util::Filename _rsa_key { };
Util::Filename _ecdsa_key { };
Util::Filename _ed25519_key { };
Expanding_reporter _request_terminal_reporter { _env,
"request_terminal",
"request_terminal" };
Terminal_registry _terminals { };
Login_registry &_logins;
pthread_t _event_thread;
/*
* 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 { };
Session_registry _sessions { };
Session_registry _new_sessions { };
uint32_t _session_id { 0 };
void _initialize_channel_callbacks();
void _initialize_session_callbacks();
void _initialize_bind_callbacks();
void _cleanup_session(Session &s);
void _cleanup_sessions();
void _parse_config(Genode::Xml_node const &config);
void _load_hostkey(Util::Filename const &file);
/*
* Event execution
*/
static void *_server_loop(void *arg);
bool _allow_multi_login(ssh_session s, Login const &login);
/********************
** Login messages **
********************/
void _log_failed(char const *user, Session const &s, bool pubkey);
void _log_logout(Session const &s);
void _log_login(User const &user, Session const &s, bool pubkey);
void _wake_loop();
public:
Server(Genode::Env &env,
Genode::Xml_node const &config,
Ssh::Login_registry &logins);
virtual ~Server();
void loop();
/***************************************************************
** Methods below are only used by Terminal session front end **
***************************************************************/
/**
* Attach Terminal session
*/
void attach_terminal(Ssh::Terminal &conn);
/**
* Detach Terminal session
*/
void detach_terminal(Ssh::Terminal &conn);
/**
* Update config
*/
void update_config(Genode::Xml_node const &config);
/*******************************************************
** Methods below are only used by callback back ends **
*******************************************************/
/**
* Look up Terminal for session
*/
Ssh::Terminal *lookup_terminal(Session &s);
/**
* Look up Session for SSH session
*/
Session *lookup_session(ssh_session s);
/**
* Request Terminal
*/
bool request_terminal(Session &session, const char* command = nullptr);
/**
* Handle new incoming connections
*/
void incoming_connection(ssh_session s);
/**
* Handle password authentication
*/
bool auth_password(ssh_session s, char const *u, char const *pass);
/**
* Handle public-key authentication
*/
bool auth_pubkey(ssh_session s, char const *u,
struct ssh_key_struct *pubkey,
char signature_state);
};
#endif /* _SSH_TERMINAL_SERVER_H_ */

View File

@ -1,103 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_SESSION_COMPONENT_H_
#define _SSH_TERMINAL_SESSION_COMPONENT_H_
/* Genode includes */
#include <base/capability.h>
#include <base/signal.h>
/* local includes */
#include "util.h"
#include "terminal.h"
namespace Terminal {
class Session_component;
};
class Terminal::Session_component : public Genode::Rpc_object<Session, Session_component>,
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)
{ }
virtual ~Session_component() = default;
/********************************
** 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<char>();
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;
char *buf = _io_buffer.local_addr<char>();
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;
}
};
#endif /* _SSH_TERMINAL_SESSION_COMPONENT_H_ */

View File

@ -1,304 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \author Sid Hussmann
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* libssh includes */
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
/* local includes */
#include "server.h"
/***********************
** Channel callbacks **
***********************/
/**
* Handle SSH channel data request
*/
int channel_data_cb(ssh_session session, ssh_channel channel,
void *data, uint32_t len, int is_stderr,
void *userdata)
{
using Genode::error;
if (len == 0) {
return 0;
}
Ssh::Server &server = *reinterpret_cast<Ssh::Server*>(userdata);
Ssh::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) {
error("no terminal");
return SSH_ERROR;
}
Ssh::Terminal &conn { *p->terminal };
Util::Pthread_mutex::Guard guard { conn.read_buf.mutex() };
char const *src { reinterpret_cast<char const*>(data) };
size_t num_bytes { 0 };
while ((conn.read_buf.write_avail() > 0) && (num_bytes < len)) {
char c = src[num_bytes];
/* 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++;
}
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.
*/
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 request
*/
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<Ssh::Server*>(userdata);
Ssh::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 wait for a Terminal session to be established.
*/
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
*/
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<Ssh::Server*>(userdata);
Ssh::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.
*/
int channel_shell_request_cb(ssh_session session, ssh_channel channel,
void *userdata)
{
return SSH_OK;
}
/**
* Handle SSH channel exec request
*
* Exec requests provide a command that needs to be executed.
* The command is provided while starting a new terminal using
* request_terminal().
*/
int channel_exec_request_cb(ssh_session session, ssh_channel channel,
const char *command,
void *userdata)
{
using namespace Genode;
Ssh::Server &server = *reinterpret_cast<Ssh::Server*>(userdata);
Ssh::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 wait for a Terminal session to be established.
*/
if (!p->terminal) {
p->terminal = server.lookup_terminal(*p);
if (!p->terminal) {
return server.request_terminal(*p, command) ? SSH_OK
: SSH_ERROR;
}
}
/* exec commands can only be done with newly started terminals */
return SSH_ERROR;
}
/***********************
** Session callbacks **
***********************/
/**
* Handle SSH session service requests
*/
int session_service_request_cb(ssh_session,
char const *service, void*)
{
return Genode::strcmp(service, "ssh-userauth") == 0 ? 0 : -1;
}
/**
* Handle SSH session password authentication requests
*/
int session_auth_password_cb(ssh_session session,
char const *user, char const *password,
void *userdata)
{
Ssh::Server &server = *reinterpret_cast<Ssh::Server*>(userdata);
return server.auth_password(session, user, password) ? SSH_AUTH_SUCCESS
: SSH_AUTH_DENIED;
}
/**
* Handle SSH session public-key authentication requests
*/
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<Ssh::Server*>(userdata);
return server.auth_pubkey(session, user, pubkey, state) ? SSH_AUTH_SUCCESS
: SSH_AUTH_DENIED;
}
/**
* Handle SSH session open channel requests
*/
ssh_channel session_channel_open_request_cb(ssh_session session,
void *userdata)
{
using namespace Genode;
Ssh::Server &server = *reinterpret_cast<Ssh::Server*>(userdata);
Ssh::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
*/
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<Ssh::Server*>(userdata);
try {
server.incoming_connection(session);
}
catch (...) {
ssh_disconnect(session);
ssh_free(session);
}
}

View File

@ -1,8 +0,0 @@
TARGET = ssh_terminal
SRC_CC = main.cc
SRC_CC += server.cc
SRC_CC += ssh_callbacks.cc
SRC_CC += util.cc
LIBS = base libc libssh
CC_CXX_WARN_STRICT =

View File

@ -1,282 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_TERMINAL_H_
#define _SSH_TERMINAL_TERMINAL_H_
/* Genode includes */
#include <base/capability.h>
#include <base/signal.h>
#include <session/session.h>
#include <base/log.h>
#include <terminal_session/terminal_session.h>
/* local includes */
#include "login.h"
namespace Ssh
{
using namespace Genode;
class Terminal;
}
class Ssh::Terminal
{
private:
typedef Util::Buffer<4096u> Buffer;
Mutex _write_buf_swap { };
Buffer _write_buf_a { };
Buffer _write_buf_b { };
Buffer *_write_buf_ep { &_write_buf_a };
Buffer *_write_buf_pthread { &_write_buf_b };
::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:
Buffer read_buf { };
int write_avail_fd { -1 };
/**
* Constructor
*/
Terminal(Ssh::User const &user) : _user(user) { }
virtual ~Terminal() = default;
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;
if (_attached_channels > 0) {
notify_connected();
}
}
/**
* 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)
{
{
/* swap write buffers if current is empty */
Mutex::Guard guard(_write_buf_swap);
if (!_write_buf_pthread->read_avail()) {
auto buffer_tmp = _write_buf_ep;
_write_buf_ep = _write_buf_pthread;
_write_buf_pthread = buffer_tmp;
}
}
/* write buffer for pthread is used w/o mutex, it's not used by EP */
Buffer &write_buf = *_write_buf_pthread;
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)
{
Util::Pthread_mutex::Guard guard(read_buf.mutex());
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)
{
size_t num_bytes = 0;
{
Mutex::Guard guard(_write_buf_swap);
Buffer &write_buf = *_write_buf_ep;
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 */
Libc::with_libc([&] {
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()
{
bool empty = true;
Libc::with_libc([&] {
Util::Pthread_mutex::Guard guard(read_buf.mutex());
empty = !read_buf.read_avail();
});
return empty;
}
};
#endif /* _SSH_TERMINAL_TERMINAL_H_ */

View File

@ -1,36 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* local includes */
#include "util.h"
char const *Util::get_time()
{
static char buffer[32];
char const *p = "<invalid date>";
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;
}

View File

@ -1,96 +0,0 @@
/*
* \brief Component providing a Terminal session via SSH
* \author Josef Soentgen
* \author Pirmin Duss
* \date 2019-05-29
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
* Copyright (C) 2019 gapfruit AG
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _SSH_TERMINAL_UTIL_H_
#define _SSH_TERMINAL_UTIL_H_
/* Genode includes */
#include <util/string.h>
#include <libc/component.h>
/* libc includes */
#include <pthread.h>
#include <unistd.h>
#include <time.h>
namespace Util
{
using Filename = Genode::String<256>;
template <size_t C>
struct Buffer;
/*
* get the current time from the libc backend.
*/
char const *get_time();
struct Pthread_mutex;
}
struct Util::Pthread_mutex
{
public:
class Guard
{
private:
Pthread_mutex &_mutex;
public:
explicit Guard(Pthread_mutex &mutex) : _mutex(mutex) { _mutex.lock(); }
~Guard() { _mutex.unlock(); }
};
private:
pthread_mutex_t _mutex;
public:
Pthread_mutex() { pthread_mutex_init(&_mutex, nullptr); }
~Pthread_mutex() { pthread_mutex_destroy(&_mutex); }
void lock() { pthread_mutex_lock(&_mutex); }
void unlock() { pthread_mutex_unlock(&_mutex); }
};
template <size_t C>
struct Util::Buffer
{
Util::Pthread_mutex _mutex { };
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]; }
void append(char c) { _data[_head++] = c; }
void consume(size_t n) { _tail += n; }
void reset() { _head = _tail = 0; }
Util::Pthread_mutex &mutex() { return _mutex; }
};
#endif /* _SSH_TERMINAL_UTIL_H_ */