Créer un serveur de fichiers HTTP en Assembleur x64 sur OpenIndiana

Simple, efficace, sans indexation possible.
Par contre, pas d'accents, ni d'espaces, ni de caractères spéciaux dans les noms des fichiers (seul le "." est autorisé pour les extensions de fichiers).
On place des fichiers dans /opt/WEB/DOWNLOADS et en tapant le nom du fichier à la fin de l'URL, le fichier se télécharge.

Il est aisé de cacher les fichiers en ne plaçant pas de fichiers directement dans /opt/WEB/DOWNLOADS mais dans une sous-arborescence avec des noms de dossiers complexes et aléatoires. Il devient alors quasi-impossible de trouver les ressources sans avoir le chemin exact.

Toutes les instructions sont dans le programme ci-dessous :

; /opt/WEB/server_illumos.asm --- mini serveur de fichiers (à télécharger) (illumos/OpenIndiana, amd64, NASM)
;
; Identifiez-vous en tant que root
; su -
;
; Installer NASM et tmux
; pkg install pkg://openindiana.org/developer/assembler/nasm pkg:/terminal/tmux
;
; Créer les dossiers pour le serveur web :
; mkdir -p /opt/WEB/DOWNLOADS
;
; Copier tout ce fichier dans le fichier /opt/WEB/server_illumos.asm
;
; Build :
; nasm -felf64 /opt/WEB/server_illumos.asm -o /opt/WEB/server_illumos.o && ld -o /opt/WEB/server_illumos /opt/WEB/server_illumos.o
;
; Créer un utilisateur et un groupe dédiés :
; useradd -m -s /usr/bin/sh web && groupadd web && usermod -g web web
;
; Placer le(s) fichier(s) à servir dans /opt/WEB/DOWNLOADS
;
; Donner les droits et propriétés :
; chown -R web:web /opt/WEB && chmod -R 755 /opt/WEB
;
; Lancer le serveur :
; su - web -c 'nohup /opt/WEB/server_illumos >/var/tmp/server_illumos.log 2>&1 &'
;
; Voir le serveur lancé :
; ps -fu web && netstat -an -f inet | grep 'LISTEN' | grep 8080
;
; Loguer en arrière-plan avec truss quelques temps pour s'assurer que tout va bien :
; tmux new -d -s debug "truss -p \$(pgrep server_illumos) 2> /opt/WEB/log_truss"
;
; Télécharger le(s) fichier(s) servi(s) :
; Se rendre sur l'URL : "http://IP-SERVEUR:8080/FICHIER" pour le télécharger directement.
;
; Tuer le serveur :
; pkill -u web -f /opt/WEB/server_illumos
;

BITS 64
default rel
global _start

; ---------------- NUMÉROS SYS ILLUMOS ----------------
%define SYS_REXIT 1
%define SYS_READ 3
%define SYS_WRITE 4
%define SYS_CLOSE 6
%define SYS_LSEEK 19
%define SYS_SO_SOCKET 230
%define SYS_BIND 232
%define SYS_LISTEN 233
%define SYS_ACCEPT 234
%define SYS_SETSOCKOPT 246
%define SYS_SIGACTION 98
%define SYS_OPENAT 68
%define SYS_SHUTDOWN 236

; ---------------- CONSTANTES ----------------
%define AF_INET 2
%define SOCK_STREAM 2
%define IPPROTO_IP 0
%define SOV_DEFAULT 80
%define SOL_SOCKET 0xFFFF
%define SO_REUSEADDR 0x0004
%define SO_KEEPALIVE 0x0008
%define SO_SNDTIMEO 0x1005
%define SO_RCVTIMEO 0x1006
%define INADDR_ANY 0
%define SHUT_WR 1
%define SIGPIPE 13
%define SIG_IGN 1

; errno utiles
%define EINTR 4
%define EAGAIN 11
%define ENOENT 2
%define EACCES 13
%define EISDIR 21
%define EINVAL 22

; openat / lseek
%define O_RDONLY 0
%define AT_FDCWD -100
%define SEEK_SET 0
%define SEEK_END 2

; ---------------- DONNÉES ----------------
section .data
one: dd 1

