/*
* ------------------------------------------------------------------------------
* Hermes FTP Server
* Copyright (c) 2005-2014 Lars Behnke
* ------------------------------------------------------------------------------
*
* This file is part of Hermes FTP Server.
*
* Hermes FTP Server is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Hermes FTP Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Hermes FTP Server; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* ------------------------------------------------------------------------------
*/
package com.apporiented.hermesftp.cmd.impl;
import java.io.IOException;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import com.apporiented.hermesftp.cmd.AbstractFtpCmd;
import com.apporiented.hermesftp.cmd.ClientSocketModifier;
import com.apporiented.hermesftp.exception.FtpCmdException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
/**
* <b>AUTHENTICATION/SECURITY MECHANISM (AUTH)</b>
* <p>
* The argument field is a Telnet string identifying a supported mechanism. This string is
* case-insensitive. Values must be registered with the IANA, except that values beginning with "X-"
* are reserved for local use.
* <p>
* If the server does not recognize the AUTH command, it must respond with reply code 500. This is
* intended to encompass the large deployed base of non-security-aware ftp servers, which will
* respond with reply code 500 to any unrecognized command. If the server does recognize the AUTH
* command but does not implement the security extensions, it should respond with reply code 502.
* <p>
* If the server does not understand the named security mechanism, it should respond with reply code
* 504.
* <p>
* If the server is not willing to accept the named security mechanism, it should respond with reply
* code 534.
* <p>
* If the server is not able to accept the named security mechanism, such as if a required resource
* is unavailable, it should respond with reply code 431.
* <p>
* If the server is willing to accept the named security mechanism, but requires security data, it
* must respond with reply code 334.
* <p>
* If the server is willing to accept the named security mechanism, and does not require any
* security data, it must respond with reply code 234.
* <p>
* If the server is responding with a 334 reply code, it may include security data as described in
* the next section.
* <p>
* Some servers will allow the AUTH command to be reissued in order to establish new authentication.
* The AUTH command, if accepted, removes any state associated with prior FTP Security commands. The
* server must also require that the user reauthorize (that is, reissue some or all of the USER,
* PASS, and ACCT commands) in this case (see section 4 of RFC2228 for an explanation of "authorize"
* in this context).
*
* @author Lars Behnke
*/
public class FtpCmdAuth extends AbstractFtpCmd implements ClientSocketModifier, HandshakeCompletedListener {
private static Log log = LogFactory.getLog(FtpCmdAuth.class);
private boolean executed;
/**
* Some notes about SSL support: Use keytool to generate a keystore/key: <code>
* keytool -genkey -alias behnke -keyalg DSA -keystore keystore -validity 365 -storepass secret -keypass secret
* </code>
* The attributes keypass and storepass must be equal! {@inheritDoc}
*/
public void execute() throws FtpCmdException {
String prot = getArguments().toUpperCase().trim();
if (!"SSL".equals(prot) && !"TLS".equals(prot)) {
msgOut(MSG504);
executed = true;
}
if (!getCtx().getOptions().getBoolean(OPT_SSL_ALLOW_EXPLICIT, true)) {
msgOut(MSG534);
executed = true;
}
if (!executed) {
try {
/* Create secure socket */
SSLSocket sslSocket = createSslSocket();
/* Send last response message in plain text */
msgOut(MSG234, new Object[] {prot});
/* From now on all communication is encrypted */
getCtx().setClientSocket(sslSocket);
} catch (IOException e) {
msgOut(MSG431);
}
/* New authentication is required */
getCtx().resetCredentials();
getCtx().setAttribute(ATTR_SSL, Boolean.TRUE);
executed = true;
}
synchronized (this) {
notifyAll();
}
}
private SSLSocket createSslSocket() throws IOException {
String clientHost = getCtx().getClientSocket().getInetAddress().getHostAddress();
SSLContext sslContext = getCtx().getOptions().getSslContext();
SSLSocketFactory factory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) factory.createSocket(getCtx().getClientSocket(), clientHost,
getCtx().getOptions().getFtpPort(), true);
sslSocket.setUseClientMode(false);
sslSocket.addHandshakeCompletedListener(this);
enableCipherSuites(sslSocket);
log.info("Enabled cipher suites (explicit SSL): "
+ StringUtils.arrayToCommaDelimitedString(sslSocket.getEnabledCipherSuites()));
return sslSocket;
}
/**
* Enables the configured cipher suites in the passed socket.
*
* @param sslSocket The socket.
*/
private void enableCipherSuites(SSLSocket sslSocket) {
String[] cipherSuites = getCtx().getOptions().getStringArray(OPT_SSL_CIPHER_SUITES, null);
if (cipherSuites != null) {
if (cipherSuites.length == 1 && WILDCARD.equals(cipherSuites[0])) {
sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
} else {
sslSocket.setEnabledCipherSuites(cipherSuites);
}
}
}
/**
* {@inheritDoc}
*/
public boolean socketModified() {
return executed;
}
/**
* {@inheritDoc}
*/
public String getHelp() {
return "Initiates an explicit SSL/TLS connection. See RFC 2228.";
}
/**
* {@inheritDoc}
*/
public boolean isAuthenticationRequired() {
return false;
}
/**
* {@inheritDoc}
*/
public void handshakeCompleted(HandshakeCompletedEvent e) {
log.debug("Explicit SSL handshake completed. Cipher suite: " + e.getCipherSuite());
}
}