Опыт применения REXX FTP API - Замечания по применению

Индекс материала
Опыт применения REXX FTP API
Введение
Установка
Коды возврата
Функции
Замечания по применению
Послесловие

6. Замечания по применению.

В этом разделе автор попытался обобщить свой опыт применения REXX FTP API, чтобы показать не только КАК ИСПОЛЬЗОВАТЬ ту или иную функцию, но и ЧТО ПРОИСХОДИТ во время работы.

Для иллюстрации отдельных пунктов использованы программы, тексты которых приведены в разделе "Пример" описания соответствующих функций.

Отметим, что примеры выполнялись в операционной системе The Operating System/2 Version 4.50 Revision 14.088. В качестве сервера FTP использовался стандартный модуль ftpd из поставки OS/2 (хост 127.0.0.1).

Некоторые замечания уже были приведены в соответствующих разделах.

6.1. Активный и пассивный режимы FTP.

FTP использует два раздельных канала TCP/IP:
  • один - для передачи управляющих команд,
  • другой - для передачи данных.
Режимы работы FTP различаются по направлению инициирования канала данных:
активный
соединение для передачи команд инициирует клиент, а соединение для передачи данных - сервер,
пассивный
оба соединения инициирует клиент.
  1. Активный режим FTP.

    Поскольку соединение для передачи данных инициирует сервер, то клиент должен сообщить ему номер порта, по которому он готов принять данные.

    Делается это с помощью команды:
       PORT a1,a2,a3,a4,p1,p2
    -+--------- -+---
    | |
    | +--- номер порта = 256*p1+p2
    +--------------- ip-адрес клиента
    Пример получения клиентом файла с сервера FTP в активном режиме:
    Клиент (192.168.2.22)       Порты и направление   Сервер (10.10.15.254)
    ------------------------------------------------------------------------------
    Oткрывает свободный динамический порт (например, 51618) под канал управления.

    Инициирует канал управления (51618 -------> 21)
    (51618 <------- 21) 220 FTP server ready.
    USER <login> (51618 -------> 21)
    (51618 <------- 21) 331 Login ok, send password.
    PASS <password> (51618 -------> 21)
    (51618 <------- 21) 230 Login ok.
    PWD (51618 -------> 21)
    (51618 <------- 21) 257 "/" is current directory.
    TYPE I (51618 -------> 21)
    (51618 <------- 21) 200 Type set to I.

    Oткрывает свободный динамический порт (например, 51619) под канал данных
    и начинает его прослушивать, ожидая данных.


    PORT 192,168,2,22,201,163. (51618 -------> 21)
    (51618 <------- 21) 200 PORT command successful.
    RETR Test.txt (51618 -------> 21)

    (51619 <------- 20) Инициирует канал данных
    (51619 <------- 20) Передаёт данные
    (51619 <------- 20) Закрывает канал данных

    (51618 <------- 21) 226 Transfer complete.

    Закрывает динамический порт, открытый под канал данных.

    QUIT (51618 -------> 21)
    (51618 <------- 21) 221 Goodbye.

    Закрывает канал управления. (51618 -------> 21)
  2. Пассивный режим FTP.

    Поскольку соединение для передачи данных инициирует клиент, то сервер должен сообщить ему номер порта, с которого он готов отправить данные.

    Сервер делает это, отвечая на команду PASV:
       227 Entering Passive Mode (a1,a2,a3,a4,p1,p2).
    -+--------- -+---
    | |
    | +--- номер порта = 256*p1+p2
    +--------------- ip-адрес сервера
    Пример получения клиентом файла с сервера FTP в пассивном режиме:
    Клиент (192.168.2.22)       Порты и направление   Сервер (10.10.15.254)
    ------------------------------------------------------------------------------
    Oткрывает свободный динамический порт (например, 51618) под канал управления.

    Инициирует канал управления (51618 -------> 21)
    (51618 <------- 21) 220 FTP server ready.
    USER <login> (51618 -------> 21)
    (51618 <------- 21) 331 Login ok, send password.
    PASS <password> (51618 -------> 21)
    (51618 <------- 21) 230 Login ok.
    PWD (51618 -------> 21)
    (51618 <------- 21) 257 "/" is current directory.
    TYPE I (51618 -------> 21)
    (51618 <------- 21) 200 Type set to I.
    PASV (51618 -------> 21)

    Выделяет порт (например, 27087) под канал данных

    (51618 <------- 21) 227 Entering Passive Mode (10,10,15,254,105,207).

    Oткрывает свободный динамический порт (например, 51619) под канал данных.

    Инициирует канал данных (51619 ----> 27087)

    RETR Test.txt (51618 -------> 21)
    (51618 <------- 21) 150 Opening BINARY mode data connection for /Test.txt (75 bytes).

    (51619 <---- 27087) Передаёт данные
    (51619 <---- 27087) Закрывает канал данных

    (51618 <------- 21) 226 Transfer complete.

    Закрывает динамический порт, открытый под канал данных.

    QUIT (51618 -------> 21)
    (51618 <------- 21) 221 Goodbye.

    Закрывает канал управления. (51618 -------> 21)
