← На главную

Описание протокола FTP + примеры

Если вы достаточно давно читаете этот блог, то можете помнить о том, как я решил собрать в нем описание популярных (и не очень) сетевых протоколов. Зачем это мне нужно, можно прочитать в статье Достаточно полное описание протокола SMTP. Вот решил пополнить коллекцию протоколом FTP, повсеместно используемым для передачи файлов.

1. Заходим

По традиции, сразу начну с примера:

$ telnet example.ru 21 Trying 192.168.0.1... Connected to example.ru. Escape character is '^]'. 220-Welcome to Pure-FTPd You are user number 5 of 100 allowed. Local time is now 17:41. Server port: 21. 220 You will be disconnected after 15 minutes of inactivity. USER afiskon 331 User afiskon OK. Password required PASS lamepassword 230-User afiskon has group access to: coders 230 OK. Current restricted directory is /

FTP-сервер обычно работает на 21 порту. В приведенном примере строки, начинающиеся с цифр, посылаются сервером, остальные – клиентом. Запросы клиента всегда состоят из одной строки формата КОМАНДА [аргументы], в то время как ответы сервера могут содержать несколько строк.

Первая и последняя строки начинается с трех цифр, представляющих собой код ответа, за которыми идет текстовое описание ответа, отделенное от кода либо пробелом, либо тире. Если в качестве разделителя используется пробел, значит строка является последней в ответе (и, возможно, единственной), иначе – мы получили первую строку многострочного ответа. Где-то мы это уже видели, не так ли?

Существует пять групп ответов сервера:

Код Значение
1xx Команда принята и в настоящее время идет ее выполнение.
2xx Команда принята и успешно выполнена.
3xx Команда принята, требуется дополнительная команда.
4xx Команда не может быть принята в данный момент.
5xx Невозможно выполнить команду.

Как видно из примера, все начинается с посылки сервером кода 220. Затем пользователь должен залогиниться с помощью команд USER и PASS. Если все сделано правильно, на первую сервер ответит кодом 331, а на вторую – 230. Для анонимного входа (если он разрешен настройками сервера), в качестве имени пользователя следует указать «anonymous», а в качестве пароля – свой e-mail. На практике обычно посылается либо пустой e-mail, либо что-то типа root@example.ru.

Как видно, пароль передается в открытом виде, потому крайне желательно шифровать FTP-соединение с помощью SSL (это называется FTPS – FTP плюс SSL), а еще лучше – передавать файлы по SSH с помощью утилит scp, sftp или WinSCP. Первые две есть в любой unix-системе и используют для передачи файлов одноименные протоколы, работающие поверх SSH. WinSCP написан для Windows и внешне напоминает Total Commander, умеет работать как с устаревшим SCP (Secure Copy), так и SFTP (SSH File Transfer Protocol), появившимся только в SSH-2.

2. Осматриваемся

Но что-то меня не в ту степь понесло. После прохождения аутентификации (надо же, больше не путаю с авторизацией) FTP-сервер с радостью выполнит наши команды. Вот их список:

Команда Ожидаемый код Описание
DELE 250 Удалить файл
RMD 250 Удалить директорию
CWD 250 Перейти в директорию
MKD 257 Создать директорию
PWD 257 Узнать текущую директорию
QUIT 221 Закончить работу
TYPE 200 Установить тип передачи
PORT 200 Перейти в активный режим
PASV 227 Перейти в пассивный режим
LIST 150,226 Получить содержимое каталога
RETR 150,226 Скачать файл
STOR 150,226 Залить файл
ABOR 426,226 Отменить передачу
RNFR 350 Выбрать файл для переименования
RNTO 250 Переименовать файл

Здесь я перечислил лишь основные команды, которых достаточно для написания полноценного FTP-клиента. Дело в том, что в реальных условиях FTP-серверы очень избирательно относятся к поддержке команд, описанных в RFC959 и RFC3659. Так что, если мы хотим получить действительно работающее приложение, а не сферического коня в вакууме, придется ограничиться лишь командами из приведенного списка.

Самые простые команды – это QUIT, DELE, MKD, CWD и RMD. Просто командуем и проверяем код, возвращаемый сервером. Если он равен ожидаемому, значит все ОК, если нет – обрабатываем ошибку.

MKD ftp_test 257 "ftp_test" : The directory was successfully created CWD ftp_test 250 OK. Current directory is /ftp_test CWD .. 250 OK. Current directory is / RMD ftp_test 250 The directory was successfully removed

Если бы я писал FTP-клиент, то код, отвечающий за выполнение названных команд, выглядел бы примерно так:

int code; char* dir; // ... if(code = rawcmd(250, "RMD %s\r\n", dir)) printf("Error: %d\n", code); else printf("All done!\n");

Чуть сложнее с парсингом ответа сервера на команду PWD:

PWD 257 "/ftp_test" is your current location

Текущая директория передается в единственной (последней?) строке ответа сервера, заключенная в двойные кавычки. Если полное имя текущей директории содержит двойные кавычки, они заменяются на две кавычки:

PWD 257 "/ftp""test" is your current location

Для переименования файлов используется пара команд – RNFR и RNTO:

RNFR old_file.zip 350 Are you kidding? RNTO new_file.zip 250 Done!

По всей видимости, это такая оптимизация, чтобы буфер, в который сервер читает команды клиента, был порядка максимально допустимой длины полного имени файла, а не в два раза больше. В 1971-м году, когда был создан протокол, это могло быть важно.

Команда TYPE позволяет установить режим передачи файлов. Пример:

TYPE E 200 TYPE is now EBCDIC TYPE A 200 TYPE is now ASCII TYPE I 200 TYPE is now 8-bit binary

Насколько я могу судить, сегодня эта команда уже устарела и все данные можно спокойно передавать в бинарном формате (TYPE I). Цитата из Википедии:

Первые компьютеры использовали формат, размером в байт, машинное слово, двойное машинное слово, не кратное 8. Обычно они были кратны шести. Восемь бит в байте было принято при разработке системы машинных команд для IBM System/360. Это стало международным стандартом и с начала 1970-х большинство компьютеров использует байты, состоящие из 8 бит, и машинные слова, кратные 8.

3. Действуем

Особенность протокола FTP – для выполнения команд и передачи файлов используются разные соединения. Это в общем-то нормальное проектное решение. Мы же не знаем, что там в этих файлах записано, а если передавать их вместе с командами, придется как-то кодировать содержимое файла, чтобы отличить его от команд. Зачем увеличивать объем трафика и усложнять протокол, когда можно просто открыть новое соединение и послать файл, как есть?

При установлении нового соединения кто-то должен собственно соединяться, а кто-то соединение принимать. Если клиент открывает порт, а сервер коннектится к нему, режим передачи файла называется активным. В обратном случае – пассивным. За счет того, что многие пользователи Интернета сегодня сидят за NAT, обычно используется пассивный режим. И это не очень хорошо, потому что число портов у сервера ограничено.

Что интересно – существует возможность передавать файлы с одного FTP-сервера на другой напрямую. Но поскольку такая возможность часто использовалась в DDoS-атаках, сейчас она практически везде отключена.

Для перехода в пассивный режим используется команда PASV, для перехода в активный – PORT:

PORT 192,168,10,1,21,133 200 PORT command successful PASV 227 Entering Passive Mode (192,168,0,1,21,216)

Как несложно догадаться, с помощью цифр кодируется IP-адрес и порт для соединения. Допустим, мы находимся в пассивном режиме и хотим установить соединение для передачи данных:

$ telnet 192.168.0.1 `expr 21 \* 256 + 216` Trying 192.168.0.1... Connected to example.ru. Escape character is '^]'.

После чего можем, например, просмотреть содержимое текущего каталога, заюзав команду LIST:

LIST 150 Accepted data connection 226-Options: -a -l 226 5 matches total

Смотрим на вывод telnet:

drwx------ 5 afiskon coders 512 Jul 7 11:35 . drwx------ 5 afiskon coders 512 Jul 7 11:35 .. drwxr--r-- 3 afiskon coders 512 Jun 6 14:30 eax.me drwxr-xr-x 2 afiskon coders 1024 Jul 7 00:16 logs drwxr--r-- 2 afiskon coders 512 Jun 6 14:30 tmp Connection closed by foreign host.

Абсолютно аналогично происходит скачивание и аплоад файлов, только используются команды RETR {файл} и STOR {файл} соответственно. Команды RETR, STOR и LIST можно прервать в процессе выполнения с помощью команды ABOR, в ответ на которую сервер должен ответить 426 «передача прервана», а затем – 226 «отмена операции произошла успешно».

4. Заключение

На этом я, пожалуй, закончу свое повествование. Получилось 9 Кб текста против 130 Кб RFC959. По этой статье вполне можно написать несложный FTP клиент или сервер, я проверял! Самое главное – это протестировать его на совместимость с максимально возможным количеством ПО, поскольку, как я уже отмечал, в мире FTP мало кто строго следует RFC.

Дополнение: Вас также может заинтересовать заметка Памятка по использованию WebDAV