;Ftp-mirror, Mad_C aka Tarasenko Dmitry, Federal Treasury, Shpakovsky, 2004
.386
.model flat,stdcall

includelib import32.lib

.const
include                constans.inc

.data
msg            dd 6 dup (?)
hPrg           dd 0  
newhwnd        dd 0  

wsadata        db 512 dup(0)
HostName       db 64 dup(0)

RemoteHost     db 64 dup (0)
Com_User       db 'USER '
               db 64 dup (0)
Com_Pass       db 'PASS '
               db 64 dup (0)
Com_Cwd        db 'CWD '
               db 64 dup (0)

ReciveEditControlTextOffsets:
               dd offset RemoteHost
               dd offset Com_Cwd+4
               dd offset Com_User+5
               dd offset Com_Pass+5

GetDirectory   db 'LIST',0dh,0ah,0
SwichToAsc     db 'TYPE A',0dh,0ah,0
SwichToBin     db 'TYPE I',0dh,0ah,0
SayGoodby      db 'QUIT',0dh,0ah

PortCommand    db 'PORT '
IpInPort       db 31 dup (0)
PortInPort     dd ?

RetrFCommand   db 'RETR '
FNameBuf       db 256 dup (0)

datasock       dd ?

sockaddr_in:
sin_family     dw ?
sin_port       dw ?
sin_addr       dd ?
sin_zero       db 8 dup(0)
szSockaddr_in  dd 16

ThreadID_main  dd ?
ThreadID_data  dd ?
ThreadID_command              dd ?

LogFile        db 'ftpc.log',0
FHeader        dd ?
NWritten       dd ?


ReciveDataThreadStruc:
HDataReciveEvent              dd ?
HWaitDataEvent                dd ?
ReciveDataBufferOffset        dd ?
BytesRecived                  dd 0
ReciveDataSock                dd ?

ReciveCommandThreadStruc:
HCommandReciveEvent           dd ?
HWaitCommandEvent             dd ?
ReciveCommandBufferOffset     dd ?
BytesCommandRecived           dd 0
ReciveCommandSock             dd ?

HWaitCloseEvent               dd ?
HWaitConnectEvent             dd ?
DlgName		db "DLG",0
FileLenght      dd 0

CommandButtonProcsOffsets:
                dd offset ConnectPress
                dd offset DisconnectPress
                dd offset MinimizePress
                dd offset AboutPress
ErrTimeOutCommand            db 'Превышен интервал ожидания ответа сервера',0
ErrorCap        db 'Ошибка',0

DirTableStruc:
AddrOfFLen      db 0
AddrOfFName     db 0

.code
_start:

        xor     ebx,ebx
        call    GetModuleHandleA,ebx
        mov     [hPrg],eax

; Создали основной диалог клиента
        call    CreateDialogParamA,hPrg,offset DlgName,ebx,offset WndProc,ebx
        mov     [newhwnd],eax

        mov     edi,offset msg       
msg_loop:
        call    GetMessageA,edi,ebx,ebx,ebx
        or      ax,ax
        je      end_loop
        call    TranslateMessage,edi
        call    DispatchMessageA,edi
        jmp     short msg_loop
end_loop:

        call    ExitProcess,[offset msg+8]

WndProc         proc uses ebx edi esi, hwnd:dword, wmsg:dword, \
                wparam:dword, lparam:dword
        push    offset DefWin
        xor     ebx,ebx
        cmp     wmsg,WM_DESTROY
        je      Destroy
        cmp     wmsg,WM_DATASOCK
        je      datarecived
        cmp     wmsg,WM_COMMANDSOCK
        je      commandrecived
        cmp     wmsg,WM_INITDIALOG
        je      InitAll
        cmp     wmsg,WM_COMMAND
        je      ReciveCommand
        cmp     wmsg,WM_CLOSE
        je      Destroy
        pop     eax