Строго говоря, пассивный режим был придуман для преодоления проблем, возникающих из-за использования firewall/NAT, которыми защищена рабочая станция клиента, находящаяся в локальной сети.

Однако, практически все современные firewall/NAT, позволяют рабочим станциям клиента, находящимся в локальной сети, работать с серверами FTP, расположенными во внешней сети, в активном режиме:
  • firewall/NAT, защищающий локальную сеть клиента, перехватывает команду PORT, отправляемую из локальной сети во внешнюю,
  • в перехваченной команде PORT подменяет локальный адрес рабочей станции на внешний адрес firewall/NAT,
  • строит временное правило firewall, разрешающее доступ с порта 20 внешнего сервера на порт, указанный в перехваченной команде,
  • строит временное правило NAT, направляющее трафик между портом 20 внешнего сервера и портом, указанным в перехваченной команде, на соответствующую рабочую станцию в локальной сети.
Аналогичным образом могут быть защищены и серверы FTP, когда они размещены в локальной сети (чаще говорят про "демилитаризованную зону", но она тоже представляет из себя локальную сеть, только отделённую от основной как логически, так и физически).

В этом случае "умный" firewall/NAT, защищающий серверы:
  • перехватывает ответ на команду PASV, отправляемый сервером клиенту во внешнюю сеть,
  • в перехваченном ответе подменяет локальный адрес сервера на внешний адрес firewall/NAT,
  • строит временное правило firewall, разрешающее доступ с любого порта внешнего клиента на порт, указанный в перехваченном ответе,
  • строит временное правило NAT, направляющее трафик между любым портом внешнего клиента и портом, указанным в перехваченном ответе, на соответствующий сервер в локальной сети.
Но это тоже может не решить все проблемы, связанные с использованием firewall/NAT.

Иногда возникает ситуация, которая со стороны клиента внешне выглядит так:
  • не удаётся скачать файл с сервера FTP с помощью функций REXX FTP API в пассивном режиме, хотя с помощью браузера (например, Firefox), который тоже использует пассивный режим, файл скачивается.
Эта проблема возникает из-за того, что в строке, получаемой клиентом в ответ на команду PASV, указан адрес, отличный от адреса сервера FTP. И, если браузер просто игнорирует этот адрес, то функции REXX FTP API пытаются установить соединение для передачи данных именно по адресу, указанному в ответе.

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

6.2. Восстановление соединения.

Функция FtpSetUser() открывает сессию FTP, задавая имя или адрес хоста, имя пользователя, пароль и аккаунт.

Никакого обмена по сети по время выполнения этой функции не происходит. Параметры, переданные функции, используются в дальнейшем для инициализации и восстановления соединения с сервером FTP.

Фактически, как инициализация, так и восстановление соединения с сервером FTP выполняется при вызове любой функции из перечисленных ниже, если сессия FTP открыта, но соединение с сервером отсутствует:

