kolibrios/kernel/branches/net/applications/ftpd/commands.inc

878 lines
21 KiB
PHP
Raw Normal View History

struct thread_data
rb 1024
stack rb 0
home_dir rb 1024
work_dir rb 1024
fpath rb 1024*3
type db ? ; ASCII/EBDIC/IMAGE/..
mode db ? ; active/passive
socketnum dd ? ; Commands socket
state dd ? ; disconnected/logging in/logged in/..
passivesocknum dd ? ; when in passive mode, this is the listening socket
datasocketnum dd ? ; socket used for data transfers
datasock sockaddr_in
buffer rb BUFFERSIZE
ends
align 4
parse_cmd: ; esi must point to command
cmp byte [esi], 0x20 ; skip all leading characters
ja .ok
inc esi
dec ecx
cmp ecx, 3
ja parse_cmd
ret
.ok:
cmp byte [esi+3], 0x20
jae @f
mov byte [esi+3], 0
@@:
mov eax, [esi]
and eax, not 0x20202020 ; convert to upper case
mov edi, commands ; list of commands to scan
.scanloop:
cmp eax, [edi]
jne .try_next
jmp dword [edi+4]
.try_next:
add edi, 8
cmp byte [edi], 0
jne .scanloop
.error:
mcall send, [edx + thread_data.socketnum], str500, str500.length, 0
ret
align 4
commands: ; all commands must be in uppercase
db 'ABOR'
dd cmdABOR
db 'CDUP'
dd cmdCDUP
db 'CWD', 0
dd cmdCWD
db 'DELE'
dd cmdDELE
db 'LIST'
dd cmdLIST
db 'NLST'
dd cmdNLST
db 'NOOP'
dd cmdNOOP
db 'PASS'
dd cmdPASS
db 'PASV'
dd cmdPASV
db 'PORT'
dd cmdPORT
db 'PWD', 0
dd cmdPWD
db 'QUIT'
dd cmdQUIT
db 'RETR'
dd cmdRETR
db 'STOR'
dd cmdSTOR
db 'SYST'
dd cmdSYST
db 'TYPE'
dd cmdTYPE
db 'USER'
dd cmdUSER
db 0 ; end marker
align 4
cmdABOR:
; TODO: abort the current filetransfer
ret
align 4
cmdCDUP:
cmp byte [edx + thread_data.work_dir+1], 0 ; are we in "/" ?
je .done
mov ecx, 1024
xor al, al
lea edi, [edx + thread_data.work_dir]
repne scasb
std
dec edi
dec edi
dec edi
mov al,'/'
repne scasb
cld
mov byte[edi+1], 0
.done:
; Print the new working dir on the console
lea eax, [edx + thread_data.work_dir]
push eax
call [con_write_asciiz]
push str_newline
call [con_write_asciiz]
mcall send, [edx + thread_data.socketnum], str250, str250.length, 0 ; command successful
ret
align 4
cmdCWD: ; Change Working Directory
sub ecx, 4
jb .err
add esi, 4
.scan:
lea edi, [edx + thread_data.work_dir + 1]
push ecx
mov ecx, 1024
.find_zero:
cmp byte [edi], 0
je .found_zero
inc edi
loop .find_zero
.found_zero:
pop ecx
.scan2:
cmp byte [esi], '/'
jne @f
inc esi
dec ecx
jz .done
@@:
.loop:
lodsb
cmp al, 0x20
jb .done
cmp al, '.'
je .up
.continue:
stosb
loop .loop
.done:
cmp byte [edi-1], '/'
je @f
mov byte [edi], '/'
inc edi
@@:
mov byte [edi], 0
; Print the new working dir on the console
lea eax, [edx + thread_data.work_dir]
push eax
call [con_write_asciiz]
push str_newline
call [con_write_asciiz]
mcall send, [edx + thread_data.socketnum], str250, str250.length, 0
ret
.up:
lodsb
cmp al, '.'
jne .continue
;;;; TODO: find second last '\' in work_dir and make next char zero
;;;; point edi to that 0
jmp .scan2
.err:
; TODO: print correct error message (550?)
ret
align 4
cmdDELE:
ret
align 4
cmdLIST:
; If we are in active mode, it's time to open a data socket..
cmp [edx + thread_data.mode], MODE_ACTIVE
jne @f
mov ecx, [edx + thread_data.datasocketnum]
lea edx, [edx + thread_data.datasock]
mov esi, sizeof.thread_data.datasock
mcall connect
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je socketerror
@@:
; Create fpath from home_dir and work_dir
call create_path
lea eax, [edx + thread_data.fpath]
push eax
call [con_write_asciiz]
push str_newline
call [con_write_asciiz]
; Start the search
push FA_ANY
push str_mask
lea eax, [edx + thread_data.fpath]
push eax
call [file.find.first]
test eax, eax
jz .nosuchdir
lea edi, [edx + thread_data.buffer]
.parse_file:
test eax, eax ; did we find a file?
jz .done
mov ebx, eax ; yes, save the descripter in ebx
; first, convert the attributes
test [ebx + FileInfoA.Attributes], FA_FOLDER
jnz .folder
test [ebx + FileInfoA.Attributes], FA_READONLY
jnz .readonly
mov eax, '-rw-'
stosd
jmp .attr
.folder:
mov eax, 'drwx'
stosd
jmp .attr
.readonly:
mov eax, '-r--'
stosd
.attr:
mov eax, 'rw-r'
stosd
mov ax, 'w-'
stosw
mov al, ' '
stosb
; now..
mov ax, '1 '
stosw
; now write owner, everything is owned by FTP, woohoo!
mov eax, 'FTP '
stosd
stosd
; now the filesize in ascii
mov eax, [ebx + FileInfoA.FileSizeLow]
call dword_to_ascii
mov al, ' '
stosb
; then date (month/day/year)
movzx eax, [ebx + FileInfoA.DateModify + FileDateTime.month]
mov eax, [months + 4*eax]
stosd
movzx eax, [ebx + FileInfoA.DateModify + FileDateTime.day]
call dword_to_ascii
mov al, ' '
stosb
movzx eax, [ebx + FileInfoA.DateModify + FileDateTime.year]
call dword_to_ascii
mov al, ' '
stosb
; and last but not least, filename
lea esi, [ebx + FileInfoA.FileName]
mov ecx, 264
.nameloop:
lodsb
test al, al
jz .namedone
stosb
loop .nameloop
; insert a cr lf
.namedone:
mov ax, 0x0a0d
stosw
; check next file
push ebx
call [file.find.next]
jmp .parse_file
; close file desc
.done:
push ebx
call [file.find.close]
; append the string with a 0
xor al, al
stosb
; Warn the client we're about to send the data
push edi edx
mcall send, [edx + thread_data.socketnum], str150, str150.length, 0 ; here it comes..
pop edx esi
; and send it to the client
mov ecx, [edx + thread_data.datasocketnum]
lea edx, [edx + thread_data.buffer]
sub esi, edx
xor edi, edi
mcall send
; close the data socket..
mov edx, [esp+4] ; thread_data pointer
mcall close, [edx + thread_data.datasocketnum]
mov [edx + thread_data.mode], MODE_NOTREADY
; And send "transfer ok" on the base connection
mcall send, [edx + thread_data.socketnum], str226, str226.length, 0
ret
.nosuchdir:
mcall send, [edx + thread_data.socketnum], str550, str550.length, 0
ret
align 4
cmdNLST:
; TODO: same as list but simpler output format
ret
align 4
cmdNOOP:
ret
align 4
cmdPASS:
; TODO: verify password
mcall send, [edx + thread_data.socketnum], str230, str230.length, 0
push str_pass_ok
call [con_write_asciiz]
mov edx, [esp+4] ; thread_data pointer
mov [edx + thread_data.state], STATE_ACTIVE
ret
align 4
cmdPASV:
; Open a new TCP socket
mcall socket, AF_INET4, SOCK_STREAM, 0
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je socketerror
mov [edx + thread_data.passivesocknum], eax
; Bind it to a known local port
mov [edx + thread_data.datasock.sin_family], AF_INET4
mov [edx + thread_data.datasock.sin_port], 2000
mov [edx + thread_data.datasock.sin_addr], 0
mov ecx, eax ;[edx + thread_data.passivesocknum]
lea edx, [edx + thread_data.datasock]
mov esi, sizeof.thread_data.datasock
mcall bind
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je bind_err
; And set it to listen!
mcall listen, [edx + thread_data.passivesocknum], 10 ;;;;; FIXME
; Tell our thread we are ready to accept incoming calls
mov edx, [esp+4] ; thread_data pointer
mov [edx + thread_data.mode], MODE_PASSIVE_WAIT
; Now tell the client where to connect to in this format:
; 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)
; where a1.a2.a3.a4 is the IP address and p1*256+p2 is the port number.
lea edi, [edx + thread_data.buffer]
mov eax, '227 ' ; FIXME (now hardcoded to 127.0.0.1:2000)
stosd
mov eax, '(127'
stosd
mov eax, ',0,0'
stosd
mov eax, ',1,7'
stosd
mov eax, ',208'
stosd
mov al, ')'
stosb
mov ax, 0x0a0d
stosw
xor al, al
stosb
lea esi, [edi - thread_data.buffer]
sub esi, edx
mov ecx, [edx + thread_data.socketnum]
lea edx, [edx + thread_data.buffer]
xor esi, esi
mcall send
ret
align 4
cmdPWD: ; Print Working Directory
mov dword [edx + thread_data.buffer], '257 '
mov byte [edx + thread_data.buffer+4], '"'
lea edi, [edx + thread_data.buffer+5]
lea esi, [edx + thread_data.work_dir]
mov ecx, 1024
.loop:
lodsb
or al, al
jz .ok
stosb
dec ecx
jnz .loop
.ok:
mov dword [edi], '"' + 0x000a0d00 ; '"',13,10,0
lea esi, [edi - thread_data.buffer + 4]
sub esi, edx
mov ecx, [edx + thread_data.socketnum]
lea edx, [edx + thread_data.buffer]
xor edi, edi
mcall send
mov edx, [esp+4]
; Print the new working dir on the console
lea eax, [edx + thread_data.work_dir]
push eax
call [con_write_asciiz]
push str_newline
call [con_write_asciiz]
ret
align 4
cmdPORT:
; PORT a1,a2,a3,a4,p1,p2
; IP address a1.a2.a3.a4, port p1*256+p2
mov [edx + thread_data.mode], MODE_ACTIVE
lea esi, [esi+5]
; Convert the IP
call ascii_to_byte
mov bl, al
inc esi ; skip past ','
call ascii_to_byte
mov bh, al
shl ebx, 16
inc esi
call ascii_to_byte
mov bl, al
inc esi
call ascii_to_byte
mov bh, al
inc esi
rol ebx, 16
; And put it in datasock
mov [edx + thread_data.datasock.sin_addr], ebx
; Now the same with portnumber
call ascii_to_byte
mov bh, al
inc esi
call ascii_to_byte
mov bl, al
; Save it in datasock too
mov [edx + thread_data.datasock.sin_port], bx
; We will open the socket, but do not connect yet!
mov [edx + thread_data.datasock.sin_family], AF_INET4
mcall socket, AF_INET4, SOCK_STREAM, 0
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je socketerror
mov [edx + thread_data.datasocketnum], eax
; Tell the client we are ready
mov edx, [esp+4] ; thread_data pointer
mcall send, [edx + thread_data.socketnum], str225, str225.length, 0
ret
align 4
cmdQUIT:
mcall close, [edx + thread_data.datasocketnum]
mcall send, [edx + thread_data.socketnum], str221, str221.length, 0 ; 221 - bye!
mcall close;, [edx + thread_data.socketnum]
add esp, 4 ; get rid of call return address
jmp thread_exit ; now close this thread
align 4
cmdRETR:
sub ecx, 5
jb .cannot_open
cmp [edx + thread_data.mode], MODE_ACTIVE
jne @f
push esi
mov ecx, [edx + thread_data.datasocketnum]
lea edx, [edx + thread_data.datasock]
mov esi, sizeof.thread_data.datasock
mcall connect
pop esi
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je socketerror
@@:
push esi
call create_path
pop esi
dec edi
add esi, 5
mov ecx, 1024
.loop:
lodsb
cmp al, 0x20
jl .done
stosb
loop .loop
.done:
xor al, al
stosb
lea eax, [edx + thread_data.fpath]
push eax
call [con_write_asciiz]
push str_newline
call [con_write_asciiz]
push O_READ
lea eax, [edx + thread_data.fpath]
push eax
call [file.open]
test eax, eax
jz .cannot_open
push eax
mcall send, [edx + thread_data.socketnum], str150, str150.length, 0 ; here it comes..
pop ebx
mov edx, [esp+4] ; thread_data pointer
.read_more:
push BUFFERSIZE
lea eax, [edx + thread_data.buffer]
push eax
push ebx
call [file.read]
cmp eax, -1
je .cannot_open ; fixme: this is not the correct error
push eax
push ebx
mov esi, eax
mov ecx, [edx + thread_data.datasocketnum]
lea edx, [edx + thread_data.buffer]
xor esi, esi
mcall send
pop ebx
pop ecx
mov edx, [esp+4] ; thread_data pointer
cmp eax, -1
je socketerror
cmp ecx, BUFFERSIZE
je .read_more
mcall close, [edx + thread_data.datasocketnum]
mov [edx + thread_data.mode], MODE_NOTREADY
mcall send, [edx + thread_data.socketnum], str226, str226.length, 0 ; transfer ok
ret
.cannot_open:
pushd 0x0c
call [con_set_flags]
push str_notfound
call [con_write_asciiz]
pushd 0x07
call [con_set_flags]
mcall send, [edx + thread_data.socketnum], str550, str550.length, 0 ; file not found
ret
align 4
cmdSTOR:
; TODO: check if user has write permission, and write file if so
ret
align 4
cmdSYST:
mcall send, [edx + thread_data.socketnum], str215, str215.length, 0
ret
align 4
cmdTYPE:
cmp ecx, 6
jb parse_cmd.error
mov al, byte[esi+5]
and al, not 0x20
cmp al, 'A'
je .ascii
cmp al, 'E'
je .ebdic
cmp al, 'I'
je .image
cmp al, 'L'
je .local
jmp parse_cmd.error
.ascii:
mov [edx + thread_data.type], TYPE_ASCII
jmp .subtype
.ebdic:
mov [edx + thread_data.type], TYPE_EBDIC
.subtype:
cmp ecx, 8
jb .non_print
mov al, byte[esi+7]
and al, not 0x20
cmp al, 'N'
je .non_print
cmp al, 'T'
je .telnet
cmp al, 'C'
je .asacc
jmp parse_cmd.error
.non_print:
or [edx + thread_data.type], TYPE_NP
jmp .ok
.telnet:
or [edx + thread_data.type], TYPE_TELNET
jmp .ok
.asacc:
or [edx + thread_data.type], TYPE_ASA
jmp .ok
.image:
mov [edx + thread_data.type], TYPE_IMAGE
jmp .ok
.local:
cmp ecx, 8
jb parse_cmd.error
mov al, byte[esi+7]
sub al, '0'
jb parse_cmd.error
cmp al, 9
ja parse_cmd.error
or al, TYPE_LOCAL
mov [edx + thread_data.type], al
.ok:
mcall send, [edx + thread_data.socketnum], str200, str200.length, 0
ret
align 4
cmdUSER:
; TODO: check user and set home directory (and permissions)
mov [edx + thread_data.state], STATE_LOGIN
mov word [edx + thread_data.home_dir], "/" ; "/", 0
mov word [edx + thread_data.work_dir], "/" ; "/", 0
push str_logged_in
call [con_write_asciiz]
mcall send, [edx + thread_data.socketnum], str331, str331.length, 0 ; Now send me the password!
ret
align 4 ; esi = ptr to str, output in eax
ascii_to_byte:
xor eax, eax
push ebx
.loop:
movzx ebx, byte[esi]
sub bl, '0'
jb .done
cmp bl, 9
ja .done
lea eax, [eax*4 + eax] ;
shl eax, 1 ; eax = eax * 10
add eax, ebx
inc esi
jmp .loop
.done:
pop ebx
ret
align 4
dword_to_ascii: ; edi = ptr where to write, eax is number
mov eax, '1'
stosb
ret
align 4
create_path: ; combine home_dir and work_dir strings into fpath
lea edi, [edx + thread_data.fpath]
lea esi, [edx + thread_data.home_dir]
mov ecx, 1024
.loop1:
lodsb
or al, al
jz .next
stosb
loop .loop1
.next:
cmp byte[edi-1], '/'
jne @f
dec edi
@@:
lea esi, [edx + thread_data.work_dir]
mov ecx, 1024
.loop2:
lodsb
or al, al
jz .done
stosb
loop .loop2
.done:
stosb
ret
align 4
socketerror:
pushd 0x0c
call [con_set_flags]
push str_sockerr
call [con_write_asciiz]
pushd 0x07
call [con_set_flags]
mcall send, [edx + thread_data.socketnum], str425, str425.length, 0 ; data connection error
ret
str150 db '150 Here it comes...', 13, 10
.length = $ - str150
str200 db '200 Command OK.', 13, 10
.length = $ - str200
str215 db '215 UNIX type: L8', 13, 10
.length = $ - str215
str220 db '220 KolibriOS FTP Daemon 1.0', 13, 10
.length = $ - str220
str221 db '221 Bye!', 13, 10
.length = $ - str221
str225 db '225 Data connection open', 13, 10
.length = $ - str225
str226 db '226 Transfer OK, Closing connection', 13, 10
.length = $ - str226
str230 db '230 You are now logged in.', 13, 10
.length = $ - str230
str250 db '250 command successful', 13, 10
.length = $ - str250
str331 db '331 Please specify the password.', 13, 10
.length = $ - str331
str421 db '421 Timeout!', 13, 10
.length = $ - str421
str425 db '425 Cant open data connection.', 13, 10
.length = $ - str425
str500 db '500 Unsupported command', 13, 10
.length = $ - str500
str550 db '550 No such file', 13, 10
.length = $ - str550