Linux: простой web сервер на asm

Спятил, да? А ещё если напишу, что планировал сделать многопоточность и нечто CGI-интерфейса? Вполне может быть, в рамках just for fun

Но остановился на том, чтоб отдавать статические страницы - время не дало допилить, да и энтузиазм уменьшился. Идея мне пришла из за курсовой работа по "Системному программированию" В СПбГУ ИТМО. Всех заставили писать резидент под DOS, на 8086 архитектуре. Не торт.

Основные фитчи, которые я успел реализовать:

  • Форк, при необходимости
  • Чтение конфига(ini-like)
  • Создание слушающего сокета, установка параметров
  • Парсер заголовков и запроса
  • Обработчик запросов статического контента(не в /cgi-bin/)
  • В некоторых местах совершенно корявую работу со стеком

Реализовано на NetwideASM, c частичным использованием функций glibc. Работать сервер будет только на 32-битном процессоре i686+, из за того, что дёргается прерывание ядра Linux - а номера функций отличаются в зависимости от процессора.

Алгоритм работы - довольно очевидный: пришло соединение, получили запрос, разобрали, прочитали запрашиваемый файл, выдали содержимое в сокет, закончили, в ожидании нового клиента. Сразу про строку запроса - на выход из DocumentRoot НЕ проверяется - это потенциальная дыра. [offtop]Я же говорил, энтузиазм вместе со временем закончились быстро.[/offtop]. Отсутствие файла так же не обрабатывается - клиенту выдаётся пустой ответ

Начиная(по иерархии) с парсера запроса память выделяется только из стека или из кучи - это прямой путь к многопоточности. И вроде даже корректно везде освобождается.

Файлы названы по смыслу, где то иногда мелькают переменные, со странным содержимым - это для отладки. А ещё в коде очень много комментариев, на английском есс-но. Плохом английском. Особенно мне надоело писать комменты к "add esp, 4 * n" - потому как очень много вызовов glibc, следовательно много раз нужно подчищать стек. Лучше чаще чем реже, но запутаться в один прекрасный момент, а потом искать в gdb где находится бага.

GDB заслуживает отдельных ругательств слов. Это не удобный, очень сложный и гибкий отладчик. К ему сложно, но вполне реально привыкнуть. Хbотя взгляд моего знакомого - профессионального дизассемблерщика(если можно так назвать), который всегда пользовался IDA и увидел gdb - не передать :) Спасибо, Тимофей(killer_of_mouse)!

Screenshot

Чтобы разбавить сухой текст описания - скриншот отданной страницы:

Source

Дальше - много много кода. Аккуратнее :)

aweb.asm


EXTERN kSysExit, kFork, loadConfig, startupMessage, sock
EXTERN cfgBindIp, cfgBindPort, isDoFork, strProcessForked

SECTION .code
GLOBAL _start ; program start point
_start:

mov eax, dword [isDoFork] ; should we do fork() ?
cmp eax, 0 ; really?
jz .afterFork ; no, just continue loading

.doFork:
call kFork ; do fork()
cmp eax, 0 ; 0 means we are forked
je .afterFork ; run the server

jmp .exitProgram ; parent process should exit

.afterFork:
call loadConfig ; load configuration from file

call startupMessage ; echo startup message to console

call sock ; create and listen network socket

.exitProgram:
call kSysExit ; stop the process

config.asm

; External functions
EXTERN kSysExit

; External C functions
EXTERN fgets, fopen, fclose, feof, printf
EXTERN strchr, strlen, strtok, strcmp, atoi, strcpy
EXTERN inet_addr

; External data
EXTERN strFileNotFound, strTerminating, fOpenRead, fileConfig

; Exported symbols
GLOBAL loadConfig:FUNCTION
GLOBAL cfgBindPort:DATA, cfgBindIp:DATA, cfgDocumentRoot:DATA, cfgRoot:DATA

SECTION .data
cfgBindIp: dd 127 << 0 | 0 << 8 | 0 << 16 | 1 << 24
cfgBindPort: dd 808
cfgRoot: db '/srv/www/local/'
times 120 db 0

SECTION .data

var:
.fHandle dd 0
.num db 'num = %d', 10, 0
.pair db '|%s = %s|', 10, 0
.hint db ' hint = "%s"', 10, 0
.hintd db ' hint = "%d"', 10, 0
.delimiter db '= ', 0
.key dd 0
.value dd 0
.kPort db 'port', 0
.kIp db 'ip', 0
.kRoot db 'root', 0

