This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-net.git
The following commit(s) were added to refs/heads/master by this push: new 3439e4e1 Javadoc @see tags do not need to use a FQCN for classes in java.lang 3439e4e1 is described below commit 3439e4e1576b74ff486a05cab6a0ddb4c0850aac Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Mon Aug 29 07:17:36 2022 -0400 Javadoc @see tags do not need to use a FQCN for classes in java.lang --- .../org/apache/commons/net/ftp/FTPSClient.java | 1828 ++++++++++---------- .../java/org/apache/commons/net/ntp/TimeStamp.java | 936 +++++----- 2 files changed, 1382 insertions(+), 1382 deletions(-) diff --git a/src/main/java/org/apache/commons/net/ftp/FTPSClient.java b/src/main/java/org/apache/commons/net/ftp/FTPSClient.java index 7f0875b8..df4de5cf 100644 --- a/src/main/java/org/apache/commons/net/ftp/FTPSClient.java +++ b/src/main/java/org/apache/commons/net/ftp/FTPSClient.java @@ -1,914 +1,914 @@ -/* - * 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.Socket; - -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.Base64; -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 - - public static final int DEFAULT_FTPS_DATA_PORT = 989; - 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 explict 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 of the connect() methods. - * @throws IOException If it throw by _connectAction_. - * @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 = super._openDataConnection_(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; - } - - /** - * 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); - } - - /** - * Send 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.encodeBase64StringUnChunked(data)); - } - return sendCommand(CMD_ADAT); - } - - /** - * AUTH command. - * @throws SSLException If it server reply code not equal "234" and "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()); - } - } - - /** - * Send 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); - } - - /** - * Send 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 - { - final int repCode = sendCommand(CMD_CCC); -// 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 repCode; - } - - /** - * Send 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.encodeBase64StringUnChunked(data)); - } - return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? - } - - /** - * Send 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.encodeBase64StringUnChunked(data)); - } - return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? - } - - /** - * Send 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.encodeBase64StringUnChunked(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</code> - */ - 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</code> - */ - 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; - } - - /** - * Get 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; - } - - /** - * Get 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()); - } - } - - /** - * 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; - } - - /** - * 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.decodeBase64(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; - } - - /** - * 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(java.lang.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; - } - - /** - * Set 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</code> to disable. - * @since 3.4 - */ - public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) - { - hostnameVerifier = newHostnameVerifier; - } - - /** - * Set 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; - } - - // DEPRECATED - for API compatibility only - DO NOT USE - - /** - * 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"); - } - } - -} -/* kate: indent-width 4; replace-tabs on; */ +/* + * 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.Socket; + +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.Base64; +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 + + public static final int DEFAULT_FTPS_DATA_PORT = 989; + 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 explict 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 of the connect() methods. + * @throws IOException If it throw by _connectAction_. + * @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 = super._openDataConnection_(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; + } + + /** + * 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); + } + + /** + * Send 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.encodeBase64StringUnChunked(data)); + } + return sendCommand(CMD_ADAT); + } + + /** + * AUTH command. + * @throws SSLException If it server reply code not equal "234" and "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()); + } + } + + /** + * Send 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); + } + + /** + * Send 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 + { + final int repCode = sendCommand(CMD_CCC); +// 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 repCode; + } + + /** + * Send 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.encodeBase64StringUnChunked(data)); + } + return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)? + } + + /** + * Send 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.encodeBase64StringUnChunked(data)); + } + return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)? + } + + /** + * Send 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.encodeBase64StringUnChunked(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</code> + */ + 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</code> + */ + 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; + } + + /** + * Get 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; + } + + /** + * Get 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()); + } + } + + /** + * 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; + } + + /** + * 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.decodeBase64(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; + } + + /** + * 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; + } + + /** + * Set 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</code> to disable. + * @since 3.4 + */ + public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) + { + hostnameVerifier = newHostnameVerifier; + } + + /** + * Set 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; + } + + // DEPRECATED - for API compatibility only - DO NOT USE + + /** + * 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"); + } + } + +} +/* kate: indent-width 4; replace-tabs on; */ diff --git a/src/main/java/org/apache/commons/net/ntp/TimeStamp.java b/src/main/java/org/apache/commons/net/ntp/TimeStamp.java index 20a10755..f9aa8b95 100644 --- a/src/main/java/org/apache/commons/net/ntp/TimeStamp.java +++ b/src/main/java/org/apache/commons/net/ntp/TimeStamp.java @@ -1,468 +1,468 @@ -package org.apache.commons.net.ntp; -/* - * 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. - */ - - - -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/** - * TimeStamp class represents the Network Time Protocol (NTP) timestamp - * as defined in RFC-1305 and SNTP (RFC-2030). It is represented as a - * 64-bit unsigned fixed-point number in seconds relative to 0-hour on 1-January-1900. - * The 32-bit low-order bits are the fractional seconds whose precision is - * about 200 picoseconds. Assumes overflow date when date passes MAX_LONG - * and reverts back to 0 is 2036 and not 1900. Test for most significant - * bit: if MSB=0 then 2036 basis is used otherwise 1900 if MSB=1. - * <p> - * Methods exist to convert NTP timestamps to and from the equivalent Java date - * representation, which is the number of milliseconds since the standard base - * time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. - * </p> - * - * @see java.util.Date - */ -public class TimeStamp implements java.io.Serializable, Comparable<TimeStamp> -{ - private static final long serialVersionUID = 8139806907588338737L; - - /** - * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC - */ - protected static final long msb0baseTime = 2085978496000L; - - /** - * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC - */ - protected static final long msb1baseTime = -2208988800000L; - - /** - * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. - * See <code>java.text.SimpleDateFormat</code> for code descriptions. - */ - public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS"; - - /** - * Left-pad 8-character hex string with 0's - * - * @param buf - StringBuilder which is appended with leading 0's. - * @param l - a long. - */ - private static void appendHexString(final StringBuilder buf, final long l) - { - final String s = Long.toHexString(l); - for (int i = s.length(); i < 8; i++) { - buf.append('0'); - } - buf.append(s); - } - - /** - * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP - * 64-bit unsigned fixed-point number. - * @param hexString the string to convert - * - * @return NTP 64-bit timestamp value. - * @throws NumberFormatException - if the string does not contain a parsable timestamp. - */ - protected static long decodeNtpHexString(final String hexString) - throws NumberFormatException - { - if (hexString == null) { - throw new NumberFormatException("null"); - } - final int ind = hexString.indexOf('.'); - if (ind == -1) { - if (hexString.isEmpty()) { - return 0; - } - return Long.parseLong(hexString, 16) << 32; // no decimal - } - - return Long.parseLong(hexString.substring(0, ind), 16) << 32 | - Long.parseLong(hexString.substring(ind + 1), 16); - } - /** - * Constructs a NTP timestamp object and initializes it so that - * it represents the time at which it was allocated, measured to the - * nearest millisecond. - * @return NTP timestamp object set to the current time. - * @see java.lang.System#currentTimeMillis() - */ - public static TimeStamp getCurrentTime() - { - return getNtpTime(System.currentTimeMillis()); - } - - // initialization of static time bases - /* - static { - TimeZone utcZone = TimeZone.getTimeZone("UTC"); - Calendar calendar = Calendar.getInstance(utcZone); - calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); - calendar.set(Calendar.MILLISECOND, 0); - msb1baseTime = calendar.getTime().getTime(); - calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); - calendar.set(Calendar.MILLISECOND, 0); - msb0baseTime = calendar.getTime().getTime(); - } - */ - - /** - * Helper method to convert Java time to NTP timestamp object. - * Note that Java time (milliseconds) by definition has less precision - * then NTP time (picoseconds) so converting Ntptime to Javatime and back - * to Ntptime loses precision. For example, Tue, Dec 17 2002 09:07:24.810 - * is represented by a single Java-based time value of f22cd1fc8a, but its - * NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. - * @param dateMillis the milliseconds since January 1, 1970, 00:00:00 GMT. - * @return NTP timestamp object at the specified date. - */ - public static TimeStamp getNtpTime(final long dateMillis) - { - return new TimeStamp(toNtpTime(dateMillis)); - } - - /** - * Converts 64-bit NTP timestamp to Java standard time. - * - * Note that java time (milliseconds) by definition has less precision - * then NTP time (picoseconds) so converting NTP timestamp to java time and back - * to NTP timestamp loses precision. For example, Tue, Dec 17 2002 09:07:24.810 EST - * is represented by a single Java-based time value of f22cd1fc8a, but its - * NTP equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. - * - * @param ntpTimeValue the input time - * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT - * represented by this NTP timestamp value. - */ - public static long getTime(final long ntpTimeValue) - { - final long seconds = (ntpTimeValue >>> 32) & 0xffffffffL; // high-order 32-bits - long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits - - // Use round-off on fractional part to preserve going to lower precision - fraction = Math.round(1000D * fraction / 0x100000000L); - - /* - * If the most significant bit (MSB) on the seconds field is set we use - * a different time base. The following text is a quote from RFC-2030 (SNTP v4): - * - * If bit 0 is set, the UTC time is in the range 1968-2036 and UTC time - * is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, - * the time is in the range 2036-2104 and UTC time is reckoned from - * 6h 28m 16s UTC on 7 February 2036. - */ - final long msb = seconds & 0x80000000L; - if (msb == 0) { - // use base: 7-Feb-2036 @ 06:28:16 UTC - return msb0baseTime + (seconds * 1000) + fraction; - } - // use base: 1-Jan-1900 @ 01:00:00 UTC - return msb1baseTime + (seconds * 1000) + fraction; - } - - /** - * Parses the string argument as a NTP hexidecimal timestamp representation string - * (e.g. "c1a089bd.fc904f6d"). - * - * @param s - hexstring. - * @return the Timestamp represented by the argument in hexidecimal. - * @throws NumberFormatException - if the string does not contain a parsable timestamp. - */ - public static TimeStamp parseNtpString(final String s) - throws NumberFormatException - { - return new TimeStamp(decodeNtpHexString(s)); - } - - /** - * Converts Java time to 64-bit NTP time representation. - * - * @param millis Java time - * @return NTP timestamp representation of Java time value. - */ - protected static long toNtpTime(final long millis) - { - final boolean useBase1 = millis < msb0baseTime; // time < Feb-2036 - final long baseTimeMillis; - if (useBase1) { - baseTimeMillis = millis - msb1baseTime; // dates <= Feb-2036 - } else { - // if base0 needed for dates >= Feb-2036 - baseTimeMillis = millis - msb0baseTime; - } - - long seconds = baseTimeMillis / 1000; - final long fraction = ((baseTimeMillis % 1000) * 0x100000000L) / 1000; - - if (useBase1) { - seconds |= 0x80000000L; // set high-order bit if msb1baseTime 1900 used - } - - return seconds << 32 | fraction; - } - - /** - * Converts 64-bit NTP timestamp value to a <code>String</code>. - * The NTP timestamp value is represented as hex string with - * seconds separated by fractional seconds by a decimal point; - * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 - * @param ntpTime the 64 bit timestamp - * - * @return NTP timestamp 64-bit long value as hex string with seconds - * separated by fractional seconds. - */ - public static String toString(final long ntpTime) - { - final StringBuilder buf = new StringBuilder(); - // high-order second bits (32..63) as hexstring - appendHexString(buf, (ntpTime >>> 32) & 0xffffffffL); - - // low-order fractional seconds bits (0..31) as hexstring - buf.append('.'); - appendHexString(buf, ntpTime & 0xffffffffL); - - return buf.toString(); - } - - /** - * NTP timestamp value: 64-bit unsigned fixed-point number as defined in RFC-1305 - * with high-order 32 bits the seconds field and the low-order 32-bits the - * fractional field. - */ - private final long ntpTime; - - private DateFormat simpleFormatter; - - private DateFormat utcFormatter; - - /** - * Constructs a newly allocated NTP timestamp object - * that represents the Java Date argument. - * - * @param d - the Date to be represented by the Timestamp object. - */ - public TimeStamp(final Date d) - { - ntpTime = d == null ? 0 : toNtpTime(d.getTime()); - } - - /** - * Constructs a newly allocated NTP timestamp object - * that represents the native 64-bit long argument. - * @param ntpTime the timestamp - */ - public TimeStamp(final long ntpTime) - { - this.ntpTime = ntpTime; - } - - /** - * Constructs a newly allocated NTP timestamp object - * that represents the value represented by the string - * in hexdecimal form (e.g. "c1a089bd.fc904f6d"). - * @param hexStamp the hex timestamp - * - * @throws NumberFormatException - if the string does not contain a parsable timestamp. - */ - public TimeStamp(final String hexStamp) throws NumberFormatException - { - ntpTime = decodeNtpHexString(hexStamp); - } - - /** - * Compares two Timestamps numerically. - * - * @param anotherTimeStamp - the <code>TimeStamp</code> to be compared. - * @return the value <code>0</code> if the argument TimeStamp is equal to - * this TimeStamp; a value less than <code>0</code> if this TimeStamp - * is numerically less than the TimeStamp argument; and a - * value greater than <code>0</code> if this TimeStamp is - * numerically greater than the TimeStamp argument - * (signed comparison). - */ - @Override - public int compareTo(final TimeStamp anotherTimeStamp) - { - final long thisVal = this.ntpTime; - final long anotherVal = anotherTimeStamp.ntpTime; - return (Long.compare(thisVal, anotherVal)); - } - - /** - * Compares this object against the specified object. - * The result is <code>true</code> if and only if the argument is - * not <code>null</code> and is a <code>Long</code> object that - * contains the same <code>long</code> value as this object. - * - * @param obj the object to compare with. - * @return <code>true</code> if the objects are the same; - * <code>false</code> otherwise. - */ - @Override - public boolean equals(final Object obj) - { - if (obj instanceof TimeStamp) { - return ntpTime == ((TimeStamp) obj).ntpValue(); - } - return false; - } - - /** - * Converts NTP timestamp to Java Date object. - * - * @return NTP Timestamp in Java Date - */ - public Date getDate() - { - return new Date(getTime(ntpTime)); - } - - /** - * Returns low-order 32-bits representing the fractional seconds. - * - * @return fractional seconds represented by this NTP timestamp. - */ - public long getFraction() - { - return ntpTime & 0xffffffffL; - } - - /** - * Returns high-order 32-bits representing the seconds of this NTP timestamp. - * - * @return seconds represented by this NTP timestamp. - */ - public long getSeconds() - { - return (ntpTime >>> 32) & 0xffffffffL; - } - - /** - * Converts NTP timestamp to Java standard time. - * - * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT - * represented by this NTP timestamp value. - */ - public long getTime() - { - return getTime(ntpTime); - } - - /** - * Computes a hashcode for this Timestamp. The result is the exclusive - * OR of the two halves of the primitive <code>long</code> value - * represented by this <code>TimeStamp</code> object. That is, the hashcode - * is the value of the expression: - * <blockquote><pre> - * {@code (int)(this.ntpValue()^(this.ntpValue() >>> 32))} - * </pre></blockquote> - * - * @return a hash code value for this object. - */ - @Override - public int hashCode() - { - return (int) (ntpTime ^ (ntpTime >>> 32)); - } - - /** - * Returns the value of this Timestamp as a long value. - * - * @return the 64-bit long value represented by this object. - */ - public long ntpValue() - { - return ntpTime; - } - - /** - * Converts this <code>TimeStamp</code> object to a <code>String</code> - * of the form: - * <blockquote><pre> - * EEE, MMM dd yyyy HH:mm:ss.SSS</pre></blockquote> - * See java.text.SimpleDataFormat for code descriptions. - * - * @return a string representation of this date. - */ - public String toDateString() - { - if (simpleFormatter == null) { - simpleFormatter = new SimpleDateFormat(NTP_DATE_FORMAT, Locale.US); - simpleFormatter.setTimeZone(TimeZone.getDefault()); - } - final Date ntpDate = getDate(); - return simpleFormatter.format(ntpDate); - } - - /** - * Converts this <code>TimeStamp</code> object to a <code>String</code>. - * The NTP timestamp 64-bit long value is represented as hex string with - * seconds separated by fractional seconds by a decimal point; - * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 - * - * @return NTP timestamp 64-bit long value as hex string with seconds - * separated by fractional seconds. - */ - @Override - public String toString() - { - return toString(ntpTime); - } - - /** - * Converts this <code>TimeStamp</code> object to a <code>String</code> - * of the form: - * <blockquote><pre> - * EEE, MMM dd yyyy HH:mm:ss.SSS UTC</pre></blockquote> - * See java.text.SimpleDataFormat for code descriptions. - * - * @return a string representation of this date in UTC. - */ - public String toUTCString() - { - if (utcFormatter == null) { - utcFormatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", - Locale.US); - utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); - } - final Date ntpDate = getDate(); - return utcFormatter.format(ntpDate); - } - - /* - Serialization is unnecessary for this class. - Reject attempts to do so until such time as the Serializable attribute can be dropped. - */ - - private void writeObject(final java.io.ObjectOutputStream out) throws IOException - { - throw new UnsupportedOperationException("Serialization is not supported"); - } - - private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException - { - throw new UnsupportedOperationException("Serialization is not supported"); - } - -} +package org.apache.commons.net.ntp; +/* + * 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. + */ + + + +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * TimeStamp class represents the Network Time Protocol (NTP) timestamp + * as defined in RFC-1305 and SNTP (RFC-2030). It is represented as a + * 64-bit unsigned fixed-point number in seconds relative to 0-hour on 1-January-1900. + * The 32-bit low-order bits are the fractional seconds whose precision is + * about 200 picoseconds. Assumes overflow date when date passes MAX_LONG + * and reverts back to 0 is 2036 and not 1900. Test for most significant + * bit: if MSB=0 then 2036 basis is used otherwise 1900 if MSB=1. + * <p> + * Methods exist to convert NTP timestamps to and from the equivalent Java date + * representation, which is the number of milliseconds since the standard base + * time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. + * </p> + * + * @see java.util.Date + */ +public class TimeStamp implements java.io.Serializable, Comparable<TimeStamp> +{ + private static final long serialVersionUID = 8139806907588338737L; + + /** + * Baseline NTP time if bit-0=0 is 7-Feb-2036 @ 06:28:16 UTC + */ + protected static final long msb0baseTime = 2085978496000L; + + /** + * Baseline NTP time if bit-0=1 is 1-Jan-1900 @ 01:00:00 UTC + */ + protected static final long msb1baseTime = -2208988800000L; + + /** + * Default NTP date string format. E.g. Fri, Sep 12 2003 21:06:23.860. + * See <code>java.text.SimpleDateFormat</code> for code descriptions. + */ + public static final String NTP_DATE_FORMAT = "EEE, MMM dd yyyy HH:mm:ss.SSS"; + + /** + * Left-pad 8-character hex string with 0's + * + * @param buf - StringBuilder which is appended with leading 0's. + * @param l - a long. + */ + private static void appendHexString(final StringBuilder buf, final long l) + { + final String s = Long.toHexString(l); + for (int i = s.length(); i < 8; i++) { + buf.append('0'); + } + buf.append(s); + } + + /** + * Convert NTP timestamp hexstring (e.g. "c1a089bd.fc904f6d") to the NTP + * 64-bit unsigned fixed-point number. + * @param hexString the string to convert + * + * @return NTP 64-bit timestamp value. + * @throws NumberFormatException - if the string does not contain a parsable timestamp. + */ + protected static long decodeNtpHexString(final String hexString) + throws NumberFormatException + { + if (hexString == null) { + throw new NumberFormatException("null"); + } + final int ind = hexString.indexOf('.'); + if (ind == -1) { + if (hexString.isEmpty()) { + return 0; + } + return Long.parseLong(hexString, 16) << 32; // no decimal + } + + return Long.parseLong(hexString.substring(0, ind), 16) << 32 | + Long.parseLong(hexString.substring(ind + 1), 16); + } + /** + * Constructs a NTP timestamp object and initializes it so that + * it represents the time at which it was allocated, measured to the + * nearest millisecond. + * @return NTP timestamp object set to the current time. + * @see System#currentTimeMillis() + */ + public static TimeStamp getCurrentTime() + { + return getNtpTime(System.currentTimeMillis()); + } + + // initialization of static time bases + /* + static { + TimeZone utcZone = TimeZone.getTimeZone("UTC"); + Calendar calendar = Calendar.getInstance(utcZone); + calendar.set(1900, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + msb1baseTime = calendar.getTime().getTime(); + calendar.set(2036, Calendar.FEBRUARY, 7, 6, 28, 16); + calendar.set(Calendar.MILLISECOND, 0); + msb0baseTime = calendar.getTime().getTime(); + } + */ + + /** + * Helper method to convert Java time to NTP timestamp object. + * Note that Java time (milliseconds) by definition has less precision + * then NTP time (picoseconds) so converting Ntptime to Javatime and back + * to Ntptime loses precision. For example, Tue, Dec 17 2002 09:07:24.810 + * is represented by a single Java-based time value of f22cd1fc8a, but its + * NTP equivalent are all values from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. + * @param dateMillis the milliseconds since January 1, 1970, 00:00:00 GMT. + * @return NTP timestamp object at the specified date. + */ + public static TimeStamp getNtpTime(final long dateMillis) + { + return new TimeStamp(toNtpTime(dateMillis)); + } + + /** + * Converts 64-bit NTP timestamp to Java standard time. + * + * Note that java time (milliseconds) by definition has less precision + * then NTP time (picoseconds) so converting NTP timestamp to java time and back + * to NTP timestamp loses precision. For example, Tue, Dec 17 2002 09:07:24.810 EST + * is represented by a single Java-based time value of f22cd1fc8a, but its + * NTP equivalent are all values ranging from c1a9ae1c.cf5c28f5 to c1a9ae1c.cf9db22c. + * + * @param ntpTimeValue the input time + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this NTP timestamp value. + */ + public static long getTime(final long ntpTimeValue) + { + final long seconds = (ntpTimeValue >>> 32) & 0xffffffffL; // high-order 32-bits + long fraction = ntpTimeValue & 0xffffffffL; // low-order 32-bits + + // Use round-off on fractional part to preserve going to lower precision + fraction = Math.round(1000D * fraction / 0x100000000L); + + /* + * If the most significant bit (MSB) on the seconds field is set we use + * a different time base. The following text is a quote from RFC-2030 (SNTP v4): + * + * If bit 0 is set, the UTC time is in the range 1968-2036 and UTC time + * is reckoned from 0h 0m 0s UTC on 1 January 1900. If bit 0 is not set, + * the time is in the range 2036-2104 and UTC time is reckoned from + * 6h 28m 16s UTC on 7 February 2036. + */ + final long msb = seconds & 0x80000000L; + if (msb == 0) { + // use base: 7-Feb-2036 @ 06:28:16 UTC + return msb0baseTime + (seconds * 1000) + fraction; + } + // use base: 1-Jan-1900 @ 01:00:00 UTC + return msb1baseTime + (seconds * 1000) + fraction; + } + + /** + * Parses the string argument as a NTP hexidecimal timestamp representation string + * (e.g. "c1a089bd.fc904f6d"). + * + * @param s - hexstring. + * @return the Timestamp represented by the argument in hexidecimal. + * @throws NumberFormatException - if the string does not contain a parsable timestamp. + */ + public static TimeStamp parseNtpString(final String s) + throws NumberFormatException + { + return new TimeStamp(decodeNtpHexString(s)); + } + + /** + * Converts Java time to 64-bit NTP time representation. + * + * @param millis Java time + * @return NTP timestamp representation of Java time value. + */ + protected static long toNtpTime(final long millis) + { + final boolean useBase1 = millis < msb0baseTime; // time < Feb-2036 + final long baseTimeMillis; + if (useBase1) { + baseTimeMillis = millis - msb1baseTime; // dates <= Feb-2036 + } else { + // if base0 needed for dates >= Feb-2036 + baseTimeMillis = millis - msb0baseTime; + } + + long seconds = baseTimeMillis / 1000; + final long fraction = ((baseTimeMillis % 1000) * 0x100000000L) / 1000; + + if (useBase1) { + seconds |= 0x80000000L; // set high-order bit if msb1baseTime 1900 used + } + + return seconds << 32 | fraction; + } + + /** + * Converts 64-bit NTP timestamp value to a <code>String</code>. + * The NTP timestamp value is represented as hex string with + * seconds separated by fractional seconds by a decimal point; + * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 + * @param ntpTime the 64 bit timestamp + * + * @return NTP timestamp 64-bit long value as hex string with seconds + * separated by fractional seconds. + */ + public static String toString(final long ntpTime) + { + final StringBuilder buf = new StringBuilder(); + // high-order second bits (32..63) as hexstring + appendHexString(buf, (ntpTime >>> 32) & 0xffffffffL); + + // low-order fractional seconds bits (0..31) as hexstring + buf.append('.'); + appendHexString(buf, ntpTime & 0xffffffffL); + + return buf.toString(); + } + + /** + * NTP timestamp value: 64-bit unsigned fixed-point number as defined in RFC-1305 + * with high-order 32 bits the seconds field and the low-order 32-bits the + * fractional field. + */ + private final long ntpTime; + + private DateFormat simpleFormatter; + + private DateFormat utcFormatter; + + /** + * Constructs a newly allocated NTP timestamp object + * that represents the Java Date argument. + * + * @param d - the Date to be represented by the Timestamp object. + */ + public TimeStamp(final Date d) + { + ntpTime = d == null ? 0 : toNtpTime(d.getTime()); + } + + /** + * Constructs a newly allocated NTP timestamp object + * that represents the native 64-bit long argument. + * @param ntpTime the timestamp + */ + public TimeStamp(final long ntpTime) + { + this.ntpTime = ntpTime; + } + + /** + * Constructs a newly allocated NTP timestamp object + * that represents the value represented by the string + * in hexdecimal form (e.g. "c1a089bd.fc904f6d"). + * @param hexStamp the hex timestamp + * + * @throws NumberFormatException - if the string does not contain a parsable timestamp. + */ + public TimeStamp(final String hexStamp) throws NumberFormatException + { + ntpTime = decodeNtpHexString(hexStamp); + } + + /** + * Compares two Timestamps numerically. + * + * @param anotherTimeStamp - the <code>TimeStamp</code> to be compared. + * @return the value <code>0</code> if the argument TimeStamp is equal to + * this TimeStamp; a value less than <code>0</code> if this TimeStamp + * is numerically less than the TimeStamp argument; and a + * value greater than <code>0</code> if this TimeStamp is + * numerically greater than the TimeStamp argument + * (signed comparison). + */ + @Override + public int compareTo(final TimeStamp anotherTimeStamp) + { + final long thisVal = this.ntpTime; + final long anotherVal = anotherTimeStamp.ntpTime; + return (Long.compare(thisVal, anotherVal)); + } + + /** + * Compares this object against the specified object. + * The result is <code>true</code> if and only if the argument is + * not <code>null</code> and is a <code>Long</code> object that + * contains the same <code>long</code> value as this object. + * + * @param obj the object to compare with. + * @return <code>true</code> if the objects are the same; + * <code>false</code> otherwise. + */ + @Override + public boolean equals(final Object obj) + { + if (obj instanceof TimeStamp) { + return ntpTime == ((TimeStamp) obj).ntpValue(); + } + return false; + } + + /** + * Converts NTP timestamp to Java Date object. + * + * @return NTP Timestamp in Java Date + */ + public Date getDate() + { + return new Date(getTime(ntpTime)); + } + + /** + * Returns low-order 32-bits representing the fractional seconds. + * + * @return fractional seconds represented by this NTP timestamp. + */ + public long getFraction() + { + return ntpTime & 0xffffffffL; + } + + /** + * Returns high-order 32-bits representing the seconds of this NTP timestamp. + * + * @return seconds represented by this NTP timestamp. + */ + public long getSeconds() + { + return (ntpTime >>> 32) & 0xffffffffL; + } + + /** + * Converts NTP timestamp to Java standard time. + * + * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT + * represented by this NTP timestamp value. + */ + public long getTime() + { + return getTime(ntpTime); + } + + /** + * Computes a hashcode for this Timestamp. The result is the exclusive + * OR of the two halves of the primitive <code>long</code> value + * represented by this <code>TimeStamp</code> object. That is, the hashcode + * is the value of the expression: + * <blockquote><pre> + * {@code (int)(this.ntpValue()^(this.ntpValue() >>> 32))} + * </pre></blockquote> + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() + { + return (int) (ntpTime ^ (ntpTime >>> 32)); + } + + /** + * Returns the value of this Timestamp as a long value. + * + * @return the 64-bit long value represented by this object. + */ + public long ntpValue() + { + return ntpTime; + } + + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code> + * of the form: + * <blockquote><pre> + * EEE, MMM dd yyyy HH:mm:ss.SSS</pre></blockquote> + * See java.text.SimpleDataFormat for code descriptions. + * + * @return a string representation of this date. + */ + public String toDateString() + { + if (simpleFormatter == null) { + simpleFormatter = new SimpleDateFormat(NTP_DATE_FORMAT, Locale.US); + simpleFormatter.setTimeZone(TimeZone.getDefault()); + } + final Date ntpDate = getDate(); + return simpleFormatter.format(ntpDate); + } + + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code>. + * The NTP timestamp 64-bit long value is represented as hex string with + * seconds separated by fractional seconds by a decimal point; + * e.g. c1a089bd.fc904f6d == Tue, Dec 10 2002 10:41:49.986 + * + * @return NTP timestamp 64-bit long value as hex string with seconds + * separated by fractional seconds. + */ + @Override + public String toString() + { + return toString(ntpTime); + } + + /** + * Converts this <code>TimeStamp</code> object to a <code>String</code> + * of the form: + * <blockquote><pre> + * EEE, MMM dd yyyy HH:mm:ss.SSS UTC</pre></blockquote> + * See java.text.SimpleDataFormat for code descriptions. + * + * @return a string representation of this date in UTC. + */ + public String toUTCString() + { + if (utcFormatter == null) { + utcFormatter = new SimpleDateFormat(NTP_DATE_FORMAT + " 'UTC'", + Locale.US); + utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); + } + final Date ntpDate = getDate(); + return utcFormatter.format(ntpDate); + } + + /* + Serialization is unnecessary for this class. + Reject attempts to do so until such time as the Serializable attribute can be dropped. + */ + + private void writeObject(final java.io.ObjectOutputStream out) throws IOException + { + throw new UnsupportedOperationException("Serialization is not supported"); + } + + private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException + { + throw new UnsupportedOperationException("Serialization is not supported"); + } + +}