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. Кстати, англоязычные ресурсы не блещут количеством информации.

  • woklex

    Мне сразу пришла это идея в голову, когда я начал изучать асм. И вот случайно наткнулся на реализацию :)