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

7 июля 2010

Если вы достаточно давно читаете этот блог, то можете помнить о том, как я решил собрать в нем описание популярных (и не очень) сетевых протоколов. Зачем это мне нужно, можно прочитать в статье Достаточно полное описание протокола 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

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.