FtpAppend()
FtpChDir()
FtpDelete()
FtpDir()
FtpGet()
FtpLs()
FtpMkDir()
FtpPut()
FtpPutUnique()
FtpPwd()
FtpQuote()
FtpRename()
FtpRmDir()
FtpSetActiveMode()
FtpSite()
FtpSys()

  1. Инициализация соединения.

    Выполняется, если соединение с сервером ещё ни разу не устанавливалось после вызова FtpSetUser().

    Сначала посылается запрос на получение адреса хоста серверу DNS. Если сервер DNS в системе не определён, то для поиска адреса хоста используется файл TCPIP\ETC\HOSTS.

    Затем устанавливается соединение с сервером FTP и выполняется следующая последовательность команд:
       USER <login>
    PASS <password>
    ACCT <account>
    PWD
  2. Восстановление соединения.

    Выполняется, если соединение с сервером было разорвано во время выполнения.

    Сначала устанавливается соединение с сервером FTP. Затем выполняется следующая последовательность команд:
       USER <login>
    PASS <password>
    ACCT <account>
    СWD <directory>
    где directory - имя текущего каталога на момент разрыва соединения.

6.3. О полезности FtpSys().

FtpSys() выдаёт информацию об операционной системе, под которой работает сервер FTP, а иногда и о нём самом. Эту информацию можно использовать для вынесения некоторых предположений о том, что можно ожидать от данного сервера.

Например, в каком формате будет предоставляться список содержимого каталога, формируемого функцией FtpDir(). Или, как будет выглядеть имя текущего каталоге в строке, возвращаемой функцией FtpPwd().

Многое, конечно, зависит от фантазии автора исходного кода конкретного сервера FTP и умения администратора, настроившего его на рабочем месте, но всё-таки какие-то основные правила обычно соблюдаются.

Примеры ответов некоторых серверов FTP на запрос информации об операционной системе:
   Сервер                 Ответ