DefWin:
        xor     eax,eax
        ret

ReciveCommand:
;Получили команду, команды могут быть от 10 до 13, проверяем это и вызываем
;соответствующий обработчик 
        mov     eax,wparam
        cmp     eax,10
        jb      nomy
        cmp     eax,13
        jg      nomy
        sub     eax,10
        call    [offset CommandButtonProcsOffsets+eax*4]
nomy:
        ret     0
ConnectPress:
;нажали кнопку соединения
;создаем основной поток программы
        call    CreateThread,ebx,ebx,offset MainThread,ebx,NORMAL_PRIORITY_CLASS,offset ThreadID_main
        ret     0
DisconnectPress:
        ret     0
MinimizePress:
        ret     0
AboutPress:
        ret     0
InitAll:
;инициализация основных параметров программы
        call    WSAStartup, 101h, offset wsadata

        call    VirtualAlloc,ebx,1024000,MEM_COMMIT+MEM_RESERVE,PAGE_READWRITE
        mov     ReciveDataBufferOffset,eax

        call    VirtualAlloc,ebx,10240,MEM_COMMIT+MEM_RESERVE,PAGE_READWRITE
        mov     ReciveCommandBufferOffset,eax

        call    CreateEventA,ebx,ebx,ebx,ebx
        mov	HDataReciveEvent,eax
        call    CreateEventA,ebx,ebx,ebx,ebx
        mov	HCommandReciveEvent,eax

        call    CreateEventA,ebx,ebx,ebx,ebx
        mov     HWaitCommandEvent,eax
        call    CreateEventA,ebx,ebx,ebx,ebx
        mov     HWaitConnectEvent,eax
        call    CreateEventA,ebx,ebx,ebx,ebx
        mov     HWaitDataEvent,eax
        call    CreateEventA,ebx,ebx,ebx,ebx
        mov     HWaitCloseEvent,eax

;создаем два потока приема ответов от сервера
;для управляющего и соединения данных
        call    CreateThread,ebx,ebx,offset ReciveThread,offset ReciveDataThreadStruc,NORMAL_PRIORITY_CLASS,offset ThreadID_data
        call    CreateThread,ebx,ebx,offset ReciveThread,offset ReciveCommandThreadStruc,NORMAL_PRIORITY_CLASS,offset ThreadID_command

;получаем имя нашего хоста, затем его IP-адрес
        call    gethostname, offset HostName,64
        call    gethostbyname,offset HostName
        mov     eax,[eax+12]
        mov     eax,[eax]
        mov     eax,[eax]
        call    inet_ntoa,eax
        call    lstrcpy,offset IpInPort,eax
        mov     esi,offset IpInPort
NextSymIp:
        lodsb
        or      al,al
        jz      short endip
        cmp     al,'.'
        jnz     NextSymIp
        mov     byte ptr [esi-1],','
        jmp     short NextSymIp
endip:
        mov     byte ptr [esi-1],','
        mov     PortInPort,esi

        ret     0
commandrecived:
        cmp     lparam,FD_READ
        jnz	connectcompare
;получено сообщение, что есть новые данные в управляющем соединении, стартуем поток за него отвечающий
        call    SetEvent,HCommandReciveEvent
        ret     0
connectcompare:
        cmp     lparam,FD_CONNECT
        jnz	exitcommandcompare
;получено сообщение о том, что мы удачно приконектились, устанавливаем соответствующее событие
        call    SetEvent,HWaitConnectEvent
       
exitcommandcompare:
        ret     0
datarecived:
        cmp     lparam,FD_ACCEPT
        jnz     readcompare
;произошел конект к сокету данных, принимаем входящее соединение
        call    accept, datasock, 0, 0
        mov     ReciveDataSock,eax
        ret     0
readcompare:
        cmp     lparam,FD_READ
        jnz	closecompare
;пришли данные на соединение данных, запускаем поток приема
        call    SetEvent,HDataReciveEvent

