;name: httpserver.asm
;
;build: nasm -felf64 httpserver.asm -o httpserver.o
; ld -s -melf_x86_64 -o httpserver httpserver.o
;
;description: A very very basic httpserver demonstration.
; For this I got my inspiration from the 32 bit application httpd from
; asmutils-0.18. (http://asm.sourceforge.net/asmutils.html)
;
;updates: 4/11/2014 : bug in creating the socket,
; mov rdi, PF_INET
; mov rsi, SOCK_STREAM
; added %define for constants AF_INET, SOCK_STREAM, PF_INET, IPPROTO_IP,INADDR_ANY
;
;todos: - configuration file
; - execute binary files (cgi-bin)
; - environment variables
;
;use: Start program from a terminal and open browser. go to localhost:4444
; while keeping the terminal open. Send some data and watch http header
; in terminal.
; To terminate this program either use kill [pid] or ctrl-C.
; notes about google chrome : if the content-length isn't correct it hangs
; use firefox (matters less) to see the real content-length
bits 64
[list -]
%include "unistd.inc"
%include "sys/wait.inc"
%include "sys/socket.inc"
%include "errors.inc"
[list +]
section .bss
sockfd: resq 1
sock_addr: resq 1
section .data
; message to keep user confortable that the server is actually running
server_listening: db "server is listening",10
.length: equ $-server_listening
lf: db 10
trying: db "starting..."
.length: equ $-trying
dot: db "."
; the buffer to read the client request.
buffer: times 1024 db 0
.length: equ $-buffer
reply: db 'HTTP/1.1 200 OK',10
;change 'demo web server' to your own name
db 'Server: demo web server',10
;the length of the content to send,
;this should be calculated programatically
db 'Content-length: 295',10
db 'Content-Type: text/html',10
;cookies
db 'Set-Cookie:UserID=XYZ',10
db 'Set-Cookie:Password=XYZ123',10
db 'Set-Cookie:Domain=www.agguro.be',10
db 'Set-Cookie:Path=/',10
db 10 ;extra line !!
;start of content
db 'demo w'
db 'ebserverDemo we'
db 'bserverSend data'
;end of content
db 0 ;for later use with strlen
reply.length: equ $-reply
socketerror: db "socketerror", 10
.length: equ $-socketerror
listenerror: db "listenerror", 10
.length: equ $-listenerror
binderror: db "binderror", 10
.length: equ $-binderror
port: db 17,92 ; port 4444 (256 * 17 + 92)
version: db "demo httpserver v0.1 (by Agguro)",10
db "inspir(at)ed by asmutils(c) (see:http://asm.sourceforge.net/asmutils.html)",10
db "license: GNU General Public License, version 2",10
db "(https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html)",10
.length: equ $-version
section .text
global _start
_start:
pop rax ;get argc
pop rsi ;ptr to name of program
pop rsi ;ptr to argv
push rsi ;restore stack
push rsi
push rax
cmp rax,2 ;cmdline arguments supplied?
jl .start
mov rax,qword[rsi]
mov rdx,"-version"
xor rdx,rax
jz .showversion
mov dx,"-v"
xor dx,ax
jnz .start
.showversion:
syscall write,stdout,version,version.length
syscall exit,0
.start:
push rax ;restore stack
;create a socket
syscall socket,PF_INET,SOCK_STREAM,IPPROTO_IP
cmp rax,0
jz .socketerror
mov qword[sockfd], rax
;fill in sock_addr structure on stack
xor r8,r8 ;INADDR_ANY = 0
;if we only want to connect locally uncomment next line
mov r8,0x100007F
push r8 ;push r8 to the stack
push word [port] ;port number
push word AF_INET ;protocol argument
mov qword[sock_addr],rsp ;Save the sock_addr_in
;bind the socket to the address, keep trying until we succeed.
;if the address is still in use, bind will fail, we can avoid this with the
;setsockopt syscall, but we use INADDR_ANY so anyone can
;bind to the server's socket.
;information: http://hea-www.harvard.edu/~fine/Tech/addrinuse.html
;Instead I keep trying until the server allows us to bind again,
;in the meanwhile we wait ....
syscall write,stdout,trying,trying.length
.tryagain:
syscall bind,qword[sockfd],qword[sock_addr],16
and rax,rax
js .checkerror
jmp .bindsucces
.checkerror:
cmp rax,EADDRINUSE
jne .binderror
;if the socket is still in use the terminal is flooded with dots, a timer can resolve this
syscall write,stdout,dot,1
jmp .tryagain
.bindsucces:
; first end the previous line with LF
syscall write,stdout,lf,1
syscall listen,qword[sockfd],0
and rax,rax
jnz .listenerror
; inform user that the server is listening
mov rsi,server_listening
mov rdx,server_listening.length
mov rdi,STDOUT
mov rax,SYS_WRITE
syscall
.acceptloop:
syscall accept,qword[sockfd],0,0
test rax,rax
js .acceptloop
mov r12,rax ;use the accept socket from here
;two waits to prevent zombie processes
syscall wait4,-1,0,WNOHANG,0
syscall wait4,-1,0,WNOHANG,0
;we have accepted a connection, let a child do the work while the parent
;wait to accept other connections.
syscall fork
and rax, rax
jz .serveclient ;child continues here
;parent closes the connection
syscall close,r12
jmp .acceptloop ;and go back to accept new incoming connections
.serveclient:
;the client has send a request, we read this and put it in a buffer
syscall read,r12,buffer,buffer.length
;write received request to the terminal (later this can be a log file)
;the actual read bytes are stored in rax
syscall write,stdout,buffer,rax
;additional end of line
syscall write,stdout,lf,1
;here we parse the request from client that's put in STDIN
;now we just reply with the so called "reply"
;decision making stuff comes here, exp: CGI scripts, request for additional pages etc...
;see the original source.
; send the reply to the client
syscall write,r12,reply,reply.length
syscall close,r12
;we are done, exit child process
syscall exit,0
; the errors
.binderror:
mov rsi,binderror
mov rdx,binderror.length
jmp .print
.listenerror:
mov rsi,listenerror
mov rdx,listenerror.length
jmp .print
.socketerror:
mov rsi,socketerror
mov rdx,socketerror.length
.print:
syscall write,stdout
.exit:
syscall exit,0