diff --git a/programs/network/ftpc/ftpc.asm b/programs/network/ftpc/ftpc.asm new file mode 100644 index 0000000000..af7d0c657a --- /dev/null +++ b/programs/network/ftpc/ftpc.asm @@ -0,0 +1,403 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; +;; Copyright (C) KolibriOS team 2013. All rights reserved. ;; +;; Distributed under terms of the GNU General Public License ;; +;; ;; +;; ftpc.asm - FTP client for KolibriOS ;; +;; ;; +;; Written by hidnplayr@kolibrios.org ;; +;; ;; +;; GNU GENERAL PUBLIC LICENSE ;; +;; Version 2, June 1991 ;; +;; ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +format binary as "" + +__DEBUG__ = 0 +__DEBUG_LEVEL__ = 1 +BUFFERSIZE = 1024 + +STATUS_CONNECTING = 0 +STATUS_CONNECTED = 1 +STATUS_NEEDPASSWORD = 2 +STATUS_LOGGED_IN = 3 + +use32 +; standard header + db 'MENUET01' ; signature + dd 1 ; header version + dd start ; entry point + dd i_end ; initialized size + dd mem+0x1000 ; required memory + dd mem+0x1000 ; stack pointer + dd s ; parameters + dd 0 ; path + +include '../../macros.inc' +purge mov,add,sub +include '../../proc32.inc' +include '../../dll.inc' +include '../../debug-fdo.inc' +include '../../network.inc' + +include 'usercommands.inc' +include 'servercommands.inc' + +; entry point +start: + + DEBUGF 1, "hello" +; load libraries + stdcall dll.Load, @IMPORT + test eax, eax + jnz exit +; initialize console + push 1 + call [con_start] + push title + push 25 + push 80 + push 25 + push 80 + call [con_init] + +; Check for parameters + cmp byte [s], 0 + jne resolve + +main: + call [con_cls] +; Welcome user + push str1 + call [con_write_asciiz] + +; write prompt + push str2 + call [con_write_asciiz] +; read string + mov esi, s + push 256 + push esi + call [con_gets] +; check for exit + test eax, eax + jz done + cmp byte [esi], 10 + jz done + +resolve: + +; delete terminating '\n' + mov esi, s + @@: + lodsb + cmp al, 0x20 + ja @r + mov byte [esi-1], 0 + +; call [con_cls] + push str3 + call [con_write_asciiz] + push s + call [con_write_asciiz] + +; resolve name + push esp ; reserve stack place + push esp ; fourth parameter + push 0 ; third parameter + push 0 ; second parameter + push s ; first parameter + call [getaddrinfo] + pop esi +; test for error + test eax, eax + jnz fail + +; write results + push str8 + call [con_write_asciiz] +; mov edi, esi + +; convert IP address to decimal notation + mov eax, [esi+addrinfo.ai_addr] + mov eax, [eax+sockaddr_in.sin_addr] + mov [sockaddr1.ip], eax + push eax + call [inet_ntoa] +; write result + push eax + call [con_write_asciiz] +; free allocated memory + push esi + call [freeaddrinfo] + + push str9 + call [con_write_asciiz] + + mcall socket, AF_INET4, SOCK_STREAM, 0 + cmp eax, -1 + je fail2 + mov [socketnum], eax + + push str11 + call [con_write_asciiz] + + mcall connect, [socketnum], sockaddr1, 18 + + mcall 40, EVM_STACK + + mov [status], STATUS_CONNECTING + mov [offset], buffer_ptr + + push str12 + call [con_write_asciiz] + +wait_for_serverdata: + mcall 10 + + call [con_get_flags] + test eax, 0x200 ; con window closed? + jnz exit + +; receive socket data + mcall recv, [socketnum], [offset], BUFFERSIZE, 0 + inc eax + jz wait_for_serverdata + dec eax + jz wait_for_serverdata + +; extract commands, copy them to "s" buffer + add eax, buffer_ptr ; eax = end pointer + mov esi, buffer_ptr ; esi = current pointer + .nextcommand: + mov edi, s + .byteloop: + cmp esi, eax + jae wait_for_serverdata + lodsb + cmp al, 10 ; excellent, we might have a command + je .got_command + cmp al, 13 ; just ignore this crap + je .byteloop + stosb + jmp .byteloop + +; we have a newline check if its a command + .got_command: + xor al, al + stosb +; push esi eax + +; print it to the screen + pushd s + call [con_write_asciiz] + pushd str4 ; newline + call [con_write_asciiz] + +; cmp byte[s+2], " " +; jne .not_command + + lea ecx, [edi - s] + call server_parser + +; .not_command: +; pop eax esi +; jmp .nextcommand + + + + +wait_for_usercommand: + + cmp [status], STATUS_CONNECTED + je .connected + + cmp [status], STATUS_NEEDPASSWORD + je .needpass + +; write prompt + push str2 + call [con_write_asciiz] +; read string + mov esi, s + push 256 + push esi + call [con_gets] + + call [con_get_flags] + test eax, 0x200 ; con window closed? + jnz exit + + cmp dword[s], "list" + je cmd_list + + cmp dword[s], "help" + je cmd_help + + push str_unkown + call [con_write_asciiz] + + jmp wait_for_usercommand + + + .connected: + + push str_user + call [con_write_asciiz] + + mov dword[s], "USER" + mov byte[s+4], " " + +; mov [status], STATUS_NEEDPASSWORD + inc [status] + + jmp .send + + + .needpass: + push str_pass + call [con_write_asciiz] + + mov dword[s], "PASS" + mov byte[s+4], " " + +; mov [status], STATUS_LOGGED_IN + inc [status] + + .send: +; read string + mov esi, s+5 + push 256 + push esi + call [con_gets] + + mov edi, s+5 + mov ecx, 256 + xor al, al + repne scasb + lea esi, [edi-s-1] + mcall send, [socketnum], s + + jmp wait_for_usercommand + + + + + + +open_dataconnection: + cmp [status], STATUS_LOGGED_IN + jne .fail + + mov dword[s], "PASV" + mov byte[s+4], 10 + mcall send, [socketnum], s, 5 + + ret + + .fail: + push str6 + call [con_write_asciiz] + + ret + + +fail2: + push str6 + call [con_write_asciiz] + + jmp fail.wait + +fail: + push str5 + call [con_write_asciiz] + .wait: + push str10 + call [con_write_asciiz] + call [con_getch2] + jmp main + +done: + push 1 + call [con_exit] +exit: + + mcall close, [socketnum] + mcall -1 + + + +; data +title db 'FTP client',0 +str1 db 'FTP client for KolibriOS v0.01',10,10,'Please enter ftp server address.',10,0 +str2 db '> ',0 +str3 db 'Resolving ',0 +str4 db 10,0 +str5 db 10,'Name resolution failed.',10,0 +str6 db 10,'Socket error.',10,0 +str8 db ' (',0 +str9 db ')',10,0 +str10 db 'Push any key to continue.',0 +str11 db 'Connecting',10,0 +str12 db 'Connected!',10,0 +str_user db "username: ",0 +str_pass db "password: ",0 +str_unkown db "unkown command",10,0 +str_help db "available commands:",10,10 + db "help list",10,0 + +str_open db "opening data socket",10,0 + +sockaddr1: + dw AF_INET4 +.port dw 0x1500 ; 21 +.ip dd 0 + rb 10 + +sockaddr2: + dw AF_INET4 +.port dw 0 +.ip dd 0 + rb 10 + +include_debug_strings ; ALWAYS present in data section + + + +; import +align 4 +@IMPORT: + +library network, 'network.obj', console, 'console.obj' + +import network, \ + getaddrinfo, 'getaddrinfo', \ + freeaddrinfo, 'freeaddrinfo', \ + inet_ntoa, 'inet_ntoa' + +import console, \ + con_start, 'START', \ + con_init, 'con_init', \ + con_write_asciiz,'con_write_asciiz', \ + con_exit, 'con_exit', \ + con_gets, 'con_gets',\ + con_cls, 'con_cls',\ + con_getch2, 'con_getch2',\ + con_set_cursor_pos, 'con_set_cursor_pos',\ + con_write_string, 'con_write_string',\ + con_get_flags, 'con_get_flags' + + +i_end: + +active_passive db ? +socketnum dd ? +datasocket dd ? +buffer_ptr rb 2*BUFFERSIZE +status db ? +offset dd ? + +s rb 1024 + +mem: diff --git a/programs/network/ftpc/servercommands.inc b/programs/network/ftpc/servercommands.inc new file mode 100644 index 0000000000..5f06ab96d0 --- /dev/null +++ b/programs/network/ftpc/servercommands.inc @@ -0,0 +1,114 @@ +server_parser: + +; Commands are always 3 numbers and followed by a space +; If a server decides it needs multiline output, +; first lines will have a dash instead of space after numbers, +; thus they are simply ignored. + + cmp dword[s], "150 " + je data_ok + + cmp dword[s], "220 " + je welcome + + cmp dword[s], "227 " + je pasv_ok + + cmp dword[s], "230 " + je login_ok + + cmp dword[s], "331 " + je pass + + ret + + +welcome: + + mov [status], STATUS_CONNECTED + ret + + +pass: + + mov [status], STATUS_NEEDPASSWORD + ret + + +login_ok: + + mov [status], STATUS_LOGGED_IN + ret + + +pasv_ok: + + sub ecx, 5 + jb .fail + mov al, "(" + mov edi, s + 5 + repne scasb + + mcall socket, AF_INET4, SOCK_STREAM, 0 + cmp eax, -1 + je fail + mov [datasocket], eax + + mov esi, edi + call ascii_dec + mov byte[sockaddr2.ip+0], bl + call ascii_dec + mov byte[sockaddr2.ip+1], bl + call ascii_dec + mov byte[sockaddr2.ip+2], bl + call ascii_dec + mov byte[sockaddr2.ip+3], bl + + call ascii_dec + mov byte[sockaddr2.port+1], bl + call ascii_dec + mov byte[sockaddr2.port+0], bl + + push str_open + call [con_write_asciiz] + + mcall connect, [datasocket], sockaddr2, 18 + + .fail: + ret + + +data_ok: + + mcall recv, [datasocket], buffer_ptr, BUFFERSIZE, 0 ; fixme: use other buffer + inc eax + jz .fail + dec eax + jz .fail + + mov byte[buffer_ptr + eax], 0 + pushd buffer_ptr + call [con_write_asciiz] + + .fail: + ret + + +ascii_dec: + + xor ebx, ebx + mov cl, 3 + .loop: + lodsb + sub al, '0' + jb .done + cmp al, 9 + ja .done + lea ebx, [ebx*4+ebx] + shl ebx, 1 + add bl, al + dec cl + jnz .loop + + .done: + ret \ No newline at end of file diff --git a/programs/network/ftpc/usercommands.inc b/programs/network/ftpc/usercommands.inc new file mode 100644 index 0000000000..57a4582459 --- /dev/null +++ b/programs/network/ftpc/usercommands.inc @@ -0,0 +1,17 @@ +cmd_list: + + call open_dataconnection + + mov dword[s], "LIST" + mov word[s+4], 0x0d0a + mcall send, [socketnum], s, 6 + + jmp wait_for_serverdata + + +cmd_help: + + push str_help + call [con_write_asciiz] + + jmp wait_for_usercommand \ No newline at end of file