;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; 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 ""

BUFFERSIZE              = 4096

STATUS_CONNECTING       = 0
STATUS_CONNECTED        = 1
STATUS_NEEDPASSWORD     = 2
STATUS_LOGGED_IN        = 3

OPERATION_NONE          = 0
OPERATION_LIST          = 1
OPERATION_RETR          = 2
OPERATION_STOR          = 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 '../../network.inc'

include 'usercommands.inc'
include 'servercommands.inc'

start:
; disable all events
        mcall   40, 0
; load libraries
        stdcall dll.Load, @IMPORT
        test    eax, eax
        jnz     exit
; initialize console
        invoke  con_start, 1
        invoke  con_init, 80, 25, 80, 250, str_title
; Check for parameters, if there are some, resolve the address right away
        cmp     byte [s], 0
        jne     resolve

main:
; Clear screen
        invoke  con_cls
; Welcome user
        invoke  con_write_asciiz, str_welcome
; write prompt (in green color)
        invoke  con_set_flags, 0x0a
        invoke  con_write_asciiz, str_prompt
; read string
        invoke  con_gets, s, 256
; check for exit
        test    eax, eax
        jz      done
        cmp     byte [s], 10
        jz      done
; reset color back to grey and print newline
        invoke  con_set_flags, 0x07
        invoke  con_write_asciiz, str_newline

resolve:
; delete terminating '\n'
        mov     esi, s
  @@:
        lodsb
        cmp     al, 0x20
        ja      @r
        mov     byte [esi-1], 0
; Say to the user that we're resolving
        invoke  con_write_asciiz, str_resolve
        invoke  con_write_asciiz, s
; resolve name
        push    esp     ; reserve stack place
        invoke  getaddrinfo, s, 0, 0, esp
        pop     esi
; test for error
        test    eax, eax
        jnz     error_resolve
; write results
        invoke  con_write_asciiz, str8          ; ' (',0
        mov     eax, [esi+addrinfo.ai_addr]     ; convert IP address to decimal notation
        mov     eax, [eax+sockaddr_in.sin_addr] ;
        mov     [sockaddr1.ip], eax             ;
        invoke  inet_ntoa, eax                  ;
        invoke  con_write_asciiz, eax           ; print ip
        invoke  freeaddrinfo, esi               ; free allocated memory
        invoke  con_write_asciiz, str9          ; ')',10,0
; open the socket
        mcall   socket, AF_INET4, SOCK_STREAM, 0
        cmp     eax, -1
        je      error_socket
        mov     [socketnum], eax
; connect to the server
        invoke  con_write_asciiz, str_connect
        mcall   connect, [socketnum], sockaddr1, 18
        mov     [status], STATUS_CONNECTING
; Tell the user we're waiting for the server now.
        invoke  con_write_asciiz, str_waiting

; Reset 'offset' variable, it's used by the data receiver
        mov     [offset], 0

wait_for_servercommand:
; Any commands still in our buffer?
        cmp     [offset], 0
        je      .receive                        ; nope, receive some more
        mov     esi, [offset]
        mov     edi, s
        mov     ecx, [size]
        add     ecx, esi
        jmp     .byteloop

; receive socket data
  .receive:
        mcall   recv, [socketnum], buffer_ptr, BUFFERSIZE, 0
        inc     eax
        jz      error_socket
        dec     eax
        jz      wait_for_servercommand

        mov     [offset], 0

; extract commands, copy them to "s" buffer
        lea     ecx, [eax + buffer_ptr]         ; ecx = end pointer
        mov     esi, buffer_ptr                 ; esi = current pointer
        mov     edi, s
  .byteloop:
        cmp     esi, ecx
        jae     wait_for_servercommand
        lodsb
        cmp     al, 10                          ; excellent, we might have a command
        je      .got_command
        cmp     al, 13                          ; just ignore this byte
        je      .byteloop
        stosb
        jmp     .byteloop
  .got_command:                                 ; we have a newline check if its a command
        cmp     esi, ecx
        je      .no_more_data
        mov     [offset], esi
        sub     ecx, esi
        mov     [size], ecx
        jmp     .go_cmd
  .no_more_data:
        mov     [offset], 0
  .go_cmd:
        lea     ecx, [edi - s]                  ; length of command
        xor     al, al
        stosb

        invoke  con_set_flags, 0x03             ; change color
        invoke  con_write_asciiz, s             ; print servercommand
        invoke  con_write_asciiz, str_newline
        invoke  con_set_flags, 0x07             ; reset color

        jmp     server_parser                   ; parse command



wait_for_usercommand:

; change color to green for user input
        invoke  con_set_flags, 0x0a

; If we are not yet connected, request username/password
        cmp     [status], STATUS_CONNECTED
        je      .connected

        cmp     [status], STATUS_NEEDPASSWORD
        je      .needpass

; write prompt
        invoke  con_write_asciiz, str_prompt
; read string
        invoke  con_gets, s, 256