-------------------------------------------------------------
ftp.os2.ru OS/2 operating system
os2.fannet.ru UNIX type:OS/2
ftp.software.ibm.com UNIX Type: L8
ftp.relcom.ru UNIX Type: L8 Version: BSD-199506
ftp.demos.su UNIX Type: L8 Version: tnftpd 20080609
ftp.sun.com UNIX Type: L8 Version: SUNOS
ftp.asus.com UNIX emulated by FileZilla
ftp.gnupg.org UNIX
ftp.microsoft.com Windows_NT
ftp.softlab.ru Windows_NT version 5.0
ftp.mt.xplain.com MACOS Peter's Server
Итак, что же можно ожидать от сервера FTP, получив от него информацию об операционной системе:
  1. UNIX ...

    1. Независимо от операционной системы, под которой работает данный сервер FTP, функция FtpDir() будет возвращать списки содержимого каталогов в UNIX-подобном формате:

      Пример 1:
         X:>rxFtpDir ftp.software.ibm.com

      <...пропуск...>
      drwxr-sr-x 7 102004493 201 256 Mar 9 2004 ps
      lrwxrwxrwx 1 102004493 1 14 Sep 25 2009 pub -> software/lotus
      drwxr-sr-x 19 102004493 201 4096 Jul 22 1999 publications
      -rw-r--r-- 1 102004493 201 75 Feb 16 08:52 robots.txt
      drwxr-sr-x 21 102004493 201 4096 Aug 20 2002 rs6000
      drwxr-sr-x 38 102004493 201 4096 Apr 28 2008 s390
      <...пропуск...>
      где каждая запись состоит из нескольких полей, разделённых пробелами. Большинство реализаций серверов FTP формирует записи, состоящие из 9 полей, но встречаются и такие, которые формируют записи из 8 полей (например, DeleGate).

      Важно тут то, что первое поле всегда содержит тип записи и права доступа для владельца, группы и остальных пользователей, а в последних пяти всегда последовательно располагаются длина, месяц, число, год (или время) создания/модификации файла и, наконец, имя.

      Тип записи определяется по первому символу первого поля (подсвечен в вышеприведённом примере):
      d - каталог, l - линк, - - файл и т.д.

      Имя всегда находится в последнем поле (подсвечено в вышеприведённом примере) и может само содержать пробелы, что несколько усложняет разбор из-за разницы числа полей в записи для разных серверов.

    2. Домашний каталог будет корневым:

      Пример 2:
         X:>rxFtpPwd ftp.software.ibm.com

      "/" is your current location
    3. Функция FtpPwd() возвращает имя текущего каталога так, как оно хранится в файловой системе, включая прописные и строчные буквы:

      Пример 3:
         X:>rxFtpDir ftp.dlink.ru/pub

      drwxrwsr-x 73 33 106 4096 Apr 5 09:24 ADSL
      drwxrwsr-x 57 33 106 4096 Dec 17 14:43 Adapter
      drwxrwsr-x 13 33 106 4096 Feb 1 2006 Bootrom
      <...пропуск...>

      X:>rxFtpChDir ftp.dlink.ru/pub/Bootrom

      "/pub/Bootrom" is your current location
    Тем не менее могут быть незначительные различия в работе функций с серверами различного исполнения:

    • в списках содержимого каталогов (FtpDir()) могут присутствовать записи типа total:
         total 208
      drwxr-sr-x 13 102004493 201 4096 Apr 18 2008 4700
      -rw-r--r-- 1 102004493 201 6981 Dec 20 1995 TRADEMARKS.TXT
      <...пропуск...>
      в начале или в конце списка,

    • в списках содержимого каталогов (FtpDir()) могут присутствовать записи текущего и родительского каталогов:
         dr--r--r--  1 ftp      -----      0 Jan  2  1980 .
      dr--r--r-- 1 ftp ----- 0 Jan 2 1980 ..
      dr--r--r-- 1 ftp ----- 0 Dec 19 2007 fido
      <...пропуск...>
      в начале списка,

    • в имени текущего каталога (FtpPwd) может присутствовать закрывающий символ '/':
         "/pub/" is your current location
  2. OS/2 operating system

    Такую строку выдаёт стандартный модуль ftpd из поставки OS/2.

    1. Функция FtpDir() будет возвращать списки содержимого каталогов в неповторимой полуосёвой манере:

      Пример 4:
         X:>rxFtpDir 127.0.0.1

      351 A 04-11-10 11:10 error.log
      0 A DIR 04-12-10 08:59 Misc Tools
      0 DIR 04-10-10 08:33 Public
      50 04-10-10 08:34 ReadMe.txt
      10 A 04-10-10 13:57 Test.txt
      Конечно, строго говоря, здесь каждая запись состоит из 6 полей, разделённых пробелами.

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

      Однако, поля атрибутов и индикатора каталога могут быть пустыми (заполнены пробелами), что несколько усложняет разбор.

      Если каталог пуст, то выдаётся единственная запись:
         total 0
    2. Домашний каталог будет содержать полный путь, включая букву диска:

      Пример 5:
         X:>rxFtpPwd 127.0.0.1

      "E:/data/ftp" is current directory.
      что надо учитывать при переходах от корня. В программах, используемых здесь в качестве примера, как раз применяется механизм перехода от корня:

      Пример 6:
         X:>rxFtpChDir 127.0.0.1/Public

      FtpChDir
      FTPERRNO=FTPCOMMAND

      X:>rxFtpChDir 127.0.0.1/data/ftp/Public

      "E:/data/ftp/public" is current directory.
    3. Функция FtpPwd() возвращает имя текущего каталога исключительно строчными буквами:

      Пример 7:
         X:>rxFtpDir 127.0.0.1

      351 A 04-11-10 11:10 error.log
      0 A DIR 04-12-10 08:59 Misc Tools
      0 DIR 04-10-10 08:33 Public
      50 04-10-10 08:34 ReadMe.txt
      10 A 04-10-10 13:57 Test.txt

      X:>rxFtpChDir 127.0.0.1/data/ftp/Public

      "E:/data/ftp/public" is current directory.
  3. Windows_NT

    Так идентифицируются, по всей видимости, Microsoft IIS с версией меньше 5.

    1. Функция FtpDir() будет возвращать списки содержимого каталогов в стиле Windows:

      Пример 8:
         X:>rxFtpDir ftp.microsoft.com/MISC

      08-05-09 01:13PM <DIR> beckyk
      04-08-94 06:13PM 15749 CBCP.TXT
      08-05-09 01:13PM <DIR> csformat
      08-05-09 01:13PM <DIR> DAILYKB
      04-12-93 05:30PM 710 DISCLAIM.TXT
      <...пропуск...>
      Конечно, строго говоря, здесь каждая запись состоит из 5 полей, разделённых пробелами.

      В третьем поле отображается индикатор каталога.
      В четвёртом - длина файла.
      В пятом - имя, которое само может содержать пробелы.

      Однако, для записей каталогов заполняется пробелами поле длины, а для записей файлов пробелами заполняется поле индикатора каталога, что и облегчает разбор, сводя всё к четырём полям.

    2. Остальные замечания аналогичны UNIX ... серверам.

  4. Windows_NT version 5.0

    Так идентифицируются, по всей видимости, Microsoft IIS с версией 5 и выше.

    Все замечания аналогичны UNIX ... серверам.

  5. MACOS Peter's Server

    Понятно, что это некий сервер под MACOS. Дальше мысль останавливается :)

    1. Функция FtpDir() будет возвращать списки содержимого каталогов в формате, похожем на формат UNIX:

      Пример 9:
         X:>rxFtpDir ftp.mt.xplain.com

      -rwxr-xr-x 0 214 214 Jun 30 2005 !readme
      drwxr-xr-x folder 0 Jul 17 2006 adsales
      drwxr-xr-x folder 0 Jul 17 2006 challenge
      <...пропуск...>
      drwxr-xr-x folder 0 Jul 17 2006 thinkref
      drwxr-xr-x folder 0 Jul 17 2006 util
      -rwxr-xr-x 332 7 339 Feb 5 2040 welcome.txt
      Конечно, строго говоря, здесь каждая запись состоит из 8 полей, разделённых пробелами.

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

      Однако, для записей каталогов второе поле заполняется пробелами. Поэтому при разборе можно считать, что записи каталогов содержат 7 полей, а записи файлов - 8.

    2. Остальные замечания аналогичны UNIX ... серверам.