SECTION .bss
buffLen: equ 1024
buff: times buffLen db

SECTION .code

; procedure load and parse configuration data
loadConfig:
push dword fOpenRead ; mode - read
push dword fileConfig ; config location
CALL fopen ; do fopen()
add esp, 2 * 4 ; correct stack
mov [var.fHandle], eax ; var.fHandle = eax
cmp eax, 0 ; eax ?
jne .readFile ; eax != 0 - that's ok!

.cantOpen:
; Output error message
push dword fileConfig
push dword strFileNotFound
CALL printf
add esp, 2 * 4
push dword strTerminating
CALL printf
add esp, 4
CALL kSysExit ; quit...
jmp .endProc

.readFile:
.checkEof: ; Check for end of file
push dword [var.fHandle] ; push file handle
CALL feof ; feof()
add esp, 4 ; correct stack head
test eax, eax ; eax?
jnz .closeFile ; EOF?!

.getLine: ; get 1 line from file
push dword [var.fHandle] ; push handle again
push dword buffLen ; buffer length
push dword buff ; buffer pointer
CALL fgets ; call fgets()
add esp, 4 * 3 ; corrent stack
test eax, eax ; check for error
jz .readFile ; eax = 0 means error

.correctString: ; Correct end of string - where '#' located
push dword '#' ; symbol
push dword buff ; config string
CALL strchr ; call strchr()
add esp, 4 * 2 ; stack correct
cmp eax, 0 ; eax == 0 ?
je .checkLength ; so check length next

mov [eax], byte 0 ; set end of string

.checkLength: ; check for zero length
push dword buff ; string from file
CALL strlen ; strlen()
add esp, 4 ; esp+=4..
test eax, eax ; what about eax?
jz .readFile ; It's empty string!?

.removeChr10: ; remove end-of string symbol
push dword 10 ; symbol
push dword buff ; config string
CALL strchr ; call strchr()
add esp, 4 * 2 ; stack correct
test eax, eax
jz .isKeyValuePair

mov [eax], byte 0

.isKeyValuePair: ; Search for '=', which split name = value" pair
push dword '=' ; symbol
push dword buff ; config string
CALL strchr ; call strchr()
add esp, 4 * 2 ; stack correct
test eax, eax ; eax?!
jz .readFile ; if eax == 0, read next line

.split: ; Splits pair
push dword var.delimiter ; push addr to delimiter '='
push dword buff ; string from config file
CALL strtok ; strtok()
add esp, 4 * 2 ; esp += 8
mov [var.key], eax ; key string

push dword var.delimiter; string
push dword 0 ; push NULL to retrive next token
CALL strtok ; strtok(NULL, delimiter)
add esp, 4 * 2 ; esp += 8
mov [var.value], eax ; value string

.checkForPort: ; is key == port?
push dword [var.key] ; string 1
push dword var.kPort ; string 2
CALL strcmp ; do compare
add esp, 4 * 2 ; fix stack
test eax, eax ; what about eax?
jne .checkForIp ; not equals? next check

push dword [var.value] ; addr of string with number
CALL atoi ; do calculate integer value
add esp, 4 ; correct stack

mov [cfgBindPort], ax ; save value

jmp .checkEnd ; we are done

.checkForIp: ; is key == ip?
push dword [var.key] ; string 1
push dword var.kIp ; string 2
CALL strcmp ; do strcmp()
add esp, 4 * 2 ; correct stack
test eax, eax ; what about eax?
jne .checkForRoot ; not equals? next check

push dword [var.value] ; ip addr
CALL inet_addr ; do transform
add esp, 4 ; correct stack

mov [cfgBindIp], eax ; save value

.checkForRoot: ; is key == root?
push dword [var.key] ; string 1
push dword var.kRoot ; string 2
CALL strcmp ; do strcmp()
add esp, 4 * 2 ; correct stack
test eax, eax ; what about eax?
jne .checkEnd ; not equals? next check

push dword [var.value] ; copy from
push dword cfgRoot ; copy to
CALL strcpy ; copy config value item
add esp, 4 * 2 ; correct stack

.checkEnd:
jmp .readFile

push dword [var.value]
push dword [var.key]
push dword var.pair
call printf
add esp, 4 * 3

