mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 17:52:52 +00:00
parent
d4a6342295
commit
ecb1a6187c
@ -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 $@)
|
@ -1 +0,0 @@
|
||||
2021-10-13 c49961ec747733def5b1d6c7f0a0fca302ecda8f
|
@ -1,10 +0,0 @@
|
||||
base
|
||||
gems
|
||||
libc
|
||||
libssh
|
||||
nic_session
|
||||
report_session
|
||||
os
|
||||
terminal_session
|
||||
timer_session
|
||||
vfs
|
@ -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 :
|
@ -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 :
|
@ -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.
|
@ -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> ®,
|
||||
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_ */
|
@ -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); }
|
@ -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_ */
|
@ -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> ®,
|
||||
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));
|
||||
}
|
@ -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> ®,
|
||||
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> ®,
|
||||
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_ */
|
@ -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_ */
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 =
|
@ -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_ */
|
@ -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;
|
||||
}
|
@ -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_ */
|
Loading…
x
Reference in New Issue
Block a user