6.4. Отличия FTPFAILURE и FTPCOMMAND.

В оригинальной документации значение FTPFAILURE отсутствует в списке возможных значений, которые может получать специальная переменная FTPERRNO.

Состояние FTPFAILURE похоже на состояние FTPCOMMAND, но отличается механизмом возникновения и результатом.

Пример 1:
   X:>rxFtpQuote 127.0.0.1 XXX

FtpQuote() FTPERRNO=FTPFAILURE
В этом примере серверу, работающему на хосте 127.0.0.1, передается команда
   XXX
Поскольку эта команда не реализована в данном сервере FTP, то в ответ он посылает сообщение:
   502 Unknown command.
В результате, обработчик возврата функции REXX FTP API формирует состояние FTPFAILURE в специальной переменной FTPERRNO. Соединение с сервером не разрывается.

Пример 2:
   X:>rxFtpQuote 127.0.0.1 TYPE

FtpQuote() FTPERRNO=FTPCOMMAND
В этом примере серверу, работающему на хосте 127.0.0.1, передается команда
   TYPE
Эта команда реализована в сервере FTP, но в данном случае записана не полностью (отсутствует обязательный параметр). Сервер, начав выполнение команды, ожидает получения недостающего параметра и, не дождавшись, прекращает выполнение команды. После чего посылает сообщение:
   221 You could at least say goodbye.
и разрывает соединение.

В результате, обработчик возврата функции REXX FTP API формирует состояние FTPCOMMAND в специальной переменной FTPERRNO. Соединение с сервером разорвано по инициативе сервера.

6.5. Проблемы и недостатки.

  1. Неполная поставка

    В некоторых случаях при возникновении ошибок библиотека может самостоятельно выводить на устройство stderr (обычно, консоль) диагностические сообщения. Например, возникновение ошибки FTPABORT может сопровождаться сообщением:
       abort: The file or directory specified cannot be found.
    Файл сообщений об ошибках DDE4.MSG должен находиться в одном из каталогов, перечисленных в переменной окружения DPATH, задаваемой в файле CONFIG.SYS.

    Одним из недостатков является то, что вывод этих сообщений невозможно подавить, а другим - то, что файл DDE4.MSG отсутствует в поставке OS/2. Надо добавить, что в версиях eComStation, использующих LANGE, он присутствует.

  2. Неполная реализация

    К сожалению, в REXX FTP API реализованы далеко не все функции OS/2 FTP API, чей надстройкой он, в сущности, и является.

    Кроме того, и в самом OS/2 FTP API отсутствуют некоторые полезные функции, реализующие, например, докачку файлов с сервера FTP.

  3. Искажение ответа сервера FTP в FtpQuote()

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

    Более существенный недостаток состоит в том, что ответ сервера FTP искажается:

    • обрезается до 197 символов,
    • в каждой строке откидываются несколько символов от начала строки и опускаются символы <CR><LF> в конце строки, что особенно сказывается на многострочных ответах.
  4. Наивная реализация пассивного режима

    Серверы FTP могут быть защищены с помощью firewall/NAT, когда они располагаются в локальной сети (чаще говорят про "демилитаризованную зону", но она тоже представляет из себя локальную сеть, только отделённую от основной как логически, так и физически).

    Соответственно, ответ сервера на команду PASV будет корректироваться "умным" firewall/NAT, защищающим сервер, с целью замены в нём локального адреса сервера на внешний адрес firewall/NAT. В зависимости от фантазии строителя сети, в которой находится сервер FTP, ответ сервера может пройти через несколько "умных" firewall/NAT пока доберётся до клиента. И каждый такой "умный" firewall/NAT возможно будет корректировать данные ответа.

    В результате до клиента может добраться ответ, в котором указан адрес, несоответствующий адресу сервера FTP.

    И, если ушлые браузеры, использующие пассивный режим (например, Firefox), игнорируют этот адрес и строят канал передачи данных с тем же сервером, с которым установлено управляющее соединение, то функции REXX FTP API честно пытаются соединиться с хостом, чей адрес указан в ответе.

  5. Проблемы FtpProxy()

    • Документирование

      В оригинальной документации отмечено, что параметр, определяющий режим передачи файлов, не обязателен. Однако, если его опустить или задать неправильное значение, то FtpProxy() просто вернёт код возврата равный 0, не выполняя никаких действий, что затрудняет диагностику ошибок.

    • Функционирование

      Порождаются самой схемой работы функции:

      • потоки управляющих команд организуются между хостом, на котором выполняется программа, использующая FtpProxy(), и серверами,
      • потоки данных организуются между серверами напрямую.

      Поэтому работоспособность функции сильно зависит от наличия различных firewall/NAT не только между хостом и серверами, которыми он управляет, но и между самими серверами FTP.

      Кроме того, серверы FTP могут проверять адрес, передаваемый в команде PORT, на соответствие адресу хоста, с которого осуществляется управление, и запрещать работу при несовпадении.
  6. Невозврат из функций REXX FTP API

    Иногда наблюдается невозврат из функций REXX FTP API, приводящий к зависанию программ. Проявление этого эффекта было отмечено в некоторых программах, которые в течение одного сеанса многократно обращаются к серверу, например, в программе построения дерева каталогов сервера FTP.

    Оказывается, функции REXX FTP API во время выполнения цепочек команд обильно уснащают процесс общения с сервером FTP командами NOOP. И, если передача значащих команд и получение ответов на них контролируется жёстко (выполняется повторная отправка команд каждые 64 секунды, через определённое количество неудачных попыток соединение разрывается и формируется соответствующая ошибка), то обработчик ошибок самовольно вставленных команд NOOP не так строг (повторная отправка команды не выполняется, соединение остаётся в состоянии ESTABLISHED до тех пор, пока не будет закрыто каким-либо способом).

    Например, если функция FtpChDir() присутствует в цепочке других команд, то дополнительные команды NOOP могут быть выданы как перед командой CWD, так и после неё.

    В этом случае неполучение ответа на команду CWD приведёт к возникновению соответствующей ошибки, а неполучение ответа на команду NOOP, самовольно добавленную REXX FTP API, приводит к невыходу из функции FtpChDir() (по крайней мере, до закрытия соединения).

    Надо сказать, что эта ситуация возникает далеко не со всеми серверами FTP и, видимо, сильно зависит от настроек конкретного экземпляра сервера.

  7. Несоответствие оригинальной документации реальному исполнению

    Оригинальная документация достаточно скупа. Кроме того, в ряде случаев она содержит утверждения прямо противоположные действительности.

    Например, утверждается, что по умолчанию функции REXX FTP API используют пассивный режим FTP, хотя на самом деле по умолчанию используется активный режим.