jmp .readFile ; read next line

.closeFile:
push dword [var.fHandle] ; file handle to close
CALL fclose ; make fclose()
add esp, 4 ; stack..

.endProc:
ret

consts.asm

SEGMENT .rodata

GLOBAL fOpenRead:DATA, fOpenWrite:DATA, fOpenAppend:DATA
fOpenRead: db 'r', 0 ; read only flag
fOpenWrite: db 'w', 0 ; write flag
fOpenAppend: db 'a', 0 ; append file

GLOBAL strFileNotFound:DATA, strTerminating:DATA, strDebugGeneral:DATA
GLOBAL strStartMsg:DATA, strProcessForked:DATA, strHandlerStatic:DATA
strFileNotFound: db 'File "%s" is not found!', 10, 0
strTerminating: db 'Terminating application..', 10, 0
strDebugGeneral: db '%s: %s', 10, 0
strStartMsg: db 10, 'Starting up server, build date: ', \
__DATE__, ' ',__TIME__, \
'. Bind to %s:%d', 10, 0
strProcessForked: db 'Process forked, pid: %d', 10, 0
strHandlerStatic: db 'handlerStatic: %s', 10, 0

GLOBAL fileDebug:DATA, fileConfig:DATA
fileDebug: db './debug.log',0 ; file for debug info
fileConfig: db './aweb.conf',0 ; config file

GLOBAL maxConnections:DATA
maxConnections: dd 1024

GLOBAL maxHeaders:DATA
maxHeaders: dd 1024

GLOBAL cgiBinFolder:DATA
cgiBinFolder: db '/cgi-bin', 0 ; prefix for cgi programs

GLOBAL headerHTTP200:DATA, headerStd:DATA, headerServer:DATA
headerHTTP200: db 'HTTP/1.0 200 OK', 10, 13, 0
headerServer: db "Server: ruX's simple web server written in NASM", 10, 13, 0
headerStd:
db 'Connection: close', 10, 13
db 'Cache-Control: no-cache,no-store,max-age=0,must-revalidate', 10, 13
db 10, 13
db 0

GLOBAL isDoFork:DATA
isDoFork: dd 1

sockets.asm


EXTERN cfgBindIp, cfgBindPort, maxConnections, headerServer
EXTERN htons, shutdown, fprintf, printf, malloc, free, realloc, strlen
EXTERN kSocketCall, kClose, processRequest
GLOBAL sendServerHeader:FUNCTION

; Constants used for arguments(from kernel source)
%define AF_INET 2
%define IPPROTO_TCP 6
%define SOCK_STREAM 1
%define PF_INET 2
%define MSG_PEEK 2
%define MSG_WAITALL 100h
%define SOL_SOCKET 1
%define SO_REUSEADDR 2

; Function numbers
%define SYS_SOCKET 1
%define SYS_BIND 2
%define SYS_LISTEN 4
%define SYS_ACCEPT 5
%define SYS_SEND 9
%define SYS_RECV 10
%define SYS_SETSOCKOPT 14

; Step increase memory for receive buffer
%define REALLOC_STEP 64

SECTION .data

connect db "We have a connection!", 10, 0 ; First string...
hello db "Hello... =]", 10, 0 ; Second string...
goodbye db "Goodbye... =[", 0 ; Third string...
socksbroke db "ERROR: socket() failed!", 10, 0 ; Fourth string...
bindsbroke db "ERROR: bind() failed!", 10, 0 ; Fifth string...
listensbroke db "ERROR: listen() failed!", 10, 0 ; Sixth string...
acceptsbroke db "ERROR: accept() failed!", 10, 0 ; Seventh string...
retval db "value buffer: %d, recv: %d", 10, 0
retstr db "recived: %s", 10 , 0
html db "HTTP 1.1", 10, 0, '$'
html_len equ $ - html
one dd 1

sa: ; sockaddr_in structure
.sin_family dw 0
.sin_port dw 0
.sin_addr dd 0

sock_args: ; socket() function argument
dd PF_INET, SOCK_STREAM, IPPROTO_TCP

bind_args: ; bind() function arguments
.fd dd 0 ; socket handle
.sockaddr dd sa ; pointer to sockaddr structure
.socklen_t dd 16 ; socklen_t addrlen

listen_args: ; listen() function arguments
.sock dd 0 ; sock handle
.backlog dd maxConnections ; max connections

accept_args: ; accept() arguments
.sockfd dd 0 ; socket handle
.addr dd 0 ; struct sockaddr *addr
.addrlen dd 0 ; socklen_t *addrlen

sockopts_args: ; setsockopt() and getsockopt() args
.sockfd dd 0 ; socket handle
.level dd SOL_SOCKET ; manipulation level
.optname dd SO_REUSEADDR ; option in selected level
.optval dd one ; option value
.optlen dd $ - one ; size of option value

socketfd: dd 0 ; socket handle

section .code ; Section Declaration
GLOBAL sock

sock:

; mov ebp, esp

;int socket(int domain, int type, int protocol);
.doSocket:
mov ebx, SYS_SOCKET ; socket() = int call 1
mov ecx, dword sock_args ; arguments to socket()
CALL kSocketCall
mov [socketfd], eax ; save socket handle
;
cmp eax, -1
je sockerr

;int setsockopt(int s, int level, int optname,
; const void *optval, socklen_t optlen);
.setSockOpt:
; ignoring port in state TIME_WAIT
mov ebx, SYS_SETSOCKOPT
mov eax, dword [socketfd]
mov [sockopts_args.sockfd], eax
mov ecx, dword sockopts_args
CALL kSocketCall

cmp eax, -1
je sockerr

;int bind(int socket, const struct sockaddr *address,
; socklen_t address_len);
.doBind:
; fillup sockaddr
mov [sa.sin_family], word AF_INET ; address family
mov al, byte [cfgBindPort+1] ; reverse bytes hi=>lo
mov ah, byte [cfgBindPort] ; reverse bytes lo=>hi
mov [sa.sin_port], ax ; set port
mov eax, dword [cfgBindIp] ; get ip from config
mov [sa.sin_addr], eax ; set ip to structure

; form arguments
mov eax, dword [socketfd] ; copy socket
mov [bind_args.fd], eax ; handle to struct

mov ebx, SYS_BIND ; require bind()
mov ecx, bind_args ; bind arguments
CALL kSocketCall
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Do a little error-echecking here...
cmp eax, -1
je binderr

; int listen(int socket, int backlog);
.doListen:
mov eax, [socketfd] ; copy socket handle
mov [listen_args.sock], eax ; to structure listen_args
mov ebx, SYS_LISTEN ; require listen()
mov ecx, listen_args ; arguments
CALL kSocketCall
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Do a little error-echecking here...
cmp eax, -1
je listenerr

;accept(fd, NULL, 0);
.doAccept:
mov eax, [socketfd] ; copy socket handle
mov [accept_args.sockfd], eax ; to structure

mov ebx, SYS_ACCEPT ; we need accept() function
mov ecx, accept_args ; ptr to arguments
CALL kSocketCall

; Do a little error-echecking here...
cmp eax, -1
je accepterr

.processConnection: ; here we have a connected socket peer in eax
push eax
CALL connectionHandler

jmp .doAccept
;
;exit_s(0)
exit_s:
; mov esp, ebp
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; These are just to print an error message to stdout should
; one of our socketcall()'s fail.
sockerr:
mov eax, 4
mov ebx, 1
mov ecx, socksbroke
mov edx, 24
int 0x80
jmp exit_s

binderr:
mov eax, 4
mov ebx, 1
mov ecx, bindsbroke
mov edx, 22
int 0x80
jmp exit_s

listenerr:
mov eax, 4
mov ebx, 1
mov ecx, listensbroke
mov edx, 24
int 0x80
jmp exit_s

accepterr:
mov eax, 4
mov ebx, 1
mov ecx, acceptsbroke
mov edx, 24
int 0x80
jmp exit_s

; connection handler function
; get 1 arg - socket handle
connectionHandler:
push ebp
mov ebp, esp ; preserve esp
%define sock dword [ebp+8] ; connected socket
jmp .initVars

.vars: ; declare variables (allocate memory for each)
; begin recv_args structure (argument for recv function)
%define recv_args 8 ; main offset from esp
%define recv_args_sock esp+recv_args
%define recv_args_buffer esp+4+recv_args
%define recv_args_length esp+8+recv_args
%define recv_args_flags esp+12+recv_args
sub esp, 16 ; allocate memory for recv_args
%define send_args 16
%define send_args_sock 16