; print a newline and reset the color back to grey
        invoke  con_write_asciiz, str_newline
        invoke  con_set_flags, 0x07

        cmp     dword[s], "cwd "
        je      cmd_cwd

        cmp     dword[s], "mkd "
        je      cmd_mkd

        cmp     dword[s], "rmd "
        je      cmd_rmd

        cmp     dword[s], "pwd" + 10 shl 24
        je      cmd_pwd

        cmp     dword[s], "bye" + 10 shl 24
        je      cmd_bye

        cmp     byte[s+4], " "
        jne     @f

        cmp     dword[s], "lcwd"
        je      cmd_lcwd

        cmp     dword[s], "retr"
        je      cmd_retr

        cmp     dword[s], "stor"
        je      cmd_stor

        cmp     dword[s], "dele"
        je      cmd_dele

  @@:
        cmp     byte[s+4], 10
        jne     @f

        cmp     dword[s], "list"
        je      cmd_list

        cmp     dword[s], "help"
        je      cmd_help

        cmp     dword[s], "cdup"
        je      cmd_cdup

  @@:
; Uh oh.. unknown command, tell the user and wait for new input
        invoke  con_write_asciiz, str_unknown
        jmp     wait_for_usercommand


  .connected:
; request username
        invoke  con_write_asciiz, str_user
        mov     dword[s], "USER"
        mov     byte[s+4], " "
        jmp     .send


  .needpass:
; request password
        invoke  con_write_asciiz, str_pass
        mov     dword[s], "PASS"
        mov     byte[s+4], " "
        invoke  con_set_flags, 0x00     ; black text on black background for password

  .send:
; read string
        mov     esi, s+5
        invoke  con_gets, esi, 256

; find end of string
        mov     edi, s+5
        mov     ecx, 256
        xor     al, al
        repne   scasb
        lea     esi, [edi-s]
        mov     word[edi-2], 0x0a0d
; and send it to the server
        mcall   send, [socketnum], s, , 0

        invoke  con_write_asciiz, str_newline
        invoke  con_set_flags, 0x07     ; reset color
        jmp     wait_for_servercommand



open_dataconnection:                    ; only passive for now..
        cmp     [status], STATUS_LOGGED_IN
        jne     .fail

        mcall   send, [socketnum], str_PASV, str_PASV.length, 0
        ret

  .fail:
        invoke  con_get_flags
        push    eax
        invoke  con_set_flags, 0x0c                     ; print errors in red
        invoke  con_write_asciiz, str_err_socket
        invoke  con_set_flags                           ; reset color
        ret



error_socket:
        invoke  con_set_flags, 0x0c                     ; print errors in red
        invoke  con_write_asciiz, str_err_socket
        jmp     wait_for_keypress

error_resolve:
        invoke  con_set_flags, 0x0c                     ; print errors in red
        invoke  con_write_asciiz, str_err_resolve

wait_for_keypress:
        invoke  con_set_flags, 0x07                     ; reset color to grey
        invoke  con_write_asciiz, str_push
        invoke  con_getch2
        mcall   close, [socketnum]
        jmp     main

done:
        invoke  con_exit, 1

exit:
        mcall   close, [socketnum]
        mcall   -1



; data
str_title       db 'FTP client',0
str_welcome     db 'FTP client for KolibriOS v0.10',10
                db 10
                db 'Please enter ftp server address.',10,0

str_prompt      db '> ',0
str_resolve     db 'Resolving ',0
str_newline     db 10,0
str_err_resolve db 10,'Name resolution failed.',10,0
str_err_socket  db 10,'Socket error.',10,0
str8            db ' (',0
str9            db ')',10,0
str_push        db 'Push any key to continue.',0
str_connect     db 'Connecting...',10,0
str_waiting     db 'Waiting for welcome message.',10,0
str_user        db "username: ",0
str_pass        db "password: ",0
str_unknown     db "Unknown command or insufficient parameters - type help for more information.",10,0
str_lcwd        db "Local working directory is now: ",0

str_open        db "opening data socket",10,0

str_help        db "available commands:",10
                db 10
                db "bye             - close the connection",10
                db "cdup            - change to parent of current directory on the server",10
                db "cwd <directory> - change working directoy on the server",10
                db "dele <file>     - delete file from the server",10
                db "list            - list files and folders in current server directory",10
                db "lcwd <path>     - change local working directory",10
                db "mkd <directory> - make directory on the server",10
                db "pwd             - print server working directory",10
                db "retr <file>     - retreive file from the server",10
                db "rmd <directory> - remove directory from the server",10
                db "stor <file>     - store file on the server",10
                db 10,0


; FTP strings

str_PASV        db 'PASV',13,10
.length = $ - str_PASV

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

; 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', \
        con_set_flags,  'con_set_flags'


i_end:

; uninitialised data

status          db ?
active_passive  db ?

socketnum       dd ?
datasocket      dd ?
offset          dd ?
size            dd ?
operation       dd ?

filestruct:
.subfn  dd ?
.offset dd ?
        dd ?
.size   dd ?
.ptr    dd ?
.name   rb 1024

buffer_ptr      rb BUFFERSIZE+1
buffer_ptr2     rb BUFFERSIZE+1

s               rb 1024

mem: