Java FTPS не может получить список файлов (клиент FileZilla работает нормально)

Я использую Apache Commons Net (v3.5) с Java 8 для подключения к удаленному сайту FTPS (т.е. в Интернете). Я могу легко подключиться к клиенту FileZilla на своем компьютере с Windows 10, но моя программа Java не может выполнить те же действия. Я googled вверх и вниз, но не могу найти основную причину. Вот вещи, которые я подтвердил:

  • Я убедился, что команды Java FTP находятся в том же порядке, что и клиент FileZilla.
  • Я отключил брандмауэр Windows и антивирус на ПК
  • Я повторно включил брандмауэр Windows и включил ведение журнала. При использовании FileZilla в журнале брандмауэра Windows указывается TCP-соединение, когда установлено соединение в пассивном режиме. Я не вижу такой записи с программой Java.
  • Я установил сервер FileZilla на свой компьютер. Программа Java заработала после того, как я снял флажок «Требовать возобновления сеанса TLS при подключении данных при использовании PROT P». Исключение Java было другим, поэтому я не верю, что это дымящийся пистолет.
  • Я успешно запустил этот же код на сервере test.rebex.com.

Ниже приведен код, и любые мысли приветствуются:

import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;

public class testProgram {

  public static void main(String[] args) {

    String ftpServer = "ftp.domain.com";
    String ftpUsername = "[email protected]";
    String ftpPassword = "********";

    FTPSClient ftp = null;

    // CONNECT TO THE SERVER
    try {
        // I have tried "SSL" as the argument, but same result
        ftp = new FTPSClient(); 
        ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));

        ftp.connect(ftpServer,21);

        int reply = ftp.getReplyCode();

        if (!FTPReply.isPositiveCompletion(reply)) {
            ftp.disconnect();
            System.err.println("---------->FTP server refused connection.\n");

        } 

    } catch (Exception e) {
        System.out.println(e.getMessage());
        e.printStackTrace();

    }

    // LOGIN INTO SERVER
    try {
        if (!ftp.login(ftpUsername, ftpPassword)) {
            ftp.logout();

        } else {

            ftp.sendCommand("OPTS UTF8 ON");            
            ftp.execPBSZ(0);            
            ftp.execPROT("P");
            ftp.pwd();
            ftp.setFileType(FTP.BINARY_FILE_TYPE);      
            ftp.enterLocalPassiveMode();

            /* The next command always fails.

               The FTP Server responds with "150 Accepted data connection" then:

                org.apache.commons.net.ftp.FTPConnectionClosedException: Connection closed without indication.
                at org.apache.commons.net.ftp.FTP.__getReply(FTP.java:316)
                at org.apache.commons.net.ftp.FTP.__getReply(FTP.java:292)
                at org.apache.commons.net.ftp.FTP.getReply(FTP.java:712)
                at org.apache.commons.net.ftp.FTPClient.completePendingCommand(FTPClient.java:1857)
                at org.apache.commons.net.ftp.FTPClient.listNames(FTPClient.java:2919)
                at org.apache.commons.net.ftp.FTPClient.listNames(FTPClient.java:2952)
                at myPackage.testProgram.main(testProgram.java:78)

                I have tried other commands, but it disconnects here...
             */

            FTPFile[] ftpFiles = ftp.listFiles();
            System.out.println("---------->Number of Files = " + ftpFiles.length);
            ftp.logout();

        }
    } catch (Exception e) {

        e.printStackTrace();
    } 

    //Ensure Disconnected at the end.
    if (ftp.isConnected()) {
        try {
            ftp.disconnect();
        } catch (IOException f) {
            // do nothing
        }

    }
  }
}

Вот журнал клиента FileZilla с моего ПК:

2016-09-06 09:09:50 4756 1 Status: Resolving address of ftp.domain.com
2016-09-06 09:09:51 4756 1 Status: Connecting to h1.h2.h3.h4:21...
2016-09-06 09:09:51 4756 1 Status: Connection established, waiting for welcome message...
2016-09-06 09:09:51 4756 1 Response: 220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
2016-09-06 09:09:51 4756 1 Response: 220-You are user number 2 of 50 allowed.
2016-09-06 09:09:51 4756 1 Response: 220-Local time is now 13:09. Server port: 21.
2016-09-06 09:09:51 4756 1 Response: 220-This is a private system - No anonymous login
2016-09-06 09:09:51 4756 1 Response: 220-IPv6 connections are also welcome on this server.
2016-09-06 09:09:51 4756 1 Response: 220 You will be disconnected after 15 minutes of inactivity.
2016-09-06 09:09:51 4756 1 Command: AUTH TLS
2016-09-06 09:09:51 4756 1 Response: 234 AUTH TLS OK.
2016-09-06 09:09:51 4756 1 Status: Initializing TLS...
2016-09-06 09:09:51 4756 1 Status: Verifying certificate...
2016-09-06 09:09:51 4756 1 Status: TLS connection established.
2016-09-06 09:09:51 4756 1 Command: USER [email protected]
2016-09-06 09:09:51 4756 1 Response: 331 User [email protected] OK. Password required
2016-09-06 09:09:51 4756 1 Command: PASS *************
2016-09-06 09:09:51 4756 1 Response: 230 OK. Current restricted directory is /
2016-09-06 09:09:51 4756 1 Command: SYST
2016-09-06 09:09:51 4756 1 Response: 215 UNIX Type: L8
2016-09-06 09:09:51 4756 1 Command: FEAT
2016-09-06 09:09:51 4756 1 Response: 211-Extensions supported:
2016-09-06 09:09:51 4756 1 Response:  EPRT
2016-09-06 09:09:51 4756 1 Response:  IDLE
2016-09-06 09:09:51 4756 1 Response:  MDTM
2016-09-06 09:09:51 4756 1 Response:  SIZE
2016-09-06 09:09:51 4756 1 Response:  MFMT
2016-09-06 09:09:51 4756 1 Response:  REST STREAM
2016-09-06 09:09:51 4756 1 Response:  MLST type*;size*;sizd*;modify*;UNIX.mode*;UNIX.uid*;UNIX.gid*;unique*;
2016-09-06 09:09:51 4756 1 Response:  MLSD
2016-09-06 09:09:51 4756 1 Response:  AUTH TLS
2016-09-06 09:09:51 4756 1 Response:  PBSZ
2016-09-06 09:09:51 4756 1 Response:  PROT
2016-09-06 09:09:51 4756 1 Response:  UTF8
2016-09-06 09:09:51 4756 1 Response:  TVFS
2016-09-06 09:09:51 4756 1 Response:  ESTA
2016-09-06 09:09:51 4756 1 Response:  PASV
2016-09-06 09:09:51 4756 1 Response:  EPSV
2016-09-06 09:09:51 4756 1 Response:  SPSV
2016-09-06 09:09:51 4756 1 Response:  ESTP
2016-09-06 09:09:51 4756 1 Response: 211 End.
2016-09-06 09:09:51 4756 1 Command: OPTS UTF8 ON
2016-09-06 09:09:51 4756 1 Response: 200 OK, UTF-8 enabled
2016-09-06 09:09:51 4756 1 Command: PBSZ 0
2016-09-06 09:09:51 4756 1 Response: 200 PBSZ=0
2016-09-06 09:09:51 4756 1 Command: PROT P
2016-09-06 09:09:52 4756 1 Response: 200 Data protection level set to "private"
2016-09-06 09:09:52 4756 1 Status: Logged in
2016-09-06 09:09:52 4756 1 Status: Retrieving directory listing...
2016-09-06 09:09:52 4756 1 Command: PWD
2016-09-06 09:09:52 4756 1 Response: 257 "/" is your current location
2016-09-06 09:09:52 4756 1 Command: TYPE I
2016-09-06 09:09:52 4756 1 Response: 200 TYPE is now 8-bit binary
2016-09-06 09:09:52 4756 1 Command: PASV
2016-09-06 09:09:52 4756 1 Response: 227 Entering Passive Mode (h1,h2,h3,h4,133,150)
2016-09-06 09:09:52 4756 1 Command: MLSD
2016-09-06 09:09:52 4756 1 Response: 150 Accepted data connection
2016-09-06 09:09:52 4756 1 Response: 226-Options: -a -l 
2016-09-06 09:09:52 4756 1 Response: 226 6 matches total

По совету Майка я включил отладку TLS. Похоже, программа снова проходит рукопожатие TLS. Вывод очень длинный, но после выполнения команды list я вижу «*** ClientHello, TLSv1.2» и что-то похожее на те же команды, что и при инициировании FTP-соединения.

Разница появляется в конце:

%% Cached client session: [Session-2, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
main, received EOFException: ignored
main, called closeInternal(false)
main, SEND TLSv1.2 ALERT:  warning, description = close_notify
main, WRITE: TLSv1.2 Alert, length = 26
main, called closeSocket(false)
main, called close()
main, called closeInternal(true)
main, called close()
main, called closeInternal(true)
main, received EOFException: ignored
main, called closeInternal(false)
main, SEND TLSv1.2 ALERT:  warning, description = close_notify
main, WRITE: TLSv1.2 Alert, length = 26
main, called closeSocket(false)
org.apache.commons.net.ftp.FTPConnectionClosedException: Connection closed without indication.

person SpartanXL01    schedule 05.09.2016    source источник
comment
Во-первых, ваш блок try слишком длинный. Разбейте свой код на разные блоки try/catch и, пожалуйста, не перехватывайте Exception, это смертельная ловушка. Кроме того, никогда не .printStackTrace(); что это за польза? Закон об исключении   -  person fge    schedule 06.09.2016
comment
@fge Определите «слишком долго». «Разбейте свой код на разные блоки try/catch», почему? Этот совет — просто карго-культовое программирование. Блок try/catch должен быть достаточно длинным, чтобы содержать весь зависящий от него код.   -  person user207421    schedule 06.09.2016
comment
Я готов признать, что код примера должен быть намного чище, но я также знаю, что блок try-catch не имеет ничего общего с проблемой. Я четко показал, где именно возбуждается исключение.   -  person SpartanXL01    schedule 06.09.2016
comment
@EJP просто: catch (Exception e). Это вовсе не карго-культовое программирование. Дело в том, что в этом блоке try одни инструкции могут выдать исключение, а другие нет. Следовательно: разделите и определите проблему.   -  person fge    schedule 06.09.2016
comment
1) Работает ли он с простым незашифрованным FTP? 2) Покажите нам файл журнала FileZilla (настоящий файл журнала, а не журнал сообщений из графического интерфейса).   -  person Martin Prikryl    schedule 06.09.2016
comment
@fge Следовательно, отловите больше исключений и определите проблему. Половину вопросов здесь можно было бы решить, если бы ОП не написал цепочку try/catches. А «слишком долго» остается полностью субъективным и не определяемым вами.   -  person user207421    schedule 06.09.2016
comment
Я добавил журнал клиента FileZilla при подключении к удаленному FTP-серверу. Программа Java работает при подключении к серверу FileZilla, расположенному на том же компьютере, но не работает при использовании удаленного FTP-сервера в Интернете.   -  person SpartanXL01    schedule 06.09.2016
comment
Попробуйте что-нибудь другое: for (String s : ftp.listNames()) { System.out.println(s);   -  person Mike    schedule 06.09.2016
comment
Пробовал listNames(), но получил тот же ответ.... FTP-сервер отвечает сообщением 150 Accepted Data Connection, а затем org.apache.commons.net.ftp.FTPConnectionClosedException: соединение закрыто без индикации.   -  person SpartanXL01    schedule 06.09.2016
comment
Включите ведение журнала согласования SSL java -Djavax.net.debug=ssl ... Похоже, он больше не проходит через TLS на порту данных. Порт 21 — это явный порт SSL, который должен быть в порядке с конструктором FTPClient по умолчанию. Еще одна вещь, которую я заметил, это то, что я обычно устанавливаю размер буфера и защиту перед входом в систему, вы также можете попробовать это.   -  person Mike    schedule 06.09.2016
comment
Обновлен вопрос относительно ведения журнала согласования SSL. Я добавил ftp.setBufferSize(0) и переместил ftp.execPROT(P) перед входом в систему, но результаты не изменились. Я понял, что нулевой размер буфера означает бесконечность, поэтому я надеюсь, что это было правильно.   -  person SpartanXL01    schedule 06.09.2016
comment
Глупый вопрос, у вас есть хранилище ключей/доверенных сертификатов с установленным сертификатом или вы полагаетесь на Windows?   -  person Mike    schedule 06.09.2016
comment
Не глупый вопрос, потому что я никогда не рассматривал его. У меня ничего не установлено, поэтому, предположительно, я полагаюсь на Windows. Погуглил сейчас, чтобы понять это лучше....   -  person SpartanXL01    schedule 06.09.2016
comment
Я успешно запустил точно такой же код на test.rebex.net. Я до сих пор понятия не имею, почему FileZilla может подключаться, а моя программа на Java — нет.   -  person SpartanXL01    schedule 07.09.2016


Ответы (2)


Хотя это выглядит как старый пост, сегодня я столкнулся с аналогичной проблемой и не смог найти (изначально) решение. Я мог подключиться через FileZilla, но не через FTPSClient, и после запуска ftpClient.enterLocalPassiveMode() я получал 425 cannot open data connection

Мое решение состояло в том, чтобы изменить ftpClient.enterLocalPassiveMode() перед входом в систему, но после подключения к FTP-серверу, и это сработало. Обычно все примеры кода, которые я видел, используют enterlocalpassivemode перед отправкой/получением данных, но после входа в систему. См. ниже код для примера, который сработал для меня.

FTPSClient ftpClient = new FTPSClient(false);
ftpClient.connect("remote.ftp.server", port);
ftpClient.enterLocalPassiveMode();// Run the passive mode command now  instead of after loggin in.
ftpClient.login("username", "password");
ftpClient.execPBSZ(0);
ftpClient.execPROT("P");
ftpClient.type(FTP.BINARY_FILE_TYPE);
//ftpClient.enterLocalPassiveMode(); Previously it was here.
FTPFile[] files = ftpClient.listDirectories("/");

Надеюсь это поможет. Также обратите внимание, что весь остальной код и передовой опыт опущены, чтобы сделать ответ кратким.

person Trying-to-learn    schedule 29.01.2017

Этот пример работает с безопасностью TLS.
Сервер — VSFTPD в Centos

----------VSFTPD.conf Добавление TLS ---------
....

rsa_cert_file=путь к файлу .pem/.p12
rsa_private_key_file=путь к файлу .pem/.p12
ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=YES
force_local_logins_ssl =ДА
ssl_enable=ДА
allow_anon_ssl=НЕТ
ssl_tlsv1=ДА
ssl_sslv2=ДА
ssl_sslv3=ДА
require_ssl_reuse=НЕТ

--------------------------------------------
Апач 3.5 код Commons Net Java ниже
----------------------------------------

public static final void main(String[] args) throws Exception {
    // System.setProperty("javax.net.debug", "ssl");
    ftps_ = createFtpClient();
    ftpsClient_.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
    ftps_.connect("<FTPS SERVER Address>");
    boolean login = ftps_.login("user", "password");
    ftps_.type(FTP.ASCII_FILE_TYPE);
    ftps_.execPROT("P");
    System.out.println("Login status -- " + login);
    System.out.println("----------Listing files---------");
    String dirName = "<dirName>";
    listFiles(dirName);
    ftps_.disconnect();
}

/**
 * Create the FTPS client.
 */
private static FTPSClient createFtpClient() throws Exception {
    String type = "PKCS12";
    String file = "<path to .p12 cert file>";
    String password = "ftpserver";

    KeyStore keyStore = KeyStore.getInstance(type);
    FileInputStream keyStoreFileInputStream = new FileInputStream(new File(file));
    try {
        keyStore.load(keyStoreFileInputStream, password.toCharArray());
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }
    KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyMgrFactory.init(keyStore, password.toCharArray());
    KeyStore trustStore = KeyStore.getInstance(type);
    FileInputStream trustStoreFileInputStream = new FileInputStream(new File(file));
    try {
        trustStore.load(trustStoreFileInputStream, password.toCharArray());
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }
    TrustManagerFactory trustMgrFactory = TrustManagerFactory
            .getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustMgrFactory.init(trustStore);
    SSLContext sslContext = SSLContext.getInstance("TLSv1");
    sslContext.init(keyMgrFactory.getKeyManagers(), trustMgrFactory.getTrustManagers(), new SecureRandom());
    FTPSClient client = new FTPSClient(sslContext);
    return client;
}

private static void listFiles(String dirName) throws IOException {
    try {
        FTPFile[] list = ftps_.listFiles(dirName);
        for (int i = 0; i < list.length; i++) {
            System.out.println(list[i].getName());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
} 
person Jimmy USU    schedule 14.05.2018