.initVars:
mov eax, sock
mov [recv_args_sock], eax
mov [recv_args_flags], dword MSG_PEEK
mov [recv_args_length], dword REALLOC_STEP

.code:

.alloc:
push dword [recv_args_length]; count of bytes we need
CALL malloc ; allocate memory from heap
add esp, 4 ; correct stack
mov [recv_args_buffer], eax ; save pointer to buffer

.recvLength:
mov ebx, SYS_RECV ; we need recv()
mov ecx, esp
add ecx, recv_args ; arguments
CALL kSocketCall

cmp eax, dword -11 ; try again code?
je .recvLength ; recieve again

mov edx, eax
mov ebx, dword [recv_args_length]
push edx
push ebx
push retval
call printf
add esp, 8
pop eax

cmp eax, [recv_args_length]
jb .recv

.realoc:
add [recv_args_length], dword REALLOC_STEP
mov eax, dword [recv_args_buffer]
push dword [recv_args_length]
push eax
CALL realloc
add esp, 2 * 4
mov [recv_args_buffer], eax

jmp .recvLength

.recv:

mov ebx, SYS_RECV ; we need recv()
mov [recv_args_flags], dword 0 ; without flags!
mov ecx, esp ; point ecx ..
add ecx, recv_args ; .. to arguments
CALL kSocketCall ; do recv()
mov [recv_args_length], eax ; store received message length

cmp eax, 0 ; is there any error?
jbe .closeSocket ; then cancel request

push dword [recv_args_buffer]
push dword sock
CALL processRequest ; let's process request!

.closeSocket:
push sock
call kClose

.freeMem:
mov eax, dword [recv_args_buffer]
push eax
CALL free
add esp, 8
mov [recv_args_length], dword 0 ; mark block as free

.return:
cmp [recv_args_length], dword 0 ; we used malloc?
jne .freeMem ; yeah, so we need free memory

mov esp, ebp ; restore stack poiter
pop ebp
ret 4

;write(1, "We have a connection!", 22);
mov eax, 4 ; write() syscall
mov ebx, 1 ; stdout
mov ecx, connect ; our string
mov edx, 22 ; 22 characters in length
int 0x80 ; Call the kernel
;
;write(fd, "Hello... =]", 12);
mov eax, 4 ; write() syscall
mov ebx, edi ; sockfd
mov ecx, hello ; our string to send
mov edx, 12 ; 12 characters in length
int 0x80 ; Call the kernel
;push dword hello
;push dword edi
;call fprintf
;add esp, 8
;
;write(fd, "Goodbye... =[", 14);
mov eax, 4 ; write() syscall
mov ebx, edi ; sockfd
mov ecx, goodbye ; our string to send
mov edx, 14 ; 14 charactes in length
int 0x80 ; Call the kernel
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Now we clean up (Close the open file descriptor/s etc.) and exit_s
push dword edi
Call shutdown
pop eax

;close(fd)
push edi
CALL kClose

mov eax, 0
ret

; send to socket 'Server' header
; first argument in stack - sock handle
sendServerHeader:
push ebp
mov ebp, esp

push dword headerServer ; string to send
call strlen ; get string length
add esp, 4 ; correct stack

push dword 0 ; flags
push eax ; length
push dword headerServer ; header string
push dword [ebp+8] ; socket handle
mov ecx, esp ; ptr to struct in stack
mov ebx, 9 ; send() function number
call kSocketCall ; call it
add esp, 4 * 4 ; correct stack space

mov esp, ebp ; restore base stack pointer
pop ebp

ret 4

helpers.asm

; debugLine TYPE, INFO
%macro debugLine 2
EXTERN fileDebug, fopen, fclose, fprintf, fOpenAppend, strDebugGeneral
jmp %%code ; go to code area
%%type: db %1, 0 ; type of message
%%info: db %2, 0 ; message

%%code:
pusha ; save registers

push fOpenAppend ; just 'a\0' string
push fileDebug ; filename
CALL fopen ; call fopen()
add esp, 2 * 4 ; clean stack

push eax ; small trick - push arg for fclose

push %%type ; offset to first %s
push %%info ; second %s
push strDebugGeneral; format string
push eax ; file handle
CALL fprintf ; fprintf()
add esp, 4 * 4 ; correct stack only for fprintf

