; ------------------------------------------------------------------------
; HeavyThing x86_64 assembly language library and showcase programs
; Copyright © 2015-2018 2 Ton Digital
; Homepage: https://2ton.com.au/
; Author: Jeff Marrison <jeff@2ton.com.au>
;
; This file is part of the HeavyThing library.
;
; HeavyThing is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License, or
; (at your option) any later version.
;
; HeavyThing is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along
; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
; ------------------------------------------------------------------------
; sshecho.asm: simple SSH2 echo server, listens on INADDR_ANY:8001
;
; All HeavyThing goods must start with settings and the main include
include '../../ht_defaults.inc'
include '../../ht.inc'
; unlike the simple TCP echo server, SSH2 is an IO chain (aka layer).
; The HeavyThing library design for IO chaining really does mean you could
; nest them arbitrarily (the webserver is also an IO chain, so you could
; do TLS -> SSH2 -> TLS -> SSH2 -> webserver, hahah, that hurts my head,
; but it would work a treat).
; So, instead of using the epoll vtable for our echoserver virtual methods
; we need to use the io layer's.
; our "onconnect" function gets called when a new connection arrives.
; arguments: rdi == epoll object, rsi == sockaddr ptr, edx == length of same
; we only wish to send our greeting line on connect.
falign
sshecho_connected:
prolog sshecho_connected
mov rsi, .greeting
mov edx, .greetinglen
; unlike our simple TCP echo server, which is tied directly to the epoll layer
; we are an IO chain, so we can't call epoll$send from here, we need to use
; io$send instead.
call io$send
; Note: we could have also done:
; mov rcx, [rdi] ; our virtual method table pointer
; call qword [rcx+io_vsend] ; its send function
; and this would allow us to do "overrides" without modifying code later :-)
epilog
dalign
.greeting:
db 'HeavyThing library SSH2 echo server example reporting for duty',13,10
.greetinglen = $ - .greeting
; our data receive function, which gets three arguments: rdi == epoll object,
; rsi == ptr to data, rdx == length of same
falign
sshecho_received:
prolog sshecho_received
; also unlike the simple TCP echo server, since we are an io chain and not
; directly using the epoll object itself, we can just call io$send and be
; done with it.
; since we will be getting keystroke-at-a-time, if we get a 3 (Ctrl-C), KO
; the connection:
cmp byte [rsi], 3
je .ko
; so that our echo appears to actually do linefeeds, haha, catch CR too:
cmp byte [rsi], 13
je .carriagereturn
call io$send
; Also note from above, we could have also done:
; mov rcx, [rdi] ; our virtual method table pointer
; call qword [rcx+io_vsend] ; its send function
; and this would allow us to do "overrides" without modifying code later :-)
; the io layers lets the receive function determine whether to close the
; connection or not, so if we return 1 here, our connection will be closed
; and 0 will keep it open.
xor eax, eax
epilog
calign
.carriagereturn:
mov rsi, .crlf
mov edx, 2
call io$send
xor eax, eax
epilog
calign
.ko:
mov eax, 1
epilog
dalign
.crlf:
db 13, 10
; similar to C++ virtual methods, we need a virtual method table, copied
; and modified from io$vtable (because we are an IO layer this time, not a
; direct epoll layer user).
dalign
sshecho_vtable:
dq io$destroy, io$clone, sshecho_connected, io$send, sshecho_received, io$error, io$timeout
public _start
_start:
; every HeavyThing program needs to start with a call to initialise it
call ht$init
; first thing we need is a default io object for our sshecho layer:
call io$new
mov r12, rax
; set its virtual method table to our own:
mov qword [rax+io_vmethods_ofs], sshecho_vtable
; next in our layers needs to be the SSH server layer:
xor edi, edi ; use /etc/ssh by default
call ssh$new_server
test rax, rax
jz .hostkeyerror
mov r13, rax
; we have to link our initial io layer with our ssh layer
mov [r12+io_child_ofs], rax ; app layer child == SSH layer
mov [rax+io_parent_ofs], r12 ; SSH layer parent == app layer
; and the final layer needs to be an epoll object so that it has network access
mov rdi, epoll$default_vtable ; the default vtable is fine, it handles io chaining for us
xor esi, esi ; no extra space needed in the epoll object
call epoll$new
mov r14, rax
; we have to link the epoll object to the SSH layer:
mov [r13+io_child_ofs], rax ; SSH layer child == epoll layer
mov [r14+io_parent_ofs], r13 ; epoll layer parent == SSH2 layer
; setup a sockaddr_in for our listener:
sub rsp, sockaddr_in_size
mov rdi, rsp
mov esi, 8001
call inaddr_any
; setup our actual socket/listener, noting here that the epoll$inbound (as well as outbounds)
; will deal with io chains correctly, so we only have to pass the topmost in our layer, which
; is our sshecho io object:
mov rdi, rsp
mov esi, sockaddr_in_size
mov rdx, r12 ; topmost layer (though we could do lowest too)
call epoll$inbound
; epoll$inbound returns 0 on failure (bind)
test eax, eax
jz .bindfailed
; display our banner to our controlling terminal, we don't want to be too quiet
mov rdi, .banner
call string$to_stdoutln
; and finally, turn control over to the epoll layer, which won't come back.
call epoll$run
; epoll$run does not return.
calign
.bindfailed:
mov rdi, .bindfail
call string$to_stdoutln
mov eax, syscall_exit
mov edi, 1
syscall
calign
.hostkeyerror:
mov rdi, .hostkeys
call string$to_stdoutln
mov eax, syscall_exit
mov edi, 1
syscall
cleartext .bindfail, 'bind to INADDR_ANY:8001 failed.'
cleartext .banner, 'SSH2 echo server listening on port 8001'
cleartext .hostkeys, '/etc/ssh host keys and/or contents error.'
; include the global data segment:
include '../../ht_data.inc'