; timeouts (LP64 timeval = 16 octets)
rcvto_sec: dq 3
rcvto_usec: dq 0
sndto_sec: dq 5
sndto_usec: dq 0

; <<< RÉPERTOIRE DE BASE POUR LES FICHIERS SERVIS >>>
base_dir: db "/opt/WEB/DOWNLOADS",0

; En-têtes HTTP (réponse succès uniquement)
ok_header: db "HTTP/1.1 200 OK",13,10
 db "Content-Type: application/octet-stream",13,10
 db "Content-Disposition: attachment",13,10
 db "Content-Length: "
ok_hdr_len equ $-ok_header

ok_suffix: db 13,10,"Connection: close",13,10,13,10
ok_sfx_len equ $-ok_suffix

; Messages d'erreur stderr (debug)
err_socket: db "ERR socket",10
err_socket_len equ $-err_socket
err_setsock: db "ERR setsockopt",10
err_setsock_len equ $-err_setsock
err_bind: db "ERR bind",10
err_bind_len equ $-err_bind
err_listen: db "ERR listen",10
err_listen_len equ $-err_listen
err_accept: db "ERR accept",10
err_accept_len equ $-err_accept
err_file: db "ERR open/size file",10
err_file_len equ $-err_file

section .rodata
; sockaddr_in (16 octets) --- port 8080 (htons 8080 = 0x1F90 -> imm LE 0x901F)
addr:
 dw AF_INET
 dw 0x901F
 dd INADDR_ANY
 dq 0
addr_len equ $-addr

; ---------------- BSS ----------------
section .bss
reqbuf: resb 4096 ; requête HTTP + espace pour digits CL (fin du tampon)
abs_path: resb 1024 ; chemin absolu final: base_dir + "/" + path
sa: resq 4 ; sigaction
filebuf: resb 8192 ; buffer I/O fichier (sert aussi au "read test" 1 octet)

; ---------------- CODE ----------------
section .text
_start:
 ; ignorer SIGPIPE
 mov qword [sa], SIG_IGN
 mov rax, SYS_SIGACTION
 mov rdi, SIGPIPE
 lea rsi, [rel sa]
 xor rdx, rdx
 syscall

 ; socket
 mov rax, SYS_SO_SOCKET
 mov rdi, AF_INET
 mov rsi, SOCK_STREAM
 mov rdx, IPPROTO_IP
 xor r10, r10
 mov r8, SOV_DEFAULT
 syscall
 jc .e_socket
 mov r12, rax

 ; setsockopt listener
 mov rax, SYS_SETSOCKOPT ; REUSEADDR
 mov rdi, r12
 mov rsi, SOL_SOCKET
 mov rdx, SO_REUSEADDR
 lea r10, [rel one]
 mov r8, 4
 mov r9, SOV_DEFAULT
 syscall
 jc .e_setsock

 mov rax, SYS_SETSOCKOPT ; KEEPALIVE
 mov rdi, r12
 mov rsi, SOL_SOCKET
 mov rdx, SO_KEEPALIVE
 lea r10, [rel one]
 mov r8, 4
 mov r9, SOV_DEFAULT
 syscall

 mov rax, SYS_SETSOCKOPT ; RCVTIMEO
 mov rdi, r12
 mov rsi, SOL_SOCKET
 mov rdx, SO_RCVTIMEO
 lea r10, [rel rcvto_sec]
 mov r8, 16
 mov r9, SOV_DEFAULT
 syscall

 mov rax, SYS_SETSOCKOPT ; SNDTIMEO
 mov rdi, r12
 mov rsi, SOL_SOCKET
 mov rdx, SO_SNDTIMEO
 lea r10, [rel sndto_sec]
 mov r8, 16
 mov r9, SOV_DEFAULT
 syscall

 ; bind/listen
 mov rax, SYS_BIND
 mov rdi, r12
 lea rsi, [rel addr]
 mov rdx, addr_len
 mov r10, SOV_DEFAULT
 xor r8, r8
 xor r9, r9
 syscall
 jc .e_bind

 mov rax, SYS_LISTEN
 mov rdi, r12
 mov rsi, 64
 mov rdx, SOV_DEFAULT
 xor r10, r10
 xor r8, r8
 xor r9, r9
 syscall
 jc .e_listen

