FTPSClient.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.net.ftp;
- import java.io.BufferedReader;
- import java.io.BufferedWriter;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.OutputStreamWriter;
- import java.net.Inet6Address;
- import java.net.InetAddress;
- import java.net.InetSocketAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.Base64;
- import javax.net.ssl.HostnameVerifier;
- import javax.net.ssl.KeyManager;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.SSLException;
- import javax.net.ssl.SSLHandshakeException;
- import javax.net.ssl.SSLSocket;
- import javax.net.ssl.SSLSocketFactory;
- import javax.net.ssl.TrustManager;
- import org.apache.commons.net.util.SSLContextUtils;
- import org.apache.commons.net.util.SSLSocketUtils;
- import org.apache.commons.net.util.TrustManagerUtils;
- /**
- * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to see wire-level SSL details.
- *
- * Warning: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or
- * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification. Verification is only performed on client mode connections.
- *
- * @since 2.0
- */
- public class FTPSClient extends FTPClient {
- // From http://www.iana.org/assignments/port-numbers
- // ftps-data 989/tcp ftp protocol, data, over TLS/SSL
- // ftps-data 989/udp ftp protocol, data, over TLS/SSL
- // ftps 990/tcp ftp protocol, control, over TLS/SSL
- // ftps 990/udp ftp protocol, control, over TLS/SSL
- /** Default FTPS data port. */
- public static final int DEFAULT_FTPS_DATA_PORT = 989;
- /** Default FTPS port. */
- public static final int DEFAULT_FTPS_PORT = 990;
- /** The value that I can set in PROT command (C = Clear, P = Protected) */
- private static final String[] PROT_COMMAND_VALUE = { "C", "E", "S", "P" };
- /** Default PROT Command */
- private static final String DEFAULT_PROT = "C";
- /** Default secure socket protocol name, i.e. TLS */
- private static final String DEFAULT_PROTOCOL = "TLS";
- /** The AUTH (Authentication/Security Mechanism) command. */
- private static final String CMD_AUTH = "AUTH";
- /** The ADAT (Authentication/Security Data) command. */
- private static final String CMD_ADAT = "ADAT";
- /** The PROT (Data Channel Protection Level) command. */
- private static final String CMD_PROT = "PROT";
- /** The PBSZ (Protection Buffer Size) command. */
- private static final String CMD_PBSZ = "PBSZ";
- /** The MIC (Integrity Protected Command) command. */
- private static final String CMD_MIC = "MIC";
- /** The CONF (Confidentiality Protected Command) command. */
- private static final String CMD_CONF = "CONF";
- /** The ENC (Privacy Protected Command) command. */
- private static final String CMD_ENC = "ENC";
- /** The CCC (Clear Command Channel) command. */
- private static final String CMD_CCC = "CCC";
- /** @deprecated - not used - may be removed in a future release */
- @Deprecated
- public static String KEYSTORE_ALGORITHM;
- /** @deprecated - not used - may be removed in a future release */
- @Deprecated
- public static String TRUSTSTORE_ALGORITHM;
- /** @deprecated - not used - may be removed in a future release */
- @Deprecated
- public static String PROVIDER;
- /** @deprecated - not used - may be removed in a future release */
- @Deprecated
- public static String STORE_TYPE;
- /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
- private final boolean isImplicit;
- /** The secure socket protocol to be used, e.g. SSL/TLS. */
- private final String protocol;
- /** The AUTH Command value */
- private String auth = DEFAULT_PROTOCOL;
- /** The context object. */
- private SSLContext context;
- /** The socket object. */
- private Socket plainSocket;
- /** Controls whether a new SSL session may be established by this socket. Default true. */
- private boolean isCreation = true;
- /** The use client mode flag. */
- private boolean isClientMode = true;
- /** The need client auth flag. */
- private boolean isNeedClientAuth;
- /** The want client auth flag. */
- private boolean isWantClientAuth;
- /** The cipher suites */
- private String[] suites;
- /** The protocol versions */
- private String[] protocols;
- /**
- * The FTPS {@link TrustManager} implementation, default validate only {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}.
- */
- private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager();
- /** The {@link KeyManager}, default null (i.e. use system default). */
- private KeyManager keyManager;
- /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
- private HostnameVerifier hostnameVerifier;
- /** Use Java 1.7+ HTTPS Endpoint Identification Algorithm. */
- private boolean tlsEndpointChecking;
- /**
- * Constructor for FTPSClient, calls {@link #FTPSClient(String, boolean)}.
- *
- * Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false)
- */
- public FTPSClient() {
- this(DEFAULT_PROTOCOL, false);
- }
- /**
- * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS Calls {@link #FTPSClient(String, boolean)}
- *
- * @param isImplicit The security mode (Implicit/Explicit).
- */
- public FTPSClient(final boolean isImplicit) {
- this(DEFAULT_PROTOCOL, isImplicit);
- }
- /**
- * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS The default TrustManager is set from
- * {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}
- *
- * @param isImplicit The security mode(Implicit/Explicit).
- * @param context A pre-configured SSL Context
- */
- public FTPSClient(final boolean isImplicit, final SSLContext context) {
- this(DEFAULT_PROTOCOL, isImplicit);
- this.context = context;
- }
- /**
- * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS and isImplicit {@code false} Calls {@link #FTPSClient(boolean, SSLContext)}
- *
- * @param context A pre-configured SSL Context
- */
- public FTPSClient(final SSLContext context) {
- this(false, context);
- }
- /**
- * Constructor for FTPSClient, using explicit mode, calls {@link #FTPSClient(String, boolean)}.
- *
- * @param protocol the protocol to use
- */
- public FTPSClient(final String protocol) {
- this(protocol, false);
- }
- /**
- * Constructor for FTPSClient allowing specification of protocol and security mode. If isImplicit is true, the port is set to {@link #DEFAULT_FTPS_PORT}
- * i.e. 990. The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}
- *
- * @param protocol the protocol
- * @param isImplicit The security mode(Implicit/Explicit).
- */
- public FTPSClient(final String protocol, final boolean isImplicit) {
- this.protocol = protocol;
- this.isImplicit = isImplicit;
- if (isImplicit) {
- setDefaultPort(DEFAULT_FTPS_PORT);
- }
- }
- /**
- * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing
- * a connection, rather than reimplementing all the connect() methods.
- *
- * @throws IOException If there is any problem with establishing the connection.
- * @see org.apache.commons.net.SocketClient#_connectAction_()
- */
- @Override
- protected void _connectAction_() throws IOException {
- // Implicit mode.
- if (isImplicit) {
- applySocketAttributes();
- sslNegotiation();
- }
- super._connectAction_();
- // Explicit mode.
- if (!isImplicit) {
- execAUTH();
- sslNegotiation();
- }
- }
- /**
- * Returns a socket of the data connection. Wrapped as an {@link SSLSocket}, which carries out handshake processing.
- *
- * @param command The int representation of the FTP command to send.
- * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no arguments.
- * @return corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the establishment and
- * initialization of the connection.
- * @throws IOException If there is any problem with the connection.
- * @see FTPClient#_openDataConnection_(int, String)
- * @deprecated (3.3) Use {@link FTPClient#_openDataConnection_(FTPCmd, String)} instead
- */
- @Override
- // Strictly speaking this is not needed, but it works round a Clirr bug
- // So rather than invoke the parent code, we do it here
- @Deprecated
- protected Socket _openDataConnection_(final int command, final String arg) throws IOException {
- return _openDataConnection_(FTPCommand.getCommand(command), arg);
- }
- /**
- * Returns a socket of the data connection. Wrapped as an {@link SSLSocket}, which carries out handshake processing.
- *
- * @param command The textual representation of the FTP command to send.
- * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no arguments.
- * @return corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the establishment and
- * initialization of the connection.
- * @throws IOException If there is any problem with the connection.
- * @see FTPClient#_openDataConnection_(int, String)
- * @since 3.2
- */
- @Override
- protected Socket _openDataConnection_(final String command, final String arg) throws IOException {
- final Socket socket = openDataSecureConnection(command, arg);
- _prepareDataSocket_(socket);
- if (socket instanceof SSLSocket) {
- final SSLSocket sslSocket = (SSLSocket) socket;
- sslSocket.setUseClientMode(isClientMode);
- sslSocket.setEnableSessionCreation(isCreation);
- // server mode
- if (!isClientMode) {
- sslSocket.setNeedClientAuth(isNeedClientAuth);
- sslSocket.setWantClientAuth(isWantClientAuth);
- }
- if (suites != null) {
- sslSocket.setEnabledCipherSuites(suites);
- }
- if (protocols != null) {
- sslSocket.setEnabledProtocols(protocols);
- }
- sslSocket.startHandshake();
- }
- return socket;
- }
- /**
- * Performs any custom initialization for a newly created SSLSocket (before the SSL handshake happens). Called by {@link #_openDataConnection_(int, String)}
- * immediately after creating the socket. The default implementation is a no-op
- *
- * @param socket the socket to set up
- * @throws IOException on error
- * @since 3.1
- */
- protected void _prepareDataSocket_(final Socket socket) throws IOException {
- }
- /**
- * Check the value that can be set in PROT Command value.
- *
- * @param prot Data Channel Protection Level.
- * @return True - A set point is right / False - A set point is not right
- */
- private boolean checkPROTValue(final String prot) {
- for (final String element : PROT_COMMAND_VALUE) {
- if (element.equals(prot)) {
- return true;
- }
- }
- return false;
- }
- /**
- * Close open sockets.
- *
- * @param socket main socket for proxy if enabled
- * @param sslSocket ssl socket
- * @throws IOException closing sockets is not successful
- */
- private void closeSockets(final Socket socket, final Socket sslSocket) throws IOException {
- if (socket != null) {
- socket.close();
- }
- if (sslSocket != null) {
- sslSocket.close();
- }
- }
- /**
- * Create SSL socket from plain socket.
- *
- * @param socket
- * @return SSL Socket
- * @throws IOException
- */
- private SSLSocket createSSLSocket(final Socket socket) throws IOException {
- if (socket != null) {
- final SSLSocketFactory f = context.getSocketFactory();
- return (SSLSocket) f.createSocket(socket, _hostname_, socket.getPort(), false);
- }
- return null;
- }
- /**
- * Closes the connection to the FTP server and restores connection parameters to the default values.
- * <p>
- * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)} to reset the factories that may have been changed during the session, e.g.
- * by {@link #execPROT(String)}
- *
- * @throws IOException If an error occurs while disconnecting.
- * @since 3.0
- */
- @Override
- public void disconnect() throws IOException {
- super.disconnect();
- if (plainSocket != null) {
- plainSocket.close();
- }
- setSocketFactory(null);
- setServerSocketFactory(null);
- }
- /**
- * Sends the ADAT command with the specified authentication data.
- *
- * @param data The data to send with the command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execADAT(final byte[] data) throws IOException {
- if (data != null) {
- return sendCommand(CMD_ADAT, Base64.getEncoder().encodeToString(data));
- }
- return sendCommand(CMD_ADAT);
- }
- /**
- * Sends the AUTH command.
- *
- * @throws SSLException If the server reply code equals neither "234" nor "334".
- * @throws IOException If an I/O error occurs while either sending the command.
- */
- protected void execAUTH() throws SSLException, IOException {
- final int replyCode = sendCommand(CMD_AUTH, auth);
- if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
- // replyCode = 334
- // I carry out an ADAT command.
- } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
- throw new SSLException(getReplyString());
- }
- }
- /**
- * Sends the AUTH command with the specified mechanism.
- *
- * @param mechanism The mechanism name to send with the command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execAUTH(final String mechanism) throws IOException {
- return sendCommand(CMD_AUTH, mechanism);
- }
- /**
- * Sends the CCC command to the server. The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned to a plain
- * {@link Socket} instances
- *
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execCCC() throws IOException {
- // This will be performed by sendCommand(String, String)
- // if (FTPReply.isPositiveCompletion(repCode)) {
- // _socket_.close();
- // _socket_ = plainSocket;
- // _controlInput_ = new BufferedReader(
- // new InputStreamReader(
- // _socket_.getInputStream(), getControlEncoding()));
- // _controlOutput_ = new BufferedWriter(
- // new OutputStreamWriter(
- // _socket_.getOutputStream(), getControlEncoding()));
- // }
- return sendCommand(CMD_CCC);
- }
- /**
- * Sends the CONF command with the specified data.
- *
- * @param data The data to send with the command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execCONF(final byte[] data) throws IOException {
- if (data != null) {
- return sendCommand(CMD_CONF, Base64.getEncoder().encodeToString(data));
- }
- return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)?
- }
- /**
- * Sends the ENC command with the specified data.
- *
- * @param data The data to send with the command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execENC(final byte[] data) throws IOException {
- if (data != null) {
- return sendCommand(CMD_ENC, Base64.getEncoder().encodeToString(data));
- }
- return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)?
- }
- /**
- * Sends the MIC command with the specified data.
- *
- * @param data The data to send with the command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @since 3.0
- */
- public int execMIC(final byte[] data) throws IOException {
- if (data != null) {
- return sendCommand(CMD_MIC, Base64.getEncoder().encodeToString(data));
- }
- return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)?
- }
- /**
- * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
- *
- * @param pbsz Protection Buffer Size.
- * @throws SSLException If the server reply code does not equal "200".
- * @throws IOException If an I/O error occurs while sending the command.
- * @see #parsePBSZ(long)
- */
- public void execPBSZ(final long pbsz) throws SSLException, IOException {
- if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number
- throw new IllegalArgumentException();
- }
- final int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz));
- if (FTPReply.COMMAND_OK != status) {
- throw new SSLException(getReplyString());
- }
- }
- /**
- * PROT command.
- * <ul>
- * <li>C - Clear</li>
- * <li>S - Safe(SSL protocol only)</li>
- * <li>E - Confidential(SSL protocol only)</li>
- * <li>P - Private</li>
- * </ul>
- * <b>N.B.</b> the method calls {@link #setSocketFactory(javax.net.SocketFactory)} and {@link #setServerSocketFactory(javax.net.ServerSocketFactory)}
- *
- * @param prot Data Channel Protection Level, if {@code null}, use {@link #DEFAULT_PROT}.
- * @throws SSLException If the server reply code does not equal {@code 200}.
- * @throws IOException If an I/O error occurs while sending the command.
- */
- public void execPROT(String prot) throws SSLException, IOException {
- if (prot == null) {
- prot = DEFAULT_PROT;
- }
- if (!checkPROTValue(prot)) {
- throw new IllegalArgumentException();
- }
- if (FTPReply.COMMAND_OK != sendCommand(CMD_PROT, prot)) {
- throw new SSLException(getReplyString());
- }
- if (DEFAULT_PROT.equals(prot)) {
- setSocketFactory(null);
- setServerSocketFactory(null);
- } else {
- setSocketFactory(new FTPSSocketFactory(context));
- setServerSocketFactory(new FTPSServerSocketFactory(context));
- initSslContext();
- }
- }
- /**
- * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234
- *
- * @param prefix the prefix to find
- * @param reply where to find the prefix
- * @return the remainder of the string after the prefix, or null if the prefix was not present.
- */
- private String extractPrefixedData(final String prefix, final String reply) {
- final int idx = reply.indexOf(prefix);
- if (idx == -1) {
- return null;
- }
- // N.B. Cannot use trim before substring as leading space would affect the offset.
- return reply.substring(idx + prefix.length()).trim();
- }
- /**
- * Return AUTH command use value.
- *
- * @return AUTH command use value.
- */
- public String getAuthValue() {
- return this.auth;
- }
- /**
- * Returns the names of the cipher suites which could be enabled for use on this connection. When the underlying {@link Socket} is not an {@link SSLSocket}
- * instance, returns null.
- *
- * @return An array of cipher suite names, or {@code null}
- */
- public String[] getEnabledCipherSuites() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getEnabledCipherSuites();
- }
- return null;
- }
- /**
- * Returns the names of the protocol versions which are currently enabled for use on this connection. When the underlying {@link Socket} is not an
- * {@link SSLSocket} instance, returns null.
- *
- * @return An array of protocols, or {@code null}
- */
- public String[] getEnabledProtocols() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getEnabledProtocols();
- }
- return null;
- }
- /**
- * Returns true if new SSL sessions may be established by this socket. When the underlying {@link Socket} instance is not SSL-enabled (i.e. an instance of
- * {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled, this returns False.
- *
- * @return true - Indicates that sessions may be created; this is the default. false - indicates that an existing session must be resumed.
- */
- public boolean getEnableSessionCreation() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getEnableSessionCreation();
- }
- return false;
- }
- /**
- * Gets the currently configured {@link HostnameVerifier}. The verifier is only used on client mode connections.
- *
- * @return A HostnameVerifier instance.
- * @since 3.4
- */
- public HostnameVerifier getHostnameVerifier() {
- return hostnameVerifier;
- }
- /**
- * Gets the {@link KeyManager} instance.
- *
- * @return The {@link KeyManager} instance
- */
- private KeyManager getKeyManager() {
- return keyManager;
- }
- /**
- * Returns true if the socket will require client authentication. When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
- *
- * @return true - If the server mode socket should request that the client authenticate itself.
- */
- public boolean getNeedClientAuth() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getNeedClientAuth();
- }
- return false;
- }
- /**
- * Gets the secure socket protocol to be used, e.g. SSL/TLS.
- * @since 3.11.0
- * @return the protocol
- */
- protected String getProtocol() {
- return protocol;
- }
- /**
- * Gets the protocol versions. The {@link #getEnabledProtocols()} method gets the value from the socket while
- * this method gets its value from this instance's config.
- * @since 3.11.0
- * @return a clone of the protocols, may be null
- */
- protected String[] getProtocols() {
- return protocols == null ? null : protocols.clone();
- }
- /**
- * Gets the cipher suites. The {@link #getEnabledCipherSuites()} method gets the value from the socket while
- * this method gets its value from this instance's config.
- * @since 3.11.0
- * @return a clone of the suites, may be null
- */
- protected String[] getSuites() {
- return suites == null ? null : suites.clone();
- }
- /**
- * Gets the currently configured {@link TrustManager}.
- *
- * @return A TrustManager instance.
- */
- public TrustManager getTrustManager() {
- return trustManager;
- }
- /**
- * Returns true if the socket is set to use client mode in its first handshake. When the underlying {@link Socket} is not an {@link SSLSocket} instance,
- * returns false.
- *
- * @return true - If the socket should start its first handshake in "client" mode.
- */
- public boolean getUseClientMode() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getUseClientMode();
- }
- return false;
- }
- /**
- * Returns true if the socket will request client authentication. When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
- *
- * @return true - If the server mode socket should request that the client authenticate itself.
- */
- public boolean getWantClientAuth() {
- if (_socket_ instanceof SSLSocket) {
- return ((SSLSocket) _socket_).getWantClientAuth();
- }
- return false;
- }
- /**
- * Performs a lazy init of the SSL context
- *
- * @throws IOException
- */
- private void initSslContext() throws IOException {
- if (context == null) {
- context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
- }
- }
- /**
- * Gets the use client mode flag. The {@link #getUseClientMode()} method gets the value from the socket while
- * this method gets its value from this instance's config.
- * @since 3.11.0
- * @return True If the socket should start its first handshake in "client" mode.
- */
- protected boolean isClientMode() {
- return isClientMode;
- }
- /**
- * Gets whether a new SSL session may be established by this socket. Default true
- * @since 3.11.0
- * @return True if session may be established
- */
- protected boolean isCreation() {
- return isCreation;
- }
- /**
- * Return whether or not endpoint identification using the HTTPS algorithm on Java 1.7+ is enabled. The default behavior is for this to be disabled.
- *
- * This check is only performed on client mode connections.
- *
- * @return True if enabled, false if not.
- * @since 3.4
- */
- public boolean isEndpointCheckingEnabled() {
- return tlsEndpointChecking;
- }
- /**
- * Gets the security mode. (True - Implicit Mode / False - Explicit Mode)
- * @since 3.11.0
- * @return True if enabled, false if not.
- */
- protected boolean isImplicit() {
- return isImplicit;
- }
- /**
- * Gets the need client auth flag. The {@link #getNeedClientAuth()} method gets the value from the socket while
- * this method gets its value from this instance's config.
- * @since 3.11.0
- * @return True if enabled, false if not.
- */
- protected boolean isNeedClientAuth() {
- return isNeedClientAuth;
- }
- /**
- * Gets the want client auth flag. The {@link #getWantClientAuth()} method gets the value from the socket while
- * this method gets its value from this instance's config.
- * @since 3.11.0
- * @return True if enabled, false if not.
- */
- protected boolean isWantClientAuth() {
- return isWantClientAuth;
- }
- /**
- * Establishes a data connection with the FTP server, returning a Socket for the connection if successful. If a restart offset has been set with
- * {@link #setRestartOffset(long)}, a REST command is issued to the server with the offset as an argument before establishing the data connection. Active
- * mode connections also cause a local PORT command to be issued.
- *
- * @param command The text representation of the FTP command to send.
- * @param arg The arguments to the FTP command. If this parameter is set to null, then the command is sent with no argument.
- * @return A Socket corresponding to the established data connection. Null is returned if an FTP protocol error is reported at any point during the
- * establishment and initialization of the connection.
- * @throws IOException If an I/O error occurs while either sending a command to the server or receiving a reply from the server.
- * @since 3.1
- */
- private Socket openDataSecureConnection(final String command, final String arg) throws IOException {
- if (getDataConnectionMode() != ACTIVE_LOCAL_DATA_CONNECTION_MODE && getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
- return null;
- }
- final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;
- final Socket socket;
- Socket sslSocket = null;
- final int soTimeoutMillis = DurationUtils.toMillisInt(getDataTimeout());
- if (getDataConnectionMode() == ACTIVE_LOCAL_DATA_CONNECTION_MODE) {
- // if no activePortRange was set (correctly) -> getActivePort() = 0
- // -> new ServerSocket(0) -> bind to any free local port
- try (final ServerSocket server = _serverSocketFactory_.createServerSocket(getActivePort(), 1, getHostAddress())) {
- // Try EPRT only if remote server is over IPv6, if not use PORT,
- // because EPRT has no advantage over PORT on IPv4.
- // It could even have the disadvantage,
- // that EPRT will make the data connection fail, because
- // today's intelligent NAT Firewalls are able to
- // substitute IP addresses in the PORT command,
- // but might not be able to recognize the EPRT command.
- if (isInet6Address) {
- if (!FTPReply.isPositiveCompletion(eprt(getReportHostAddress(), server.getLocalPort()))) {
- return null;
- }
- } else if (!FTPReply.isPositiveCompletion(port(getReportHostAddress(), server.getLocalPort()))) {
- return null;
- }
- if (getRestartOffset() > 0 && !restart(getRestartOffset())) {
- return null;
- }
- if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) {
- return null;
- }
- // For now, let's just use the data timeout value for waiting for
- // the data connection. It may be desirable to let this be a
- // separately configurable value. In any case, we really want
- // to allow preventing the accept from blocking indefinitely.
- if (soTimeoutMillis >= 0) {
- server.setSoTimeout(soTimeoutMillis);
- }
- socket = server.accept();
- // Ensure the timeout is set before any commands are issued on the new socket
- if (soTimeoutMillis >= 0) {
- socket.setSoTimeout(soTimeoutMillis);
- }
- if (getReceiveDataSocketBufferSize() > 0) {
- socket.setReceiveBufferSize(getReceiveDataSocketBufferSize());
- }
- if (getSendDataSocketBufferSize() > 0) {
- socket.setSendBufferSize(getSendDataSocketBufferSize());
- }
- }
- } else { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE
- // Try EPSV command first on IPv6 - and IPv4 if enabled.
- // When using IPv4 with NAT it has the advantage
- // to work with more rare configurations.
- // E.g. if FTP server has a static PASV address (external network)
- // and the client is coming from another internal network.
- // In that case the data connection after PASV command would fail,
- // while EPSV would make the client succeed by taking just the port.
- final boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address;
- if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE) {
- _parseExtendedPassiveModeReply(_replyLines.get(0));
- } else {
- if (isInet6Address) {
- return null; // Must use EPSV for IPV6
- }
- // If EPSV failed on IPV4, revert to PASV
- if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) {
- return null;
- }
- _parsePassiveModeReply(_replyLines.get(0));
- }
- if (getProxy() != null) {
- socket = new Socket(getProxy());
- } else {
- socket = _socketFactory_.createSocket();
- }
- if (getReceiveDataSocketBufferSize() > 0) {
- socket.setReceiveBufferSize(getReceiveDataSocketBufferSize());
- }
- if (getSendDataSocketBufferSize() > 0) {
- socket.setSendBufferSize(getSendDataSocketBufferSize());
- }
- if (getPassiveLocalIPAddress() != null) {
- socket.bind(new InetSocketAddress(getPassiveLocalIPAddress(), 0));
- }
- // For now, let's just use the data timeout value for waiting for
- // the data connection. It may be desirable to let this be a
- // separately configurable value. In any case, we really want
- // to allow preventing the accept from blocking indefinitely.
- if (soTimeoutMillis >= 0) {
- socket.setSoTimeout(soTimeoutMillis);
- }
- socket.connect(new InetSocketAddress(getPassiveHost(), getPassivePort()), connectTimeout);
- if (getProxy() != null) {
- sslSocket = context.getSocketFactory().createSocket(socket, getPassiveHost(), getPassivePort(), true);
- }
- if (getRestartOffset() > 0 && !restart(getRestartOffset())) {
- closeSockets(socket, sslSocket);
- return null;
- }
- if (!FTPReply.isPositivePreliminary(sendCommand(command, arg))) {
- closeSockets(socket, sslSocket);
- return null;
- }
- }
- if (isRemoteVerificationEnabled() && !verifyRemote(socket)) {
- // Grab the host before we close the socket to avoid NET-663
- final InetAddress socketHost = socket.getInetAddress();
- closeSockets(socket, sslSocket);
- throw new IOException(
- "Host attempting data connection " + socketHost.getHostAddress() + " is not same as server " + getRemoteAddress().getHostAddress());
- }
- return getProxy() != null ? sslSocket : socket;
- }
- /**
- * Parses the given ADAT response line and base64-decodes the data.
- *
- * @param reply The ADAT reply to parse.
- * @return the data in the reply, base64-decoded.
- * @since 3.0
- */
- public byte[] parseADATReply(final String reply) {
- if (reply == null) {
- return null;
- }
- return Base64.getDecoder().decode(extractPrefixedData("ADAT=", reply));
- }
- /**
- * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer. Issues the command and parses the response to return the negotiated value.
- *
- * @param pbsz Protection Buffer Size.
- * @throws SSLException If the server reply code does not equal "200".
- * @throws IOException If an I/O error occurs while sending the command.
- * @return the negotiated value.
- * @see #execPBSZ(long)
- * @since 3.0
- */
- public long parsePBSZ(final long pbsz) throws SSLException, IOException {
- execPBSZ(pbsz);
- long minvalue = pbsz;
- final String remainder = extractPrefixedData("PBSZ=", getReplyString());
- if (remainder != null) {
- final long replysz = Long.parseLong(remainder);
- if (replysz < minvalue) {
- minvalue = replysz;
- }
- }
- return minvalue;
- }
- // DEPRECATED - for API compatibility only - DO NOT USE
- /**
- * Send an FTP command. A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance to be assigned to a plain
- * {@link Socket}
- *
- * @param command The FTP command.
- * @return server reply.
- * @throws IOException If an I/O error occurs while sending the command.
- * @throws SSLException if a CCC command fails
- * @see org.apache.commons.net.ftp.FTP#sendCommand(String)
- */
- // Would like to remove this method, but that will break any existing clients that are using CCC
- @Override
- public int sendCommand(final String command, final String args) throws IOException {
- final int repCode = super.sendCommand(command, args);
- /* If CCC is issued, restore socket i/o streams to unsecured versions */
- if (CMD_CCC.equals(command)) {
- if (FTPReply.COMMAND_OK != repCode) {
- throw new SSLException(getReplyString());
- }
- _socket_.close();
- _socket_ = plainSocket;
- _controlInput_ = new BufferedReader(new InputStreamReader(_socket_.getInputStream(), getControlEncoding()));
- _controlOutput_ = new BufferedWriter(new OutputStreamWriter(_socket_.getOutputStream(), getControlEncoding()));
- }
- return repCode;
- }
- /**
- * Sets AUTH command use value. This processing is done before connected processing.
- *
- * @param auth AUTH command use value.
- */
- public void setAuthValue(final String auth) {
- this.auth = auth;
- }
- /**
- * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation.
- *
- * @param cipherSuites The cipher suites.
- */
- public void setEnabledCipherSuites(final String[] cipherSuites) {
- suites = cipherSuites.clone();
- }
- /**
- * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation.
- *
- * @param protocolVersions The protocol versions.
- */
- public void setEnabledProtocols(final String[] protocolVersions) {
- protocols = protocolVersions.clone();
- }
- /**
- * Controls whether a new SSL session may be established by this socket.
- *
- * @param isCreation The established socket flag.
- */
- public void setEnabledSessionCreation(final boolean isCreation) {
- this.isCreation = isCreation;
- }
- /**
- * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled.
- *
- * This check is only performed on client mode connections.
- *
- * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
- * @since 3.4
- */
- public void setEndpointCheckingEnabled(final boolean enable) {
- tlsEndpointChecking = enable;
- }
- /**
- * Override the default {@link HostnameVerifier} to use. The verifier is only used on client mode connections.
- *
- * @param newHostnameVerifier The HostnameVerifier implementation to set or {@code null} to disable.
- * @since 3.4
- */
- public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) {
- hostnameVerifier = newHostnameVerifier;
- }
- /**
- * Sets a {@link KeyManager} to use
- *
- * @param keyManager The KeyManager implementation to set.
- * @see org.apache.commons.net.util.KeyManagerUtils
- */
- public void setKeyManager(final KeyManager keyManager) {
- this.keyManager = keyManager;
- }
- /**
- * Configures the socket to require client authentication.
- *
- * @param isNeedClientAuth The need client auth flag.
- */
- public void setNeedClientAuth(final boolean isNeedClientAuth) {
- this.isNeedClientAuth = isNeedClientAuth;
- }
- /**
- * Override the default {@link TrustManager} to use; if set to {@code null}, the default TrustManager from the JVM will be used.
- *
- * @param trustManager The TrustManager implementation to set, may be {@code null}
- * @see org.apache.commons.net.util.TrustManagerUtils
- */
- public void setTrustManager(final TrustManager trustManager) {
- this.trustManager = trustManager;
- }
- /**
- * Configures the socket to use client (or server) mode in its first handshake.
- *
- * @param isClientMode The use client mode flag.
- */
- public void setUseClientMode(final boolean isClientMode) {
- this.isClientMode = isClientMode;
- }
- /**
- * Configures the socket to request client authentication, but only if such a request is appropriate to the cipher suite negotiated.
- *
- * @param isWantClientAuth The want client auth flag.
- */
- public void setWantClientAuth(final boolean isWantClientAuth) {
- this.isWantClientAuth = isWantClientAuth;
- }
- /**
- * SSL/TLS negotiation. Acquires an SSL socket of a control connection and carries out handshake processing.
- *
- * @throws IOException If server negotiation fails
- */
- protected void sslNegotiation() throws IOException {
- plainSocket = _socket_;
- initSslContext();
- final SSLSocket socket = createSSLSocket(_socket_);
- socket.setEnableSessionCreation(isCreation);
- socket.setUseClientMode(isClientMode);
- // client mode
- if (isClientMode) {
- if (tlsEndpointChecking) {
- SSLSocketUtils.enableEndpointNameVerification(socket);
- }
- } else { // server mode
- socket.setNeedClientAuth(isNeedClientAuth);
- socket.setWantClientAuth(isWantClientAuth);
- }
- if (protocols != null) {
- socket.setEnabledProtocols(protocols);
- }
- if (suites != null) {
- socket.setEnabledCipherSuites(suites);
- }
- socket.startHandshake();
- // TODO the following setup appears to duplicate that in the super class methods
- _socket_ = socket;
- _controlInput_ = new BufferedReader(new InputStreamReader(socket.getInputStream(), getControlEncoding()));
- _controlOutput_ = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), getControlEncoding()));
- if (isClientMode && hostnameVerifier != null && !hostnameVerifier.verify(_hostname_, socket.getSession())) {
- throw new SSLHandshakeException("Hostname doesn't match certificate");
- }
- }
- }