@ret_00:
        ret     0
closecompare:
        cmp     lparam,FD_CLOSE
        jnz     @ret_00
;соединение данных закрыто со стороны сервера, устанавливаем событие
        call    SetEvent,HWaitCloseEvent
        ret     0
Destroy:
;выход из программы, очищаем буфера приема, отключаем winsocks, выходим
        call    VirtualFree,ReciveDataBufferOffset,ebx,MEM_RELEASE
        call    VirtualFree,ReciveCommandBufferOffset,ebx,MEM_RELEASE
        call    WSACleanup
        call    PostQuitMessage,0
        xor     eax,eax
        ret     0
WndProc         endp

;процедура посылки команды по управляющему соединению, описана в статье
SendCommandInSocket proc uses ebx ecx esi edi, hSocket:dword, OutBufOffset:dword
        mov     edi,OutBufOffset
        push    edi
        mov     eax,0ah
        mov     ecx,100
        repne   scasb
        sub     edi,OutBufOffset
        mov     ecx,edi
        pop     esi
        push    edi
        mov     edi,ReciveCommandBufferOffset
        add     edi,BytesCommandRecived
        rep     movsb
        pop     edi
        add     BytesCommandRecived,edi
        call    send,hSocket,OutBufOffset,edi,ebx
        mov     eax,5001
Wait2Answer:
        dec     eax
        push    eax
        call    WaitAnswerRecive
        or      eax,eax
        jnz     ErrorProcessed

        mov     edi,ReciveCommandBufferOffset
        mov     ecx,BytesCommandRecived
        dec     ecx
        dec     ecx
        add     edi,ecx
        mov     al,0ah
        std
        repne   scasb
        cld
        xor     eax,eax
        mov     cl,[edi+2]
        cmp     cl,'1'
        jz      Wait2Answer
        cmp     cl,'3'
        jna     NoErrorProcessed
        call    MessageBoxA,newhwnd,edi,offset ErrorCap,40h
ErrorProcessed:
        xor     eax,eax
        inc     eax
NoErrorProcessed:
        ret
SendCommandInSocket endp

;процедура ожидания ответа сервера по управляющему соединению
WaitAnswerRecive proc TimeToWait:dword
        call    WaitForSingleObject,HWaitCommandEvent,TimeToWait
        or      eax,eax
        jz      NoTimeOutGet
        call    MessageBoxA,newhwnd,offset ErrTimeOutCommand,offset ErrorCap,40h
        call    ResetEvent,HWaitCommandEvent
NoTimeOutGet:
        ret
WaitAnswerRecive endp

;основной поток программы
MainThread      proc parametr:dword

        xor     ebx,ebx
        mov     BytesRecived,ebx
        mov     BytesCommandRecived,ebx
        mov     FileLenght,ebx
        mov     esi,30
nexteditcontrol:
        mov     eax,esi
        sub     eax,30
        mov     eax,[offset ReciveEditControlTextOffsets+eax*4]
        push    eax
;получаем содержимое edit'ов диалога
        call    GetDlgItemTextA,newhwnd,esi,eax,62
        or      eax,eax
        jz      error_no_info
        pop     edi
        cmp     esi,30
        jz      short no_crlf
        mov     word ptr [edi+eax],0a0dh
no_crlf:
        inc     esi
        cmp     esi,33
        jng     nexteditcontrol

;создаем сокет для управляющего соединения
        call    socket, AF_INET, SOCK_STREAM, IPPROTO_TCP
        mov     ReciveCommandSock,eax
        call    WSAAsyncSelect, ReciveCommandSock, newhwnd, WM_COMMANDSOCK,FD_READ+FD_CONNECT
        call    gethostbyname,offset RemoteHost
        or      eax,eax
        je      error
        mov     eax,[eax+12]
        mov     eax,[eax]
        mov     eax,[eax]

        mov     sin_port,1500h
        mov     sin_family,AF_INET
        mov     sin_addr,eax