.accept_loop:
.accept_retry:
 mov rax, SYS_ACCEPT
 mov rdi, r12
 xor rsi, rsi
 xor rdx, rdx
 mov r10, SOV_DEFAULT
 xor r8, r8
 xor r9, r9
 syscall
 jnc .after_accept
 cmp rax, EINTR
 je .accept_retry
 cmp rax, EAGAIN
 je .accept_retry
 jmp .e_accept

.after_accept:
 mov r13, rax ; connfd

 ; lire la requête
 mov rax, SYS_READ
 mov rdi, r13
 lea rsi, [rel reqbuf]
 mov rdx, 4096
 syscall

 ; RAX = nb d'octets lus (peut être <=0). En cas de doute -> silence.
 test rax, rax
 jle .finish_silent

 ; ---- parser "GET /path HTTP" ----
 lea rbx, [rel reqbuf] ; RBX = start
 mov r8, rax ; r8 = len lue
 cmp r8, 14
 jb .finish_silent

 ; vérifier "GET "
 cmp byte [rbx+0], 'G'
 jne .finish_silent
 cmp byte [rbx+1], 'E'
 jne .finish_silent
 cmp byte [rbx+2], 'T'
 jne .finish_silent
 cmp byte [rbx+3], ' '
 jne .finish_silent

 ; trouver début du path (après l'espace)
 lea rsi, [rbx+4] ; rsi -> path
 mov rcx, r8
 sub rcx, 4 ; rcx = max chars restants
 mov rdx, rsi ; rdx = cursor

.find_sp:
 cmp rcx, 0
 je .finish_silent
 cmp byte [rdx], ' '
 je .got_path_end
 cmp byte [rdx], '?'
 je .got_path_end
 cmp byte [rdx], 13
 je .got_path_end
 cmp byte [rdx], 10
 je .got_path_end
 inc rdx
 dec rcx
 jmp .find_sp

.got_path_end:
 ; RSI = start, RDX = end (non-inclus)

 ; refuse ".." dans le path
 mov r9, rsi
.scan_dotdot:
 cmp r9, rdx
 jae .ok_path_sanit
 cmp byte [r9], '.'
 jne .adv1
 cmp r9, rdx
 jae .ok_path_sanit
 cmp byte [r9+1], '.'
 jne .adv1
 jmp .finish_silent
.adv1:
 inc r9
 jmp .scan_dotdot

.ok_path_sanit:
 ; construire abs_path = base_dir + "/" + (path sans le 1er '/')
 lea rdi, [rel abs_path] ; dst
 lea rax, [rel base_dir] ; src
 lea r15, [rel abs_path+1023]; fin du buffer

.copy_base:
 cmp rdi, r15
 jae .finish_silent
 mov bl, [rax]
 mov [rdi], bl
 inc rax
 inc rdi
 test bl, bl
 jne .copy_base
 dec rdi

 ; ajouter "/" si base_dir ne finit pas par "/"
 cmp byte [rdi-1], '/'
 je .skip_slash
 mov byte [rdi], '/'
 inc rdi

.skip_slash:
 ; sauter les "/" en début de path
.skip_leading:
 cmp rsi, rdx
 jae .finish_silent ; GET / -> silence
 cmp byte [rsi], '/'
 jne .copy_path
 inc rsi
 jmp .skip_leading

.copy_path:
 cmp rsi, rdx
 jae .path_copied
 cmp rdi, r15
 jae .finish_silent ; chemin trop long -> silence
 mov bl, [rsi]
 mov [rdi], bl
 inc rsi
 inc rdi
 jmp .copy_path

.path_copied:
 mov byte [rdi], 0 ; NUL-terminate

 ; si le chemin final finit par '/', c'est un répertoire -> silence
 cmp rdi, abs_path
 jbe .finish_silent
 cmp byte [rdi-1], '/'
 je .finish_silent

 ; ---- openat(AT_FDCWD, abs_path, O_RDONLY, 0)
 mov rax, SYS_OPENAT
 mov rdi, AT_FDCWD
 lea rsi, [rel abs_path]
 mov rdx, O_RDONLY
 xor r10, r10
 syscall
 jc .finish_silent ; ENOENT/EACCES/whatever -> silence
 mov r14, rax ; fd fichier

 ; ---- TEST LECTURE 1 OCTET pour écarter répertoires & fd illisibles
 mov rax, SYS_READ
 mov rdi, r14
 lea rsi, [rel filebuf]
 mov rdx, 1
 syscall
 jc .silent_close_file ; erreur (EISDIR, etc.) -> silence

 ; succès: repositionner au début
 mov rax, SYS_LSEEK
 mov rdi, r14
 xor rsi, rsi
 mov rdx, SEEK_SET
 syscall
 jc .silent_close_file

 ; ---- taille = lseek(fd, 0, SEEK_END), puis rewind
 mov rax, SYS_LSEEK
 mov rdi, r14
 xor rsi, rsi
 mov rdx, SEEK_END
 syscall
 jc .silent_close_file
 mov rbx, rax

 mov rax, SYS_LSEEK
 mov rdi, r14
 xor rsi, rsi
 mov rdx, SEEK_SET
 syscall
 jc .silent_close_file

 ; ---- écrire l'en-tête 200 + Content-Length
 mov rax, SYS_WRITE
 mov rdi, r13
 lea rsi, [rel ok_header]
 mov rdx, ok_hdr_len
 syscall

 ; convertir RBX (taille) en décimal
 mov rax, rbx
 mov rcx, 10
 lea rsi, [rel reqbuf+4096]
 mov r8, 0
 test rax, rax
 jnz .cl_loop
 dec rsi
 mov byte [rsi], '0'
 mov r8, 1
 jmp .cl_done
.cl_loop:
 xor rdx, rdx
 div rcx
 add dl, '0'
 dec rsi
 mov [rsi], dl
 inc r8
 test rax, rax
 jnz .cl_loop
.cl_done:
 mov rax, SYS_WRITE
 mov rdi, r13
 mov rdx, r8
 syscall

 ; suffixe (CRLF + Connection: close + CRLFCRLF)
 mov rax, SYS_WRITE
 mov rdi, r13
 lea rsi, [rel ok_suffix]
 mov rdx, ok_sfx_len
 syscall

 ; ---- copier le fichier par blocs
.file_loop:
 mov rax, SYS_READ
 mov rdi, r14
 lea rsi, [rel filebuf]
 mov rdx, 8192
 syscall
 test rax, rax
 jle .file_done
 mov rdx, rax
 mov rax, SYS_WRITE
 mov rdi, r13
 lea rsi, [rel filebuf]
 syscall
 jmp .file_loop

.file_done:
 mov rax, SYS_CLOSE
 mov rdi, r14
 syscall
 jmp .finish_conn

.silent_close_file:
 mov rax, SYS_CLOSE
 mov rdi, r14
 syscall
 jmp .finish_silent

.finish_conn:
 ; shutdown + close (voie polie après envoi)
 mov rax, SYS_SHUTDOWN
 mov rdi, r13
 mov rsi, SHUT_WR
 mov rdx, SOV_DEFAULT
 xor r10, r10
 xor r8, r8
 xor r9, r9
 syscall

 mov rax, SYS_CLOSE
 mov rdi, r13
 syscall
 jmp .accept_loop

.finish_silent:
 ; fermeture silencieuse (rien envoyé)
 mov rax, SYS_CLOSE
 mov rdi, r13
 syscall
 jmp .accept_loop

; ---------------- ERREURS FATALES (log stderr, exit) ----------------
.e_socket:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_socket]
 mov rdx, err_socket_len
 syscall
 jmp .fatal

.e_setsock:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_setsock]
 mov rdx, err_setsock_len
 syscall
 jmp .fatal

.e_bind:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_bind]
 mov rdx, err_bind_len
 syscall
 jmp .fatal

.e_listen:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_listen]
 mov rdx, err_listen_len
 syscall
 jmp .fatal

.e_accept:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_accept]
 mov rdx, err_accept_len
 syscall
 jmp .fatal

.e_file:
 mov rax, SYS_WRITE
 mov rdi, 2
 lea rsi, [rel err_file]
 mov rdx, err_file_len
 syscall
 jmp .fatal

.fatal:
 mov rdi, 1
 mov rax, SYS_REXIT
 syscall


↑ Haut de page