CALL fclose ; call fclose(), handle pushed before
add esp, 4 ; correct stack

%%endmacro:
popa ; restore back registers

%endmacro

requests.asm


GLOBAL processRequest
EXTERN strlen, printf, fprintf, strtok, malloc, memset, strchr, free, strncmp, send
EXTERN strDebugGeneral, maxHeaders, cgiBinFolder, handlerStatic
EXTERN kSocketCall

SEGMENT .data
header_sep: db 10, 13, 0
juststr: db "%s",10,0

SEGMENT .code

STRUC tRequest, -30
.filename: resd 1
.method: resd 1
.socket: resd 1
.request: resd 1
.requestLen: resd 1
ENDSTRUC

; Process user requests
; arguments: socket/(dword) request(dword,ptr)
processRequest:
push ebp ; preserve ebp
mov ebp, esp ; new local offset
sub esp, 30 ; space for structure in stack
%define lv_offset 30
sub esp, lv_offset
%define headers ebp - 30 - lv_offset + 0
%define i ebp - 30 - lv_offset + 4
%define j ebp - 30 - lv_offset + 8
%define buf ebp - 30 - lv_offset + 12
%define len ebp - 30 - lv_offset + 16
%define str ebp - 30 - lv_offset + 20

mov eax, dword [ebp + 8] ; get socket id from stack
mov dword [ebp + tRequest.socket], eax
mov eax, dword [ebp + 12] ; get request string
mov dword [ebp + tRequest.request], eax

push dword [ebp + tRequest.request] ; calculate
call strlen ; request length
add esp, 4 ; cleanup stack
mov dword [ebp + tRequest.requestLen], eax ; remember length

push dword 0
push '->> '
mov eax, esp
push dword 0
push '<<- '

push esp
push eax
push strDebugGeneral
call printf
add esp, 4 * 3

.allocBuffers:
;.. for header line
push dword 1 << 14 ; 16Kb
call malloc ; requre some memory
add esp, 4 ; correct stack
mov [buf], eax ; save pointer to memory block

mov [eax], dword 0 ; length = 0

; ..for array of pointers to string(enviroment)
mov eax, dword [maxHeaders]
mov bl, 4
mul bl
push eax
call malloc
mov [headers], eax

push dword 0
push dword [headers]
call memset
add esp, 4 * 3

; ..for url buffer
push dword 2048 ; maximum url length (rfc)
call malloc ; do allocate
add esp, 4 ; correct stack
mov [ebp + tRequest.filename], eax ; save point to url

.breakHeaders:
push dword header_sep
push dword [ebp + tRequest.request]
call strtok
add esp, 4 * 2

cmp eax, 0
je .breakHeadersEnd

.httpRequest:
mov [str], eax ; save pointer to header string

mov ebx, [str]
mov [ebp + tRequest.method], ebx

push dword ' '
push dword [str]
call strchr
add esp, 4 * 2
mov [eax], byte 0 ; break method and url
inc eax
mov [ebp + tRequest.filename], eax

push dword ' '
push dword [ebp + tRequest.filename]
call strchr
add esp, 4 * 2
mov [eax], byte 0 ; break url and http

push dword header_sep
push dword 0
call strtok ; AAAAAA strtok_r
add esp, 4 * 2

cmp eax, 0
je .breakHeadersEnd

.breakHeadersLoop:
mov [str], eax ; save pointer to header string

push dword [str] ; get current header length

push juststr
call printf
pop eax
pop eax

; jmp .breakHeadersEnd

push dword header_sep
push dword 0
call strtok ; AAAAAA strtok_r
add esp, 4 * 2

cmp eax, 0
jne .breakHeadersLoop

.breakHeadersEnd:
push dword [ebp + tRequest.filename]
push dword juststr
call printf

mov eax, ebp
add eax, tRequest
push eax
CALL preprocessRequest

.endproc:

.freeBuffers:
push dword [headers]
call free
push dword [buf]
call free

mov esp, ebp ; restore base stack pointer
pop ebp

ret 4 * 2

preprocessRequest:
push ebp ; preserve ebp
mov ebp, esp ; new local offset

%define reqFilename ebp+16

push dword 0
push dword 10 ; count of bytes
push dword [reqFilename]
push dword [ebp+8]
pop eax
add eax, 8
push eax
call send
add esp, 4*4