;конектимся к серверу
        call    connect,ReciveCommandSock,offset sockaddr_in,16
        call    WaitForSingleObject,HWaitConnectEvent,5000
        call    ResetEvent,HWaitConnectEvent
        call    WaitAnswerRecive,5000
        or      eax,eax
        jnz     errorwithregisration
;если все прошло успешно регистрируемся на сервере посылкой USER и PASS
        call    SendCommandInSocket,ReciveCommandSock,offset Com_User
        or      eax,eax
        jnz     errorwithregisration
        call    SendCommandInSocket,ReciveCommandSock,offset Com_Pass
        or      eax,eax
        jnz     errorwithregisration
;переводим сервер в режим отправки ASCII командой TYPE A
        call    SendCommandInSocket,ReciveCommandSock,offset SwichToAsc
        or      eax,eax
        jnz     errorwithregisration
;смена каталога командой CWD
        call    SendCommandInSocket,ReciveCommandSock,offset Com_Cwd
        or      eax,eax
        jnz     errorwithregisration
;Создаем слушающий сокет на первый свободный порт
        call    CreateListenSock

;посылаем серверу команду PORT с этим портом
        call    SendCommandInSocket,ReciveCommandSock,offset PortCommand
        or      eax,eax
        jnz     errorwithregisration
;если сервер успешно соединился с нами на указаный порт, посылаем команду получения текущего каталога
        call    SendCommandInSocket,ReciveCommandSock,offset GetDirectory
        or      eax,eax
        jnz     errorwithregisration

;ждем получения
        call    WaitTransferComplete,1000

        call    VirtualAlloc,ebx,BytesRecived,MEM_COMMIT+MEM_RESERVE,PAGE_READWRITE

        push    eax
        push    eax
;копируем полученый список файлов и директорий в отдельное место
        mov     esi,ReciveDataBufferOffset
        mov     edi,eax
        mov     ecx,BytesRecived
        rep     movsb
        pop     esi
;получаем смещения на размер и имя файла из таблицы
        call    GetTypeOfDirTable,esi
        xor     eax,eax
nextstr:
;читаем из таблицы имя первого файла и его длину
        add     esi,eax
        cmp     esi,edi
        jz      AllFilesComplete     
        call    GetLenText,esi
        mov     byte ptr [esi+eax+1],bl
        cmp     byte ptr [esi],'-'
        jnz     nextstr
        push    eax
        movzx   ecx,AddrOfFName
        add     ecx,esi
        call    lstrcpy,offset FNameBuf,ecx
        movzx   ecx,AddrOfFLen
        add     ecx,esi
        call    GetLenghtFile,ecx
        mov     FileLenght,eax

        call    GetLenText,offset FNameBuf
        push    eax
        mov     byte ptr [offset FNameBuf+eax-2],bl
;пытаемся создать файл с таким именем
        call    CreateFileA, offset FNameBuf, \
                GENERIC_WRITE or GENERIC_READ,ebx,ebx,OPEN_ALWAYS, \
                FILE_ATTRIBUTE_ARCHIVE,ebx
        mov     [FHeader],eax
        pop     eax
        mov     byte ptr [offset FNameBuf+eax-2],0dh
        call    GetLastError
;если фала такого еще нет идем далее на получение файла от сервера
        or      eax,eax
        jz      WriteNewFile
;если произошла ошибка ищем следующий файл в таблице
        cmp     eax,ERROR_ALREADY_EXISTS
        jnz     ErrorOpenFile
;если файл уже существует, проверяем его длину
        call    GetFileSize,FHeader,ebx
;если длина так же равна, пропускаем файл, если нет, получаем его от сервера
        cmp     FileLenght,eax
        jz      FileIsEqivalent
