spartserv

Simple client and server for the spartan protocol
git clone https://noulin.net/git/spartserv.git
Log | Files | Refs | README

commit b197dcecdd9ce2ac9aa2b9b90dd4ff52e5a0bd62
parent b02155d53bfc6b4c96744e79ce70c4adea2b823a
Author: Remy Noulin <loader2x@gmail.com>
Date:   Thu, 23 Feb 2023 14:49:07 +0200

Add spartasm, a spartan server written in assembly

README.md              |   6 +-
spartasm/README.md     |  21 ++++
spartasm/clean.sh      |   1 +
spartasm/constants.asm |  41 +++++++
spartasm/data.asm      |  36 ++++++
spartasm/macros.asm    |  21 ++++
spartasm/main.asm      | 314 +++++++++++++++++++++++++++++++++++++++++++++++++
spartasm/make.sh       |   2 +
spartasm/release.sh    |   3 +
spartasm/string.asm    | 195 ++++++++++++++++++++++++++++++
spartasm/syscall.asm   | 159 +++++++++++++++++++++++++
11 files changed, 797 insertions(+), 2 deletions(-)

Diffstat:
MREADME.md | 6++++--
Aspartasm/README.md | 21+++++++++++++++++++++
Aspartasm/clean.sh | 1+
Aspartasm/constants.asm | 41+++++++++++++++++++++++++++++++++++++++++
Aspartasm/data.asm | 36++++++++++++++++++++++++++++++++++++
Aspartasm/macros.asm | 21+++++++++++++++++++++
Aspartasm/main.asm | 314+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aspartasm/make.sh | 2++
Aspartasm/release.sh | 3+++
Aspartasm/string.asm | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aspartasm/syscall.asm | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 797 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md @@ -1,11 +1,13 @@ -This repository has 2 simple clients and a simple server for the spartan protocol written in C. +This repository has 2 simple clients and 2 simple servers for the spartan protocol written in C and x64 assembly. About the spartan protocol: [Spartan on the web](https://portal.mozz.us/spartan/spartan.mozz.us/) [Spartan on gemini](gemini://spartan.mozz.us) [Spartan on spartan](spartan://spartan.mozz.us) -To build this, you need a shell and the GCC C compiler and run: +`spartasm` is the server written in assembly and the information about `spartasm` is in `spartasm/README.md`. + +To build the clients and server written in C, you need a shell and the GCC C compiler and run: ``` apt-get install gcc ./build.sh diff --git a/spartasm/README.md b/spartasm/README.md @@ -0,0 +1,21 @@ +# Spartasm server +Spartasm is a spartan server written in Intel x64 assembly. + +# Install +``` +apt-get install yasm +./release.sh +``` + +# Usage +``` +./spartasm ./ 1300 +``` + +# Feature + +- Serves text/gemini files +- 8.7KB binary +- Single threaded +- Serves files from specified document root +- Listen on specified port diff --git a/spartasm/clean.sh b/spartasm/clean.sh @@ -0,0 +1 @@ +rm -rf main.o spartasm diff --git a/spartasm/constants.asm b/spartasm/constants.asm @@ -0,0 +1,41 @@ +%define BUFFER_SIZE 8192 ; 8KB recv buffer +%define URL_LENGTH_LIMIT 2000 +%define DIRECTORY_LENGTH_LIMIT 100 + +%define MMAP_PROT_READ 0x1 +%define MMAP_PROT_WRITE 0x2 +%define MMAP_MAP_PRIVATE 0x2 +%define MMAP_MAP_ANON 0x20 + +%define FD_STDOUT 0x1 + +%define OPEN_RDONLY 00 +%define OPEN_DIRECTORY 0x10000 ; Open will fail if path is not a directory + +%define LSEEK_SET 0 ; seek to offset bytes +%define LSEEK_END 2 ; seek to end plus offset + +%define AF_INET 2 +%define SOCK_STREAM 1 +%define PROTO_TCP 6 + +%define LEVEL_SOL_TCP 1 +%define LEVEL_IPPROTO_TCP 6 +%define SOCKOPT_TCP_REUSEADDR 2 +%define SOCKOPT_TCP_CORK 3 + +;System Call Values +%define SYS_WRITE 1 ;int fd, const void *buf, size_t count +%define SYS_OPEN 2 ;const char *pathname, int flags, mode_t mode +%define SYS_CLOSE 3 ;unsigned int fd +%define SYS_LSEEK 8 ;int fd, off_t offset, int whence +%define SYS_MMAP 9 ;void *addr, size_t length, int prot, int flags, int fd, off_t offset +%define SYS_SENDFILE 40 ;int out_fd, int in_fd, off_t *offset, size_t count +%define SYS_SOCKET 41 ;int domain, int type, int protocol +%define SYS_ACCEPT 43 ;int sockfd, struct sockaddr *addr, socklen_t *addrlen +%define SYS_SENDTO 44 ;int sockfd, const void *buf, size_t len, int flags, ... +%define SYS_RECVFROM 45 ;int sockfd, void *buf, size_t len, int flags +%define SYS_BIND 49 ;int sockfd, const struct sockaddr *addr, socklen_t addrlen +%define SYS_LISTEN 50 ;int sockfd, int backlog +%define SYS_SETSOCKOPT 54; int sockfd, int level, int optname,const void *optval, socklen_t optlen +%define SYS_EXIT_GROUP 231 ;int status diff --git a/spartasm/data.asm b/spartasm/data.asm @@ -0,0 +1,36 @@ + struc sockaddr_in + sin_family: resw 1 + sin_port: resw 1 + sin_addr: resd 1 + endstruc + + sa: istruc sockaddr_in + at sin_family, dw AF_INET + at sin_port, dw 0 + at sin_addr, dd 0 ;INADDR_ANY + iend + + new_line db 0x0a + + invalid_s db "4 Invalid request",0x0d,0x0a,0x00 + invalid_s_len equ $ - invalid_s + + text_gemini_s db "2 text/gemini",0x0d,0x0a,0x00 + text_gemini_s_len equ $ - text_gemini_s + + filter_prev_dir db "../",0x00 + filter_prev_dir_len equ $ - filter_prev_dir + + crlf db 0x0d,0x0a,0x00 + crlf_len equ $ - crlf + + msg_bind_error db "Error - Bind() failed. Check if port is in use or you have sufficient privileges.",0x00 + msg_bind_error_len equ $ - msg_bind_error + msg_error db "An error has occured, exiting",0x00 + msg_error_len equ $ - msg_error + msg_help db "Usage: ./spartasm /path/to/directory port",0x00 + msg_help_len equ $ - msg_help + msg_not_a_directory dd "Error: Specified document root is not a directory",0x00 + msg_not_a_directory_len equ $ - msg_not_a_directory + msg_request_log db 0x0a,"Request: ",0x00 + msg_request_log_len equ $ - msg_request_log diff --git a/spartasm/macros.asm b/spartasm/macros.asm @@ -0,0 +1,21 @@ +%macro stackpush 0 + push rdi + push rsi + push rdx + push r10 + push r8 + push r9 + push rbx + push rcx +%endmacro + +%macro stackpop 0 + pop rcx + pop rbx + pop r9 + pop r8 + pop r10 + pop rdx + pop rsi + pop rdi +%endmacro diff --git a/spartasm/main.asm b/spartasm/main.asm @@ -0,0 +1,314 @@ +%include "constants.asm" +%include "macros.asm" + +;Follwing amd64 syscall standards for internal function calls: rdi rsi rdx r10 r8 r9 + +section .data + %include "data.asm" + +section .bss + listen_socket: resq 1 + listen_port: resw 1 + one_constant: resq 1 + directory_path: resq 1 + +section .text + %include "string.asm" + %include "syscall.asm" + +global _start + +_start: + + mov QWORD [one_constant], 1 + + mov rdi, [rsp] ;Num of args + cmp rdi,3 ;Exit if no argument, should be directory location + jne exit_with_help + + mov rdi, [rsp+16+8]; Port (second) parameter + call string_atoi + xchg al, ah + mov [listen_port], eax + + mov rax, [rsp+16] ;Directory (first) parameter + mov [directory_path], rax + + ;Try opening directory + mov rdi, [directory_path] + call sys_open_directory + + cmp rax, 0 + jl exit_with_no_directory_error + + ;Create socket + call sys_create_tcp_socket + cmp rax, 0 + jl exit_error + mov [listen_socket], rax + + ;reuseaddr + mov rdi, [listen_socket] + call sys_reuse + + ;Bind to port + call sys_bind_server + cmp rax, 0 + jl exit_bind_error + + ;Start listening + call sys_listen + + mov rbp, rsp + sub rsp, 16 + ;Offsets: 8 - socket fd, 16 - buffer + + mov QWORD [rbp-16], 0 ; Used for pointer to recieve buffer + + mov rdi, BUFFER_SIZE+2+URL_LENGTH_LIMIT+DIRECTORY_LENGTH_LIMIT ; Allow room to append nul, and to create path + call sys_mmap_mem + mov QWORD [rbp-16], rax + +event_loop_start: + + call sys_accept + + mov [rbp-8], rax ; save fd + + mov rdi, rax + call sys_cork ; cork it + + ;Network stuff starts here + mov rdi, QWORD [rbp-8] ;fd + mov rsi, [rbp-16] ;buffer + mov rdx, BUFFER_SIZE ;size + call sys_recv + + cmp rax, 0 + jle close_client_connection + + push rax ; save original received length + + ; add nul + mov rdi, [rbp-16] + add rdi, rax + mov BYTE [rdi], 0x00 + + ;Make sure its a valid request + mov rdi, [rbp-16] + mov rsi, crlf + call string_ends_with + cmp rax, 1 + jne invalid_request_response_pop_length + + ;Find request + mov rax, 0x2F ; '/' character + mov rdi, [rbp-16] ;scan buffer + mov rcx, -1 ;Start count + cld ;Increment rdi + repne scasb + jne invalid_request_response_pop_length + mov rax, -2 + sub rax, rcx ;Get the length + + mov r8, rax ; start offset for requested file + mov rdi, [rbp-16] + add rdi, r8 + mov rax, 0x20 ;'space' character + mov rcx, -1 + cld + repne scasb + jne invalid_request_response_pop_length + mov rax, -1 + sub rax, rcx + mov r9, rax + add r9, r8 ;end offset + + ;TODO: Assuming it's a file, need directory handling too + + pop r11 ; restore orig recvd length + mov rdi, [rbp-16] + add rdi, r11 ; end of buffer, lets use it! + mov r12, r11 ; keeping count + + mov rsi, [directory_path] + xor r15, r15 ; Make sure directory path does not exceed DIRECTORY_LENGTH_LIMIT + +append_directory_path: + inc r15 + cmp r15, DIRECTORY_LENGTH_LIMIT + je invalid_request_response + lodsb + stosb + inc r12 + cmp al, 0x00 + jne append_directory_path + + dec r12 ; get rid of 0x00 + + ; Adds the file to the end of buffer ( where we just put the document prefix ) + mov rsi, [rbp-16] + add rsi, r8 ; points to beginning of path + mov rdi, [rbp-16] + add rdi, r12 ;go to end of buffer + mov rcx, r9 + sub rcx, r8 + cmp rcx, URL_LENGTH_LIMIT ; Make sure this does not exceed URL_PATH_LENGTH + jg invalid_request_response + add r12, rcx + rep movsb + + dec r12 ; exclude space character + mov rdi, [rbp-16] + add rdi, r12 + mov BYTE [rdi], 0x00 ; add nul + + mov r9, r11 ; saving offset into a stack saved register + ; [rbp-16] + r9 now holds string for file opening + + remove_pre_dir: + + ;-----Simple request logging + ; mov rdi, msg_request_log + ; mov rsi, msg_request_log_len + ; call sys_write + ; mov rdi, [rbp-16] + ; add rdi, r9 + ; call get_string_length + ; mov rsi, rax + ; call print_line + ;-----End Simple logging + + mov rdi, [rbp-16] + add rdi, r9 + mov rsi, filter_prev_dir ; remove any '../' + call string_remove + cmp rax, 0 + jne remove_pre_dir + + + ; TODO content types + ; mov rdi, [rbp-16] + ; add rdi, r9 + ; call detect_content_type + ; mov r8, rax ;r8: Content Type + + ;------------response---------------- + + ;Try to open requested file + mov rdi, [rbp-16] + add rdi, r9 + call sys_open + cmp rax, 0 + + jl invalid_request_response ;file not found + + ; Done with buffer offsets, put response and data into it starting at beg + mov r10, rax ; r10: file fd + + ; we're good to go + + ;---------2 Response Start------------ + + ; get file size + mov rdi, r10 ; fd + xor rsi, rsi + mov rdx, LSEEK_END + call sys_lseek + + ;rax - total filesize + push rax + + ;Seek to beg of file + mov rdi, r10 ; fd + xor rsi, rsi + mov rdx, LSEEK_SET + call sys_lseek + + ; send response header + mov rdi, [rbp-8] ; socket + mov rsi, text_gemini_s + mov rdx, text_gemini_s_len + call sys_send + + cmp rax, 0 + jle close_file + + ; send file + mov rdi, [rbp-8] ; socket + mov rsi, r10 ; fd + pop rdx ; file size + call sys_sendfile + jmp close_file + ;---------2 Response End-------------- + + ;---------4 Response Start------------ +invalid_request_response_pop_length: + pop rsi + +invalid_request_response: + mov rdi, [rbp-8] ; socket + mov rsi, invalid_s + mov rdx, invalid_s_len + call sys_send + + jmp close_client_connection + ;---------4 Response End-------------- + + close_file: + ;Uncork + mov rdi, [rbp-8] + call sys_uncork + + ;Close File + mov rdi, r10 + call sys_close + + ;Close Socket +close_client_connection: + mov rdi, [rbp-8] + call sys_close + + jmp event_loop_start + +exit_with_no_directory_error: + mov rdi, msg_not_a_directory + mov rsi, msg_not_a_directory_len + call print_line + jmp exit + +exit_with_help: + mov rdi, msg_help + mov rsi, msg_help_len + call print_line + jmp exit + +exit_error: + mov rdi, msg_error + mov rsi, msg_error_len + call print_line + + mov rdi, -1 + mov rax, SYS_EXIT_GROUP + syscall + jmp exit + +exit_bind_error: + mov rdi, msg_bind_error + mov rsi, msg_bind_error_len + call print_line + + mov rdi, -1 + mov rax, SYS_EXIT_GROUP + syscall + jmp exit + +exit: + + mov rdi, [listen_socket] + call sys_close + + xor rdi, rdi + mov rax, SYS_EXIT_GROUP + syscall + diff --git a/spartasm/make.sh b/spartasm/make.sh @@ -0,0 +1,2 @@ +yasm -g dwarf2 -f elf64 -a x86 main.asm -o main.o +ld main.o -o spartasm diff --git a/spartasm/release.sh b/spartasm/release.sh @@ -0,0 +1,3 @@ +yasm -f elf64 -a x86 main.asm -o main.o +ld main.o -o spartasm +strip -s spartasm diff --git a/spartasm/string.asm b/spartasm/string.asm @@ -0,0 +1,195 @@ +get_string_length: ; rdi = pointer, ret rax + stackpush + cld + mov r10, -1 + mov rsi, rdi +get_string_length_start: + inc r10 + lodsb + cmp al, 0x00 + jne get_string_length_start + mov rax, r10 + stackpop + ret + +string_copy: ; rdi = dest, rsi = source, rdx = bytes to copy + stackpush + mov rcx, rdx + inc rcx ; to get null + cld + rep movsb + stackpop + ret + +string_atoi: ; rdi = string, rax = int + stackpush + + mov r8, 0 ; ;return + + call get_string_length + mov r10, rax ; length + cmp rax, 0 + je string_atoi_ret_empty + + mov r9, 1 ; multiplier + + dec r10 + string_atoi_loop: + xor rbx, rbx + mov bl, BYTE [rdi+r10] + sub bl, 0x30 ;get byte, subtract to get real from ascii value + mov rax, r9 + mul rbx ; multiply value by multiplier + add r8, rax ; add result to running total + dec r10 ; next digit + mov rax, 10 ; multiply r9 ( multiplier ) by 10 + mul r9 + mov r9, rax + cmp r10, -1 + jne string_atoi_loop + jmp string_atoi_ret + + string_atoi_ret_empty: + mov rax, -1 + stackpop + ret + + string_atoi_ret: + mov rax, r8 + stackpop + ret + +string_contains: ;rdi = haystack, rsi = needle, ret = rax: location of string, else -1 + stackpush + + xor r10, r10 ; total length from beginning + xor r8, r8 ; count from offset + + string_contains_start: + mov dl, BYTE [rdi] + cmp dl, 0x00 + je string_contains_ret_no + cmp dl, BYTE [rsi] + je string_contains_check + inc rdi + inc r10 ; count from base ( total will be r10 + r8 ) + jmp string_contains_start + + string_contains_check: + inc r8 ; already checked at pos 0 + cmp BYTE [rsi+r8], 0x00 + je string_contains_ret_ok + mov dl, [rdi+r8] + cmp dl ,0x00 + je string_contains_ret_no + cmp dl, [rsi+r8] + je string_contains_check + + inc rdi + inc r10 + xor r8, r8 + jmp string_contains_start + + string_contains_ret_ok: + mov rax, r10 + jmp string_contains_ret + + string_contains_ret_no: + mov rax, -1 + + string_contains_ret: + stackpop + ret + +;Removes first instance of string +string_remove: ;rdi = source, rsi = string to remove, ret = 1 for removed, 0 for not found + stackpush + + mov r9, 0 ; return flag + + call get_string_length + mov r8, rax ; r8: source length + cmp r8, 0 + mov rax, 0 + jle string_remove_ret ; source string empty? + + push rdi + mov rdi, rsi + call get_string_length + mov r10, rax ; r10: string to remove length + pop rdi + cmp r10, 0 + mov rax, 0 + jle string_remove_ret ; string to remove is blank? + + string_remove_start: + + call string_contains + + cmp rax,-1 + je string_remove_ret + + ;Shift source string over + add rdi, rax + mov rsi, rdi + add rsi, r10 ; copying to itself sans found string + + cld + string_remove_do_copy: + lodsb + stosb + cmp al, 0x00 + jne string_remove_do_copy + + mov r9, 1 + + string_remove_ret: + mov rax, r9 + stackpop + ret + +string_ends_with:;rdi = haystack, rsi = needle, ret = rax: 0 false, 1 true + stackpush + + ;Get length of haystack, store in r8 + call get_string_length + mov r8, rax + + ;Get length of needle, store in r10 + push rdi + mov rdi, rsi + call get_string_length + mov r10, rax + pop rdi + + add rdi, r8 + add rsi, r10 + + xor rax, rax + xor rdx, rdx + + string_ends_with_loop: + ;Start from end, dec r10 till 0 + mov dl, BYTE [rdi] + cmp dl, BYTE [rsi] + jne string_ends_with_ret + dec rdi + dec rsi + dec r10 + cmp r10, 0 + jne string_ends_with_loop + mov rax, 1 + + string_ends_with_ret: + stackpop + ret + +print_line: ; rdi = pointer, rsi = length + stackpush + call sys_write + mov rdi, new_line + mov rsi, 1 + call sys_write + stackpop + ret + diff --git a/spartasm/syscall.asm b/spartasm/syscall.asm @@ -0,0 +1,159 @@ +sys_open_directory:;rdi = path, rax = ret ( fd ) + stackpush + mov rsi, OPEN_DIRECTORY | OPEN_RDONLY ;flags + mov rax, SYS_OPEN + syscall + stackpop + ret + +sys_create_tcp_socket: + stackpush + mov rdi, AF_INET + mov rsi, SOCK_STREAM + mov rdx, PROTO_TCP + mov rax, SYS_SOCKET + syscall + stackpop + ret + +sys_reuse:;rdi - socket + stackpush + mov r8, 8 ;sizeof int + mov r10, one_constant ;pointer to 1 + mov rsi, LEVEL_SOL_TCP + mov rdx, SOCKOPT_TCP_REUSEADDR + mov rax, SYS_SETSOCKOPT + syscall + stackpop + ret + +sys_bind_server: + stackpush + + mov rsi, [listen_port] + mov [sa + sin_port], rsi + + mov rdi, [listen_socket] + mov rsi, sa + mov rdx, 16 + mov rax, SYS_BIND + syscall + stackpop + ret + +sys_listen: + stackpush + mov rdi, [listen_socket] + mov rsi, 100000000;backlog + mov rax, SYS_LISTEN + syscall + stackpop + ret + +sys_mmap_mem: + stackpush + mov rsi, rdi ;Size + xor rdi, rdi ;Preferred address (don't care) + mov rdx, MMAP_PROT_READ | MMAP_PROT_WRITE ;Protection Flags + mov r10, MMAP_MAP_PRIVATE | MMAP_MAP_ANON ;Flags + xor r8, r8 + dec r8 ;-1 fd because of MMAP_MAP_ANON + xor r9, r9 ;Offset + mov rax, SYS_MMAP + syscall + stackpop + ret + +sys_accept: + stackpush + mov rdi, [listen_socket] + xor rsi, rsi + xor rdx, rdx + mov rax, SYS_ACCEPT + syscall + stackpop + ret + +sys_cork:;rdi - socket + stackpush + mov r10, one_constant ;pointer to 1 + mov r8, 8 ;sizeof int + mov rsi, LEVEL_IPPROTO_TCP + mov rdx, SOCKOPT_TCP_CORK + mov rax, SYS_SETSOCKOPT + syscall + stackpop + ret + +sys_uncork:;rdi - socket + stackpush + mov r10, one_constant ;pointer to 1 + mov r8, 8 ;sizeof int + mov rsi, LEVEL_IPPROTO_TCP + mov rdx, SOCKOPT_TCP_CORK + mov rax, SYS_SETSOCKOPT + syscall + stackpop + ret + +sys_sendfile: ;rdi - outfd, rsi - infd, rdx - file size + stackpush + mov r10, rdx + xor rdx, rdx + mov rax, SYS_SENDFILE + syscall + stackpop + ret + +sys_open: + stackpush + mov rsi, OPEN_RDONLY ;flags + mov rax, SYS_OPEN + syscall + stackpop + ret + +sys_close: + stackpush + mov rax, SYS_CLOSE + syscall + stackpop + ret + +sys_write: + stackpush + mov rdx, rsi ;length + mov rsi, rdi ;buffer + mov rdi, FD_STDOUT + mov rax, SYS_WRITE + syscall + stackpop + ret + +sys_send: + stackpush + xor r10, r10 + xor r8, r8 + xor r9, r9 + mov rax, SYS_SENDTO + syscall + stackpop + ret + +sys_recv: + stackpush + xor r10, r10 ; flags + xor r8, r8 + xor r9, r9 + mov rax, SYS_RECVFROM + syscall + stackpop + ret + +sys_lseek:; rdi - fd, rsi - offset, rdx - flag + stackpush + mov rax, SYS_LSEEK + syscall + stackpop + ret +