push dword cgiBinFolder ; get '/cgi-bin'
call strlen ; get length of string
add esp, 4 ; correct stack

push eax ; compare length is a last argument
push dword cgiBinFolder ; address of string
push dword [reqFilename]; addr of file name
call strncmp ; compare start of strings
add esp, 4 * 3 ; correct stack
cmp eax, 0 ; is start are same in both strings?
jz .cgiHere ; hey! there is cgi program!

.staticHere:
mov ebx, ebp ; filename - first field in structure
add ebx, 16
push dword [ebx]
call handlerStatic ; call static content processor
jmp .endproc

.cgiHere:
jmp .cgiHereNext
.cgiherestring:
db 'Hey! There is cgi interface!', 10, 0
.cgiHereNext:
push dword .cgiherestring
call printf
add esp, 4
jmp .endproc

.endproc:

mov esp, ebp ; restore base stack pointer
pop ebp

ret 4

procs.asm


EXTERN cfgBindPort, cfgBindIp, strStartMsg
EXTERN inet_ntoa, printf

GLOBAL startupMessage:FUNCTION

; Just output startup message
startupMessage:
xor eax, eax
mov ax, [cfgBindPort]
push eax

push dword [cfgBindIp]
call inet_ntoa
add esp, 4

push eax
push strStartMsg
call printf
add esp, 4 * 3

ret

Makefile


##
# SPbSU IFMO, Zaharov Ruslan, 3156.
# 11.03.2010 -
# Course work - simple web server writen in 80386 asm
# Using NASM syntax
##

################################
# Some variables
NASM = nasm -t -g -O0 -f elf
NASM_DBG = -F dwarf
GCC = gcc -march=i686 -m32 -nostartfiles -Wall -g
PRG = aweb
TARGETS = aweb.o kcall.o config.o vars.o strings.o procs.o \
consts.o sockets.o requests.o \
handler_static.o handler_cgi.o

################################
# Default target
prog: all

################################
# Objects
kcall.o:
$(NASM) kcall.asm -o $@ -l $@.lst $(NASM_DBG)

aweb.o:
$(NASM) aweb.asm -o $@ -l $@.lst $(NASM_DBG)

config.o:
$(NASM) config.asm -o $@ -l $@.lst $(NASM_DBG)

consts.o:
$(NASM) consts.asm -o $@ -l $@.lst $(NASM_DBG)

vars.o:
$(NASM) vars.asm -o $@ -l $@.lst $(NASM_DBG)

strings.o:
$(NASM) strings.asm -o $@ -l $@.lst $(NASM_DBG)

procs.o:
$(NASM) procs.asm -o $@ -l $@.lst $(NASM_DBG)

sockets.o:
$(NASM) sockets.asm -o $@ -l $@.lst $(NASM_DBG)

requests.o:
$(NASM) requests.asm -o $@ -l $@.lst $(NASM_DBG)

handler_static.o:
$(NASM) handler_static.asm -o $@ -l $@.lst $(NASM_DBG)

handler_cgi.o:
$(NASM) handler_cgi.asm -o $@ -l $@.lst $(NASM_DBG)

################################
# General targets
all: $(TARGETS)
$(GCC) $(TARGETS) -g -o $(PRG)

strip: all
strip $(PRG)

clean:
rm -rf $(TARGETS) $(PRG) *.o.lst

run: all
./$(PRG)

gdb: all
gdb ./$(PRG)

ddd: all
ddd ./$(PRG) > /dev/null

stat-code:
@echo Total lines in source: `cat *asm Makefile | wc -l`
@echo Chars in source: `cat *asm Makefile | wc -c`

stat: stat-code

Statistics

Сделал простую статистику по коду: количество строк и символов в исходниках:

$ make stat
Total lines in source: 1477
Chars in source: 40455

Просто иногда интересно посмотреть бывает :)

Download

Можно скачать всё сразу в архиве: aweb.tar.gz
Перед запуском не забудьте поменять DocumentRoot

Final

Для чего я выложил обрезок от веб сервера, в котором есть уязвимости и нет CGI и многопоточности? Уверен, те, кто пришёл к этому посту из поисковика найдут для себя ОЧЕНЬ много полезного, потому как информации в рунете псро программирование под линукс на NASM очень мало, и большенство примеров - всего лишь hello word. Кстати, англоязычные ресурсы не блещут количеством информации.