WriteNewFile:
;переводим сервер в бинарный режим передачи 
        call    SendCommandInSocket,ReciveCommandSock,offset SwichToBin
        or      eax,eax
        jnz     errorwithgettingfile
;создаем соединение данных
        call    CreateListenSock
;указываем серверу порт соединения
        call    SendCommandInSocket,ReciveCommandSock,offset PortCommand
        or      eax,eax
        jnz     errorwithgettingfile
        mov     BytesRecived,ebx
;получаем файл
        call    SendCommandInSocket,ReciveCommandSock,offset RetrFCommand
        or      eax,eax
        jnz     errorwithgettingfile
        call    WaitTransferComplete,1500
;записываем его на диск
        call    WriteFile,[FHeader],ReciveDataBufferOffset ,BytesRecived , offset NWritten,0
        call    SetEndOfFile,FHeader
FileIsEqivalent:
        call    CloseHandle,[FHeader]
ErrorOpenFile:
        pop     eax
;обработка следующего файла из таблицы
        jmp     nextstr
errorwithgettingfile:
        call    closesocket,ReciveDataSock
        call    closesocket,datasock
        jmp     FileIsEqivalent
AllFilesComplete:
        pop     eax ; Вспомнили смещение на буфер для директории
        call    VirtualFree,eax,ebx,MEM_RELEASE
errorwithregisration:
;прощаемся с сервером
        call    SendCommandInSocket,ReciveCommandSock,offset SayGoodby
        call    closesocket,ReciveCommandSock
error:
;создаем файл лога и пишем в него весь буфер управляющего соединения
        call    CreateFileA, offset LogFile, \
                GENERIC_WRITE or GENERIC_READ,ebx,ebx,OPEN_ALWAYS, \
                FILE_ATTRIBUTE_ARCHIVE,ebx
        mov     [FHeader],eax
        call    WriteFile,[FHeader],ReciveCommandBufferOffset,BytesCommandRecived,offset NWritten,ebx
        call    SetEndOfFile,FHeader
        call    CloseHandle,[FHeader]
error_no_info:
        call    ExitThread,ebx
        ret
MainThread      endp

;процедура переводит ASCII представление длины файла из таблицы
;в регистровый формат
GetLenghtFile   proc uses ebx edi esi edx, LOffset:dword
        std
        xor     edi,edi
        mov     ebx,10
        mov     esi,LOffset
        movzx   edi,byte ptr [esi]
        sub     edi,30h
        dec     esi
nextrazryad:
        xor     eax,eax
        lodsb
        cmp     al,' '
        jz      endgettinglenght
        sub     al,30h
        mul     ebx
        add     edi,eax
        xchg    ebx,eax
        mov     ebx,10
        mul     ebx
        xchg    ebx,eax
        jmp     nextrazryad
endgettinglenght:
        mov     eax,edi
        cld 
        ret
GetLenghtFile   endp

;возвращает длину строки оканчивающейся 13,10
GetLenText      proc uses ecx edi, TextOffset:dword
        mov     edi,TextOffset
        mov     ecx,BytesRecived
        mov     al,10
        repne   scasb
        sub     edi,TextOffset
        mov     eax,edi
        ret
GetLenText      endp

;процедура ожидания окончания получения информации по соединению данных
WaitTransferComplete proc uses ecx edi, TimeToWaitEndTransfer:dword
WaitProgress:
        call    WaitForSingleObject,HWaitCloseEvent,-1
        call    WaitForSingleObject,HWaitDataEvent,TimeToWaitEndTransfer
        or      eax,eax
        jz      CloseDataSocks
        cmp     TimeToWaitEndTransfer,1000 ;если ждем каталог
        jz      CloseDataSocks
        call    MessageBoxA,newhwnd,offset ErrTimeOutCommand,offset ErrorCap,40h

CloseDataSocks:
        call    ResetEvent,HWaitDataEvent
        call    closesocket,ReciveDataSock
        call    closesocket,datasock
        ret
WaitTransferComplete endp

;создает слушающий сокет
CreateListenSock proc 
        pusha
        call     socket, AF_INET, SOCK_STREAM, IPPROTO_TCP
        mov      datasock,eax
        call     WSAAsyncSelect, datasock, newhwnd, WM_DATASOCK, FD_ACCEPT+FD_READ+FD_CLOSE
        mov      sin_port,0
        mov      sin_family,AF_INET
        mov      sin_addr,INADDR_ANY
        call     bind, datasock, offset sockaddr_in, 16
        call     getsockname,datasock,offset sockaddr_in,offset szSockaddr_in
        xor      eax,eax
        mov      ax,sin_port
        call     ntohs,eax
        push     eax
        shr      eax,8
        call     DECtoASCII,eax,PortInPort
        mov      al,','
        stosb
        pop      eax
        and      eax,0ffh
        call     DECtoASCII,eax,edi
        mov      ax,0a0dh
        stosw
        mov      esi,PortInPort
        call     listen, datasock, 1
        popa
        ret 
CreateListenSock endp

;поток получения данных
ReciveThread     proc parametr:dword
        mov      edi,parametr
InfinityLoop:
        call     WaitForSingleObject,dword ptr [edi],-1
        mov      esi,[edi+8]
        add	 esi,[edi+12]
        call     recv,dword ptr [edi+16],esi,4096,0
        or       eax,eax
        je       InfinityLoop
        cmp      eax,-1
        je       InfinityLoop
        add      [edi+12],eax
        mov      ebx,[edi+4]
        cmp      edi,offset ReciveDataThreadStruc
        je	 comparefordata
        mov      eax,[edi+12]
        mov      esi,[edi+8]
        cmp      byte ptr [esi+eax-1],10
        je       short CallEvent
        jmp      InfinityLoop
comparefordata:
        mov      eax,[edi+12]
        cmp      FileLenght,eax
        jne      InfinityLoop
CallEvent:
        call     SetEvent,ebx
        jmp      InfinityLoop
ReciveThread     endp

;процедура преобразования регистр -> ASCII
DECtoASCII       proc
        mov      ebp,esp
        xor      ecx,ecx
        xor      edx,edx
        mov      ebx,1000
        mov      edi,[ebp+8]
        xor      esi,esi
NextSym:
        mov      eax,[ebp+4]
        div      ebx
        or       al,al
        jz       noflag
        inc      esi
noflag:
        or       esi,esi
        jz       nostosb
        add      al,30h
        stosb
        sub      al,30h
nostosb:
        mul      ebx
        sub      [ebp+4],eax
        mov      eax,10
        xchg     eax,ebx
        div      ebx
        mov      ebx,eax
        test     ebx,1
        jz       short NextSym
        mov      eax,[ebp+4]
        add      eax,30h
        stosb
        ret      8
DECtoASCII       endp

;определяет смещения имени и длины файла в таблице директории, возвращаемой сервером
GetTypeOfDirTable proc uses esi edi ecx ebx edx, OffsetTable:dword
        mov      edi,OffsetTable
NewLine:
        mov      esi,edi
        call     GetLenText,edi
        cmp      [edi],'atot'
        jnz      SkipBSD
        add      edi,eax
        jmp      short NewLine
SkipBSD:
        mov      ecx,eax
        mov      edx,5
        mov      al,' '
        call     NextClumn
        sub      ebx,2
        sub      ebx,esi
        mov      [AddrOfFLen],bl
        mov      edx,3
        call     NextClumn
        dec      edi
        mov      ebx,edi
        sub      ebx,esi
        mov      [AddrOfFName],bl
        ret
NextClumn:
        repne    scasb
        mov      ebx,edi
        repe     scasb
        dec      edx
        jnz      NextClumn
        ret      0
GetTypeOfDirTable endp

include          extrn.inc
ends
end              _start